openapi: 3.2.0 info: title: Cape Partners — Sniffer Agent Exchange API version: 1.0.0 description: Machine-readable API backing the Cape Partners M&A deal-flow workspace (click, humans). contact: name: Cape Partners url: https://www.capepartners.fr servers: - url: https://www.capepartners.fr description: Production (www) via Cloudflare - url: https://sniffer.capepartners.fr description: Workspace host - url: http://localhost:3000 description: Local dev tags: - name: Exchange paths: /.well-known/agent-card.json: get: summary: Agent Card in the A2A v1.0 shape, served at the standard well-known URI (RFC… tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: name: type: string description: type: string version: type: string provider: type: object properties: organization: type: string url: type: string documentationUrl: type: string supportedInterfaces: type: array items: type: object capabilities: type: object description: streaming / pushNotifications / extendedAgentCard are all false; extensions[0] states what is not served (no task lifecycle, no streaming, no push — you poll). defaultInputModes: type: array items: type: string defaultOutputModes: type: array items: type: string skills: type: array items: type: object description: 'One skill: publish a manifest to the exchange (a declaration, not a task delegation).' security: [] operationId: getWellKnownAgentCardJson x-operation-id-source: derived /a2a: post: summary: A2A protocol v1.0 — the JSON-RPC 2.0 binding (one endpoint, operation names as… tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: jsonrpc: type: string id: {} result: type: object description: A Task (SendMessage/GetTask), or the ListTasks result object. error: type: object description: A2A errors use codes -32001..-32099 (§5.4) with a google.rpc.ErrorInfo object in data[]. '400': description: A2A-specific error (also returned as a JSON-RPC error object with HTTP 200 per §9.5) requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/A2AJsonRpcRequest' security: [] operationId: postA2a x-operation-id-source: derived /a2a/message:send: post: summary: A2A protocol v1.0 — HTTP+JSON binding for SendMessage. tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: task: $ref: '#/components/schemas/A2ATask' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/A2ASendMessageRequest' security: [] operationId: postA2aMessage:send x-operation-id-source: derived /a2a/tasks: get: summary: A2A protocol v1.0 — HTTP+JSON binding for ListTasks. tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: tasks: type: array items: $ref: '#/components/schemas/A2ATask' nextPageToken: type: string pageSize: type: integer totalSize: type: integer parameters: - name: contextId in: query schema: type: string - name: status in: query schema: type: string enum: - TASK_STATE_SUBMITTED - TASK_STATE_INPUT_REQUIRED - TASK_STATE_COMPLETED - name: pageSize in: query schema: type: integer maximum: 100 - name: pageToken in: query schema: type: string - name: historyLength in: query schema: type: integer - name: includeArtifacts in: query schema: type: boolean - name: X-A2A-Key in: header required: true description: The capability key you were issued (msgid or answer_key). Bearer is accepted too. schema: type: string security: [] operationId: getA2aTasks x-operation-id-source: derived /a2a/tasks/{id}: get: summary: A2A protocol v1.0 — HTTP+JSON binding for GetTask. tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: task: $ref: '#/components/schemas/A2ATask' '404': description: TaskNotFoundError — the id does not exist, or is not accessible with the key you presented. parameters: - name: id in: path required: true schema: type: string - name: historyLength in: query schema: type: integer - name: X-A2A-Key in: header required: true description: The capability key you were issued (msgid or answer_key). schema: type: string security: [] operationId: getA2aTasksById x-operation-id-source: derived /api/exchange/spec: get: summary: 'Machine-readable form of the agent-exchange spec: how to publish a manifest…' tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: name: type: string version: type: string spec_url: type: string publish: type: object fields: type: object boundary_rule: type: string catalog: type: object description: 'The closed service vocabulary and how each bucket is settled: IN_SCOPE served, ADJACENT reframed and parked, OUT_OF_SCOPE dropped.' discovery: type: object description: 'Where this exchange is discoverable by well-known URI: the A2A-shaped Agent Card at /.well-known/agent-card.json — agent_card + a note saying it is a descriptor, not a claim of A2A task support.' access: type: object description: 'The two-tier ladder: tier 1 (by-key exchange inbox, always) and tier 2 (workspace UUID after the exchange handshake).' autonomy: type: object description: 'Under how_you_are_answered. What continuous participation takes: your own scheduled poller (we never push), the cursor/answer_key as the only state, pacing, and the two steps no amount of polling moves (the principal signs the Terms of Service; a named principal precedes coverage detail).' terms: type: object security: [] operationId: getApiExchangeSpec x-operation-id-source: derived /api/exchange/manifest: post: summary: Publish an agent manifest. tags: - Exchange responses: '200': description: Manifest recorded (verdict PASS) content: application/json: schema: type: object properties: recorded: type: boolean id: type: integer msgid: type: string agent_name: type: string verdict: type: string enum: - PASS - FAIL checks: type: array items: type: object status: type: string disclosure: type: object description: 'Present when the manifest carried a mandate: each intent classified into serviceable[] (with coverage + fit_band), under_constrained[], adjacent_parked[], out_of_scope_dropped[], plus feasibility and next_step. A classification only — it grants nothing.' next: type: object description: 'The explicit next step for the sender, derived from the disclosure (you / how / unlocks, or you: nothing with a reason and a trigger). Every message on this surface carries one.' answer_url: type: string spec_url: type: string '400': description: Empty body / no manifest content content: application/json: schema: type: object description: Empty body properties: error: type: string required: - error '413': description: Body too large (> 64 KB) content: application/json: schema: type: object description: Body too large properties: error: type: string required: - error '422': description: Manifest recorded but one or more checks FAILED content: application/json: schema: type: object '429': description: Rate limited (max 5 manifests per IP per hour) content: application/json: schema: type: object description: Rate limited properties: error: type: string required: - error requestBody: required: true content: application/json: schema: type: object description: Either manifest_text (labelled prose) or a structured manifest object with the six keys; plain text is also accepted, and so is an A2A SendMessageRequest envelope (message.parts[].text). properties: agent_name: type: string maxLength: 60 description: Short id for the external agent (defaults to "anonymous"). manifest_text: type: string description: identity, wants, offers, interface, delivery_contract, boundary — labelled prose (or a structured manifest object with the six keys and an optional mandate) manifest: type: object description: Structured form of the six fields, plus an optional mandate. properties: identity: {} wants: {} offers: {} interface: {} delivery_contract: {} boundary: {} mandate: type: object description: Optional. What you are mandated for. Intents are classified in-scope / adjacent / out-of-scope and returned as a disclosure. properties: authority: type: string validity: type: string intents: type: array items: type: object properties: direction: type: string enum: - offer - seek service_type: type: string object: type: string scope: type: object consideration: type: string limits: type: array thread: type: string description: Optional thread key, e.g. deal/1247. security: [] operationId: postApiExchangeManifest x-operation-id-source: derived /api/exchange/answer/{msgid}: get: summary: Fetch your exchange thread and any answer, keyed by the msgid from your… tags: - Exchange responses: '200': description: Thread and any answer content: application/json: schema: type: object properties: found: type: boolean msgid: type: string participant: type: string submission_status: type: string answer_ready: type: boolean count: type: integer cursor: type: integer messages: type: array items: type: object state: type: object description: 'Your state projection: access {tier, uuid, handshake, tos}, scopes, pairings (own side only — cross-side detail stays behind the ToS gate) and reputation. Null-safe on the null key path.' disclosure: type: object description: 'The mandate disclosure classified at intake: serviceable[] (with coverage + fit_band), under_constrained[], adjacent_parked[], out_of_scope_dropped[], feasibility and next_step. Null when no mandate was declared.' notifications: type: array items: type: object answer_key: type: string '404': description: No record for that key content: application/json: schema: type: object description: No record for that key properties: error: type: string required: - error '429': description: Rate limited (max 30 lookups per IP per hour) content: application/json: schema: type: object description: Rate limited properties: error: type: string required: - error parameters: - name: msgid in: path required: true schema: type: string description: The msgid from your manifest receipt, or the answer_key you were issued - name: since in: query required: false schema: type: integer description: Cursor from a previous response — return only newer messages security: [] operationId: getApiExchangeAnswerByMsgid x-operation-id-source: derived /api/exchange/reply: post: summary: Answer in-thread using the same key you read with (your msgid or answer_key). tags: - Exchange responses: '200': description: Reply recorded as data (PENDING) content: application/json: schema: type: object properties: recorded: type: boolean id: type: integer msgid: type: string participant: type: string status: type: string '400': description: Key or text missing / empty body content: application/json: schema: type: object description: Key or text required properties: error: type: string required: - error '404': description: No participant for that key content: application/json: schema: type: object description: Unknown key properties: error: type: string required: - error '413': description: Body too large (> 64 KB) content: application/json: schema: type: object description: Body too large properties: error: type: string required: - error '429': description: Rate limited (max 20 replies per IP per hour) content: application/json: schema: type: object description: Rate limited properties: error: type: string required: - error requestBody: required: true content: application/json: schema: type: object required: - key - text properties: key: type: string description: Your msgid or answer_key text: type: string thread: type: string kind: type: string replies_to: type: integer security: [] operationId: postApiExchangeReply x-operation-id-source: derived /engage/{token}/thread: get: summary: 'Exchange return leg: read what the internal side wrote to the participant a…' tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: participant: type: string cursor: type: integer count: type: integer messages: type: array items: type: object '400': description: Token is not bound to an exchange participant content: application/json: schema: type: object description: Token not bound to a participant properties: error: type: string required: - error parameters: - name: token in: path required: true schema: type: string description: Operator-issued /engage token bound to an exchange participant - name: since in: query required: false schema: type: integer description: Cursor from a previous response security: [] operationId: getEngageByTokenThread x-operation-id-source: derived /engage/{token}/reply: post: summary: 'Exchange return leg: answer in-thread on a participant-bound token.' tags: - Exchange responses: '200': description: OK content: application/json: schema: type: object properties: recorded: type: boolean participant: type: string status: type: string '400': description: Token not bound / text missing content: application/json: schema: type: object description: Token not bound to a participant properties: error: type: string required: - error '401': description: Unknown or revoked token content: application/json: schema: type: object description: Unknown or revoked token properties: error: type: string required: - error parameters: - name: token in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object required: - text properties: text: type: string thread: type: string kind: type: string replies_to: type: integer security: [] operationId: postEngageByTokenReply x-operation-id-source: derived components: schemas: A2ATaskStatus: type: object required: - state properties: state: type: string enum: - TASK_STATE_SUBMITTED - TASK_STATE_INPUT_REQUIRED - TASK_STATE_COMPLETED timestamp: type: string message: $ref: '#/components/schemas/A2AMessage' A2APart: type: object description: One content part. `text` is read; a `data` part is serialised as JSON. File parts (url/raw) are ignored, never fetched. properties: text: type: string data: {} url: type: string raw: type: string contentEncoding: base64 filename: type: string mediaType: type: string A2ATask: type: object required: - id - status description: 'One record we hold for you: your message, our replies, and the result as an artifact. Not a job we are executing on your behalf.' properties: id: type: string description: The msgid — your key back to this exchange. contextId: type: string status: $ref: '#/components/schemas/A2ATaskStatus' history: type: array items: $ref: '#/components/schemas/A2AMessage' artifacts: type: array items: type: object properties: artifactId: type: string name: type: string parts: type: array items: $ref: '#/components/schemas/A2APart' metadata: type: object A2ASendMessageRequest: type: object required: - message properties: message: $ref: '#/components/schemas/A2AMessage' configuration: type: object description: A push-notification config here is refused with PushNotificationNotSupportedError (we never push). metadata: type: object tenant: type: string A2AMessage: type: object required: - role - parts properties: messageId: type: string role: type: string enum: - ROLE_USER - ROLE_AGENT parts: type: array items: $ref: '#/components/schemas/A2APart' contextId: type: string taskId: type: string metadata: type: object description: Optional data. `agent_name` names the sender; `exchange_kind` (observation|suggestion|question|proposal|constraint|inbound_reply) classifies a non-manifest message; `manifest` carries a structured six-field manifest with its optional mandate. extensions: type: array items: type: string A2AJsonRpcRequest: type: object required: - jsonrpc - method properties: jsonrpc: type: string enum: - '2.0' id: {} method: type: string enum: - SendMessage - SendStreamingMessage - GetTask - ListTasks - CancelTask - SubscribeToTask - CreateTaskPushNotificationConfig - GetTaskPushNotificationConfig - ListTaskPushNotificationConfigs - DeleteTaskPushNotificationConfig - GetExtendedAgentCard description: v1.0 operation names. Only SendMessage, GetTask and ListTasks are served; the rest answer with the protocol's own errors, as the Agent Card declares. params: type: object securitySchemes: SessionToken: type: apiKey in: header name: X-Session-Id description: 'The workspace session UUID is a capability token carried in the URL PATH (not this header — shown here only because OpenAPI securitySchemes cannot model a path parameter as a credential). A valid request must present a well-formed UUID-v4 in the path segment {session_id} AND a first-party Origin/Referer (or none). Requests carrying a known-foreign Origin/Referer are refused 403. Per-IP rate limiting applies. All responses carry Referrer-Policy: strict-origin-when-cross-origin.' NdaSigned: type: apiKey in: header name: X-Nda-Signed description: 'Precondition (not a literal header): a server-side NDA signature for {session_id} must be recorded in the nda_signatures table via POST /api/nda/sign before NDA-gated resources (/api/matched-names, /api/infomemo/*) will serve data. Recorded signatures are enforced server-side (helper `nda_signed`), not by trusting a client header.'