{"components":{"schemas":{"AgentKey":{"properties":{"agentId":{"format":"uuid","type":"string"},"alg":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"expiresAt":{"format":"date-time","type":["string","null"]},"id":{"format":"uuid","type":"string"},"keyId":{"description":"Canonical key id: key_ + sha256(pem)[:16].","type":"string"},"revokedAt":{"format":"date-time","type":["string","null"]},"status":{"enum":["active","revoked"],"type":"string"}},"type":"object"},"ChallengeIssueRequest":{"properties":{},"type":"object"},"ChallengeIssueResult":{"properties":{"challengeId":{"format":"uuid","type":"string"},"expiresAt":{"format":"date-time","type":"string"},"fieldId":{"type":"string"},"maxAttempts":{"type":"integer"},"prompt":{"description":"Human-readable challenge prompt.","type":"string"},"type":{"enum":["schema"],"type":"string"}},"type":"object"},"ChallengeRespondRequest":{"properties":{"answer":{"description":"The answer value for the challenged field."}},"required":["answer"],"type":"object"},"ChallengeRespondResult":{"properties":{"failedAt":{"description":"Present and true when the attempt budget is exhausted.","type":"boolean"},"passExpiresAt":{"description":"Present when passed=true.","format":"date-time","type":"string"},"passed":{"type":"boolean"},"remainingAttempts":{"description":"Present when passed=false.","type":"integer"}},"required":["passed"],"type":"object"},"ErrorEnvelope":{"properties":{"_docs":{"type":"string"},"_guide":{"type":"string"},"apiVersion":{"type":"string"},"code":{"description":"Stable machine-readable error code.","type":"string"},"error":{"type":"string"},"errors":{"description":"Present on validation_failed; one item per field rule violation.","items":{"$ref":"#/components/schemas/FieldError"},"type":"array"}},"required":["error","code","apiVersion"],"type":"object"},"FieldError":{"properties":{"code":{"type":"string"},"field":{"type":"string"},"help":{"description":"Doc anchor for this code.","type":"string"},"message":{"type":"string"}},"required":["field","code","message","help"],"type":"object"},"Form":{"description":"A form record (shape mirrors the builder/UI form object).","properties":{"currentVersion":{"type":"integer"},"description":{"type":["string","null"]},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"schema":{"type":"object"},"settings":{"type":"object"},"status":{"enum":["draft","published","archived"],"type":"string"},"teamId":{"format":"uuid","type":"string"},"workspaceId":{"format":"uuid","type":"string"}},"type":"object"},"FormCreateRequest":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"schema":{"description":"Defaults to { fields: [] }.","type":"object"},"workspaceId":{"description":"Defaults to the team's oldest workspace.","format":"uuid","type":"string"}},"required":["name"],"type":"object"},"FormDiffRequest":{"description":"Diffs-only edit. Operations apply in order.","properties":{"expectedVersion":{"description":"Optimistic concurrency; must equal current version or 409.","type":"integer"},"operations":{"items":{"$ref":"#/components/schemas/FormPatchOp"},"minItems":1,"type":"array"}},"required":["operations"],"type":"object"},"FormPatchOp":{"description":"One diff op, tagged by `op`: setMeta | addField | updateField | removeField | moveField.","properties":{"afterFieldId":{"type":"string"},"description":{"type":"string"},"field":{"type":"object"},"fieldId":{"type":"string"},"index":{"type":"integer"},"name":{"type":"string"},"op":{"enum":["setMeta","addField","updateField","removeField","moveField"],"type":"string"},"set":{"type":"object"},"settings":{"type":"object"},"toIndex":{"type":"integer"}},"required":["op"],"type":"object"},"KeyRegisterRequest":{"properties":{"alg":{"default":"ed25519","description":"Algorithm; defaults to ed25519.","type":"string"},"publicKeyPem":{"description":"Ed25519 public key in PEM format.","type":"string"}},"required":["publicKeyPem"],"type":"object"},"SubmissionPatchRequest":{"additionalProperties":false,"properties":{"edits":{"items":{"additionalProperties":false,"properties":{"fieldId":{"type":"string"},"value":{"description":"The replacement JSON value. Null removes the field."}},"required":["fieldId","value"],"type":"object"},"maxItems":100,"minItems":1,"type":"array"},"expectedUpdatedAt":{"description":"The exact `updatedAt` value returned by the most recent GET.","type":"string"}},"required":["expectedUpdatedAt","edits"],"type":"object"},"SubmissionRequest":{"properties":{"data":{"description":"The submitted field values keyed by field id.","type":"object"},"metadata":{"type":"object"}},"required":["data"],"type":"object"},"SuccessEnvelope":{"properties":{"_docs":{"type":"string"},"_guide":{"type":"string"},"apiVersion":{"type":"string"},"data":{"description":"The endpoint-specific response payload."}},"required":["data","apiVersion"],"type":"object"},"ValidateResult":{"description":"The `data` payload of a validate response (always HTTP 200).","properties":{"errors":{"items":{"$ref":"#/components/schemas/FieldError"},"type":"array"},"missingRequired":{"items":{"type":"string"},"type":"array"},"valid":{"type":"boolean"},"warnings":{"description":"Includes unknown_field (a warning on validate).","items":{"$ref":"#/components/schemas/FieldError"},"type":"array"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"in":"header","name":"x-api-key","type":"apiKey"},"FormAccessKey":{"description":"The published form capability. It must never be placed in a URL.","in":"header","name":"x-form-access-key","type":"apiKey"},"RespondentToken":{"description":"A session token from POST /papi/v1/auth/token (agent) or /auth/anonymous.","in":"header","name":"x-respondent-token","type":"apiKey"},"SignatureScheme":{"description":"HTTP Message Signatures (RFC-9421 subset). Ed25519 asymmetric key. Headers: Signature, Signature-Input. The keyId in Signature-Input identifies a registered agent signing key.","scheme":"signature","type":"http"}}},"info":{"description":"The isolated, agent-friendly public API. Every response carries `apiVersion`, `_docs`, and `_guide` pointers. Builder endpoints authenticate with an API key (`x-api-key`). Submitter endpoints identify the form with the `x-form-access-key` header and authenticate the actor with a session or signature. The agent identity surface are fully live. Signed requests use HTTP Message Signatures (RFC-9421, Ed25519). Full reference: https://docs.kinoforms.com/developer/api-reference","title":"KinoForms Public API","version":"v1"},"openapi":"3.1.0","paths":{"/papi/v1/auth/anonymous":{"post":{"description":"Mint a short-lived anonymous session for the form selected by `x-form-access-key`. If the form enables CAPTCHA (`settings.captcha.enabled`), a `turnstileToken` is verified once here. Body: `{ turnstileToken? }`.","responses":{"200":{"description":"`data: { token, expiresAt }`."},"403":{"description":"Form disallows anonymous, or CAPTCHA failed/required."},"503":{"description":"CAPTCHA verification temporarily unavailable."}},"security":[{"FormAccessKey":[]}],"summary":"Mint an anonymous session","tags":["submitter"]}},"/papi/v1/auth/token":{"post":{"description":"Exchange an agent's opaque secret (body `clientSecret` or `x-agent-secret` header) for a 7-day session token. IP-rate-limited; uniform 401 on any failure.","responses":{"200":{"description":"`data: { token, expiresAt }`."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Invalid/disabled/revoked/expired credential (uniform)."},"429":{"description":"Mint rate limit (per IP)."}},"security":[],"summary":"Mint an agent session (7-day)","tags":["submitter"]}},"/papi/v1/docs":{"get":{"responses":{"200":{"description":"HTML reference page."}},"security":[],"summary":"Interactive API reference (Scalar).","tags":["management"]}},"/papi/v1/identity/agents":{"get":{"responses":{"200":{"description":"`data: { agents[] }`."}},"summary":"List agents","tags":["identity"]},"post":{"description":"Provision an agent (UI session OR a `forms:write` API key). The secret is returned ONCE. Body: `{ name, workspaceId? }`.","responses":{"200":{"description":"`data: { id, clientId, secret, secretPrefix, name, status }` — secret shown once."}},"summary":"Register an agent","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}":{"delete":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { id, status }`."}},"summary":"Revoke (status=revoked)","tags":["identity"]},"get":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { agent }`."},"404":{"description":"Not found."}},"summary":"Get an agent","tags":["identity"]},"patch":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { agent }`."}},"summary":"Rename / enable / disable","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}/grants":{"get":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { grants[] }`."}},"summary":"List grants","tags":["identity"]},"post":{"description":"Grant (or update) scopes on a form. `agent:submit` is operator-only (NEVER grantable via API key). Body: `{ formId, scopes[], respondentId?, bindingRequired? }`.","parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { grant }`."},"403":{"description":"submit_not_grantable_by_api_key / insufficient_workspace_role."}},"summary":"Grant a form scope","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}/grants/{grant_id}":{"delete":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"grant_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { id, deleted }`."}},"summary":"Delete a grant","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}/keys":{"get":{"description":"List all signing keys (active + revoked) registered for this agent. Tenant-scoped.","parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { keys[] }` where each key has id, keyId, alg, status, createdAt, revokedAt, expiresAt."},"404":{"description":"Agent not found."}},"summary":"List an agent's signing keys","tags":["identity"]},"post":{"description":"Register a public key for this agent. The private key never leaves the agent. Returns the canonical `keyId` (sha256(pem)[:16]).","parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyRegisterRequest"}}},"required":true},"responses":{"200":{"description":"`data: { keyId, status, alg }`. The registered key is immediately usable for signing."},"400":{"description":"Invalid PEM."},"404":{"description":"Agent not found."}},"summary":"Register an Ed25519 signing key","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}/keys/{key_id}":{"delete":{"description":"Soft-revoke (status='revoked'). The key stops authenticating immediately.","parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"The canonical keyId (key_ prefix).","in":"path","name":"key_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`data: { keyId, status }`."},"404":{"description":"Key not found."}},"summary":"Revoke a signing key","tags":["identity"]}},"/papi/v1/identity/agents/{agent_id}/rotate-secret":{"post":{"parameters":[{"in":"path","name":"agent_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"`data: { id, secret, secretPrefix }` — new secret shown once."}},"summary":"Rotate the agent secret","tags":["identity"]}},"/papi/v1/management/forms":{"get":{"description":"List the API key's team forms, newest first.","parameters":[{"in":"query","name":"workspaceId","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"page","required":false,"schema":{"minimum":1,"type":"integer"}},{"in":"query","name":"limit","required":false,"schema":{"maximum":100,"minimum":1,"type":"integer"}}],"responses":{"200":{"description":"Paginated form list (in the success envelope's `data`)."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Missing/invalid API key."}},"summary":"List forms","tags":["management"]},"post":{"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormCreateRequest"}}},"required":true},"responses":{"200":{"description":"Created form (in `data.form`)."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Validation error."},"401":{"description":"Missing/invalid API key."}},"summary":"Create a form","tags":["management"]}},"/papi/v1/management/forms/{form_id}":{"get":{"parameters":[{"in":"path","name":"form_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"The form (in `data.form`)."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Form not found."}},"summary":"Get a form","tags":["management"]},"patch":{"parameters":[{"in":"path","name":"form_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormDiffRequest"}}},"required":true},"responses":{"200":{"description":"Updated form (in `data.form`)."},"400":{"description":"Empty/invalid operations."},"404":{"description":"Form or field not found."},"409":{"description":"Version mismatch or duplicate field id."}},"summary":"Edit a form (diffs only)","tags":["management"]}},"/papi/v1/management/forms/{form_id}/submissions":{"get":{"description":"Requires `submissions:read`.","parameters":[{"in":"path","name":"form_id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"page","required":false,"schema":{"minimum":1,"type":"integer"}},{"in":"query","name":"limit","required":false,"schema":{"maximum":100,"minimum":1,"type":"integer"}},{"in":"query","name":"status","required":false,"schema":{"enum":["pending","completed","partial"],"type":"string"}}],"responses":{"200":{"description":"Paginated submissions in `data.submissions`."}},"summary":"List a form's submissions","tags":["management"]}},"/papi/v1/management/forms/{form_id}/submissions/{submission_id}":{"delete":{"description":"Requires `submissions:delete`.","responses":{"200":{"description":"Deletion confirmation."},"404":{"description":"Submission not found."}},"summary":"Delete a submission","tags":["management"]},"get":{"description":"Requires `submissions:read`.","responses":{"200":{"description":"Submission in `data.submission`."},"404":{"description":"Submission not found."}},"summary":"Get a submission","tags":["management"]},"parameters":[{"in":"path","name":"form_id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"submission_id","required":true,"schema":{"format":"uuid","type":"string"}}],"patch":{"description":"Requires `submissions:update`. Uses optimistic concurrency through `expectedUpdatedAt`; every change is audited to the API key.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionPatchRequest"}}},"required":true},"responses":{"200":{"description":"Updated submission."},"409":{"description":"Optimistic concurrency conflict."}},"summary":"Edit submission fields","tags":["management"]}},"/papi/v1/openapi.json":{"get":{"responses":{"200":{"description":"The OpenAPI 3.1 document."}},"security":[],"summary":"This OpenAPI spec.","tags":["management"]}},"/papi/v1/submitter/challenges/{challenge_id}/responses":{"post":{"description":"Submit an answer to a previously-issued challenge. SIGNED-ONLY — the agent that issued the challenge must sign with its registered key. Max 3 attempts; on pass the agent earns a challenge-pass that satisfies the policy gate.","parameters":[{"in":"path","name":"challenge_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChallengeRespondRequest"}}},"required":true},"responses":{"200":{"description":"`data: { passed, passExpiresAt? }` on success or `{ passed: false, remainingAttempts, failedAt? }` on wrong answer."},"400":{"description":"Challenge not issuable (already resolved or expired)."},"403":{"description":"signature_required / challenge_expired / challenge_failed."},"429":{"description":"Rate limited (per IP)."}},"security":[{"SignatureScheme":[]}],"summary":"Respond to a challenge (signed-only)","tags":["challenge"]}},"/papi/v1/submitter/form/challenges":{"post":{"description":"Issue a challenge for a form field with validation constraints. The agent must answer correctly to prove capability. Rate-limited per IP. Accepts signed OR bearer-authenticated requests.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChallengeIssueRequest"}}}},"responses":{"200":{"description":"`data: { challengeId, type, prompt, fieldId, expiresAt, maxAttempts }`. 2-minute TTL."},"400":{"description":"Form has no checkable field (no_checkable_field)."},"429":{"description":"Rate limited (per IP)."}},"security":[{"FormAccessKey":[],"RespondentToken":[]},{"FormAccessKey":[],"SignatureScheme":[]}],"summary":"Issue a schema-derived challenge","tags":["challenge"]}},"/papi/v1/submitter/form/submissions":{"post":{"description":"Validate then persist. `?partial=true` saves an incomplete row (status=partial; agents/respondents only). On validation failure returns 422 `validation_failed` with `errors[]`.","parameters":[{"description":"Save-and-continue (partial).","in":"query","name":"partial","required":false,"schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionRequest"}}},"required":true},"responses":{"200":{"description":"`data: { id, status, missingRequired[] }`."},"403":{"description":"Authorization failed (agent_inactive / no_grant / missing_scope / workspace_mismatch / …)."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}},"description":"Validation failed."},"429":{"description":"Rate limited (per-agent 1/5s or per-IP)."}},"security":[{"FormAccessKey":[],"RespondentToken":[]},{"FormAccessKey":[],"SignatureScheme":[]}],"summary":"Submit a response (always validates)","tags":["submitter"]}},"/papi/v1/submitter/form/validation":{"post":{"description":"Validate a payload against the form WITHOUT persisting. ALWAYS returns 200. `unknown_field` is a warning here (an error on submit).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"properties":{"data":{"$ref":"#/components/schemas/ValidateResult"}}}]}}},"description":"Validation result in the success envelope. `data` contains `{ valid, errors[], warnings[], missingRequired[] }`."}},"security":[{"FormAccessKey":[],"RespondentToken":[]},{"FormAccessKey":[],"SignatureScheme":[]}],"summary":"Validate a submission (dry-run)","tags":["submitter"]}}},"security":[{"ApiKeyAuth":[]}],"servers":[{"description":"Same host as this document","url":"/"}],"tags":[{"description":"Manage forms and submissions with scoped API keys.","name":"management"},{"description":"Validate and submit form responses (session-based, optionally signed).","name":"submitter"},{"description":"Agent identity, credentials, signing keys, and grants.","name":"identity"},{"description":"CAPTCHA for AIs — schema-derived challenges that test form-field knowledge.","name":"challenge"}]}