Skip to content
Join our Slack

API changelog

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

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.

  • Every operation carries one resource tag in place of the shared platform tag: Identity, Usage, Forms, Templates, AI generation, Workspaces, Submissions, Workflows, Themes, or Domains. The document’s tags list 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 title naming its discriminator value.
  • INVALID_INPUT issues carry params when their message is built from values, such as field and after for range_order, so a client can write its own text for each code.
  • A second Form Title block on a page answers the issue code form_title_duplicate instead of form_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 a since later than until on the submission list and export answers range_order, instead of the generic custom code.
  • Only successful calls spend credits. A call that ends in a 4xx or 5xx answer, 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.
  • generateForm stops after 30 seconds instead of 120 and answers 503 AI_SERVICE_UNAVAILABLE. Generation usually finishes in a few seconds.
  • 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 with duplicateForm instead. The path now answers 404 NOT_FOUND.
  • deleteWorkspace (DELETE /v1/workspaces/{workspace_public_id}) and the default_workspace_protected issue 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-Key request header on createForm, duplicateForm, generateForm, createFormWorkflow, and createWorkspace, the Idempotent-Replayed response header, the 409 IDEMPOTENCY_KEY_IN_PROGRESS and 422 IDEMPOTENCY_KEY_REUSED codes, and the idempotency_key_length issue code. A failed create writes nothing and spends no credits, so fix the cause and send it again. The header is now ignored.

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_owner and email_notifications_recipient_respondent in settings: the members who receive owner emails, and the Email block whose answer receives the respondent confirmation. Setting the respondent recipient needs the Pro feature respondent_email_notifications. Stored email subjects and templates are kept on every write. See Settings.
  • duplicate_response_rule in access, on the form resource and on the Draft and Published Documents, and as a field of updateFormAccess (PATCH /v1/forms/{form_public_id}/access). See Access.
  • listThemes (GET /v1/themes) and getTheme (GET /v1/themes/{theme_id}), and a writable styles.theme_id on createForm and updateFormDraft. is_system marks a theme of the fomr catalog and is_new_form_default the theme a new form receives. See Themes.
  • listDomains (GET /v1/domains), setFormDomain (PUT /v1/forms/{form_public_id}/domain), and deleteFormDomain (DELETE /v1/forms/{form_public_id}/domain), and domain on the form resource. Setting a domain needs the Pro feature domains and the domains:write permission. See Domains.
  • createWorkspace (POST /v1/workspaces), getWorkspace (GET /v1/workspaces/{workspace_public_id}), updateWorkspace (PATCH /v1/workspaces/{workspace_public_id}), and deleteWorkspace (DELETE /v1/workspaces/{workspace_public_id}), with the app’s rules: unique names without regard to case, the app’s default color, reordering with position, and a protected Default Workspace. Creating needs the Pro feature workspaces and room under the new max_workspaces value of limit.key; every write needs the workspace_management permission. 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, and deleteFormWorkflow under /v1/forms/{form_public_id}/workflows: the Workflow Document with its form_completed and google_sheets_add_row nodes, the app builder’s graph checks, enabling and disabling, and the new max_active_workflows_per_form value of limit.key. Every call needs forms and workflows access. 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_name is null rather than a provider connection ID or placeholder. See Workflows.
  • Turning email_notifications_enabled_owner on while email_notifications_recipients_owner is empty adds the token owner as the recipient, as the app does.
  • Changing styles.theme_id clears 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 Yes or No instead of true or false, dates and times use the question’s date and time format, and phone numbers are written in international format.
  • With header=label and no published_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_at sooner than that is refused with 400 INVALID_INPUT, as the app’s custom expiry picker already required. See Authentication.
  • A new meta_favicon_url or meta_ogimage_url must use https, as in the app. A stored http value 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_id on 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, and validateForm refuse a Private Template with 400 INVALID_INPUT at is_template, as the app does: a template is never published. x-fomr-permission on publish and unpublish now reads forms: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-Key on createForm is bound to the target workspace_public_id as well as the body: retrying with the same key and body into another workspace answers 422 IDEMPOTENCY_KEY_REUSED instead of replaying the first form.
  • Concurrent createForm, generateForm, duplicateForm, and unmarkFormTemplate calls can no longer take an organization past max_forms; the calls that would exceed it answer 402 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.
  • updateFormDraft stores 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=label now 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_id in getMe and the name and mime_type of a File Upload answer now declare the pattern and length limits they always had.

