The Platform API contract is dated. The version of the Public OpenAPI Document is the Contract Revision: a date in YYYY-MM-DD form naming the day the latest Additive Change shipped. Every revision gets an entry on this page, newest first. An entry lists Added, Changed defaults, Deprecated, Removed, and Fixed items, and leaves out any heading with nothing to report. Older entries are kept as written; where a later revision changed something, the older entry links to it.
Current Contract Revision: 2026-10-26
How the contract evolves
Section titled “How the contract evolves”The compatibility promise below takes effect at general availability. Until then, a revision may still correct a shape inside /v1; its entry below says so and names what clients must update.
Within the /v1 path version, only Additive Changes ship: new optional inputs, new response fields, new node types, new enum values, loosened limits, new Publish Issue codes, and changed defaults. Your client stays compatible by keeping what it does not recognize.
Breaking Changes ship only under a new path version. They include removing or renaming a field, a node type, an enum value, or a Publish Issue code; adding a required input; tightening validation; changing a JSON type; and making a response field optional.
Deprecated items are marked deprecated in the Public OpenAPI Document with their replacement named. They stay accepted for the rest of /v1, are deprecated for at least six months before a new path version may drop them, and /v1 stays served for at least twelve months after /v2 is generally available.
2026-10-26
Section titled “2026-10-26”- Every operation carries one resource tag in place of the shared
platformtag: Identity, Usage, Forms, Templates, AI generation, Workspaces, Submissions, Workflows, Themes, or Domains. The document’stagslist names them in that order, and the API reference groups operations the same way. - Every operation description ends with its cost in API credits, after the permission it needs.
- Each member of a discriminated union, such as a node type or a workflow node, carries a
titlenaming its discriminator value. INVALID_INPUTissues carryparamswhen their message is built from values, such asfieldandafterforrange_order, so a client can write its own text for eachcode.
Changed defaults
Section titled “Changed defaults”- A second Form Title block on a page answers the issue code
form_title_duplicateinstead ofform_title_placement, which now only means a Form Title block on a page that is not a normal page. - Duplicate page or node public IDs in a blueprint answer
public_id_duplicate, and asincelater thanuntilon the submission list and export answersrange_order, instead of the genericcustomcode. - Only successful calls spend credits. A call that ends in a
4xxor5xxanswer, such as a blocked publish or an AI provider failure, returns its credits at once. Failed calls still count toward the API Minute Cap. See Credits and costs. generateFormstops after 30 seconds instead of 120 and answers503 AI_SERVICE_UNAVAILABLE. Generation usually finishes in a few seconds.
Removed
Section titled “Removed”unmarkFormTemplate(POST /v1/forms/{form_public_id}/unmark-template). A Private Template is no longer turned back into a form, matching the app; start a form from the template withduplicateForminstead. The path now answers404 NOT_FOUND.deleteWorkspace(DELETE /v1/workspaces/{workspace_public_id}) and thedefault_workspace_protectedissue code. Deleting a workspace takes every form in it offline and moves all of its items to Trash, and the workspace itself cannot be restored, so it is done in the app only. Move a form to Trash to retire a single form.- The
Idempotency-Keyrequest header oncreateForm,duplicateForm,generateForm,createFormWorkflow, andcreateWorkspace, theIdempotent-Replayedresponse header, the409 IDEMPOTENCY_KEY_IN_PROGRESSand422 IDEMPOTENCY_KEY_REUSEDcodes, and theidempotency_key_lengthissue code. A failed create writes nothing and spends no credits, so fix the cause and send it again. The header is now ignored.
2026-10-21
Section titled “2026-10-21”This revision completes form settings, adds custom share domains, themes, workspace management, and workflows. Every change is additive except two. publish, unpublish, and validate now refuse a Private Template, which the app never publishes; clients that call them on a template should stop. CSV exports now hold the same columns and answer text as the app’s Responses export; clients that parse Matrix answers as JSON from a CSV cell should read the per-row columns instead.
email_notifications_recipients_ownerandemail_notifications_recipient_respondentinsettings: the members who receive owner emails, and the Email block whose answer receives the respondent confirmation. Setting the respondent recipient needs the Pro featurerespondent_email_notifications. Stored email subjects and templates are kept on every write. See Settings.duplicate_response_ruleinaccess, on the form resource and on the Draft and Published Documents, and as a field ofupdateFormAccess(PATCH /v1/forms/{form_public_id}/access). See Access.listThemes(GET /v1/themes) andgetTheme(GET /v1/themes/{theme_id}), and a writablestyles.theme_idoncreateFormandupdateFormDraft.is_systemmarks a theme of the fomr catalog andis_new_form_defaultthe theme a new form receives. See Themes.listDomains(GET /v1/domains),setFormDomain(PUT /v1/forms/{form_public_id}/domain), anddeleteFormDomain(DELETE /v1/forms/{form_public_id}/domain), anddomainon the form resource. Setting a domain needs the Pro featuredomainsand thedomains:writepermission. See Domains.createWorkspace(POST /v1/workspaces),getWorkspace(GET /v1/workspaces/{workspace_public_id}),updateWorkspace(PATCH /v1/workspaces/{workspace_public_id}), anddeleteWorkspace(DELETE /v1/workspaces/{workspace_public_id}), with the app’s rules: unique names without regard to case, the app’s default color, reordering withposition, and a protected Default Workspace. Creating needs the Pro featureworkspacesand room under the newmax_workspacesvalue oflimit.key; every write needs theworkspace_managementpermission. Deleting a workspace moves its forms, Private Templates, and assets to Trash and unpublishes its published forms. See Workspaces.listFormWorkflows(GET /v1/forms/{form_public_id}/workflows),getFormWorkflow,createFormWorkflow,replaceFormWorkflow,enableFormWorkflow,disableFormWorkflow, anddeleteFormWorkflowunder/v1/forms/{form_public_id}/workflows: the Workflow Document with itsform_completedandgoogle_sheets_add_rownodes, the app builder’s graph checks, enabling and disabling, and the newmax_active_workflows_per_formvalue oflimit.key. Every call needsformsandworkflowsaccess. A dry run reports the same workflow-count and enabled-workflow issues as a real write, and an edge from a node back to itself is refused as in the app. See Workflows.listIntegrationConnections(GET /v1/integration-connections): the organization’s integration connections a workflow node can reference, without credentials, named as the app names them:display_nameisnullrather than a provider connection ID or placeholder. See Workflows.
Changed defaults
Section titled “Changed defaults”- Turning
email_notifications_enabled_owneron whileemail_notifications_recipients_owneris empty adds the token owner as the recipient, as the app does. - Changing
styles.theme_idclears the form’s color, font, border, and option layout overrides, as choosing a theme in the app does. - CSV exports hold the columns of the app’s Responses export. A Matrix question gets one column per row, headed
{question label}: {row label}or{node_public_id}.{row_value}, instead of one column of JSON. A question or Matrix row that only older published versions carry gets a column only when an exported submission answered it. See Exporting submissions. - CSV answers read as the Responses export writes them: Checkbox and Switch answers are
YesorNoinstead oftrueorfalse, dates and times use the question’s date and time format, and phone numbers are written in international format. - With
header=labeland nopublished_version, labels come from the newest published version, as in the app, instead of from the unpublished draft. - A Personal Access Token must stay valid for at least one hour: an
expires_atsooner than that is refused with400 INVALID_INPUT, as the app’s custom expiry picker already required. See Authentication.
- A new
meta_favicon_urlormeta_ogimage_urlmust usehttps, as in the app. A storedhttpvalue still round-trips unchanged. - Owner email recipients now match organization members without regard to case and are stored as the member’s email, as the app stores them.
- Changing
styles.theme_idon a draft that had no style row now creates the row instead of leaving the theme unchanged, and such a draft can be read. publishForm,unpublishForm, andvalidateFormrefuse a Private Template with400 INVALID_INPUTatis_template, as the app does: a template is never published.x-fomr-permissionon publish and unpublish now readsforms:write; form_publish.- Owner email settings created over the API are stored in the same shape as the app stores them, with the first recipient as the primary one.
- Duplicating a Private Template as another Private Template now records the source in
source_template_public_id, as copying a template does in the app. - An
Idempotency-KeyoncreateFormis bound to the targetworkspace_public_idas well as the body: retrying with the same key and body into another workspace answers422 IDEMPOTENCY_KEY_REUSEDinstead of replaying the first form. - Concurrent
createForm,generateForm,duplicateForm, andunmarkFormTemplatecalls can no longer take an organization pastmax_forms; the calls that would exceed it answer402 PLAN_LIMIT_REACHED. - Page order now matches the editor’s page list everywhere: in the Draft and Published Documents, Publish Issue paths, CSV export columns, notification emails, and workflow answers. Forms with a regular page order read as before. For an irregular stored order, an ending no longer lands between question pages, and a page linked to both a question page and an ending continues to the question page. See Ordering.
updateFormDraftstores the page order exactly as the editor does when you reorder pages, with one link from each page to the next, and removes extra links an older write could leave behind.- Export labels with
header=labelnow fall back to the question’s placeholder and show a piped answer as[Question], as the app does, instead of falling back to the node public ID and dropping the pipe. - The organization
public_idingetMeand thenameandmime_typeof a File Upload answer now declare the pattern and length limits they always had.
2026-10-17
Section titled “2026-10-17”This revision adds the Media and Progress Bar node types, back navigation, and number input settings. Every change is additive.
- The
medianode type: send a publicmedia_urlfrom a supported service and the server resolves the embed, returned as read-onlymedia_embed. An unsupported link is a400 INVALID_INPUTat itsmedia_urlpath. - The
progress_barnode type. Add it to any page and the server copies it to every page with the same meta. It does not count toward the node limits. allow_back_navigationinproperties, default1. See Settings, properties, access, and styles.number_style,number_button_icon,number_minmax,number_step, andnumber_whole_onlyonnumber_input, and the Publish Issue codeFORM_PUBLISH_NUMBER_SETTINGS_INVALID.input_background_color,input_text_color,logo_width, andis_logo_full_widthin theme overrides.
Changed defaults
Section titled “Changed defaults”- A page sent without a page-level
actionsnode gets a default one, last on the page, as in the editor:Starton the intro,Submiton the last question page, andNextelsewhere. Anactionsnode sent earlier on the page moves to the end.
- Blocking a user from the admin console or through a safety enforcement now revokes their Personal Access Tokens in the same transaction, with the
user_blockedreason, instead of only refusing them while the block lasts. - An admin blocking a member in Team members revokes that member’s tokens the same way. Unblocking does not restore them; the member creates new ones.
- A call the burst budget rejects no longer spends credits from the per-minute cap.
- A dry run with an
Idempotency-Keyno longer stores an outcome, so the real call can follow under the same key.
2026-10-11
Section titled “2026-10-11”This revision adds Conditional logic, multiple endings, workspaces, and Trash. Because the compatibility promise is not yet in effect, it also includes changes the compatibility promise would otherwise reserve for a new path version: DELETE /v1/forms/{form_public_id} moves a form to Trash, an intro page must be the first page, and Publish Issue codes and fields that were never returned are removed. Update clients that treat a delete as final, place an intro page later in the form, or match on the removed codes.
logic_rulesandcalculated_valueson the Form Blueprint and the Canonical Blueprint. Rule, condition, action, and Calculated value IDs are yours to choose, 1 to 22 URL-safe characters. OnupdateFormDraft, omitting either key keeps the stored list and an empty array removes it. Theredirectaction needs the Pro featureredirect_on_completion. See Conditional logic and Calculated values.- Multiple endings: a form holds one to ten final pages after every other page. The first is the Default ending; jump rules reach the others. An ending may hold
text,image,divider, andlayout_rowcontent and at most onethank_you, but no questions. Publishing several endings, or an ending with content beyond one Thank You, needs the Pro featurecustom_endings(402 PRO_FEATURE_REQUIRED, pathpages). See Endings. - Workspaces:
workspace_public_idon the form resource;listWorkspaces(GET /v1/workspaces); aworkspace_public_idfilter onlistForms, query parameter oncreateForm, and body field ongenerateForm; andmoveForm(POST /v1/forms/{form_public_id}/move), which needs the Pro featureworkspaces. A duplicate stays in its source’s workspace. See Workspaces. restoreForm(POST /v1/forms/{form_public_id}/restore): brings a form back from Trash as a draft. See Deleting and restoring a form.422 FORM_SAFETY_REJECTED: automated safety checks refused a publish, or a draft write that sets meta tags or respondent email content to abusive text.403 FORM_PUBLISH_EMAIL_UNVERIFIED: the token owner must verify their email address in the app before publishing.409 FORM_PUBLISH_REVISION_CONFLICT: the form changed during a publish. Publish again.validateFormalso reports what publish would refuse:FORM_PUBLISH_EMAIL_UNVERIFIED,PRO_FEATURE_REQUIREDwith the blocked path, andFORM_SAFETY_REJECTEDappear as Publish Issues, sovalid: truenow means publish will pass these checks too.
Changed defaults
Section titled “Changed defaults”deleteFormmoves the form to Trash instead of deleting it. The form is unpublished and answers404 NOT_FOUNDon every read until restored; Trash deletes it permanently after 30 days.- A
settingsorpropertieskey left out ofupdateFormDraftkeeps the form’s stored value instead of being reset to its default; send the default to reset it.createFormstill applies defaults to omitted keys, includingshow_branding: 0on Pro, as a form created in the app does. - An intro page must be the first page.
createFormandupdateFormDraftrefuse an intro page anywhere else with400 INVALID_INPUT(pages/<index>/type), and publish reports it asFORM_PUBLISH_BLUEPRINT_INVALID. Idempotency-Keyvalues are scoped to the organization and operation, not to the member: any token in the organization that resends a key with the same body gets the stored outcome, and a different body is still refused with422 IDEMPOTENCY_KEY_REUSED.- Forms in Trash count toward the plan’s form limit until they are permanently deleted.
GET /v1/usagecounts.formsincludes them, and the create, duplicate, generate, and unmark-template limit checks use that count. Restoring a form never answers402 PLAN_LIMIT_REACHED. - The limit of two concurrent submission exports applies to the whole organization. A third export still answers
503 SERVICE_UNAVAILABLEwithretry_after_seconds. - Draft Write Validation accepts up to ten final pages and endings with content beyond one Thank You node, and refuses a hidden ending or an intro page that holds a question. It no longer requires exactly one final page.
Deprecated
Section titled “Deprecated”thank_you_redirect_urlon the Thank You node. It had no effect since completion redirects moved to theredirectlogic action; a write that sends it is accepted and the value is ignored.
validateFormpredicts the versionpublishFormwill produce: an unchanged draft keeps the latest published version number instead of reporting the next one.- When the automated safety check cannot run, publish answers
503 SERVICE_UNAVAILABLEwithretry_after_secondsandRetry-Afterinstead of500 SERVER. - Removed the Publish Issue codes
FORM_PUBLISH_LOGIC_*,FORM_PUBLISH_PAGE_MISSING_QUESTION, andFORM_PUBLISH_THANK_YOU_REDIRECT_URL_INVALID, and the Publish Issue fieldslogic_rule_public_idandlogic_item_id. None was ever returned: publishing does not check logic rules, and a rule that no longer fits the form is skipped when respondents fill it in.
2026-09-28
Section titled “2026-09-28”This is the contract the API launched with. Because the compatibility promise is not yet in effect, it corrects two response shapes that the compatibility promise would otherwise reserve for a new path version: update clients that parse Publish Issue paths or File Upload answers.
- Templates listed in the public template gallery are invisible to the Platform API: every call on one answers
404 NOT_FOUND, including for the organization that maintains it. TheFORM_CONVERSION_BLOCKEDreasonenum reservespublic_template, which no call returns. Treat unknown reasons as a refusal you cannot clear. See Private Templates. 402 PRO_FEATURE_REQUIREDonduplicateFormwhen the source’s settings or properties use a feature the organization’s plan does not include, such as a form built on Pro and duplicated after a move to Free.featureandpathname the field; nothing is copied.ai_generation_failedissues ongenerateForm: when the AI cannot turn a prompt into a valid form, the call answers400 INVALID_INPUTwith one issue at path["prompt"].- A page
nameaccepts up to 255 characters on write.
Changed defaults
Section titled “Changed defaults”- Page
is_auto_jump_next_pageis derived from the page’s nodes on every read and write:trueon a non-final page that holds exactly one visible question of an eligible type,falseotherwise. The value you send is ignored, so writing back a pulled document can change a stored page flag.properties.auto_jump_next_pageremains the form setting. See Pages. generateFormspends its 10 credits when the call is admitted, for every attempt, including anIdempotency-Keyreplay and a call answered with409 IDEMPOTENCY_KEY_IN_PROGRESSor422 IDEMPOTENCY_KEY_REUSED. A replay still returns the stored form without contacting the AI provider.exportSubmissionsstreams rows newest published version first, then newest submission first within a version. An organization can run two exports at a time; a third answers503 SERVICE_UNAVAILABLEwithretry_after_seconds.x-fomr-permissionnames every permission an operation accepts:forms:read or templates:readfor form reads and validation,forms:write or templates:writefor writes,forms:write or templates:write; form_publishfor publish and unpublish, andforms:write and templates:writefor the two conversions. Submission operations remainforms:read. The permissions each call checks are unchanged.
- A Publish Issue
pathis an array of property names and numeric indexes, such as["pages", 1, "nodes", 0, "meta", "select_options_data"], and an empty array for a form-level issue. It was a slash-separated string, ornull. It now has the same shape as anINVALID_INPUTissue path. - A File Upload answer is always an array of file objects, even when the respondent uploaded one file, and files that have not finished uploading are left out. A single file was returned as a bare object.
- A page with
is_entrypoint: trueother than the first page is refused with400 INVALID_INPUTat["pages", <index>, "is_entrypoint"]. The document previously described the flag as resolved to one non-final page. - The draft write operation’s
operationIdisupdateFormDraft. It was published asreplaceFormDraft; the method and path are unchanged. - Response schemas no longer declare
additionalProperties: false, so clients generated from the document accept fields added by later revisions. Request schemas stay strict. - A matrix answer pair is a closed two-item tuple in the document.
- Error bodies, usage, list, and result shapes are named components, and list and export query parameters are described inline on each operation instead of through shared query components.
validateFormno longer lists503 SERVICE_UNAVAILABLE; it writes nothing and takes no write slot.GET /v1/openapi.jsonignores query strings and always returns the committed document.- The API Explorer sends requests to the origin it was opened on.
- Field descriptions for NPS legend labels, Linear Scale endpoint labels and range, and Rating stars now describe the right field.
2026-09-25
Section titled “2026-09-25”- The
npsnode type with a fixed 0–10 scale, numbers or smileys, score-group colors, and configurable legend labels. - Linear Scale supports
rating_start, reverse display order, and endpoint labels, matching the editor. - Input validation errors include
accepted_valuesfor node types and enum values. Theirpathis an array of property names and indexes, including the rejected property for unknown keys. - Plan-limit and Pro-feature errors include an
upgrade_url.
2026-09-12
Section titled “2026-09-12”validateForm(POST /v1/forms/{form_public_id}/validate) returnswould_publish_versionandshare_urlalongsidevalidandissues. These preview the next published version and its share link without a second form read. They describe the form at validation time and reserve no version. Whenvalid: false, the preview applies only after the Publish Issues are fixed.
2026-09-11
Section titled “2026-09-11”listSubmissions(GET /v1/forms/{form_public_id}/submissions): lists submissions as{ data, next_cursor }, with an opaque cursor overcreated_atand the submission public ID.order=descis the newest-first default;order=ascreturns the oldest first. Filter bystatus, inclusivesinceoruntil, andpublished_version; the default status iscompleted.getSubmission(GET /v1/forms/{form_public_id}/submissions/{submission_public_id}): reads one submission with its published version, timestamps, answers keyed by Form Node public ID, capturedhidden_values, and five-minute signed download URLs for File Upload answers.exportSubmissions(GET /v1/forms/{form_public_id}/submissions/export): synchronously streams one row per matching submission as an attachment. CSV is the default, JSONL is available withformat=jsonl, andheaderchooses current question labels (or labels from a requestedpublished_version) or stable Form Node public IDs for answer keys.- Partial Response reads through
status=partial,status=all, or a known partial submission ID. These need the Pro plan’spartial_response_accessfeature; a Free organization receives402 PRO_FEATURE_REQUIRED. Completed submissions remain available on every plan. - All three submission operations need
forms:readand cost one credit. Private Templates return no submissions. See Submissions.
2026-09-10
Section titled “2026-09-10”generateForm(POST /v1/forms/generate): synchronously generates a form from a prompt, creates it as an ordinary draft, and answers201with the form resource, its stored Draft Document, and that draft’sETagas a response header. The call can take up to 120 seconds and acceptsis_template: trueto create a Private Template. It needsforms:write, ortemplates:writefor a Private Template.- The
generateFormrequest body,{ prompt, is_template? }and nothing else.promptis trimmed and must contain 1 to 5,000 characters. Generated forms contain at most 10 pages, including the final page. - An API Request Cost of 10 credits for
generateForm, plus a separate per-IP cap of five generation requests per minute. The request must fit inside both organization budget policies before generation starts. - The
Idempotency-Keyrequest header ongenerateForm, with the same 24-hour successful-response replay ascreateForm. A replay neither contacts the AI provider nor creates a second form. A failed call leaves the key unused. 503 AI_SERVICE_UNAVAILABLEongenerateForm, withretry_after_secondsin the body and the same delay in theRetry-Afterresponse header. Wait, then retry with the same idempotency key.
Changed defaults
Section titled “Changed defaults”- AI-generated Thank You content is centered when a generated blueprint is consumed. A generated form with more than one non-final page defaults
styles.override_properties.page_actions_show_back_buttonto1when the provider omitted it; an explicit setting is preserved.
2026-09-09
Section titled “2026-09-09”duplicateForm(POST /v1/forms/{form_public_id}/duplicate): copies a form or a Private Template into a new draft and answers201with the copy, its stored Draft Document underdraft, and that draft’sETagas a response header. Page and block public IDs, theaccessschedule, the theme with any per-form override, and the hidden values are copied; the copy is never published and inherits no submissions. Needs read on the source and write on the kind the copy becomes.- The
duplicateFormbody,{ name?, is_template? }and nothing else. Left out,namegives the source name a copy suffix — “Contact us” becomes “Contact us copy”, then “Contact us copy (2)” — trimmed at the base to fit the 100-character limit, andis_templatekeeps the kind of the source. Sendingis_template: falseagainst a Private Template starts a form from it, recording the template insource_template_public_idand counting a use against it. - The
Idempotency-Keyrequest header onduplicateForm, with the same 24-hour semantics ascreateForm. The key covers the source ID along with the body. 402 PLAN_LIMIT_REACHEDonduplicateFormwhen the copy is a form. A copy that is a Private Template is not counted against the plan’s form limit.markFormTemplate(POST /v1/forms/{form_public_id}/mark-template): turns a form into a Private Template in place and answers200with the form resource. Thepublic_id, the draft, and the page and block IDs inside it are kept, and any custom domain binding is dropped. A resource that is already a Private Template answers200and writes nothing. Needs bothforms:writeandtemplates:write.unmarkFormTemplate(POST /v1/forms/{form_public_id}/unmark-template): turns a Private Template back into a form in place, keeping thepublic_id, the draft, and its IDs, and answers200with the form resource. The template details are kept dormant, so marking it again reuses them. The resource counts against the plan’s form limit again, so402 PLAN_LIMIT_REACHEDis possible. A resource that is already a form answers200and writes nothing. Needs bothtemplates:writeandforms:write. Removed in 2026-10-26.409 FORM_CONVERSION_BLOCKEDonmarkFormTemplate, carryingreason:publishedfor a form that is live, which unpublishing clears, andhas_submissionsfor a form that holds answers, which duplicating as a template works around.409 FORM_ARCHIVEDonmarkFormTemplateandunmarkFormTemplate, for an archived resource.
2026-09-08
Section titled “2026-09-08”validateForm(POST /v1/forms/{form_public_id}/validate): runs Publish Validation against the stored draft and answers200 { valid, issues }whether the draft is ready or not. It writes nothing. Needsforms:read, ortemplates:readfor a Private Template.publishForm(POST /v1/forms/{form_public_id}/publish): publishes the stored draft as the next version and answers the form resource. There are no preconditions and noIf-Match; the version number is computed under the form lock, so the last call wins. Every call publishes a new version. Needsform_publishplusforms:writeortemplates:write.unpublishForm(POST /v1/forms/{form_public_id}/unpublish): takes the form offline, so the share link stops serving. The version history is kept, so a form last live on version 3 republishes as version 4. A form already offline answers200and writes nothing. Needsform_publishplusforms:writeortemplates:write.closeForm(POST /v1/forms/{form_public_id}/close): setsaccess.is_closedand leaves any schedule alone.reopenForm(POST /v1/forms/{form_public_id}/reopen): clearsaccess.is_closedand clears whichever schedule currently holds the form closed — aclose_atin the past or anopen_atin the future — while keeping aclose_atthat has not arrived yet.updateFormAccess(PATCH /v1/forms/{form_public_id}/access): takes any ofis_closed,close_at, andopen_at. An omitted field keeps its current value, andnullon either date clears that schedule. A body that changes nothing answers200and writes nothing.deleteForm(DELETE /v1/forms/{form_public_id}): answers204with no body. The share link stops serving immediately, the form leaves every list and read, and its submissions stop being reachable. There is no restore in/v1. An archived form can still be deleted. Changed in 2026-10-11: delete now moves the form to Trash.- The Publish Issue shape on the wire:
code,message, a slash-separatedpathinto the Draft Document that isnullwhen the issue is about the whole form (an array since 2026-09-28), andpage_public_idandnode_public_idthat arenullwhen the issue is not about one page or one block. 422 FORM_PUBLISH_VALIDATION_FAILEDonpublishForm, carrying the sameissuesarrayvalidateFormreturns.409 NO_DRAFT_DURING_PUBLISHonvalidateFormandpublishForm, for a form that holds no draft.409 FORM_ARCHIVEDonvalidateForm,publishForm,closeForm,reopenFormandupdateFormAccess.unpublishFormis a no-op200on an archived form, because archiving already took it offline.402 PRO_FEATURE_REQUIREDonpublishForm, when the stored draft uses a feature the organization’s plan does not include.featurenames it andpathpoints at the Draft Document field, or is an empty string when the blocker is not a document field.
2026-09-07
Section titled “2026-09-07”replaceFormDraft(PUT /v1/forms/{form_public_id}/draft, renamedupdateFormDraftin 2026-09-28): sends the whole Draft Document back. Pages and blocks are matched by public ID and updated in place, so IDs, answer lineage, and answer pipes survive; an item without an ID is created with a minted ID; an item you leave out is removed with its answers kept; a removed ID you send again is restored. Array order is canonical, the first page is the entrypoint, and the response is the stored draft with a freshETagequal to what the nextGETreturns.- A
PUTwhose document already matches the stored draft writes nothing and returns the unchangedETag, which makes retries and repeated deploys free. dry_run=trueonreplaceFormDraft: validates and resolves every ID without writing, and answers200with the document the real call would have stored and noETag.409 FORM_ARCHIVED: a draft write against an archived form, which has no editable draft.
2026-09-06
Section titled “2026-09-06”createForm(POST /v1/forms): creates a form, or a Private Template withis_templatetrue, from a Form Blueprint. The response is the form resource with the stored Draft Document underdraftand itsETagas a response header, so every minted page and block ID arrives with the create.dry_run=trueoncreateForm: runs the same validation and ID minting, writes nothing, and answers200with the document the real call would have stored.- The
Idempotency-Keyrequest header oncreateForm, remembered for 24 hours per organization, withIdempotent-Replayed: trueon a replay. PLAN_LIMIT_REACHEDandPRO_FEATURE_REQUIREDbodies now carry the blocking limit or feature;SERVICE_UNAVAILABLEcarriesretry_after_seconds.
2026-09-05
Section titled “2026-09-05”listForms(GET /v1/forms): one page of forms, or of Private Templates withis_template=true. Filter withstatusandq, order withsortandorder, and page withlimit(1 to 100, default 25) and the opaquecursor. The envelope is{ data, next_cursor }.getForm(GET /v1/forms/{form_public_id}): the form resource —status(draft,published,archived),has_unpublished_changes,published_version,published_at,source_template_public_id,accesswith the derivedaccepting_submissions, submission counts,urls.editorandurls.share,preview_image_url, and timestamps.getFormDraft(GET /v1/forms/{form_public_id}/draft): the Draft Document, the whole form as it stands in the editor with hidden pages and blocks included, every ID present, sort orders renumbered from one inside each column, and every omitted setting filled in. The response carries a weakETagover the draft’s content.getFormPublished(GET /v1/forms/{form_public_id}/published): the Published Document, the live version after publish-time stripping.404 FORM_NOT_PUBLISHEDwhen nothing is published.- The form’s own
public_idinside the Draft and Published Documents, marked read-only.
2026-09-04
Section titled “2026-09-04”getUsage(GET /v1/usage): the plan, the plan limits, the organization’s form and submission counts, and the live API Burst, API Minute Cap and API Request Cost figures. The call is exempt from the budget and spends nothing. See Rate limits and plans.RateLimit-PolicyandRateLimitresponse fields on every authenticated response, with both policies always listed, andRetry-Afterin seconds on every 429.- The throttled error body:
API_COOLDOWNandTOO_MANY_REQUESTSnow carrypolicy,plan,limit,used,remaining,cost,window_seconds,cooldown_until,resets_at,retry_after_secondsandupgrade_urlbeside the envelope’scode,messageandrequest_id.
2026-09-03
Section titled “2026-09-03”- The first Contract Revision: the Form Blueprint write shape and the Canonical Blueprint read shape, with all 30 node types as named components.
- The Platform Error code catalog.
- The Publish Issue code catalog.
- The first two operations:
getMe(GET /v1/me) andrevokeCurrentToken(DELETE /v1/me/token). See Authentication. - The Platform Error envelope on the wire, with
X-Request-Idon every response andWWW-Authenticate: Bearer realm="platform.fomr.io"on every 401.
Deprecations
Section titled “Deprecations”Every deprecated field, node type, or enum value of the Public OpenAPI Document, with its replacement and the earliest point at which it may be removed: a new path version, at least six months after the deprecation date.
Deprecated items carry a Deprecated badge in the API reference, and each is listed under Deprecated in the entry of the revision that deprecated it.