{"openapi":"3.1.0","info":{"title":"forecraft API","version":"1.0.0","summary":"Create, preview, publish and read forms. Built for agents.","description":"forecraft is a form platform whose first user is an AI agent. A form is ONE JSON document, the *definition*: `{ schema, ui?, rules?, appearance? }`.\n\nTypical flow: `POST /forms` (draft) -> `POST /forms/{id}/preview` (HTML + audit) -> `POST /forms/{id}/publish` -> people submit through the public page or `POST /f/{slug}/submissions` -> `GET /forms/{id}/submissions`.\n\nAuthenticate with `Authorization: Bearer fc_live_…` (create a key in the dashboard or with `POST /keys`). Errors are RFC 9457 `application/problem+json`; a rejected definition or submission carries `errors[]` with a JSON pointer for each fault.\n\n`schema` is a strict JSON Schema subset (`@faqir-ui/forms`); `ui` is a Faqir UI schema (`widget`, `placeholder`, `rows`, `enumLabels`, `width`, `orientation`, `minLabel`/`maxLabel`, `ui:groups`, `ui:wizard`); `rules` is `@faqir-ui/rules` input. `GET /vocabulary` lists the valid appearance values.\n\nReports and documents: a *report* is ONE JSON definition (`{ schema, sample, layout, appearance, … }`; blocks and curated appearance are listed by `GET /vocabulary/reports`) rendered to an A4 or Letter PDF. Flow: `GET /report-starters` -> `POST /reports` (from a starter) -> `POST /reports/{id}/preview` (preflight findings and page images) -> `POST /reports/{id}/publish` -> `POST /reports/{id}/documents` with `issue: true` (a gapless number, a PDF, a share link) -> `POST /documents/{id}/void` if it must be cancelled. Rendering has its own rate limit (30/min per key) and can answer 503 `renderer-busy` with `Retry-After`.\n\nAn MCP server for the same API ships in `packages/mcp` (see /docs/mcp)."},"servers":[{"url":"https://forecraft.contzo.lol/api/v1","description":"This forecraft server"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"forms","description":"Create, edit, preview and publish forms."},{"name":"submissions","description":"Read what people sent. Sending is the public endpoint."},{"name":"workspace","description":"The workspace, its members and API keys."},{"name":"reports","description":"Report definitions: starters, edit, preview with preflight, publish."},{"name":"documents","description":"Issued PDFs: numbers, voiding, share links. Public share views are /d/{token}."},{"name":"assets","description":"Logos and images used by reports."},{"name":"meta","description":"Vocabulary an agent needs to write a definition."}],"paths":{"/f/{slug}/submissions":{"post":{"operationId":"submitToForm","tags":["submissions"],"summary":"Submit answers to a published form (public, no key)","description":"What the public page posts to. Rate limited per client address (10 per minute). Validated with the form's rules; a rule failure is a 422 listing each finding. A filled honeypot field `_fc_hp` is answered 202 and discarded.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"description":"The form's public slug.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SubmissionBody"},{"type":"object","additionalProperties":true,"description":"Or the answers object itself."}]}},"application/x-www-form-urlencoded":{"schema":{"type":"object","additionalProperties":true}},"multipart/form-data":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"Stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionReceipt"}}}},"202":{"description":"Honeypot tripped: acknowledged, nothing stored.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"null"},"received":{"type":"boolean"}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict","description":"The form is not published (`form-unpublished`), closed (`form-closed`) or full (`form-full`)."},"415":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/workspace":{"get":{"operationId":"getWorkspace","tags":["workspace"],"summary":"The workspace the key belongs to","responses":{"200":{"description":"The workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"renameWorkspace","tags":["workspace"],"summary":"Rename the workspace","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1}},"required":["name"]}}}},"responses":{"200":{"description":"The updated workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/workspace/members":{"get":{"operationId":"listMembers","tags":["workspace"],"summary":"Members and pending invites","responses":{"200":{"description":"Members and open invitations.","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"joined_at":{"type":"string","format":"date-time"}}}},"invites":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}},"required":["members","invites"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/workspace/invites":{"post":{"operationId":"inviteMember","tags":["workspace"],"summary":"Invite a person by email (signed-in people only)","description":"Needs a dashboard session. With an API key this answers 403 `session-required`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"201":{"description":"The invitation and its one-time link.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"url":{"type":"string","format":"uri"}},"required":["id","email","url"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/keys":{"get":{"operationId":"listKeys","tags":["workspace"],"summary":"List API keys (never the secrets)","responses":{"200":{"description":"Keys, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createKey","tags":["workspace"],"summary":"Create an API key","description":"Signed-in people only (session cookie): an API key gets 403, so a leaked key cannot mint successors. The secret (`fc_live_…`) is in this response only. Store it now.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","default":"API key"}}}}}},"responses":{"201":{"description":"The key and its secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/keys/{id}":{"delete":{"operationId":"revokeKey","tags":["workspace"],"summary":"Revoke an API key","description":"Signed-in people only (session cookie): an API key gets 403.","parameters":[{"name":"id","in":"path","required":true,"description":"Key id (key_…).","schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms":{"get":{"operationId":"listForms","tags":["forms"],"summary":"List forms (without definitions)","responses":{"200":{"description":"Forms, most recently updated first.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/FormSummary"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createForm","tags":["forms"],"summary":"Create a draft form","description":"Body is `{ definition, slug? }`, or the bare definition. The definition is compiled: anything outside the supported subset is a 422 `invalid-definition` with `errors[]` pointers. Nothing is public until `POST /forms/{id}/publish`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CreateForm"},{"$ref":"#/components/schemas/FormDefinition"}]}}}},"responses":{"201":{"description":"The form.","headers":{"Location":{"schema":{"type":"string"},"description":"/api/v1/forms/{id}"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"get":{"operationId":"getForm","tags":["forms"],"summary":"Get a form with its draft definition","responses":{"200":{"description":"The form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateForm","tags":["forms"],"summary":"Change the draft definition, status, limit or slug","description":"Takes any of `definition` (replaces the whole draft), `status`, `submission_limit`, `slug`. Unknown keys are a 422. Editing the definition does not touch the live version; publish again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormPatch"}}}},"responses":{"200":{"description":"The updated form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteForm","tags":["forms"],"summary":"Delete a form and its submissions","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/publish":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"post":{"operationId":"publishForm","tags":["forms"],"summary":"Publish the draft as the next version","responses":{"201":{"description":"The new live version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishedVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/versions":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"get":{"operationId":"listVersions","tags":["forms"],"summary":"Published versions, newest first","responses":{"200":{"description":"Versions.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/VersionSummary"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/versions/{version}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}},{"name":"version","in":"path","required":true,"description":"Version number.","schema":{"type":"integer","minimum":1}}],"get":{"operationId":"getVersion","tags":["forms"],"summary":"One published version with its definition","responses":{"200":{"description":"The version.","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"definition":{"$ref":"#/components/schemas/FormDefinition"}},"required":["version","published_at","definition"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/preview":{"post":{"operationId":"previewDefinition","tags":["forms"],"summary":"Render and audit a definition without storing it","description":"Body is `{ definition }` or the bare definition.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/FormDefinition"}},"required":["definition"]},{"$ref":"#/components/schemas/FormDefinition"}]}}}},"responses":{"200":{"description":"Rendered page and audit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/preview":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"post":{"operationId":"previewForm","tags":["forms"],"summary":"Render and audit a form's draft (or a proposed definition)","description":"With an empty body, previews the saved draft. With `{ definition }`, previews that instead. Nothing is stored.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/FormDefinition"}}}}}},"responses":{"200":{"description":"Rendered page and audit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Preview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/validate":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"post":{"operationId":"validateSubmission","tags":["forms"],"summary":"Dry-run answers against the draft's rules","description":"Returns the verdict (always 200, even when `valid` is false). Nothing is stored.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true}},"required":["data"]}}}},"responses":{"200":{"description":"The verdict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Verdict"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/submissions":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"get":{"operationId":"listSubmissions","tags":["submissions"],"summary":"List a form's submissions, newest first","parameters":[{"name":"q","in":"query","description":"Substring match over the stored answers.","schema":{"type":"string"}},{"name":"version","in":"query","description":"Only this published version.","schema":{"type":"integer","minimum":1}},{"name":"since","in":"query","description":"created_at >= (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","description":"created_at <= (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size, 1-1000.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of submissions.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"total":{"type":"integer"}},"required":["items","total"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/forms/{id}/submissions/export":{"parameters":[{"name":"id","in":"path","required":true,"description":"Form id (frm_…).","schema":{"type":"string"}}],"get":{"operationId":"exportSubmissions","tags":["submissions"],"summary":"Export submissions as CSV or JSON (at most 1000)","parameters":[{"name":"format","in":"query","schema":{"type":"string","enum":["csv","json"],"default":"csv"}},{"name":"q","in":"query","description":"Substring match over the stored answers.","schema":{"type":"string"}}],"responses":{"200":{"description":"The export, as an attachment.","content":{"text/csv":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"object","properties":{"form":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/submissions/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Submission id (sub_…).","schema":{"type":"string"}}],"get":{"operationId":"getSubmission","tags":["submissions"],"summary":"One submission","responses":{"200":{"description":"The submission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteSubmission","tags":["submissions"],"summary":"Delete a submission","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/vocabulary":{"get":{"operationId":"getVocabulary","tags":["meta"],"summary":"Valid appearance values and defaults","responses":{"200":{"description":"Themes, accents, layouts and the defaults.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vocabulary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/vocabulary/reports":{"get":{"operationId":"getReportVocabulary","tags":["meta"],"summary":"Blocks, helpers, print themes, type pairings, locales and preflight codes","description":"Every block with one JSON example, the expression helpers, the curated appearance values, numbering tokens, the summary data shape and the preflight codes. Read it before writing a report definition.","responses":{"200":{"description":"The report vocabulary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportVocabulary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/report-starters":{"get":{"operationId":"listReportStarters","tags":["reports"],"summary":"Complete starter reports to begin from","description":"invoice, letter, business-report, confirmation, survey-summary and blank. Pass an id as `starter` to `POST /reports`.","responses":{"200":{"description":"The starters.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Starter"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports":{"get":{"operationId":"listReports","tags":["reports"],"summary":"List reports (without definitions)","responses":{"200":{"description":"Reports, most recently updated first.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ReportSummary"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createReport","tags":["reports"],"summary":"Create a draft report from a starter or a definition","description":"Body is `{ starter, title?, appearance?, sample?, form? }` (recommended: start from a starter and choose appearance from the curated values), or `{ definition }`, or the bare definition. `form` (frm_…) is required for the confirmation and survey-summary starters. Nothing can be issued until `POST /reports/{id}/publish`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CreateReportFromStarter"},{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ReportDefinition"}},"required":["definition"],"additionalProperties":false},{"$ref":"#/components/schemas/ReportDefinition"}]}}}},"responses":{"201":{"description":"The report.","headers":{"Location":{"schema":{"type":"string"},"description":"/api/v1/reports/{id}"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid","description":"The definition is invalid; `errors[]` has a JSON pointer per fault, and a line and column for an expression."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"get":{"operationId":"getReport","tags":["reports"],"summary":"Get a report with its draft definition","responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateReport","tags":["reports"],"summary":"Replace the draft definition","description":"Body is `{ definition }`, the WHOLE definition. Editing the draft does not touch published versions or issued documents; publish again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ReportDefinition"}},"required":["definition"],"additionalProperties":false}}}},"responses":{"200":{"description":"The updated report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteReport","tags":["reports"],"summary":"Delete a report","description":"Deletes the report, its versions and its drafts. A report that has issued or void documents is kept: 409 `has-documents` (its numbered series is a record).","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}/publish":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"post":{"operationId":"publishReport","tags":["reports"],"summary":"Publish the draft as the next version","description":"Documents are issued from the published version. Documents already issued keep the version they were made with.","responses":{"201":{"description":"The new version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishedReportVersion"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}/versions":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"get":{"operationId":"listReportVersions","tags":["reports"],"summary":"Published versions, newest first","responses":{"200":{"description":"Versions.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"live":{"type":"boolean"}},"required":["version","published_at","live"]}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}/versions/{version}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}},{"name":"version","in":"path","required":true,"description":"Version number.","schema":{"type":"integer","minimum":1}}],"get":{"operationId":"getReportVersion","tags":["reports"],"summary":"One published version with its definition","responses":{"200":{"description":"The version.","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"definition":{"$ref":"#/components/schemas/ReportDefinition"}},"required":["version","published_at","definition"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}/sample":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"get":{"operationId":"getReportSample","tags":["reports"],"summary":"The data the report designs against","description":"For a `summary` or `submissions` source: the data computed from the bound form's real submissions (in `since`–`until` when given), or, while the form has none at all, from made-up submissions shaped like its questions (`origin: synthetic`). For `submission`: the latest answers with the form's schema. For `data`: the definition's sample. `form` and `kind` override the saved source, for a working copy not saved yet. `schema` is the data's JSON Schema with the report's title. Nothing is stored or printed.","parameters":[{"name":"form","in":"query","description":"A form (frm_…) instead of the saved source's.","schema":{"type":"string","pattern":"^frm_"}},{"name":"kind","in":"query","description":"A source kind instead of the saved one.","schema":{"type":"string","enum":["data","submission","summary","submissions"]}},{"name":"since","in":"query","description":"Period start (YYYY-MM-DD), summary and submissions.","schema":{"type":"string","format":"date"}},{"name":"until","in":"query","description":"Period end (YYYY-MM-DD), inclusive.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The data, its schema and where it came from.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"schema":{"type":"object","description":"JSON Schema (root type object) of `data`.","additionalProperties":true},"origin":{"type":"string","enum":["submissions","synthetic","definition"],"description":"submissions: real answers; synthetic: made up, the form has none yet; definition: the definition's sample."},"count":{"type":"integer","description":"How many real submissions the data comes from (0 when synthetic)."},"source":{"type":"object","properties":{"kind":{"type":"string","enum":["data","submission","summary","submissions"]},"form":{"type":"string"}},"required":["kind"]}},"required":["data","schema","origin","count","source"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/reports/{id}/preview":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"post":{"operationId":"previewReport","tags":["reports"],"summary":"Render the draft and run preflight; nothing is stored","description":"Prints the saved draft (or `definition` from the body) with `data` (default: the report's sample), a `submission` or a `period`. A summary or submissions report previews the bound form's real submissions (the period's, when given), or made-up ones shaped like its questions while it has none. `format` png (default) answers JSON with the preflight `findings` and base64 PNG `images`; pdf answers the PDF itself with `Preflight-Errors` and `Preflight-Warnings` headers; html answers JSON with the page's HTML. Fix every finding with severity error before publishing. Shares the render rate limit (30/min).","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewRequest"}}}},"responses":{"200":{"description":"The preview, in the requested format.","headers":{"Preflight-Errors":{"schema":{"type":"integer"},"description":"format=pdf only: the number of error findings."},"Preflight-Warnings":{"schema":{"type":"integer"},"description":"format=pdf only: the number of warning findings."}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PreviewResult"},{"$ref":"#/components/schemas/HtmlPreview"}]}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}},"/reports/{id}/documents":{"parameters":[{"name":"id","in":"path","required":true,"description":"Report id (rpt_…).","schema":{"type":"string"}}],"post":{"operationId":"createDocument","tags":["documents"],"summary":"Make a document from the published version (issue it now, or keep a draft)","description":"`issue: true` assigns the next gapless number and stores the PDF; the document is then immutable (void it with a reason to cancel). Without it a draft is created. 409 if the report has no published version; 422 `invalid-data` (pointers into `data`) or `preflight-failed` (with `findings`). Shares the render rate limit (30/min).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDocument"}}}},"responses":{"201":{"description":"The document.","headers":{"Location":{"schema":{"type":"string"},"description":"/api/v1/documents/{id}"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentIssued"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict","description":"The report is not published (or the manual number is taken)."},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}},"/documents":{"get":{"operationId":"listDocuments","tags":["documents"],"summary":"List documents, newest first","parameters":[{"name":"report","in":"query","description":"Only this report (rpt_…).","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","issued","void"]}},{"name":"number","in":"query","description":"The exact document number.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring match.","schema":{"type":"string"}},{"name":"since","in":"query","description":"created_at >= (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","description":"created_at <= (ISO 8601).","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Page size (default 50).","schema":{"type":"integer","minimum":0,"default":50}},{"name":"offset","in":"query","description":"Items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of documents.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Document"}},"total":{"type":"integer"}},"required":["items","total"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/documents/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document id (doc_…).","schema":{"type":"string"}}],"get":{"operationId":"getDocument","tags":["documents"],"summary":"One document with its data","responses":{"200":{"description":"The document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentWithData"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateDocument","tags":["documents"],"summary":"Change a draft's data, locale or currency","description":"Drafts only: an issued or void document answers 409 `immutable`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"locale":{"type":"string"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}},"additionalProperties":false}}}},"responses":{"200":{"description":"The updated draft.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentWithData"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict","description":"`immutable`: the document is issued or void."},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteDocument","tags":["documents"],"summary":"Delete a draft","description":"Drafts only: an issued document keeps its number, so void it instead.","responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/documents/{id}/pdf":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document id (doc_…).","schema":{"type":"string"}}],"get":{"operationId":"getDocumentPdf","tags":["documents"],"summary":"The document's PDF","description":"An issued document serves the same bytes on every download (see its `pdf_sha256`).","responses":{"200":{"description":"The PDF (application/pdf), as an attachment named after the document number.","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"The filename is the document number."},"ETag":{"schema":{"type":"string"},"description":"The PDF's sha-256; send it back as If-None-Match for a 304."}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"Not modified (If-None-Match matched)."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}},"/documents/{id}/issue":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document id (doc_…).","schema":{"type":"string"}}],"post":{"operationId":"issueDocument","tags":["documents"],"summary":"Issue a draft: number, PDF, immutable","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"number":{"type":"string","description":"Manual numbering only."}},"additionalProperties":false}}}},"responses":{"200":{"description":"The issued document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentIssued"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict","description":"`already-issued`."},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}},"/documents/{id}/void":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document id (doc_…).","schema":{"type":"string"}}],"post":{"operationId":"voidDocument","tags":["documents"],"summary":"Void a document with a reason","description":"The document keeps its number (never reused) and its PDF prints VOID.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"],"additionalProperties":false}}}},"responses":{"200":{"description":"The void document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/documents/{id}/share/rotate":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document id (doc_…).","schema":{"type":"string"}}],"post":{"operationId":"rotateShareLink","tags":["documents"],"summary":"Replace the share link; the old one stops working","responses":{"200":{"description":"The new links.","content":{"application/json":{"schema":{"type":"object","properties":{"share_url":{"type":"string","format":"uri"},"share_pdf_url":{"type":"string","format":"uri"},"note":{"type":"string"}},"required":["share_url","share_pdf_url"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/submissions/{id}/pdf":{"parameters":[{"name":"id","in":"path","required":true,"description":"Submission id (sub_…).","schema":{"type":"string"}}],"get":{"operationId":"getSubmissionPdf","tags":["submissions"],"summary":"Print any submission through the built-in submission record","description":"No report needed: every answer of the submission, on A4. Not numbered and not stored.","responses":{"200":{"description":"The PDF.","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"The filename is the document number."},"ETag":{"schema":{"type":"string"},"description":"The PDF's sha-256; send it back as If-None-Match for a 304."}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}},"/assets":{"get":{"operationId":"listAssets","tags":["assets"],"summary":"List uploaded images","responses":{"200":{"description":"Assets, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Asset"}}},"required":["items"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"uploadAsset","tags":["assets"],"summary":"Upload a logo or image (PNG, JPEG, WebP, SVG; about 1 MB at most)","description":"Send the raw bytes with an image content type (`?name=` names it), or multipart with the field `file`. The type is sniffed from the bytes: a script disguised as an image is a 415, an SVG with active content a 422. Use the returned `ref` (`asset:ast_…`) as `appearance.logo` or in an image block. The bytes are public at `url` (no key), served with a nosniff header.","parameters":[{"name":"name","in":"query","description":"A file name to keep with the asset.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/svg+xml":{"schema":{"type":"string","format":"binary"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"The asset.","headers":{"Location":{"schema":{"type":"string"},"description":"/api/v1/assets/{id}"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"$ref":"#/components/responses/Problem"},"415":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Invalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/assets/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Asset id (ast_…).","schema":{"type":"string"}}],"get":{"operationId":"getAsset","tags":["assets"],"summary":"One asset's metadata","description":"The image bytes are served without a key at `{origin}/assets/{id}` (the asset's `url`), outside /api/v1.","responses":{"200":{"description":"The asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/d/{token}":{"servers":[{"url":"https://forecraft.contzo.lol","description":"Share views live at the site root, not under /api/v1."}],"parameters":[{"name":"token","in":"path","required":true,"description":"The share token in a document's `share_url`.","schema":{"type":"string"}}],"get":{"operationId":"viewSharedDocument","tags":["documents"],"summary":"Share view of a document (public, no key)","description":"An HTML page with the document and a download button, marked noindex. Replace the link with `POST /documents/{id}/share/rotate`.","security":[],"responses":{"200":{"description":"The share page.","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"No such link (never existed, or rotated)."},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/d/{token}.pdf":{"servers":[{"url":"https://forecraft.contzo.lol","description":"Share views live at the site root, not under /api/v1."}],"parameters":[{"name":"token","in":"path","required":true,"description":"The share token in a document's `share_url`.","schema":{"type":"string"}}],"get":{"operationId":"downloadSharedDocument","tags":["documents"],"summary":"The PDF behind a share link (public, no key)","security":[],"parameters":[{"name":"download","in":"query","description":"`1` sends it as an attachment instead of inline.","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The PDF.","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"The filename is the document number."},"ETag":{"schema":{"type":"string"},"description":"The PDF's sha-256; send it back as If-None-Match for a 304."}},"content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"No such link."},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/RendererBusy"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"fc_live_…","description":"A workspace API key: `Authorization: Bearer fc_live_…`. The dashboard's session cookie also works for same-origin calls."}},"responses":{"Problem":{"description":"Problem details (RFC 9457).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No such resource in this workspace.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Conflict":{"description":"State conflict (slug taken, form closed/full/unpublished).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Invalid":{"description":"The request, definition or submission is invalid; `errors[]` names each fault.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RendererBusy":{"description":"The renderer is busy (`renderer-busy`) or failed to print (`render-failed`): retry after `Retry-After` seconds. Or it is not installed on the server (`renderer-not-installed`, whose detail says to run `npx playwright install --only-shell chromium`) or could not start (`renderer-unavailable`).","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"Too many requests (240/min per key; 30 renders/min per key; 10 submissions/min per address).","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 problem details.","properties":{"type":{"type":"string","format":"uri","description":"e.g. https://forecraft.dev/problems/invalid-definition"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string","description":"The request path."},"errors":{"type":"array","description":"Present on 422: one entry per fault.","items":{"type":"object","properties":{"pointer":{"type":"string","description":"JSON pointer into the request body, e.g. /schema/properties/a/type."},"detail":{"type":"string"},"field":{"type":"string","description":"Submission faults: the field path."},"rule":{"type":"string","description":"Submission faults: the rule that failed (e.g. required, minLength, or a rules id)."}},"required":["pointer","detail"]}}},"required":["type","title","status"],"additionalProperties":true},"Workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","created_at"]},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"The first characters of the secret."},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"}},"required":["id","name","prefix","created_at"]},"ApiKeyCreated":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"secret":{"type":"string","pattern":"^fc_live_","description":"Shown once."},"note":{"type":"string"}},"required":["secret"]}]},"FormDefinition":{"type":"object","description":"The whole form in one document. Unknown top-level keys are refused.","properties":{"schema":{"type":"object","description":"JSON Schema in the subset @faqir-ui/forms renders: root type object with a title and at least one property; properties are scalars (string with format email|uri|date|…, enum, number, integer, boolean), nested objects, arrays of enum strings, or arrays of objects with scalar fields. Conditional keywords (if/then/else, dependentRequired) become rules.","properties":{"type":{"const":"object"},"title":{"type":"string","minLength":1,"description":"The form's title."},"description":{"type":"string"},"properties":{"type":"object","minProperties":1,"additionalProperties":true},"required":{"type":"array","items":{"type":"string"}}},"required":["type","title","properties"],"additionalProperties":true},"ui":{"type":"object","description":"Faqir UI schema. Per-field keys: widget (textarea, radio, select, switch, checkbox-group, multi-select, scale, ...), placeholder, rows, enumLabels, width (full|half|third), orientation, minLabel/maxLabel (scale), addLabel, removeLabel, items (repeatable row UI); forecraft adds prefix/suffix (number units) and verbatim (long text a summary may quote). Root keys: ui:groups [{title, description?, fields}] or ui:wizard {label, steps[{title, description?, fields, when?}]}).","additionalProperties":true},"rules":{"type":"object","description":"@faqir-ui/rules input: { rules: [{ id, show|require: field, when } | { id, validate, path, message } | { id, compute, value }], messages?, defaultLocale?, fields? }. Conditions are JSONLogic.","properties":{"rules":{"type":"array","items":{"type":"object","required":["id"],"additionalProperties":true}}},"additionalProperties":true},"appearance":{"$ref":"#/components/schemas/Appearance"}},"required":["schema"],"additionalProperties":false},"Appearance":{"type":"object","description":"How the public page looks. Every key is optional; see GET /vocabulary for the defaults.","properties":{"theme":{"type":"string","enum":["aurora","brutalist","candy","clay","clinical","contrast","deco","default","document","document-serif","editorial","editorial-document","fintech","glass","ink","ink-document","luxe","memphis","midnight","monolith","neo","neumorph","nordic","organic","paper","slate","soft","sunset","swiss","swiss-document","terminal"]},"accent":{"type":"string","enum":["theme","rose","red","orange","blue","indigo","violet","purple","fuchsia","pink"],"description":"\"theme\" keeps the theme's own primary colour."},"scheme":{"type":"string","enum":["auto","light","dark"]},"layout":{"type":"string","enum":["card","split","page"]},"cover":{"type":["object","null"],"properties":{"image":{"type":"string","description":"http(s) URL, a /path on this site, or empty."},"eyebrow":{"type":"string"}},"additionalProperties":false},"submitLabel":{"type":"string","minLength":1},"thankYou":{"type":"object","properties":{"title":{"type":"string"},"message":{"type":"string"}},"required":["title","message"]},"redirect":{"type":["string","null"],"format":"uri","description":"http(s) URL to send people to after a submission."}},"additionalProperties":false},"CreateForm":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/FormDefinition"},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$","description":"Public slug (3-64 chars). Omit to derive one from the title."}},"required":["definition"]},"FormPatch":{"type":"object","minProperties":1,"properties":{"definition":{"$ref":"#/components/schemas/FormDefinition"},"status":{"type":"string","enum":["open","closed"]},"submission_limit":{"type":["integer","null"],"minimum":1,"description":"null removes the limit."},"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$"}},"additionalProperties":false},"FormSummary":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["open","closed"]},"submission_limit":{"type":["integer","null"]},"published_version":{"type":["integer","null"],"description":"null until first published."},"has_unpublished_changes":{"type":"boolean"},"submission_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"url":{"type":"string","format":"uri","description":"The public page (live once published)."},"preview_url":{"type":"string","format":"uri","description":"The dashboard preview."}},"required":["id","slug","title","status","published_version","has_unpublished_changes","submission_count","created_at","updated_at","url","preview_url"]},"Form":{"allOf":[{"$ref":"#/components/schemas/FormSummary"},{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/FormDefinition"}},"required":["definition"]}]},"PublishedVersion":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"url":{"type":"string","format":"uri"}},"required":["version","published_at","url"]},"VersionSummary":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"live":{"type":"boolean"},"submission_count":{"type":"integer"}},"required":["version","published_at","live","submission_count"]},"Preview":{"type":"object","properties":{"html":{"type":"string","description":"The complete rendered page."},"audit":{"$ref":"#/components/schemas/Audit"},"rules":{"type":"object","description":"The compiled @faqir-ui/rules definition the page carries.","additionalProperties":true}},"required":["html","audit","rules"]},"Audit":{"type":"object","properties":{"passed":{"type":"boolean"},"counts":{"type":"object","properties":{"critical":{"type":"integer"},"error":{"type":"integer"},"warning":{"type":"integer"},"info":{"type":"integer"}},"required":["critical","error","warning","info"]},"findings":{"type":"array","items":{"type":"object","properties":{"rule_id":{"type":"string"},"severity":{"type":"string"},"line":{"type":"integer"},"message":{"type":"string"}},"required":["rule_id","severity","line","message"]}}},"required":["passed","counts","findings"]},"Verdict":{"type":"object","properties":{"valid":{"type":"boolean"},"findings":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"rule":{"type":"string"},"message":{"type":"string"},"params":{"type":"object","additionalProperties":true}},"required":["path","rule","message"]}},"data":{"type":"object","additionalProperties":true,"description":"What would be stored: coerced, hidden fields dropped, computed values filled in."}},"required":["valid","findings","data"]},"SubmissionBody":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true,"description":"Answers keyed by field name."}},"required":["data"]},"SubmissionReceipt":{"type":"object","properties":{"id":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"thank_you":{"type":"object","properties":{"title":{"type":"string"},"message":{"type":"string"}}},"redirect":{"type":["string","null"]}},"required":["id","created_at"]},"Submission":{"type":"object","properties":{"id":{"type":"string"},"form_id":{"type":"string"},"version":{"type":"integer","description":"The published version it was made against."},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":true}},"required":["id","form_id","version","created_at","data"]},"ReportVocabulary":{"type":"object","properties":{"blocks":{"type":"array","items":{"type":"object","properties":{"block":{"type":"string"},"group":{"type":"string"},"does":{"type":"string"},"props":{"type":"array","items":{"type":"string"}},"example":{"type":"object","additionalProperties":true}},"required":["block","does","props","example"]}},"common_props":{"type":"object","additionalProperties":{"type":"string"}},"helpers":{"description":"Expression helpers usable inside `{{ … }}`."},"system_values":{"type":"array","items":{"type":"string"}},"print_themes":{"type":"array","items":{"type":"string","enum":["document","document-serif","editorial-document","ink-document","swiss-document"]}},"type_pairings":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"note":{"type":"string"}},"required":["id"],"additionalProperties":true}},"papers":{"type":"array","items":{"type":"string"}},"orientations":{"type":"array","items":{"type":"string"}},"margins":{"type":"array","items":{"type":"string"}},"densities":{"type":"array","items":{"type":"string"}},"locales":{"type":"array","items":{"type":"string"}},"languages":{"type":"array","items":{"type":"string"}},"labels":{"type":"array","items":{"type":"string"}},"appearance_defaults":{"type":"object","additionalProperties":true},"numbering":{"type":"object","additionalProperties":true},"money":{"type":"object","additionalProperties":true},"sources":{"type":"array","items":{"type":"object","additionalProperties":true}},"summary_shape":{"type":"object","description":"The JSON Schema of the data a summary report prints. Every share in it is a percentage on the 0–100 scale.","additionalProperties":true},"submissions_shape":{"type":"object","description":"The JSON Schema of the data a submissions (register) report prints.","additionalProperties":true},"units":{"type":"object","description":"What the numbers mean: share is 0–100; percent(value) reads value on the 0–100 scale.","additionalProperties":{"type":"string"}},"preflight_codes":{"type":"array","items":{"$ref":"#/components/schemas/PreflightCode"}}},"required":["blocks","print_themes","type_pairings","papers","preflight_codes"]},"PreflightCode":{"type":"object","properties":{"code":{"type":"string","enum":["missing-data","missing-asset","expression-error","overflow","cell-overflow","too-many-columns","window-overlap","qr-capacity","barcode-value","font-fallback","page-limit","orphan-heading","split-totals","blank-page","image-resolution"]},"severity":{"type":"string","enum":["error","warning"]},"checks":{"type":"string"}},"required":["code","severity","checks"]},"Starter":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"theme":{"type":"string"},"paper":{"type":"string"},"source":{"type":"string","enum":["data","submission","summary","submissions"]},"needsForm":{"type":"boolean","description":"true: `form` (frm_…) is required to create a report from it."},"blocks":{"type":"array","items":{"type":"string"}}},"required":["id","title","description","theme","paper","source","needsForm","blocks"]},"ReportAppearance":{"type":"object","description":"Curated print look: choose from these values. There are no sizes, fonts or colours to set, apart from the one accent.","properties":{"theme":{"type":"string","enum":["document","document-serif","editorial-document","ink-document","swiss-document"]},"accent":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","description":"One hex colour; at least 4.5:1 contrast on white."},"paper":{"type":"string","enum":["a4","letter"]},"orientation":{"type":"string","enum":["portrait","landscape"]},"margins":{"type":"string","enum":["narrow","normal","wide"]},"type":{"type":"string","enum":["plex-sans","plex-serif","source-serif","editorial","inter"],"description":"Type pairing id."},"density":{"type":"string","enum":["compact","comfortable"]},"locale":{"type":"string","description":"language-REGION, e.g. en-GB, en-US, en-IE, ro-RO, de-DE, de-AT, de-CH, fr-FR, fr-BE, fr-CH."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217."},"logo":{"type":["string","null"],"pattern":"^asset:ast_","description":"An uploaded asset's `ref`."},"letter":{"type":"object","properties":{"window":{"type":"boolean"},"foldMarks":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false},"ReportBlock":{"type":"object","description":"One block of the layout. `block` names it; the other keys are that block's props (GET /vocabulary/reports lists each block's props with an example). Text props take `{{ expression }}` holes; `when` is a bare expression.","properties":{"id":{"type":"string","pattern":"^[a-z0-9-]+$","description":"Stable and unique within the report."},"block":{"type":"string","enum":["letterhead","title","parties","facts","table","totals","text","callout","stats","bars","chart","quote","section","columns","signature","qr","barcode","image","stamp","watermark","separator","page-break","answers","footer","faqir"]},"when":{"type":"string","description":"Renders only when this expression is truthy."},"keep":{"type":"string","enum":["together","with-next"]}},"required":["id","block"],"additionalProperties":true},"ReportDefinition":{"type":"object","description":"The whole report in one document. Unknown top-level keys are refused.","properties":{"schema":{"type":"object","description":"JSON Schema (root type object, root title) of the data a document prints.","additionalProperties":true},"sample":{"type":"object","description":"Realistic data satisfying the schema; previews print it.","additionalProperties":true},"layout":{"type":"array","items":{"$ref":"#/components/schemas/ReportBlock"}},"appearance":{"$ref":"#/components/schemas/ReportAppearance"},"money":{"type":"object","description":"Exact money maths over the data: the names of the fields holding the lines, quantity, unit price, tax rate and so on.","properties":{"lines":{"type":"string"},"quantity":{"type":"string"},"unitPrice":{"type":"string"},"taxRate":{"type":"string"},"discount":{"type":"string"},"paid":{"type":"string"},"prices":{"type":"string","enum":["net","gross"]},"rounding":{"type":"string","enum":["line","total"]}},"required":["lines","quantity","unitPrice"],"additionalProperties":false},"numbering":{"type":"object","description":"Gapless numbering: a pattern with {YYYY}, {YY}, {MM}, {seq} or {seq:N}, or manual.","properties":{"pattern":{"type":"string","examples":["INV-{YYYY}-{seq:4}"]},"manual":{"type":"boolean"}},"additionalProperties":false},"documentTitle":{"type":"string"},"runningHeader":{"type":"string"},"labels":{"type":"object","additionalProperties":{"type":"string"}},"source":{"type":"object","description":"Where the data comes from: `data` (JSON you send), `submission` (a form's submission), `summary` (a form's answers over a period, aggregated) or `submissions` (a register: one row per submission in a period).","properties":{"kind":{"type":"string","enum":["data","submission","summary","submissions"]},"form":{"type":"string","description":"frm_… (every kind but data)."}},"required":["kind"],"additionalProperties":false}},"required":["schema","sample","layout"],"additionalProperties":false},"CreateReportFromStarter":{"type":"object","properties":{"starter":{"type":"string","description":"A starter id from GET /report-starters."},"title":{"type":"string"},"appearance":{"$ref":"#/components/schemas/ReportAppearance"},"sample":{"type":"object","description":"Each top-level key replaces the starter's sample key, e.g. a whole { seller: { … } }.","additionalProperties":true},"form":{"type":"string","description":"frm_… for the confirmation, survey-summary and submissions-register starters. A summary's or register's sample is then that form's data."}},"required":["starter"],"additionalProperties":false},"ReportSummary":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"starter":{"type":["string","null"]},"source":{"type":"object","additionalProperties":true},"published_version":{"type":["integer","null"],"description":"null until first published."},"has_unpublished_changes":{"type":"boolean"},"document_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"preview_url":{"type":"string","format":"uri","description":"POST here to preview."},"design_url":{"type":"string","format":"uri","description":"The dashboard designer."}},"required":["id","title","starter","source","published_version","has_unpublished_changes","document_count","created_at","updated_at","preview_url","design_url"]},"Report":{"allOf":[{"$ref":"#/components/schemas/ReportSummary"},{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/ReportDefinition"}},"required":["definition"]}]},"PublishedReportVersion":{"type":"object","properties":{"version":{"type":"integer"},"published_at":{"type":"string","format":"date-time"},"documents_url":{"type":"string","format":"uri","description":"POST here to issue documents."}},"required":["version","published_at","documents_url"]},"PreviewRequest":{"type":"object","description":"Everything is optional: an empty body previews the saved draft with its sample, as PNG.","properties":{"definition":{"$ref":"#/components/schemas/ReportDefinition"},"data":{"type":"object","additionalProperties":true},"submission":{"type":"string","description":"sub_… of a submission to print."},"period":{"type":"object","properties":{"since":{"type":"string"},"until":{"type":"string"}},"additionalProperties":false},"format":{"type":"string","enum":["png","pdf","html"],"default":"png"},"locale":{"type":"string"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"},"status":{"type":"string","enum":["draft","issued"],"default":"issued","description":"Print as a draft (marked, no number) or as issued."},"pages":{"type":"integer","minimum":1,"maximum":8,"default":4,"description":"format=png: how many pages to image."},"dpi":{"type":"integer","minimum":36,"maximum":150,"default":100,"description":"format=png: image resolution."}},"additionalProperties":false},"Finding":{"type":"object","description":"A preflight finding. Severity error blocks issuing; warning does not.","properties":{"severity":{"type":"string","enum":["error","warning"]},"code":{"type":"string","description":"One of the preflight codes (GET /vocabulary/reports)."},"message":{"type":"string"},"block":{"type":["string","null"],"description":"The id of the block it concerns."},"page":{"type":"integer"},"path":{"type":"string"}},"required":["severity","code","message"],"additionalProperties":true},"PreviewResult":{"type":"object","properties":{"ok":{"type":"boolean","description":"false when any finding has severity error."},"title":{"type":"string"},"pages":{"type":"integer","description":"Total pages of the document."},"render_ms":{"type":"integer"},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"images":{"type":"array","items":{"type":"object","properties":{"page":{"type":"integer"},"dpi":{"type":"integer"},"type":{"type":"string","const":"image/png"},"data":{"type":"string","contentEncoding":"base64","description":"The PNG."}},"required":["page","dpi","type","data"]}},"money":{"type":["object","null"],"description":"The computed totals, when the report has money.","additionalProperties":true}},"required":["ok","title","pages","findings","images"]},"HtmlPreview":{"type":"object","properties":{"html":{"type":"string"},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"title":{"type":"string"}},"required":["html","findings","title"]},"CreateDocument":{"type":"object","description":"The data comes from `data`, a `submission` or a `period`, depending on the report's source.","properties":{"data":{"type":"object","additionalProperties":true,"description":"Matches the report's schema; mistakes are a 422 with pointers."},"submission":{"type":"string","description":"sub_…"},"period":{"type":"object","properties":{"since":{"type":"string"},"until":{"type":"string"}},"additionalProperties":false},"issue":{"type":"boolean","default":false,"description":"true: number it and store the PDF now. false: create an editable draft."},"number":{"type":"string","description":"Manual numbering only."},"locale":{"type":"string"},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}},"additionalProperties":false},"Document":{"type":"object","properties":{"id":{"type":"string"},"report_id":{"type":"string"},"version":{"type":"integer","description":"The published report version it was made with."},"status":{"type":"string","enum":["draft","issued","void"]},"number":{"type":["string","null"],"description":"null for drafts."},"title":{"type":"string"},"locale":{"type":"string"},"currency":{"type":"string"},"submission_id":{"type":["string","null"]},"issued_at":{"type":["string","null"],"format":"date-time"},"voided_at":{"type":["string","null"],"format":"date-time"},"void_reason":{"type":["string","null"]},"pages":{"type":["integer","null"]},"pdf_sha256":{"type":["string","null"],"description":"The hash of the issued PDF."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"pdf_url":{"type":"string","format":"uri","description":"GET with a key."},"share_url":{"type":"string","format":"uri","description":"Public view: anyone with the link."},"share_pdf_url":{"type":"string","format":"uri","description":"Public PDF: anyone with the link."}},"required":["id","report_id","version","status","number","title","locale","currency","created_at","updated_at","pdf_url","share_url","share_pdf_url"]},"DocumentWithData":{"allOf":[{"$ref":"#/components/schemas/Document"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":true}},"required":["data"]}]},"DocumentIssued":{"allOf":[{"$ref":"#/components/schemas/Document"},{"type":"object","properties":{"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"},"description":"Preflight warnings (errors would have refused the request)."}},"required":["findings"]}]},"Asset":{"type":"object","properties":{"id":{"type":"string"},"ref":{"type":"string","pattern":"^asset:ast_","description":"Use this in appearance.logo or an image block."},"type":{"type":"string","enum":["image/png","image/jpeg","image/webp","image/svg+xml"]},"name":{"type":"string"},"size":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"sha256":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"url":{"type":"string","format":"uri","description":"The bytes, public (no key)."}},"required":["id","ref","type","name","size","sha256","created_at","url"]},"Vocabulary":{"type":"object","properties":{"themes":{"type":"array","items":{"type":"string"}},"accents":{"type":"array","items":{"type":"string"}},"layouts":{"type":"array","items":{"type":"string"}},"schemes":{"type":"array","items":{"type":"string"}},"appearance_defaults":{"type":"object","additionalProperties":true}},"required":["themes","accents","layouts","schemes","appearance_defaults"]}}}}