This revision adds the Media and Progress Bar node types, back navigation, and number input settings. Every change is additive.

  • The media node type: send a public media_url from a supported service and the server resolves the embed, returned as read-only media_embed. An unsupported link is a 400 INVALID_INPUT at its media_url path.
  • The progress_bar node 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_navigation in properties, default 1. See Settings, properties, access, and styles.
  • number_style, number_button_icon, number_minmax, number_step, and number_whole_only on number_input, and the Publish Issue code FORM_PUBLISH_NUMBER_SETTINGS_INVALID.
  • input_background_color, input_text_color, logo_width, and is_logo_full_width in theme overrides.
  • A page sent without a page-level actions node gets a default one, last on the page, as in the editor: Start on the intro, Submit on the last question page, and Next elsewhere. An actions node 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_blocked reason, 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-Key no longer stores an outcome, so the real call can follow under the same key.

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_rules and calculated_values on the Form Blueprint and the Canonical Blueprint. Rule, condition, action, and Calculated value IDs are yours to choose, 1 to 22 URL-safe characters. On updateFormDraft, omitting either key keeps the stored list and an empty array removes it. The redirect action needs the Pro feature redirect_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, and layout_row content and at most one thank_you, but no questions. Publishing several endings, or an ending with content beyond one Thank You, needs the Pro feature custom_endings (402 PRO_FEATURE_REQUIRED, path pages). See Endings.
  • Workspaces: workspace_public_id on the form resource; listWorkspaces (GET /v1/workspaces); a workspace_public_id filter on listForms, query parameter on createForm, and body field on generateForm; and moveForm (POST /v1/forms/{form_public_id}/move), which needs the Pro feature workspaces. 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.
  • validateForm also reports what publish would refuse: FORM_PUBLISH_EMAIL_UNVERIFIED, PRO_FEATURE_REQUIRED with the blocked path, and FORM_SAFETY_REJECTED appear as Publish Issues, so valid: true now means publish will pass these checks too.
  • deleteForm moves the form to Trash instead of deleting it. The form is unpublished and answers 404 NOT_FOUND on every read until restored; Trash deletes it permanently after 30 days.
  • A settings or properties key left out of updateFormDraft keeps the form’s stored value instead of being reset to its default; send the default to reset it. createForm still applies defaults to omitted keys, including show_branding: 0 on Pro, as a form created in the app does.
  • An intro page must be the first page. createForm and updateFormDraft refuse an intro page anywhere else with 400 INVALID_INPUT (pages/<index>/type), and publish reports it as FORM_PUBLISH_BLUEPRINT_INVALID.
  • Idempotency-Key values 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 with 422 IDEMPOTENCY_KEY_REUSED.
  • Forms in Trash count toward the plan’s form limit until they are permanently deleted. GET /v1/usage counts.forms includes them, and the create, duplicate, generate, and unmark-template limit checks use that count. Restoring a form never answers 402 PLAN_LIMIT_REACHED.
  • The limit of two concurrent submission exports applies to the whole organization. A third export still answers 503 SERVICE_UNAVAILABLE with retry_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.
  • thank_you_redirect_url on the Thank You node. It had no effect since completion redirects moved to the redirect logic action; a write that sends it is accepted and the value is ignored.
  • validateForm predicts the version publishForm will 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_UNAVAILABLE with retry_after_seconds and Retry-After instead of 500 SERVER.
  • Removed the Publish Issue codes FORM_PUBLISH_LOGIC_*, FORM_PUBLISH_PAGE_MISSING_QUESTION, and FORM_PUBLISH_THANK_YOU_REDIRECT_URL_INVALID, and the Publish Issue fields logic_rule_public_id and logic_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.

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. The FORM_CONVERSION_BLOCKED reason enum reserves public_template, which no call returns. Treat unknown reasons as a refusal you cannot clear. See Private Templates.
  • 402 PRO_FEATURE_REQUIRED on duplicateForm when 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. feature and path name the field; nothing is copied.
  • ai_generation_failed issues on generateForm: when the AI cannot turn a prompt into a valid form, the call answers 400 INVALID_INPUT with one issue at path ["prompt"].
  • A page name accepts up to 255 characters on write.
  • Page is_auto_jump_next_page is derived from the page’s nodes on every read and write: true on a non-final page that holds exactly one visible question of an eligible type, false otherwise. The value you send is ignored, so writing back a pulled document can change a stored page flag. properties.auto_jump_next_page remains the form setting. See Pages.
  • generateForm spends its 10 credits when the call is admitted, for every attempt, including an Idempotency-Key replay and a call answered with 409 IDEMPOTENCY_KEY_IN_PROGRESS or 422 IDEMPOTENCY_KEY_REUSED. A replay still returns the stored form without contacting the AI provider.
  • exportSubmissions streams rows newest published version first, then newest submission first within a version. An organization can run two exports at a time; a third answers 503 SERVICE_UNAVAILABLE with retry_after_seconds.
  • x-fomr-permission names every permission an operation accepts: forms:read or templates:read for form reads and validation, forms:write or templates:write for writes, forms:write or templates:write; form_publish for publish and unpublish, and forms:write and templates:write for the two conversions. Submission operations remain forms:read. The permissions each call checks are unchanged.
  • A Publish Issue path is 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, or null. It now has the same shape as an INVALID_INPUT issue 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: true other than the first page is refused with 400 INVALID_INPUT at ["pages", <index>, "is_entrypoint"]. The document previously described the flag as resolved to one non-final page.
  • The draft write operation’s operationId is updateFormDraft. It was published as replaceFormDraft; 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.
  • validateForm no longer lists 503 SERVICE_UNAVAILABLE; it writes nothing and takes no write slot.
  • GET /v1/openapi.json ignores 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.
  • The nps node 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_values for node types and enum values. Their path is an array of property names and indexes, including the rejected property for unknown keys.
  • Plan-limit and Pro-feature errors include an upgrade_url.
  • validateForm (POST /v1/forms/{form_public_id}/validate) returns would_publish_version and share_url alongside valid and issues. 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. When valid: false, the preview applies only after the Publish Issues are fixed.
  • listSubmissions (GET /v1/forms/{form_public_id}/submissions): lists submissions as { data, next_cursor }, with an opaque cursor over created_at and the submission public ID. order=desc is the newest-first default; order=asc returns the oldest first. Filter by status, inclusive since or until, and published_version; the default status is completed.
  • 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, captured hidden_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 with format=jsonl, and header chooses current question labels (or labels from a requested published_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’s partial_response_access feature; a Free organization receives 402 PRO_FEATURE_REQUIRED. Completed submissions remain available on every plan.
  • All three submission operations need forms:read and cost one credit. Private Templates return no submissions. See Submissions.
  • generateForm (POST /v1/forms/generate): synchronously generates a form from a prompt, creates it as an ordinary draft, and answers 201 with the form resource, its stored Draft Document, and that draft’s ETag as a response header. The call can take up to 120 seconds and accepts is_template: true to create a Private Template. It needs forms:write, or templates:write for a Private Template.
  • The generateForm request body, { prompt, is_template? } and nothing else. prompt is 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-Key request header on generateForm, with the same 24-hour successful-response replay as createForm. A replay neither contacts the AI provider nor creates a second form. A failed call leaves the key unused.
  • 503 AI_SERVICE_UNAVAILABLE on generateForm, with retry_after_seconds in the body and the same delay in the Retry-After response header. Wait, then retry with the same idempotency key.
  • 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_button to 1 when the provider omitted it; an explicit setting is preserved.
  • duplicateForm (POST /v1/forms/{form_public_id}/duplicate): copies a form or a Private Template into a new draft and answers 201 with the copy, its stored Draft Document under draft, and that draft’s ETag as a response header. Page and block public IDs, the access schedule, 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 duplicateForm body, { name?, is_template? } and nothing else. Left out, name gives 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, and is_template keeps the kind of the source. Sending is_template: false against a Private Template starts a form from it, recording the template in source_template_public_id and counting a use against it.
  • The Idempotency-Key request header on duplicateForm, with the same 24-hour semantics as createForm. The key covers the source ID along with the body.
  • 402 PLAN_LIMIT_REACHED on duplicateForm when 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 answers 200 with the form resource. The public_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 answers 200 and writes nothing. Needs both forms:write and templates:write.
  • unmarkFormTemplate (POST /v1/forms/{form_public_id}/unmark-template): turns a Private Template back into a form in place, keeping the public_id, the draft, and its IDs, and answers 200 with 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, so 402 PLAN_LIMIT_REACHED is possible. A resource that is already a form answers 200 and writes nothing. Needs both templates:write and forms:write. Removed in 2026-10-26.
  • 409 FORM_CONVERSION_BLOCKED on markFormTemplate, carrying reason: published for a form that is live, which unpublishing clears, and has_submissions for a form that holds answers, which duplicating as a template works around.
  • 409 FORM_ARCHIVED on markFormTemplate and unmarkFormTemplate, for an archived resource.
  • validateForm (POST /v1/forms/{form_public_id}/validate): runs Publish Validation against the stored draft and answers 200 { valid, issues } whether the draft is ready or not. It writes nothing. Needs forms:read, or templates:read for 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 no If-Match; the version number is computed under the form lock, so the last call wins. Every call publishes a new version. Needs form_publish plus forms:write or templates: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 answers 200 and writes nothing. Needs form_publish plus forms:write or templates:write.
  • closeForm (POST /v1/forms/{form_public_id}/close): sets access.is_closed and leaves any schedule alone.
  • reopenForm (POST /v1/forms/{form_public_id}/reopen): clears access.is_closed and clears whichever schedule currently holds the form closed — a close_at in the past or an open_at in the future — while keeping a close_at that has not arrived yet.
  • updateFormAccess (PATCH /v1/forms/{form_public_id}/access): takes any of is_closed, close_at, and open_at. An omitted field keeps its current value, and null on either date clears that schedule. A body that changes nothing answers 200 and writes nothing.
  • deleteForm (DELETE /v1/forms/{form_public_id}): answers 204 with 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-separated path into the Draft Document that is null when the issue is about the whole form (an array since 2026-09-28), and page_public_id and node_public_id that are null when the issue is not about one page or one block.
  • 422 FORM_PUBLISH_VALIDATION_FAILED on publishForm, carrying the same issues array validateForm returns.
  • 409 NO_DRAFT_DURING_PUBLISH on validateForm and publishForm, for a form that holds no draft.
  • 409 FORM_ARCHIVED on validateForm, publishForm, closeForm, reopenForm and updateFormAccess. unpublishForm is a no-op 200 on an archived form, because archiving already took it offline.
  • 402 PRO_FEATURE_REQUIRED on publishForm, when the stored draft uses a feature the organization’s plan does not include. feature names it and path points at the Draft Document field, or is an empty string when the blocker is not a document field.
  • replaceFormDraft (PUT /v1/forms/{form_public_id}/draft, renamed updateFormDraft in 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 fresh ETag equal to what the next GET returns.
  • A PUT whose document already matches the stored draft writes nothing and returns the unchanged ETag, which makes retries and repeated deploys free.
  • dry_run=true on replaceFormDraft: validates and resolves every ID without writing, and answers 200 with the document the real call would have stored and no ETag.
  • 409 FORM_ARCHIVED: a draft write against an archived form, which has no editable draft.
  • createForm (POST /v1/forms): creates a form, or a Private Template with is_template true, from a Form Blueprint. The response is the form resource with the stored Draft Document under draft and its ETag as a response header, so every minted page and block ID arrives with the create.
  • dry_run=true on createForm: runs the same validation and ID minting, writes nothing, and answers 200 with the document the real call would have stored.
  • The Idempotency-Key request header on createForm, remembered for 24 hours per organization, with Idempotent-Replayed: true on a replay.
  • PLAN_LIMIT_REACHED and PRO_FEATURE_REQUIRED bodies now carry the blocking limit or feature; SERVICE_UNAVAILABLE carries retry_after_seconds.
  • listForms (GET /v1/forms): one page of forms, or of Private Templates with is_template=true. Filter with status and q, order with sort and order, and page with limit (1 to 100, default 25) and the opaque cursor. 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, access with the derived accepting_submissions, submission counts, urls.editor and urls.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 weak ETag over 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_PUBLISHED when nothing is published.
  • The form’s own public_id inside the Draft and Published Documents, marked read-only.
  • 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-Policy and RateLimit response fields on every authenticated response, with both policies always listed, and Retry-After in seconds on every 429.
  • The throttled error body: API_COOLDOWN and TOO_MANY_REQUESTS now carry policy, plan, limit, used, remaining, cost, window_seconds, cooldown_until, resets_at, retry_after_seconds and upgrade_url beside the envelope’s code, message and request_id.
  • 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) and revokeCurrentToken (DELETE /v1/me/token). See Authentication.
  • The Platform Error envelope on the wire, with X-Request-Id on every response and WWW-Authenticate: Bearer realm="platform.fomr.io" on every 401.

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.

Your next form could be the best-looking thing on your site.

Free forever. Unlimited responses. No signup to start.