{"openapi":"3.1.0","info":{"title":"Postbag API","version":"0.1.0","description":"A form backend that routes. Websites POST submissions to /s/{formId} (public, unauthenticated); everything under /v1 manages forms, streams, destinations, routes and deliveries and requires a bearer API key. Start at GET /v1/me, then see /llms.txt for the full agent onboarding guide."},"servers":[{"url":"https://postbag.dev"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"pb_live_… API key","description":"An API key minted via POST /v1/api-keys (or the dashboard), sent as `Authorization: Bearer pb_live_…`. Scopes are manage ⊇ read ⊇ submit — a manage key satisfies every read- or submit-scoped call too."}},"schemas":{"ErrorEnvelope":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"hint":{"type":"string"},"docs":{"type":"string"},"details":{"type":"object","additionalProperties":{}}},"required":["code","message"]}},"required":["error"]},"Form":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"project_id":{"type":"string","example":"fm_8f3kq2"},"slug":{"type":"string"},"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","paused"]},"schema_mode":{"type":"string","enum":["observe","enforce","managed"]},"current_schema_version":{"type":["integer","null"]},"settings":{"type":"object","additionalProperties":{}},"submit_url":{"type":"string"},"streams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"slug":{"type":"string"},"mapping_status":{"type":"string","enum":["valid","incomplete"]}},"required":["id","slug","mapping_status"]}},"counts":{"type":"object","properties":{"submissions":{"type":"integer"},"last_submission_at":{"type":["string","null"],"example":"2026-08-21T09:00:00.000Z"}},"required":["submissions","last_submission_at"]},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","project_id","slug","name","tags","status","schema_mode","current_schema_version","settings","submit_url","streams","counts","created_at"]},"Destination":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"type":{"type":"string","enum":["email","telegram","webhook","slack","discord"]},"name":{"type":"string"},"config":{"type":"object","additionalProperties":{}},"health":{"type":"string","enum":["unknown","ok","failing"]},"verified":{"type":"boolean"},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","type","name","config","health","verified","created_at"]},"Route":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"form_id":{"type":["string","null"]},"stream_id":{"type":["string","null"]},"destination_id":{"type":"string","example":"fm_8f3kq2"},"enabled":{"type":"boolean"},"mode":{"type":"object","additionalProperties":{}},"window":{"type":"object","additionalProperties":{}},"quality":{"type":"object","additionalProperties":{}},"filter":{"type":["string","null"]},"transform":{"type":["string","null"]},"counts":{"type":"object","additionalProperties":{}},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","form_id","stream_id","destination_id","enabled","mode"]},"Embed":{"type":"object","properties":{"html":{"type":"string"},"fetch":{"type":"string"},"react":{"type":"string"},"astro":{"type":"string"},"nextjs_action":{"type":"string"}},"required":["html","fetch","react","astro","nextjs_action"]},"Verify":{"type":"object","properties":{"curl":{"type":"string"},"then":{"type":"string"}},"required":["curl","then"]},"Project":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"slug":{"type":"string"},"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","slug","name","tags","created_at"]},"FormCreated":{"allOf":[{"$ref":"#/components/schemas/Form"},{"type":"object","properties":{"embed":{"$ref":"#/components/schemas/Embed"},"verify":{"$ref":"#/components/schemas/Verify"},"next":{"type":"array","items":{"type":"object","properties":{"why":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"body":{"type":"object","additionalProperties":{}}}}}}}]},"SchemaVersion":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{}},"ui":{"type":"object","additionalProperties":{}},"changelog":{"type":"string"},"version":{"type":"integer"},"inferred":{"type":"boolean"},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"},"created_by":{"type":"string"}},"required":["json_schema"]},"Submission":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"form_id":{"type":"string","example":"fm_8f3kq2"},"status":{"type":"string","enum":["received","quarantined","spam"]},"quarantine_reason":{"type":["string","null"]},"test":{"type":"boolean"},"data":{"type":"object","additionalProperties":{}},"form_schema_version":{"type":["integer","null"]},"spam":{"type":"object","properties":{"score":{"type":"number"},"reasons":{"type":"array","items":{"type":"string"}}},"required":["score","reasons"]},"meta":{"type":"object","additionalProperties":{}},"received_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","form_id","status","quarantine_reason","test","data","form_schema_version","spam","meta","received_at"]},"SubmissionDetail":{"allOf":[{"$ref":"#/components/schemas/Submission"},{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}},"drift":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}]},"Delivery":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"submission_id":{"type":"string","example":"fm_8f3kq2"},"route_id":{"type":"string","example":"fm_8f3kq2"},"destination_id":{"type":"string","example":"fm_8f3kq2"},"status":{"type":"string","enum":["pending","sending","sent","failed","dead","skipped"]},"skip_reason":{"type":["string","null"]},"attempts":{"type":"integer"},"next_attempt_at":{"type":["string","null"],"example":"2026-08-21T09:00:00.000Z"},"schema_version":{"type":["integer","null"]},"payload":{"type":"object","additionalProperties":{}},"last_response":{"$ref":"#/components/schemas/DeliveryResult"},"last_error":{"type":["string","null"]},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"},"sent_at":{"type":["string","null"],"example":"2026-08-21T09:00:00.000Z"}},"required":["id","submission_id","route_id","destination_id","status","skip_reason","attempts","next_attempt_at","schema_version","payload","last_error","created_at","sent_at"]},"DeliveryResult":{"type":"object","properties":{"ok":{"type":"boolean"},"status_code":{"type":["integer","null"]},"latency_ms":{"type":"number"},"response_excerpt":{"type":"string"},"error":{"type":["string","null"]}}},"Stream":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"slug":{"type":"string"},"name":{"type":"string"},"current_schema_version":{"type":["integer","null"]},"counts":{"type":"object","properties":{"sources":{"type":"integer"},"routes":{"type":"integer"},"submissions_30d":{"type":"integer"}},"required":["sources","routes","submissions_30d"]},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","slug","name","current_schema_version","counts","created_at"]},"StreamDetail":{"allOf":[{"$ref":"#/components/schemas/Stream"},{"type":"object","properties":{"schema":{"$ref":"#/components/schemas/SchemaVersion"},"sources":{"type":"array","items":{"$ref":"#/components/schemas/StreamSource"}},"routes":{"type":"array","items":{"$ref":"#/components/schemas/Route"}},"form_template":{"$ref":"#/components/schemas/SchemaVersion"}}}]},"StreamSource":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"form_id":{"type":"string","example":"fm_8f3kq2"},"selector":{"type":"string"},"mapping":{"type":"object","additionalProperties":{}},"mapping_status":{"type":"string","enum":["valid","incomplete"]},"missing":{"type":"array","items":{"type":"string"}},"stream_schema_version":{"type":"integer"}},"required":["id"]},"Event":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"type":{"type":"string"},"subject":{"type":"object","additionalProperties":{}},"data":{"type":"object","additionalProperties":{}},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"}},"required":["id","type","subject","data","created_at"]},"SystemWebhook":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"health":{"type":"string","enum":["unknown","ok","failing"]}},"required":["id","url","events"]},"SystemWebhookDelivery":{"type":"object","properties":{"id":{"type":"string","example":"fm_8f3kq2"},"webhook_id":{"type":"string","example":"fm_8f3kq2"},"event_id":{"type":"string","example":"fm_8f3kq2"},"event_type":{"type":"string"},"status":{"type":"string","enum":["pending","sending","sent","failed","dead"]},"attempts":{"type":"integer"},"next_attempt_at":{"type":["string","null"],"example":"2026-08-21T09:00:00.000Z"},"payload":{"type":"object","additionalProperties":{}},"last_response":{"$ref":"#/components/schemas/DeliveryResult"},"last_error":{"type":["string","null"]},"created_at":{"type":"string","example":"2026-08-21T09:00:00.000Z"},"sent_at":{"type":["string","null"],"example":"2026-08-21T09:00:00.000Z"}},"required":["id","webhook_id","event_id","event_type","status","attempts","next_attempt_at","payload","last_error","created_at","sent_at"]}},"parameters":{}},"paths":{"/v1/auth/providers":{"get":{"operationId":"auth_providers","tags":["discovery"],"summary":"Which sign-in methods this instance has configured","description":"Public, unauthenticated — no API key needed. Email+password is always available. `social` lists only the OAuth providers this instance has both a client id and secret for (self-host parity: a self-hosted instance with neither configured returns an empty list). The dashboard calls this once, cached, to decide which social sign-in buttons to render.","security":[],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"email_password":{"type":"boolean"},"social":{"type":"array","items":{"type":"string","enum":["google","github"]}},"sign_in_url":{"type":"string"}},"required":["email_password","social","sign_in_url"]}}}}}}},"/v1/me":{"get":{"operationId":"me_get","tags":["discovery"],"summary":"Identify the caller and summarise the organization","description":"The first call an agent holding only an API key should make: who it is, which org, which scopes the key has, plan limits and usage, and what already exists (counts). Call this before anything else.","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string"},"timezone":{"type":"string"}},"required":["id","slug","name","plan","timezone"]},"key":{"type":"object","properties":{"prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["manage","read","submit"]}}},"required":["scopes"]},"counts":{"type":"object","properties":{"projects":{"type":"integer"},"forms":{"type":"integer"},"streams":{"type":"integer"},"destinations":{"type":"integer"},"routes":{"type":"integer"}},"required":["projects","forms","streams","destinations","routes"]},"limits":{"type":"object","properties":{"forms":{"type":"integer"},"submissions_per_month":{"type":"integer"},"destinations":{"type":"integer"},"retention_days":{"type":"integer"},"used":{"type":"object","properties":{"forms":{"type":"integer"},"submissions_this_month":{"type":"integer"}},"required":["forms","submissions_this_month"]}},"required":["forms","submissions_per_month","destinations","retention_days","used"]},"next":{"type":"array","items":{"type":"object","properties":{"why":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"body":{"type":"object","additionalProperties":{}}}}}},"required":["organization","key","counts","limits","next"]}}}}}}},"/v1/api-keys":{"post":{"operationId":"api_keys_create","tags":["discovery"],"summary":"Create an API key for the active organization (session only)","description":"Requires a signed-in dashboard session, not another API key — an agent cannot mint its own first key this way. The full key is only ever returned once, in this response; store it immediately.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"A label to tell keys apart, e.g. 'CI deploy key'."},"scopes":{"type":"array","items":{"type":"string","enum":["manage","read","submit"]},"minItems":1,"default":["manage"],"description":"manage ⊇ read ⊇ submit — a manage key satisfies every read- or submit-scoped call too."}}}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]},"scopes":{"type":"array","items":{"type":"string","enum":["manage","read","submit"]}},"key":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","prefix","scopes","key","created_at"]}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"operationId":"api_keys_list","tags":["discovery"],"summary":"List API keys for the active organization","description":"Prefixes only — full key values are never returned again after creation.","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]},"scopes":{"type":"array","items":{"type":"string","enum":["manage","read","submit"]}},"enabled":{"type":"boolean"},"created_at":{"type":"string"}},"required":["id","name","prefix","scopes","enabled","created_at"]}}}}}}}},"/v1/api-keys/{id}":{"delete":{"operationId":"api_keys_revoke","tags":["discovery"],"summary":"Delete an API key","description":"Revokes the key immediately; any client still using it starts getting 401s.","parameters":[{"schema":{"type":"string","description":"The API key id (not the key value itself)."},"required":true,"description":"The API key id (not the key value itself).","name":"id","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/quickstart":{"post":{"operationId":"quickstart","tags":["discovery"],"summary":"One call to a working, routed form","description":"Creates (idempotently, by name within project) the project if missing, the form, a destination for whichever of notify_email/telegram/webhook was given, and a direct route, then returns the form, an embeddable snippet, a curl command to verify delivery, and next steps. Everything it does is also available as individual calls (POST /v1/forms, /v1/destinations, /v1/routes); this is a convenience, not a special path.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"project":{"type":"string","default":"default"},"origin":{"type":"string","format":"uri"},"notify_email":{"type":"string","format":"email"},"telegram":{"type":"object","properties":{"bot_token":{"type":"string"},"chat_id":{"type":"string"}},"required":["bot_token","chat_id"]},"webhook":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"secret":{"type":"string"}},"required":["url"]},"tags":{"type":"array","items":{"type":"string"},"default":[]},"redirect_url":{"type":"string","format":"uri"}},"required":["name"]}}}},"responses":{"201":{"description":"created (or returned)","content":{"application/json":{"schema":{"type":"object","properties":{"form":{"$ref":"#/components/schemas/Form"},"destinations":{"type":"array","items":{"$ref":"#/components/schemas/Destination"}},"routes":{"type":"array","items":{"$ref":"#/components/schemas/Route"}},"embed":{"$ref":"#/components/schemas/Embed"},"verify":{"$ref":"#/components/schemas/Verify"},"next":{"type":"array","items":{"type":"object","properties":{"why":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"body":{"type":"object","additionalProperties":{}}}}}},"required":["form","destinations","routes","embed","verify","next"]}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/projects":{"get":{"operationId":"projects_list","tags":["projects"],"summary":"List projects","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}},"post":{"operationId":"projects_create","tags":["projects"],"summary":"Create a project","description":"A project groups forms — one per site or app. Pass `if_exists: \"return\"` to make this call safe to re-run.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"tags":{"type":"array","items":{"type":"string"},"default":[]},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/projects/{projectId}":{"get":{"operationId":"projects_get","tags":["projects"],"summary":"Get a project","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"projectId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"projects_update","tags":["projects"],"summary":"Update a project","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"projectId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"tags":{"type":"array","items":{"type":"string"},"default":[]},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"projects_delete","tags":["projects"],"summary":"Delete an empty project","description":"Fails with 409 while the project still has forms — delete or move them first.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"projectId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms":{"get":{"operationId":"forms_list","tags":["forms"],"summary":"List forms","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter to one project id."},"required":false,"description":"Filter to one project id.","name":"project","in":"query"},{"schema":{"type":"string","description":"Filter to forms carrying this tag."},"required":false,"description":"Filter to forms carrying this tag.","name":"tag","in":"query"},{"schema":{"type":"string","description":"Filter to forms attached to this stream id."},"required":false,"description":"Filter to forms attached to this stream id.","name":"stream","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Form"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}},"post":{"operationId":"forms_create","tags":["forms"],"summary":"Create a form","description":"Pass `from_template` (a stream id) to attach the new form to that stream with a schema copied 1:1 from it, so the mapping is trivially complete. Pass `if_exists: \"return\"` to make this call safe to re-run.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"project":{"type":"string","default":"default"},"tags":{"type":"array","items":{"type":"string"},"default":[]},"status":{"type":"string","enum":["active","paused"],"default":"active"},"schema_mode":{"type":"string","enum":["observe","enforce","managed"],"default":"observe"},"settings":{"type":"object","properties":{"allowed_origins":{"type":"array","items":{"type":"string","format":"uri"},"default":[]},"redirect_url":{"type":["string","null"],"format":"uri"},"honeypot_field":{"type":"string","default":"_gotcha"},"turnstile":{"type":"object","properties":{"secret":{"type":"string"},"enabled":{"type":"boolean"}}},"rate_limit":{"type":"object","properties":{"per_minute":{"type":"integer","exclusiveMinimum":0,"default":10},"burst":{"type":"integer","exclusiveMinimum":0,"default":20}}},"reply_to_field":{"type":["string","null"]}}},"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]},"from_template":{"type":"string"},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormCreated"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms/{formId}":{"get":{"operationId":"forms_get","tags":["forms"],"summary":"Get a form","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"forms_update","tags":["forms"],"summary":"Update a form","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"project":{"type":"string","default":"default"},"tags":{"type":"array","items":{"type":"string"},"default":[]},"status":{"type":"string","enum":["active","paused"],"default":"active"},"schema_mode":{"type":"string","enum":["observe","enforce","managed"],"default":"observe"},"settings":{"type":"object","properties":{"allowed_origins":{"type":"array","items":{"type":"string","format":"uri"},"default":[]},"redirect_url":{"type":["string","null"],"format":"uri"},"honeypot_field":{"type":"string","default":"_gotcha"},"turnstile":{"type":"object","properties":{"secret":{"type":"string"},"enabled":{"type":"boolean"}}},"rate_limit":{"type":"object","properties":{"per_minute":{"type":"integer","exclusiveMinimum":0,"default":10},"burst":{"type":"integer","exclusiveMinimum":0,"default":20}}},"reply_to_field":{"type":["string","null"]}}},"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]},"from_template":{"type":"string"},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"forms_delete","tags":["forms"],"summary":"Delete a form","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms/{formId}/embed":{"get":{"operationId":"forms_embed","tags":["forms"],"summary":"Embed snippets","description":"Ready-to-paste HTML, fetch, React, Astro and Next.js server-action snippets for this form's submit URL.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Embed"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms/{formId}/schema":{"get":{"operationId":"forms_schema_get","tags":["forms"],"summary":"Current schema version","description":"For observe-mode forms with no published version yet, falls back to the last inferred draft (marked `inferred: true`) if one exists; publishing is still a separate, explicit act.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersion"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"forms_schema_publish","tags":["forms"],"summary":"Publish a new schema version","description":"Schemas are immutable versions — this always creates version N+1, never mutates an existing one.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]}}}},"responses":{"201":{"description":"published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersion"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms/{formId}/schema/versions":{"get":{"operationId":"forms_schema_versions","tags":["forms"],"summary":"All schema versions","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SchemaVersion"}}}}}}}},"/v1/forms/{formId}/schema/infer":{"post":{"operationId":"forms_schema_infer","tags":["forms"],"summary":"Infer a draft schema now, from recent submissions (observe mode, no schema yet)","description":"Only for observe-mode forms with no published schema. Looks at recent non-spam submissions and produces a draft schema; call forms_schema_publish with the result (or an edited version of it) to make it real.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersion"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/forms/{formId}/drift":{"get":{"operationId":"forms_drift","tags":["forms"],"summary":"Unresolved drift events","description":"Fields submitters are sending that the published schema doesn't declare. Resolve by publishing an updated schema.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{}}}}}}}}},"/v1/forms/{formId}/submissions":{"get":{"operationId":"forms_submissions_list","tags":["submissions"],"summary":"List submissions for a form","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"formId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter by status: received, quarantined or spam."},"required":false,"description":"Filter by status: received, quarantined or spam.","name":"status","in":"query"},{"schema":{"type":"string","description":"ISO timestamp; only submissions received after this."},"required":false,"description":"ISO timestamp; only submissions received after this.","name":"since","in":"query"},{"schema":{"type":"string","description":"Free-text search over submission data."},"required":false,"description":"Free-text search over submission data.","name":"q","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}}},"/v1/submissions":{"get":{"operationId":"submissions_list","tags":["submissions"],"summary":"Search submissions across the organization","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter to submissions routed through this stream id."},"required":false,"description":"Filter to submissions routed through this stream id.","name":"stream","in":"query"},{"schema":{"type":"string","description":"Filter to one form id."},"required":false,"description":"Filter to one form id.","name":"form","in":"query"},{"schema":{"type":"string","description":"Filter by status: received, quarantined or spam."},"required":false,"description":"Filter by status: received, quarantined or spam.","name":"status","in":"query"},{"schema":{"type":"string","description":"Free-text search over submission data."},"required":false,"description":"Free-text search over submission data.","name":"q","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}}},"/v1/submissions/{submissionId}":{"get":{"operationId":"submissions_get","tags":["submissions"],"summary":"Get a submission with its deliveries","parameters":[{"schema":{"type":"string"},"required":true,"name":"submissionId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionDetail"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"submissions_update","tags":["submissions"],"summary":"Change status (re-routes if moving to received)","description":"Moving a quarantined submission to `received` re-runs routing and queues deliveries for it.","parameters":[{"schema":{"type":"string"},"required":true,"name":"submissionId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["received","quarantined","spam"]}},"required":["status"]}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"submissions_delete","tags":["submissions"],"summary":"Delete permanently (GDPR)","description":"Irreversible. Use for data-subject deletion requests; deliveries already sent are unaffected.","parameters":[{"schema":{"type":"string"},"required":true,"name":"submissionId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams":{"get":{"operationId":"streams_list","tags":["streams"],"summary":"List streams","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Stream"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}},"post":{"operationId":"streams_create","tags":["streams"],"summary":"Create a stream (optionally with its first schema version and sources)","description":"A stream (shown as 'Bag' in the dashboard) fans one canonical schema in from many forms.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]},"sources":{"type":"array","items":{"type":"object","properties":{"form_id":{"type":"string","description":"The form feeding this stream (mutually exclusive with `selector` in principle, though only one source kind is currently supported)."},"selector":{"type":"string","description":"Reserved for non-form sources (not yet implemented)."},"mapping":{"type":"object","additionalProperties":{"type":"object","properties":{"from":{"type":"string","description":"Copy this field's value from the source form's submitted data."},"const":{"description":"Use this fixed value instead of anything from the source."},"expr":{"type":"string","description":"A JSONata expression evaluated against the source's data."},"default":{"description":"Fallback value when the resolved value is missing."}}},"description":"Keyed by the stream schema's field names. Each entry is exactly one of `from` (copy a field from the form's data), `const` (a fixed value) or `expr` (a JSONata expression), plus an optional `default`. Every required field in the stream's schema must be covered or this call fails 422 mapping_incomplete."}}},"default":[]},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stream"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{streamId}":{"get":{"operationId":"streams_get","tags":["streams"],"summary":"Get a stream with schema, sources, mapping status and routes","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamDetail"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"streams_update","tags":["streams"],"summary":"Update name/slug","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}$/u"},"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]},"sources":{"type":"array","items":{"type":"object","properties":{"form_id":{"type":"string","description":"The form feeding this stream (mutually exclusive with `selector` in principle, though only one source kind is currently supported)."},"selector":{"type":"string","description":"Reserved for non-form sources (not yet implemented)."},"mapping":{"type":"object","additionalProperties":{"type":"object","properties":{"from":{"type":"string","description":"Copy this field's value from the source form's submitted data."},"const":{"description":"Use this fixed value instead of anything from the source."},"expr":{"type":"string","description":"A JSONata expression evaluated against the source's data."},"default":{"description":"Fallback value when the resolved value is missing."}}},"description":"Keyed by the stream schema's field names. Each entry is exactly one of `from` (copy a field from the form's data), `const` (a fixed value) or `expr` (a JSONata expression), plus an optional `default`. Every required field in the stream's schema must be covered or this call fails 422 mapping_incomplete."}}},"default":[]},"if_exists":{"type":"string","enum":["error","return"],"default":"error"}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stream"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"streams_delete","tags":["streams"],"summary":"Delete a stream","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{streamId}/schema":{"get":{"operationId":"streams_schema_get","tags":["streams"],"summary":"Current stream schema","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchemaVersion"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"streams_schema_publish","tags":["streams"],"summary":"Publish a new stream schema version","description":"Schemas are immutable versions — this always creates version N+1. Re-validates every attached source's mapping against the new schema and returns the updated mapping statuses alongside it.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"json_schema":{"type":"object","additionalProperties":{},"description":"JSON Schema draft 2020-12 describing the submission data. `required` drives drift detection and mapping completeness."},"ui":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}},"description":"Per-field rendering hints keyed by field name (label, placeholder, widget, options, order) — used to render embed snippets."},"changelog":{"type":"string","description":"A short human note on what changed in this version, shown in the version history."}},"required":["json_schema"]}}}},"responses":{"201":{"description":"published","content":{"application/json":{"schema":{"type":"object","properties":{"schema":{"$ref":"#/components/schemas/SchemaVersion"},"mappings":{"type":"array","items":{"$ref":"#/components/schemas/StreamSource"}}},"required":["schema","mappings"]}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{streamId}/sources":{"get":{"operationId":"streams_sources_list","tags":["streams"],"summary":"List sources and their mapping status","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/StreamSource"}}}}}}},"post":{"operationId":"streams_sources_add","tags":["streams"],"summary":"Attach a form or selector with a mapping","description":"Fails 422 mapping_incomplete (with the missing fields listed) if the mapping does not cover every field the stream's current schema requires — a form can't join a stream it can't feed.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"form_id":{"type":"string","description":"The form feeding this stream (mutually exclusive with `selector` in principle, though only one source kind is currently supported)."},"selector":{"type":"string","description":"Reserved for non-form sources (not yet implemented)."},"mapping":{"type":"object","additionalProperties":{"type":"object","properties":{"from":{"type":"string","description":"Copy this field's value from the source form's submitted data."},"const":{"description":"Use this fixed value instead of anything from the source."},"expr":{"type":"string","description":"A JSONata expression evaluated against the source's data."},"default":{"description":"Fallback value when the resolved value is missing."}}},"description":"Keyed by the stream schema's field names. Each entry is exactly one of `from` (copy a field from the form's data), `const` (a fixed value) or `expr` (a JSONata expression), plus an optional `default`. Every required field in the stream's schema must be covered or this call fails 422 mapping_incomplete."}}}}}},"responses":{"201":{"description":"attached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamSource"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{streamId}/sources/{sourceId}":{"patch":{"operationId":"streams_sources_update","tags":["streams"],"summary":"Update the mapping","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"},{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"sourceId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"form_id":{"type":"string","description":"The form feeding this stream (mutually exclusive with `selector` in principle, though only one source kind is currently supported)."},"selector":{"type":"string","description":"Reserved for non-form sources (not yet implemented)."},"mapping":{"type":"object","additionalProperties":{"type":"object","properties":{"from":{"type":"string","description":"Copy this field's value from the source form's submitted data."},"const":{"description":"Use this fixed value instead of anything from the source."},"expr":{"type":"string","description":"A JSONata expression evaluated against the source's data."},"default":{"description":"Fallback value when the resolved value is missing."}}},"description":"Keyed by the stream schema's field names. Each entry is exactly one of `from` (copy a field from the form's data), `const` (a fixed value) or `expr` (a JSONata expression), plus an optional `default`. Every required field in the stream's schema must be covered or this call fails 422 mapping_incomplete."}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamSource"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"streams_sources_remove","tags":["streams"],"summary":"Detach","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"},{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"sourceId","in":"path"}],"responses":{"204":{"description":"detached"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/streams/{streamId}/preview":{"post":{"operationId":"streams_preview","tags":["streams"],"summary":"Run a sample submission through a source's mapping","description":"Dry-runs a form's attached mapping against sample data without storing anything — useful before publishing a schema change.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"streamId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"form_id":{"type":"string","example":"fm_8f3kq2","description":"The form whose attached mapping to run."},"data":{"type":"object","additionalProperties":{},"description":"Sample submission data to map through."}},"required":["form_id","data"]}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"payload":{"type":"object","additionalProperties":{}},"extras":{"type":"object","additionalProperties":{}},"problems":{"type":"array","items":{"type":"string"}}},"required":["payload","extras","problems"]}}}}}}},"/v1/destinations":{"get":{"operationId":"destinations_list","tags":["destinations"],"summary":"List destinations (secrets redacted)","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Destination"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}},"post":{"operationId":"destinations_create","tags":["destinations"],"summary":"Create a destination","description":"type is one of email, telegram, webhook, slack, discord; config shape depends on type. Call destinations_test after creating to verify it works.","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["email"]},"name":{"type":"string"},"config":{"type":"object","properties":{"to":{"type":"array","items":{"type":"string","format":"email"},"minItems":1},"cc":{"type":"array","items":{"type":"string","format":"email"},"default":[]},"subject_template":{"type":"string","default":"New submission: {{form.name}}"},"from_name":{"type":"string"}},"required":["to"]}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["telegram"]},"name":{"type":"string"},"config":{"type":"object","properties":{"bot_token":{"type":"string","minLength":1},"chat_id":{"type":"string","minLength":1},"template":{"type":"string"}},"required":["bot_token","chat_id"]}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["webhook"]},"name":{"type":"string"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"secret":{"type":"string"},"headers":{"type":"object","additionalProperties":{"type":"string"},"default":{}}},"required":["url"]}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["slack"]},"name":{"type":"string"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"template":{"type":"string"}},"required":["url"]}},"required":["type","config"]},{"type":"object","properties":{"type":{"type":"string","enum":["discord"]},"name":{"type":"string"},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"template":{"type":"string"}},"required":["url"]}},"required":["type","config"]}]}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Destination"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/destinations/{destinationId}":{"get":{"operationId":"destinations_get","tags":["destinations"],"summary":"Get a destination (secrets redacted)","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"destinationId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Destination"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"destinations_update","tags":["destinations"],"summary":"Update config","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"destinationId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A display label for this destination."},"config":{"type":"object","additionalProperties":{},"description":"Merged shallowly into the existing config (type-specific: to/cc for email, chat_id/bot_token for telegram, url/secret for webhook)."}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Destination"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"destinations_delete","tags":["destinations"],"summary":"Delete (fails if routes reference it)","description":"Fails with 409 while a route still points at this destination — delete or repoint those routes first.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"destinationId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/destinations/{destinationId}/test":{"post":{"operationId":"destinations_test","tags":["destinations"],"summary":"Send a sample payload now and return the provider response","description":"Verifies a destination is actually reachable and configured correctly — do this right after creating one, before wiring routes to it.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"destinationId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sample":{"type":"object","additionalProperties":{},"description":"Override the default sample payload sent for the test."}}}}}},"responses":{"200":{"description":"result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryResult"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/routes":{"get":{"operationId":"routes_list","tags":["routes"],"summary":"List routes","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter to routes whose source is this form id."},"required":false,"description":"Filter to routes whose source is this form id.","name":"form","in":"query"},{"schema":{"type":"string","description":"Filter to routes whose source is this stream id."},"required":false,"description":"Filter to routes whose source is this stream id.","name":"stream","in":"query"},{"schema":{"type":"string","description":"Filter to routes pointing at this destination id."},"required":false,"description":"Filter to routes pointing at this destination id.","name":"destination","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Route"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}},"post":{"operationId":"routes_create","tags":["routes"],"summary":"Create a route (source is exactly one of form_id / stream_id)","description":"A route ties one source (a form or a stream) to one destination, with a mode (instant or digest) and optional filter/transform/quality rules.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"form_id":{"type":"string"},"stream_id":{"type":"string"},"destination_id":{"type":"string"},"enabled":{"type":"boolean","default":true},"mode":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["instant"]}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["digest"]},"cron":{"type":"string","minLength":1},"timezone":{"type":"string","minLength":1}},"required":["type","cron","timezone"]}],"default":{"type":"instant"}},"window":{"type":"object","properties":{"from":{"type":["string","null"],"format":"date-time"},"until":{"type":["string","null"],"format":"date-time"}}},"quality":{"type":"object","properties":{"exclude_spam":{"type":"boolean","default":true},"exclude_quarantined":{"type":"boolean","default":true}}},"filter":{"type":"string"},"transform":{"type":"string"}},"required":["destination_id"]}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Route"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/routes/{routeId}":{"get":{"operationId":"routes_get","tags":["routes"],"summary":"Get a route","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"routeId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Route"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"operationId":"routes_update","tags":["routes"],"summary":"Update rules / enable / disable","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"routeId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Disable a route without deleting it."},"mode":{"type":"object","properties":{"type":{"type":"string","enum":["instant","digest"]},"cron":{"type":"string","description":"Cron expression for digest mode, e.g. '0 9 * * *'."},"timezone":{"type":"string","description":"IANA timezone the cron runs in; defaults to the org's timezone."}},"required":["type"],"description":"instant delivers as submissions arrive; digest batches into one payload per period."},"window":{"type":"object","properties":{"from":{"type":["string","null"],"format":"date-time"},"until":{"type":["string","null"],"format":"date-time"}},"description":"Only deliver submissions received in this ISO datetime range."},"quality":{"type":"object","properties":{"exclude_spam":{"type":"boolean"},"exclude_quarantined":{"type":"boolean"}},"description":"Defaults to excluding spam and quarantined submissions from delivery."},"filter":{"type":"string","description":"A JSONata boolean expression; only matching submissions are delivered."},"transform":{"type":"string","description":"A JSONata expression applied to the payload before delivery."}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Route"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"routes_delete","tags":["routes"],"summary":"Delete a route","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"routeId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deliveries":{"get":{"operationId":"deliveries_list","tags":["deliveries"],"summary":"List deliveries (the outbox)","description":"A submission is a row before it is anything else; delivery is this outbox drained by a worker. Poll here to confirm a submission actually arrived at its destination.","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter by status: pending, sending, sent, failed, dead or skipped."},"required":false,"description":"Filter by status: pending, sending, sent, failed, dead or skipped.","name":"status","in":"query"},{"schema":{"type":"string","description":"Filter to one route id."},"required":false,"description":"Filter to one route id.","name":"route","in":"query"},{"schema":{"type":"string","description":"Filter to one destination id."},"required":false,"description":"Filter to one destination id.","name":"destination","in":"query"},{"schema":{"type":"string","description":"Filter to one submission id."},"required":false,"description":"Filter to one submission id.","name":"submission","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}}},"/v1/deliveries/{deliveryId}":{"get":{"operationId":"deliveries_get","tags":["deliveries"],"summary":"Get a delivery with payload snapshot and last response","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"deliveryId","in":"path"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deliveries/{deliveryId}/retry":{"post":{"operationId":"deliveries_retry","tags":["deliveries"],"summary":"Re-snapshot the payload against current mapping/transform and queue again","description":"Use after fixing a destination's config or a stream's mapping so a `failed` or `dead` delivery gets another chance.","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"deliveryId","in":"path"}],"responses":{"202":{"description":"queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/events":{"get":{"operationId":"events_list","tags":["events"],"summary":"Organization event log","description":"Every notable thing that happened in this org — form.created, form.schema.changed, stream.schema.changed, submission.received, and more. System webhooks (below) are just subscriptions to this same log.","parameters":[{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","description":"Filter to one event type, e.g. 'submission.received'."},"required":false,"description":"Filter to one event type, e.g. 'submission.received'.","name":"type","in":"query"},{"schema":{"type":"string","description":"ISO timestamp; only events after this."},"required":false,"description":"ISO timestamp; only events after this.","name":"since","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}}}}},"/v1/webhooks":{"get":{"operationId":"webhooks_list","tags":["events"],"summary":"List system webhooks","description":"System webhooks notify an external system when org events happen (not to be confused with a webhook destination, which delivers submissions).","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SystemWebhook"}}}}}}},"post":{"operationId":"webhooks_create","tags":["events"],"summary":"Subscribe a URL to event types","description":"Every delivery is signed with the returned (or supplied) secret so the receiver can verify authenticity.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["submission.received","submission.quarantined","submission.spam","delivery.sent","delivery.failed","delivery.dead","digest.ready","form.created","form.schema.changed","stream.schema.changed","drift.detected","drift.resolved","destination.failing","destination.recovered"]},"minItems":1},"secret":{"type":"string"},"enabled":{"type":"boolean","default":true}},"required":["url","events"]}}}},"responses":{"201":{"description":"created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemWebhook"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhooks/{webhookId}":{"patch":{"operationId":"webhooks_update","tags":["events"],"summary":"Update a system webhook","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"webhookId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["submission.received","submission.quarantined","submission.spam","delivery.sent","delivery.failed","delivery.dead","digest.ready","form.created","form.schema.changed","stream.schema.changed","drift.detected","drift.resolved","destination.failing","destination.recovered"]},"minItems":1},"secret":{"type":"string"},"enabled":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemWebhook"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"webhooks_delete","tags":["events"],"summary":"Delete a system webhook","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"webhookId","in":"path"}],"responses":{"204":{"description":"deleted"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhooks/{webhookId}/deliveries":{"get":{"operationId":"webhooks_deliveries","tags":["events"],"summary":"Delivery history for a system webhook","parameters":[{"schema":{"type":"string","example":"fm_8f3kq2"},"required":true,"name":"webhookId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SystemWebhookDelivery"}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/health":{"get":{"operationId":"health_get","tags":["discovery"],"summary":"Liveness and readiness","description":"Public, unauthenticated. Reports database connectivity, worker heartbeat and the oldest pending delivery age.","security":[],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"db":{"type":"string","enum":["up","down"]},"worker":{"type":"object","properties":{"heartbeat_at":{"type":["string","null"]},"alive":{"type":"boolean"}},"required":["heartbeat_at","alive"]},"oldest_pending_delivery_s":{"type":["number","null"]},"version":{"type":"string"}},"required":["ok","db","worker","oldest_pending_delivery_s","version"]}}}},"503":{"description":"db down","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"db":{"type":"string","enum":["up","down"]},"worker":{"type":"object","properties":{"heartbeat_at":{"type":["string","null"]},"alive":{"type":"boolean"}},"required":["heartbeat_at","alive"]},"oldest_pending_delivery_s":{"type":["number","null"]},"version":{"type":"string"}},"required":["ok","db","worker","oldest_pending_delivery_s","version"]}}}}}}}},"webhooks":{}}