{"openapi":"3.1.0","info":{"title":"Apply2Volunteer API","version":"1.0.0","description":"Convert volunteer PDFs into progressive online forms. Organization owners and admins create API keys in the dashboard. Send a key as Authorization: Bearer a2v_... The token starts with a2v_. This API does not implement OAuth. Public submit and anonymous grade or convert do not accept the key in place of a Turnstile token. Keyless agents use /mcp and POST /a2a for anonymous grade and preview.","license":{"name":"Proprietary","url":"https://applytovolunteer.com/terms"}},"servers":[{"url":"https://applytovolunteer.com"}],"security":[{"orgApiKey":[]}],"components":{"securitySchemes":{"orgApiKey":{"type":"http","scheme":"bearer","bearerFormat":"a2v API key","description":"Organization API key. REST reads Authorization and accepts only the exact prefix \"Bearer \" (capital B, one space) followed by a token that starts with a2v_. Put the a2v_ token in the bearer credential. Do not include the word Bearer in the token. Owners and admins create keys at /app/settings/api-keys. The secret is shown once. Revoked keys are rejected. This API does not implement OAuth. Scopes are forms:read, forms:write, applications:read, and applications:write. forms:write does not include forms:read. A new key defaults to forms:read and forms:write. applications:read and applications:write are opt-in. A browser session cookie is also accepted on organization routes. Agents send the bearer key."}},"schemas":{"ApiError":{"type":"object","properties":{"error":{"type":"string","description":"Short error code or message."},"message":{"type":"string","description":"Human-readable detail when the handler sends one."},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages."},"retryable":{"type":"boolean","description":"Set on some public submit conflicts."}},"required":["error"]}},"parameters":{}},"paths":{"/api/v1/me":{"get":{"operationId":"getCurrentSession","summary":"Current session and organizations","description":"Current session and orgs. An org API key is not read. Only a browser session cookie is. With no cookie the response is 200 and user is null.","security":[],"responses":{"200":{"description":"Current session and orgs"}}}},"/api/v1/auth/magic-link":{"post":{"operationId":"sendMagicLink","summary":"Send a magic-link sign-in email","description":"Send a magic link. No API key.","security":[],"requestBody":{"required":true,"description":"JSON object with an email address.","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Required. Trimmed and lowercased. Any non-empty string is accepted."},"next":{"type":"string","description":"Post-login path. Kept only for /app, /app/..., or /tools/p/{token}. Any other value, including a non-string, is ignored."},"ref":{"type":"string","description":"Campaign slug of letters, numbers, hyphens, and underscores, up to 32 characters. Email-shaped values are dropped. Non-strings are ignored."}},"required":["email"],"description":"JSON object. email is required."}}}},"responses":{"200":{"description":"Send a magic link"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/auth/verify":{"get":{"operationId":"verifyMagicLink","summary":"Consume a magic link and start a session","description":"Consume a magic link. No API key. Success redirects (302) to the next path or the dashboard.","security":[],"parameters":[{"schema":{"type":"string","description":"Magic-link token from the email."},"required":true,"description":"Magic-link token from the email.","name":"token","in":"query"}],"responses":{"200":{"description":"Consume a magic link"},"302":{"description":"Redirects to /app, a safe next path, or the claimed form."},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/conversions":{"post":{"operationId":"createConversion","summary":"Start a PDF or URL conversion","description":"Ingest a PDF/URL (auth, rate-limited). Requires a browser session or an org API key with forms:write. The key's organization must match orgId when orgId is set.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Organization id. An API key uses its own organization when this is omitted, and must match it when it is set. A browser session uses the caller's first organization when this is omitted."},"required":false,"description":"Organization id. An API key uses its own organization when this is omitted, and must match it when it is set. A browser session uses the caller's first organization when this is omitted.","name":"orgId","in":"query"}],"requestBody":{"required":true,"description":"Send JSON { url } or multipart/form-data with file. One of the two is required.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"http(s) URL of a PDF, Word file, or HTML page. The server fetches it. Maximum 15 MB."}},"required":["url"],"description":"JSON alternative to multipart file upload."}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"PDF, Word (.docx), or image. Maximum 15 MB.","format":"binary"}},"required":["file"],"description":"Multipart alternative to a JSON url."}}}},"responses":{"202":{"description":"Conversion queued. Poll GET /api/v1/conversions/{id}.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Conversion id."}},"required":["id"]}}}},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No signed-in session and no live org API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The API key is missing a required scope, or the action is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/conversions/{id}":{"get":{"operationId":"getConversion","summary":"Poll a conversion","description":"Poll a conversion. Requires a browser session or an org API key with forms:read. A missing or wrong key is 404, not 401.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Conversion id."},"required":true,"description":"Conversion id.","name":"id","in":"path"}],"responses":{"200":{"description":"Poll a conversion"},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms":{"get":{"operationId":"listForms","summary":"List forms in an organization","description":"List org forms. Requires a browser session or an org API key with forms:read. A missing or wrong key is 404, not 401.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Organization id."},"required":true,"description":"Organization id.","name":"orgId","in":"query"}],"responses":{"200":{"description":"List org forms"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"createForm","summary":"Create a form from a conversion or a blank form","description":"Create a form from a conversion or blank. Requires forms:write on the orgId in the body. No session and no key is 401. A key for another organization is 404. A key missing forms:write is 403. A blank create from an org API key shares 60 per organization per hour with MCP. A website session is not counted.","security":[{"orgApiKey":[]}],"requestBody":{"required":true,"description":"JSON object. orgId is required.","content":{"application/json":{"schema":{"type":"object","properties":{"orgId":{"type":"string","description":"Organization id. The key must belong to this organization and include forms:write."},"conversionId":{"type":"string","description":"Finished conversion to copy. Ignored when fromReportId or fromPreviewId is set."},"title":{"type":"string","maxLength":200,"description":"Form title, at most 200 characters. Falls back to the source title, then Volunteer application."},"blank":{"type":"boolean","description":"Accepted and ignored. A blank form is created when conversionId, fromReportId, and fromPreviewId are all omitted."},"fromReportId":{"type":"string","description":"Public grade report to rebuild from. Wins over fromPreviewId and conversionId."},"fromPreviewId":{"type":"string","description":"Converter preview to rebuild from. Used when fromReportId is omitted. Wins over conversionId."}},"required":["orgId"],"description":"JSON object. orgId is required. Omit the three source ids to create a blank form."}}}},"responses":{"200":{"description":"Create a form from a conversion or blank"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No signed-in session and no live org API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The API key is missing a required scope, or the action is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}":{"get":{"operationId":"getForm","summary":"Get a form and its current version","description":"Get a form and its current version. Requires a browser session or an org API key with forms:read. A missing or wrong key is 404, not 401.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"responses":{"200":{"description":"Get a form and its current version"},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"updateForm","summary":"Update a form title, settings, or fields","description":"Update form title, settings, or fields. Add a field by omitting its id. op add also inserts a field and must not include an id. Remove a field with op remove or removeFieldIds. New field ids are returned on added. Requires a browser session or an org API key with forms:write. A missing or wrong key is 404, not 401. An org API key shares 60 updates per organization per hour with MCP, including adds and removes. A website session is not counted. A field id that is not on the current draft is 409. A field whose key has answers or consents on any version of the form cannot be removed. A field key that already has answers or consents cannot be changed. Help text is at most 2000 characters.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"requestBody":{"required":true,"description":"JSON object with title, settings, fields, or removeFieldIds.","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200,"description":"Form title, at most 200 characters. On a published form this is held as a draft until publish."},"settings":{"type":"object","properties":{"description":{"type":["string","null"],"description":"Public description. Null or an empty string clears the drafted description."},"indexable":{"type":"boolean","description":"Whether the published form may be indexed."},"confirmationMessage":{"type":["string","null"],"description":"Confirmation copy. Null or an empty string clears the drafted message."},"notifyEmails":{"type":"array","items":{"type":"string"},"description":"Addresses notified of a new application. Replaces the live list."}},"description":"description, indexable, confirmationMessage, and notifyEmails are type-checked. Other keys are stored on the live settings object. editorSession, editorSeq, and any top-level key whose name starts with __ are ignored. The stored settings JSON is at most 32 KB."},"fields":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string","enum":["add","update","remove"],"description":"add inserts a field. remove deletes it. update, or omitted with an id, patches that field. Omitted with no id inserts."},"id":{"type":"string","description":"Field id on the current draft. Required to update or remove. An op add row must omit id. The server assigns a new id."},"type":{"type":"string","enum":["short_text","long_text","email","phone","number","date","single_select","multi_select","yes_no","address","availability_grid","file","signature","consent"],"description":"Known field type. Unknown strings are rejected."},"label":{"type":"string","maxLength":500,"description":"Non-blank label, at most 500 characters. Omitted labels stay as stored."},"helpText":{"type":["string","null"],"maxLength":2000,"description":"Help text, at most 2000 characters. Null clears it."},"required":{"type":["boolean","null"],"description":"Whether the field is required. Null is accepted."},"options":{"type":["array","null"],"items":{"type":"string"},"description":"Choice options. Select and choice types need a non-empty list when type or options is sent. yes_no ignores stored options."},"stageId":{"type":["string","null"],"description":"Stage id on this form version. Null is rejected. An unknown stage id is rejected."},"order":{"type":"integer","minimum":0,"description":"Zero-based order. Two fields cannot share an order."},"key":{"type":"string","description":"Stable field key. Blank keys are rejected."},"sensitive":{"anyOf":[{"type":"boolean","enum":[false]},{"type":"string","enum":["dob","ssn","drivers_license","criminal_history","abuse_cps","driving_record","medical","minor_info","financial","protected_class","other"]},{"type":"null"}],"description":"false or null clears sensitivity. Otherwise one of the known sensitive kinds."},"consentKind":{"description":"Known consent kind, or null to clear it.","anyOf":[{"type":"string","enum":["general_waiver","background_check_authorization","photo_release","code_of_conduct","confidentiality","medical_release","custom"]},{"type":"null"}]},"consentText":{"type":["string","null"],"description":"Consent copy. Null clears it."}},"additionalProperties":false,"description":"Partial field update, or an insert. An insert requires label and type. helpText, required, options, and stageId are optional. stageId defaults to the Interest stage, or the first stage. New fields are appended. Unknown keys, including grid and validation, are rejected. Omitted keys are not written on an update. At most 20 inserts per call."},"description":"Field rows to update, add, or remove. The value must be an array when present. At most 20 adds per call. A form can hold at most 140 fields."},"removeFieldIds":{"type":"array","items":{"type":"string"},"description":"Field ids to delete from the current draft. A field whose key has answers or consents on any version of this form is 409. A field id that is not on the draft is 409. A malformed id is 400. Combined with op remove, at most 20 deletes per call."}},"description":"JSON object. An empty object changes nothing. title must be a string when present. Added field ids are returned on added. Adding or removing fields counts toward the shared 60 updates per organization per hour."}}}},"responses":{"200":{"description":"Update form title, settings, or fields. Add a field by omitting its id. op add also inserts a field and must not include an id. Remove a field with op remove or removeFieldIds. New field ids are returned on added."},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}/publish":{"post":{"operationId":"publishForm","summary":"Publish a new locked form version","description":"Publish a new locked version. A form with no fields is rejected. Publish is refused when it would remove a field that already has answers or consents. Requires a browser session or an org API key with forms:write. A missing or wrong key is 404, not 401. No request fields are read. A form with no fields is 400. Dropping a field key that already has answers or consents is 409.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"requestBody":{"required":false,"description":"No request body. The handler does not read one.","content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"description":"No fields. The handler does not read a body."}}}},"responses":{"200":{"description":"Publish a new locked version. A form with no fields is rejected. Publish is refused when it would remove a field that already has answers or consents."},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The form could not be published.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}/repair-identities":{"post":{"operationId":"repairFormFieldIdentities","summary":"Remint invalid draft field ids","description":"Remint invalid draft field ids (owner). Requires a browser session or an org API key with forms:write. A missing or wrong key is 404, not 401. No request fields are read.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"requestBody":{"required":false,"description":"No request body. The handler does not read one.","content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"description":"No fields. The handler does not read a body."}}}},"responses":{"200":{"description":"Remint invalid draft field ids (owner)"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}/discard":{"post":{"operationId":"discardFormDraft","summary":"Discard the open draft version","description":"Discard the open draft version. Requires a browser session or an org API key with forms:write. A missing or wrong key is 404, not 401. No request fields are read.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"requestBody":{"required":false,"description":"No request body. The handler does not read one.","content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"description":"No fields. The handler does not read a body."}}}},"responses":{"200":{"description":"Discard the open draft version"},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}/grade":{"get":{"operationId":"gradeSavedForm","summary":"Grade the current saved form version","description":"Owner/org grade for the current form version (auth). Use this for org-internal grades; they have no public /tools/r/{id} share link. Requires a browser session or an org API key with forms:read. A missing or wrong key is 404, not 401.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"}],"responses":{"200":{"description":"Owner/org grade for the current form version (auth). Use this for org-internal grades; they have no public /tools/r/{id} share link."},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The file type is not a PDF, Word document, or HTML page this route accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The upload contains filled-in personal data. Send a blank form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/forms/{id}/applications":{"get":{"operationId":"listFormApplications","summary":"List applications for a form","description":"List applications for a form. Requires a browser session or an org API key with applications:read. A missing or wrong key is 404, not 401. forms:read does not include this scope.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Form id."},"required":true,"description":"Form id.","name":"id","in":"path"},{"schema":{"type":"string","description":"Exact application status to keep."},"required":false,"description":"Exact application status to keep.","name":"status","in":"query"},{"schema":{"type":"string","description":"Case-insensitive match against the applicant name and email."},"required":false,"description":"Case-insensitive match against the applicant name and email.","name":"q","in":"query"}],"responses":{"200":{"description":"List applications for a form"},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/applications/{id}":{"get":{"operationId":"getApplication","summary":"Get an application","description":"Get an application. Requires a browser session or an org API key with applications:read. A missing or wrong key is 404, not 401.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Application id."},"required":true,"description":"Application id.","name":"id","in":"path"}],"responses":{"200":{"description":"Get an application"},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"updateApplicationStatus","summary":"Update an application status","description":"Update application status (FCRA-gated decline). Requires a browser session or an org API key with applications:write. A missing or wrong key is 404, not 401. Declining requires declineReason background_check, not_a_fit, or incomplete.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Application id."},"required":true,"description":"Application id.","name":"id","in":"path"}],"requestBody":{"required":true,"description":"JSON object with status, notes, or declineReason.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["new","in_review","advanced","accepted","declined","withdrawn","on_hold"],"description":"Next application status. Omitted status keeps the current one."},"notes":{"type":"string","description":"Reviewer notes. Omitted notes keep the current notes."},"declineReason":{"type":"string","description":"Required when status is declined. The handler accepts only background_check, not_a_fit, incomplete. Any other value is 400, or 409 when the reason is usable but the background-check gate blocks the decline."}},"description":"At least one of status, notes, or declineReason. The handler rejects a body that is not this object."}}}},"responses":{"200":{"description":"Update application status (FCRA-gated decline)"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/public/forms/{org}/{form}/submit":{"post":{"operationId":"submitPublicForm","summary":"Submit stage 1 of a public form","description":"Public stage-1 submit. No org API key. Anonymous calls need a Turnstile token in production. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503. Dev and test skip verification. Keyless agents should use /mcp or POST /a2a instead of calling this route without a token. This route does not submit through /mcp or /a2a.","security":[],"parameters":[{"schema":{"type":"string","description":"Organization slug."},"required":true,"description":"Organization slug.","name":"org","in":"path"},{"schema":{"type":"string","description":"Form slug."},"required":true,"description":"Form slug.","name":"form","in":"path"}],"requestBody":{"required":true,"description":"JSON object. turnstileToken is required in production. An API key does not replace it.","content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"type":"array","items":{"type":"object","properties":{"fieldId":{"type":"string","description":"Field id on the published version."},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}},{"type":"object","additionalProperties":{}},{"type":"null"}],"description":"String, number, boolean, array of strings, object, or null."}},"required":["fieldId","value"]},"description":"Answers for the stage. Omitted answers are treated as an empty list. Required fields on the stage still fail validation."},"honeypot":{"type":"string","description":"Omit this. A non-empty string returns { ok: true } and does not save the application."},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Required in production. Dev and test (ENVIRONMENT exactly dev or test) skip verification. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503 with error turnstile_misconfigured. This token is not an org API key."},"applicantEmail":{"type":"string","description":"Applicant email. Checked when the stage requires an email field or a value is present."},"applicantName":{"type":"string","description":"Applicant name. Checked when the stage requires a name field."},"source":{"type":"string","description":"Pass \"embed\" for an embedded form. Any other value is stored as hosted."},"stageId":{"type":"string","description":"Stage to submit. Omitted uses the first stage."},"submissionId":{"type":"string","description":"Optional idempotency key. A string of 8 to 64 letters, numbers, underscores, or hyphens is stored. Any other value is ignored and does not fail the request. 32 or more characters can rotate a continue link when the same submission is retried."}},"required":["turnstileToken"],"description":"JSON object. turnstileToken is required in production. An org API key is not accepted in place of that token."}}}},"responses":{"200":{"description":"Public stage-1 submit"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The organization is still a demo, or the form is not published. Submissions stay off until the organization is claimed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The body is larger than 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","headers":{"Retry-After":{"description":"Seconds until this form's submit window resets. Clamped to 1 through 3600.","schema":{"type":"integer","minimum":1,"maximum":3600}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/public/continue/{token}/submit":{"post":{"operationId":"submitContinueStage","summary":"Submit a continue-link stage","description":"Continue-link stage submit. No org API key. Anonymous calls need a Turnstile token in production. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503. Dev and test skip verification. Keyless agents should use /mcp or POST /a2a instead of calling this route without a token.","security":[],"parameters":[{"schema":{"type":"string","description":"Continue-link token."},"required":true,"description":"Continue-link token.","name":"token","in":"path"}],"requestBody":{"required":true,"description":"JSON object. This route reads answers, honeypot, and turnstileToken. Other fields are accepted and ignored. turnstileToken is required in production.","content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"type":"array","items":{"type":"object","properties":{"fieldId":{"type":"string","description":"Field id on the published version."},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}},{"type":"object","additionalProperties":{}},{"type":"null"}],"description":"String, number, boolean, array of strings, object, or null."}},"required":["fieldId","value"]},"description":"Answers for the stage. Omitted answers are treated as an empty list. Required fields on the stage still fail validation."},"honeypot":{"type":"string","description":"Omit this. A non-empty string returns { ok: true } and does not save the application."},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Required in production. Dev and test (ENVIRONMENT exactly dev or test) skip verification. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503 with error turnstile_misconfigured. This token is not an org API key."},"applicantEmail":{"type":"string","description":"Applicant email. Checked when the stage requires an email field or a value is present."},"applicantName":{"type":"string","description":"Applicant name. Checked when the stage requires a name field."},"source":{"type":"string","description":"Pass \"embed\" for an embedded form. Any other value is stored as hosted."},"stageId":{"type":"string","description":"Stage to submit. Omitted uses the first stage."},"submissionId":{"type":"string","description":"Optional idempotency key. A string of 8 to 64 letters, numbers, underscores, or hyphens is stored. Any other value is ignored and does not fail the request. 32 or more characters can rotate a continue link when the same submission is retried."}},"required":["turnstileToken"],"description":"JSON object. turnstileToken is required in production. An org API key is not accepted in place of that token."}}}},"responses":{"200":{"description":"Continue-link stage submit"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"410":{"description":"The continue link is expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The body is larger than 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/tools/grade":{"post":{"operationId":"gradePublicForm","summary":"Grade a volunteer form","description":"Form Grader (upload, URL, pasted, or form id). id and reportUrl (/tools/r/{id}) are present only for public grades. Saved-form grades omit id and reportUrl; owners use GET /api/v1/forms/{id}/grade. Anonymous grades omit restricted field labels from parsed; the score still counts them. The API key is optional. Anonymous calls need a Turnstile token in production. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503. Dev and test skip verification. Keyless agents should use /mcp or POST /a2a instead of calling this route without a token. formId or formVersionId requires forms:read and does not use Turnstile. A key without forms:read is treated as anonymous.","security":[{"orgApiKey":[]},{}],"requestBody":{"required":true,"description":"JSON or multipart. Anonymous URL, file, text, and pasted grades need turnstileToken in production. Saved-form grades need forms:read and do not use Turnstile.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Page or file URL to grade or convert."},"text":{"type":"string","description":"Plain text of the form. Used when url, pasted, and form are absent."},"pasted":{"description":"Parsed form JSON, or a structure with title and fields."},"form":{"description":"Alias of pasted."},"formId":{"type":"string","description":"Saved form id. Requires an org API key with forms:read. Not anonymous. Convert rejects this."},"formVersionId":{"type":"string","description":"Saved form version id. Requires an org API key with forms:read. Convert rejects this."},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Required in production. Dev and test (ENVIRONMENT exactly dev or test) skip verification. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503 with error turnstile_misconfigured. This token is not an org API key."}},"description":"Send one input. Precedence is formId or formVersionId, then pasted or form, then url, then text. Do not send website; a non-empty string is rejected. Anonymous calls need turnstileToken in production. Saved-form grades do not."}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"PDF, Word (.docx), or image. Maximum 15 MB.","format":"binary"},"url":{"type":"string","description":"Used when file is absent."},"pasted":{"type":"string","description":"JSON text of a parsed form. Invalid JSON becomes an empty pasted form."},"text":{"type":"string","description":"Plain text. Used when file, url, and pasted are absent."},"turnstileToken":{"type":"string","description":"Turnstile token. Required in production for anonymous calls."},"cf-turnstile-response":{"type":"string","description":"Alias of turnstileToken. Ignored when turnstileToken is present, including when that value is an empty string."}},"description":"Multipart alternative. Do not send website; a non-empty string is rejected. Provide file, url, pasted, or text."}}}},"responses":{"200":{"description":"Form Grader (upload, URL, pasted, or form id). id and reportUrl (/tools/r/{id}) are present only for public grades. Saved-form grades omit id and reportUrl; owners use GET /api/v1/forms/{id}/grade. Anonymous grades omit restricted field labels from parsed; the score still counts them.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"report":{"type":"object","additionalProperties":{}},"parsed":{"type":"object","additionalProperties":{}},"convertPath":{"type":"string"},"reportUrl":{"type":"string"}},"required":["report","parsed","convertPath"]}}}},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No signed-in session and no live org API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The file type is not a PDF, Word document, or HTML page this route accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The upload contains filled-in personal data. Send a blank form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/tools/grade/{id}":{"get":{"operationId":"getPublicGrade","summary":"Fetch a public Form Grader report","description":"Fetch a public Form Grader report. Org-internal grades 404. Restricted field labels are omitted; the stored score is unchanged. No API key. Org-internal grades are 404.","security":[],"parameters":[{"schema":{"type":"string","description":"Public grade report id."},"required":true,"description":"Public grade report id.","name":"id","in":"path"}],"responses":{"200":{"description":"Fetch a public Form Grader report. Org-internal grades 404. Restricted field labels are omitted; the stored score is unchanged."},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/tools/convert":{"post":{"operationId":"createFormPreview","summary":"Create a public converter preview","description":"Public convert preview (size-capped). The API key is optional. Anonymous calls need a Turnstile token in production. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503. Dev and test skip verification. Keyless agents should use /mcp or POST /a2a instead of calling this route without a token. formId and formVersionId are rejected. A key with forms:read stores the preview on that organization. A key without forms:read is treated as anonymous.","security":[{"orgApiKey":[]},{}],"requestBody":{"required":true,"description":"JSON or multipart file, url, pasted form, or text. turnstileToken is required in production. formId and formVersionId are rejected.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Page or file URL to grade or convert."},"text":{"type":"string","description":"Plain text of the form. Used when url, pasted, and form are absent."},"pasted":{"description":"Parsed form JSON, or a structure with title and fields."},"form":{"description":"Alias of pasted."},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Required in production. Dev and test (ENVIRONMENT exactly dev or test) skip verification. A failed check is 400 with error turnstile. A missing Turnstile configuration is 503 with error turnstile_misconfigured. This token is not an org API key."}},"description":"Send a url, pasted form, form, or text. formId and formVersionId are rejected. Do not send website; a non-empty string is rejected. turnstileToken is required in production."}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"PDF, Word (.docx), or image. Maximum 15 MB.","format":"binary"},"url":{"type":"string","description":"Used when file is absent."},"pasted":{"type":"string","description":"JSON text of a parsed form. Invalid JSON becomes an empty pasted form."},"text":{"type":"string","description":"Plain text. Used when file, url, and pasted are absent."},"turnstileToken":{"type":"string","description":"Turnstile token. Required in production for anonymous calls."},"cf-turnstile-response":{"type":"string","description":"Alias of turnstileToken. Ignored when turnstileToken is present, including when that value is an empty string."}},"description":"Multipart alternative for convert. form fields are not accepted here. turnstileToken is required in production. Do not send website."}}}},"responses":{"200":{"description":"Public convert preview (size-capped)"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The file type is not a PDF, Word document, or HTML page this route accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The upload contains filled-in personal data. Send a blank form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/tools/previews/{id}":{"get":{"operationId":"getFormPreview","summary":"Fetch a converter preview","description":"Fetch a converter preview. No API key.","security":[],"parameters":[{"schema":{"type":"string","maxLength":22,"pattern":"^[A-Za-z0-9_-]{21}[AQgw]$","description":"Preview id."},"required":true,"description":"Preview id.","name":"id","in":"path"}],"responses":{"200":{"description":"Fetch a converter preview"},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/tools/previews/{id}/claim":{"post":{"operationId":"claimFormPreview","summary":"Claim a preview as an organization draft","description":"Claim a preview as an org draft form. 200 returns formId and versionId. 202 { pending: true } means the same organization already reserved this preview and the draft is still being saved; retry. 409 is a different organization. Requires an org API key with both forms:read and forms:write, or a browser session that is an owner or admin. forms:read resolves the organization. forms:write saves the draft. A key with only forms:write is 401. The organization comes from the key, not the body. No request fields are read.","security":[{"orgApiKey":[]}],"parameters":[{"schema":{"type":"string","maxLength":22,"pattern":"^[A-Za-z0-9_-]{21}[AQgw]$","description":"Preview id."},"required":true,"description":"Preview id.","name":"id","in":"path"}],"requestBody":{"required":false,"description":"No request body. The handler does not read one.","content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"description":"No fields. The handler does not read a body."}}}},"responses":{"200":{"description":"Claim a preview as an org draft form. 200 returns formId and versionId. 202 { pending: true } means the same organization already reserved this preview and the draft is still being saved; retry. 409 is a different organization."},"202":{"description":"The same organization already reserved this preview and the draft is still being saved. Body is { pending: true }. Retry the claim. This is not a conflict.","content":{"application/json":{"schema":{"type":"object","properties":{"pending":{"type":"boolean","enum":[true]}},"required":["pending"]}}}},"400":{"description":"The body, query, or input failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No signed-in session and no live org API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The API key is missing a required scope, or the action is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The resource does not exist, or it is hidden from this caller. Several organization routes return 404 instead of 401 when the key is missing or the row is in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The write conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The file type is not a PDF, Word document, or HTML page this route accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The upload contains filled-in personal data. Send a blank form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests from this network or organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Turnstile is not configured, or a required parser is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"webhooks":{}}