{"components":{"schemas":{"AgentError":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"status":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["error","code","status"],"additionalProperties":false},"WebhookAck":{"type":"object","properties":{},"additionalProperties":false,"description":"No body is required. Any 2xx acknowledges the delivery; the response body is never read."},"DraftCreatedEvent":{"type":"object","properties":{"eventId":{"type":"string","minLength":1},"deliveryId":{"type":"string","minLength":1},"occurredAt":{"type":"string","minLength":1},"event":{"type":"string","const":"draft.created"},"data":{"type":"object","properties":{"documentId":{"type":"string","minLength":1},"blockId":{"type":"string","minLength":1},"draftId":{"type":"string","minLength":1},"authorKind":{"type":"string","enum":["human","agent"]},"authorLabel":{"type":"string","minLength":1}},"required":["documentId","blockId","draftId","authorKind","authorLabel"],"additionalProperties":false}},"required":["eventId","deliveryId","occurredAt","event","data"],"additionalProperties":false},"SectionApprovedEvent":{"type":"object","properties":{"eventId":{"type":"string","minLength":1},"deliveryId":{"type":"string","minLength":1},"occurredAt":{"type":"string","minLength":1},"event":{"type":"string","const":"section.approved"},"data":{"type":"object","properties":{"documentId":{"type":"string","minLength":1},"blockId":{"type":"string","minLength":1},"draftId":{"type":"string","minLength":1},"reviewerLabel":{"type":"string","minLength":1}},"required":["documentId","blockId","draftId","reviewerLabel"],"additionalProperties":false}},"required":["eventId","deliveryId","occurredAt","event","data"],"additionalProperties":false},"SectionRejectedEvent":{"type":"object","properties":{"eventId":{"type":"string","minLength":1},"deliveryId":{"type":"string","minLength":1},"occurredAt":{"type":"string","minLength":1},"event":{"type":"string","const":"section.rejected"},"data":{"type":"object","properties":{"documentId":{"type":"string","minLength":1},"blockId":{"type":"string","minLength":1},"draftId":{"type":"string","minLength":1},"reviewerLabel":{"type":"string","minLength":1},"reason":{"type":"string","minLength":1}},"required":["documentId","blockId","draftId","reviewerLabel","reason"],"additionalProperties":false}},"required":["eventId","deliveryId","occurredAt","event","data"],"additionalProperties":false},"ListDocumentsResponse":{"type":"object","properties":{"op":{"type":"string","const":"documents.list"},"documents":{"type":"array","items":{"type":"object","properties":{"documentId":{"type":"string","minLength":1},"path":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["pending","in_review","approved","archived"]},"pendingDraftCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"updatedAt":{"type":"number"},"createdAt":{"type":"number"}},"required":["documentId","path","title","status","pendingDraftCount","updatedAt","createdAt"],"additionalProperties":false}}},"required":["op","documents"],"additionalProperties":false},"GetBlockResponse":{"type":"object","properties":{"op":{"type":"string","const":"blocks.get"},"block":{"type":"object","properties":{"blockId":{"type":"string","minLength":1},"documentId":{"type":"string","minLength":1},"order":{"type":"number"},"ownerKind":{"type":"string","enum":["human","agent","review"]},"ownerLabel":{"type":"string"},"status":{"type":"string","enum":["clean","drafting","review","conflict"]},"body":{"type":"string"}},"required":["blockId","documentId","order","ownerKind","ownerLabel","status","body"],"additionalProperties":false},"pendingDrafts":{"type":"array","items":{"type":"object","properties":{"draftId":{"type":"string","minLength":1},"blockId":{"type":"string","minLength":1},"authorKind":{"type":"string","enum":["human","agent"]},"authorLabel":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string","enum":["pending","approved","rejected","superseded"]},"body":{"type":"string"},"createdAt":{"type":"number"}},"required":["draftId","blockId","authorKind","authorLabel","status","body","createdAt"],"additionalProperties":false}}},"required":["op","block","pendingDrafts"],"additionalProperties":false},"SubmitDraftResponse":{"type":"object","properties":{"op":{"type":"string","const":"drafts.submit"},"draft":{"type":"object","properties":{"draftId":{"type":"string","minLength":1},"blockId":{"type":"string","minLength":1},"documentId":{"type":"string","minLength":1},"authorKind":{"type":"string","const":"agent"},"status":{"type":"string","const":"pending"},"blockStatus":{"type":"string","enum":["clean","drafting","review","conflict"]},"pendingDraftCount":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["draftId","blockId","documentId","authorKind","status","blockStatus","pendingDraftCount"],"additionalProperties":false}},"required":["op","draft"],"additionalProperties":false},"SubmitDraftRequest":{"type":"object","properties":{"blockId":{"type":"string","minLength":1},"body":{"type":"string","minLength":1,"maxLength":20000},"authorLabel":{"type":"string","minLength":1,"maxLength":80}},"required":["blockId","body"],"additionalProperties":false}},"securitySchemes":{"agentToken":{"description":"An agent token minted in the agents console. The organization it acts for is a property of the credential — no header, parameter or body field can name another one.","scheme":"bearer","type":"http"}}},"info":{"description":"The REST surface an external agent calls: read the documents of the organization the token names, read one block with the drafts standing against it, and propose a draft. An agent can never approve its own work — every submission lands pending, awaiting a human verdict. The `webhooks` section is the other direction: the three signed events this product POSTs to a receiver you register.","title":"Quillstream Agent API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/api/agent/documents":{"get":{"description":"Returns the documents your organization owns, newest first, with the number of drafts each one still has pending. Optional filters: a page size (1-100) and a document status.","operationId":"documents.list","parameters":[{"description":"","in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"description":"","in":"query","name":"status","required":false,"schema":{"type":"string","enum":["pending","in_review","approved","archived"]}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDocumentsResponse"}}},"description":"List the documents of your workspace"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"A query parameter or body field is invalid. The body names the offending field and the reason; it never echoes the value received."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The `Authorization: Bearer <token>` header is missing, malformed, unknown or revoked. Answered before the request is looked at, so a refusal never confirms which parameters the endpoint understands."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The credential's organization does not own the resource. A foreign id, a missing id and a malformed id are answered IDENTICALLY, so the endpoint can never be swept to learn what exists elsewhere."}},"summary":"List the documents of your workspace"}},"/api/agent/blocks/{id}":{"get":{"description":"Returns one block of a document in your workspace together with every draft still awaiting a human verdict. A block id belonging to another workspace is refused exactly as a malformed one is, so the endpoint never confirms what exists elsewhere.","operationId":"blocks.get","parameters":[{"description":"An opaque server id, echoed and never parsed. A malformed, missing or foreign value all answer 403 alike.","in":"path","name":"id","required":true,"schema":{"minLength":1,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBlockResponse"}}},"description":"Read a block and its pending drafts"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"A query parameter or body field is invalid. The body names the offending field and the reason; it never echoes the value received."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The `Authorization: Bearer <token>` header is missing, malformed, unknown or revoked. Answered before the request is looked at, so a refusal never confirms which parameters the endpoint understands."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The credential's organization does not own the resource. A foreign id, a missing id and a malformed id are answered IDENTICALLY, so the endpoint can never be swept to learn what exists elsewhere."}},"summary":"Read a block and its pending drafts"}},"/api/agent/drafts":{"post":{"description":"Submits a proposed rewrite of one block. The draft always lands as agent-authored and pending — an agent can never approve its own work — and the reviewer decides in the editor.","operationId":"drafts.submit","parameters":[],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitDraftResponse"}}},"description":"Propose a draft on a block"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"A query parameter or body field is invalid. The body names the offending field and the reason; it never echoes the value received."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The `Authorization: Bearer <token>` header is missing, malformed, unknown or revoked. Answered before the request is looked at, so a refusal never confirms which parameters the endpoint understands."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentError"}}},"description":"The credential's organization does not own the resource. A foreign id, a missing id and a malformed id are answered IDENTICALLY, so the endpoint can never be swept to learn what exists elsewhere."}},"summary":"Propose a draft on a block","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitDraftRequest"}}},"required":true}}}},"security":[{"agentToken":[]}],"servers":[{"description":"Production","url":"https://quillstream.shiftclawco.com"}],"webhooks":{"draft.created":{"post":{"description":"Sent when a proposal is made against a section, by an agent or by a person. `data.authorKind` is which — resolved by the write path, never taken from the caller. The passage itself is never in the payload.","operationId":"draft.created","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftCreatedEvent"}}},"required":true},"responses":{"2XX":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAck"}}},"description":"Answer any 2xx to acknowledge. Anything else — or no answer inside the request timeout — is retried on the published schedule and then abandoned."}},"summary":"A draft was proposed against a section"}},"section.approved":{"post":{"description":"Sent when a human accepts a proposal. An agent can never approve its own work, so this event is always the record of a person deciding.","operationId":"section.approved","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectionApprovedEvent"}}},"required":true},"responses":{"2XX":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAck"}}},"description":"Answer any 2xx to acknowledge. Anything else — or no answer inside the request timeout — is retried on the published schedule and then abandoned."}},"summary":"A human approved a proposed section"}},"section.rejected":{"post":{"description":"Sent when a human refuses a proposal. `data.reason` carries the rationale the reviewer typed and is always present — an agent that learns only \"no\" retries blind; the rationale is what lets it revise instead.","operationId":"section.rejected","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectionRejectedEvent"}}},"required":true},"responses":{"2XX":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAck"}}},"description":"Answer any 2xx to acknowledge. Anything else — or no answer inside the request timeout — is retried on the published schedule and then abandoned."}},"summary":"A human rejected a proposed section, with the reason"}}},"x-webhook-delivery":{"destinationRules":[{"id":"https_only","sentence":"The URL must use https."},{"id":"public_host","sentence":"The host must be publicly resolvable — never loopback, a private range, link-local or *.local."},{"id":"no_credentials","sentence":"The URL must not embed a username or password."},{"id":"no_fragment","sentence":"The URL must not carry a fragment."}],"maxAttempts":5,"retryDelaysSeconds":[30,120,600,3600],"signatureHeader":"X-Quillstream-Signature","signatureRecipe":"HMAC-SHA256(secret, `${timestamp}.${rawBody}`) — compare in constant time against the v1= value","signatureVersionPrefix":"v1=","timestampHeader":"X-Quillstream-Timestamp","timestampToleranceSeconds":300,"verificationSnippet":"import { createHmac, timingSafeEqual } from 'node:crypto';\n\nconst SIGNATURE_HEADER = 'x-quillstream-signature';\nconst TIMESTAMP_HEADER = 'x-quillstream-timestamp';\nconst VERSION_PREFIX = 'v1=';\nconst TOLERANCE_SECONDS = 300;\n\n// `rawBody` MUST be the exact bytes received, before any JSON parsing:\n// re-serialising the object changes the bytes and the signature will not match.\nexport function verifyQuillstreamDelivery(headers, rawBody, secret) {\n  const signature = headers[SIGNATURE_HEADER];\n  if (typeof signature !== 'string' || !signature.startsWith(VERSION_PREFIX)) return false;\n\n  const timestamp = Number(headers[TIMESTAMP_HEADER]);\n  if (!Number.isFinite(timestamp)) return false;\n  // Refuse a stale delivery BEFORE computing an HMAC: a valid signature never\n  // expires on its own, so this window is what bounds a replay.\n  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > TOLERANCE_SECONDS) return false;\n\n  const expected =\n    VERSION_PREFIX + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');\n\n  const received = Buffer.from(signature);\n  const computed = Buffer.from(expected);\n  // Constant time, and length-checked first: timingSafeEqual throws on a\n  // length mismatch.\n  return received.length === computed.length && timingSafeEqual(received, computed);\n}"}}