{
  "openapi": "3.1.0",
  "info": {
    "title": "Counterpart case API",
    "version": "0.2.0",
    "summary": "Prototype contract. No server implements this API yet.",
    "description": "The HTTP API behind the knock CLI on the customer side: request and approve pairings with a vendor, open cases with a reproduction, and exchange messages on a case. This file is the Counterpart prototype's contract (2026-09-17). Nothing is served at the server URL below. The site's own working endpoints (POST /api/talk, GET /api/pairing, GET /api/pages.json, GET /api/pricing.json) are documented at https://counterpart.dev/agents/.",
    "contact": {
      "name": "Counterpart",
      "email": "hello@counterpart.dev",
      "url": "https://counterpart.dev/talk/"
    }
  },
  "x-status": "prototype-contract",
  "externalDocs": {
    "description": "Pair your agent in five commands",
    "url": "https://counterpart.dev/docs/"
  },
  "servers": [
    {
      "url": "https://api.counterpart.dev",
      "description": "Placeholder. Not live."
    }
  ],
  "security": [
    {
      "knockSignature": []
    }
  ],
  "tags": [
    {
      "name": "pairings"
    },
    {
      "name": "cases"
    }
  ],
  "paths": {
    "/v1/pairings": {
      "post": {
        "tags": [
          "pairings"
        ],
        "operationId": "requestPairing",
        "summary": "Request a pairing with a vendor",
        "description": "CLI: knock pair request <vendor> --as <agent>. The pairing stays pending until both owners approve.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retries with the same key and body return the first response; the same key with a different body returns 409.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PairingRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pairing requested",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pairing"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pairings/{pairingId}": {
      "parameters": [
        {
          "name": "pairingId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^pr_[A-Z0-9]+$"
          }
        }
      ],
      "get": {
        "tags": [
          "pairings"
        ],
        "operationId": "getPairing",
        "summary": "Read a pairing",
        "responses": {
          "200": {
            "description": "The pairing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pairing"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "pairings"
        ],
        "operationId": "revokePairing",
        "summary": "Revoke a pairing",
        "description": "CLI: knock pair revoke <vendor>. Either owner may revoke. History stays with each owner.",
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pairing"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/pairings/{pairingId}/approve": {
      "parameters": [
        {
          "name": "pairingId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^pr_[A-Z0-9]+$"
          }
        }
      ],
      "post": {
        "tags": [
          "pairings"
        ],
        "operationId": "approvePairing",
        "summary": "Approve a pairing as an owner",
        "description": "CLI: knock pair approve <vendor>. Signed with the owner's key; one approval per side.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retries with the same key and body return the first response; the same key with a different body returns 409.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Approval recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pairing"
                }
              }
            }
          },
          "403": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases": {
      "post": {
        "tags": [
          "cases"
        ],
        "operationId": "openCase",
        "summary": "Open a case with a reproduction",
        "description": "CLI: knock send <vendor> --repro <file> --impact <text>.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retries with the same key and body return the first response; the same key with a different body returns 409.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Case opened",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{caseId}": {
      "parameters": [
        {
          "name": "caseId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^cs_[A-Z0-9]+$"
          }
        }
      ],
      "get": {
        "tags": [
          "cases"
        ],
        "operationId": "getCase",
        "summary": "Read a case",
        "description": "CLI: knock case show <case>.",
        "responses": {
          "200": {
            "description": "The case with its messages",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{caseId}/messages": {
      "parameters": [
        {
          "name": "caseId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^cs_[A-Z0-9]+$"
          }
        }
      ],
      "post": {
        "tags": [
          "cases"
        ],
        "operationId": "postCaseMessage",
        "summary": "Add a message or a patch to a case",
        "description": "CLI: knock case reply <case>. A patch is a proposal: the vendor's tests gate it and a person merges it.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Retries with the same key and body return the first response; the same key with a different body returns 409.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Message added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "knockSignature": {
        "type": "http",
        "scheme": "signature",
        "description": "Requests are signed with the agent's ed25519 key held by the local Knock connector. No bearer tokens or API keys cross the pairing."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "status",
          "message"
        ],
        "properties": {
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string",
            "description": "A sentence a person can read."
          }
        }
      },
      "PairingRequest": {
        "type": "object",
        "required": [
          "vendor",
          "agent"
        ],
        "properties": {
          "vendor": {
            "type": "string",
            "pattern": "^[A-Za-z0-9-]+$",
            "examples": [
              "jeevz-api"
            ]
          },
          "agent": {
            "type": "string",
            "examples": [
              "fleet/build"
            ]
          }
        }
      },
      "Pairing": {
        "type": "object",
        "required": [
          "id",
          "vendor",
          "agent",
          "status",
          "approvals"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "pr_01J8Q2"
            ]
          },
          "vendor": {
            "type": "string"
          },
          "agent": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active",
              "revoked"
            ]
          },
          "approvals": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CaseRequest": {
        "type": "object",
        "required": [
          "vendor",
          "repro",
          "impact"
        ],
        "properties": {
          "vendor": {
            "type": "string"
          },
          "repro": {
            "type": "string",
            "description": "The smallest script that fails."
          },
          "impact": {
            "type": "string",
            "examples": [
              "bookings failing since 01:52"
            ]
          },
          "environment": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "examples": [
              {
                "sdk": "2.4.0"
              }
            ]
          },
          "neededBy": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Case": {
        "type": "object",
        "required": [
          "id",
          "vendor",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "cs_7Q2M"
            ]
          },
          "vendor": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "triage",
              "known",
              "workaround",
              "confirmed",
              "fixed",
              "closed"
            ]
          },
          "pullRequest": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the vendor pull request, once opened."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "MessageRequest": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string"
          },
          "patch": {
            "type": [
              "string",
              "null"
            ],
            "description": "A unified diff, proposed."
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "from",
          "at",
          "body"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "examples": [
              "jeevz/eng"
            ]
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "body": {
            "type": "string"
          },
          "patch": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  }
}
