These rules hold for every Platform API endpoint. Learn them once and the individual operation pages stay short.
Base URL and path version
Section titled “Base URL and path version”The base URL is https://platform.fomr.io, and the version sits in the path. Every request goes to /v1:
https://platform.fomr.io/v1/meFrom general availability, /v1 only grows and breaking changes ship under a new path version, /v2. Until then, a revision may still correct a response shape inside /v1, and the API changelog flags every such change. The changelog holds the full compatibility and deprecation policy.
Public reference routes
Section titled “Public reference routes”The contract is described by the Public OpenAPI Document. Its version field is the Contract Revision: a date in YYYY-MM-DD form naming the day the latest additive change shipped. The API reference in this help center is generated from that document when the site is built, so the reference and the server cannot drift apart.
- The Public OpenAPI Document is the JSON document itself. Query strings are ignored, so a cache-busting parameter returns the same document, and responses may be cached for five minutes. It sends
Access-Control-Allow-Origin: *, so tools on another origin can read it. - The API Explorer renders that document and lets you try operations. Its requests go to the origin you opened it on, so they stay on the platform host.
Both routes are unauthenticated and sit outside your organization’s API budget, so they return no RateLimit fields; the per-IP limit still applies. If you try an authenticated operation in the API Explorer, use a Personal Access Token with a short expiry and revoke it when you finish.
JSON in, JSON out
Section titled “JSON in, JSON out”Requests and responses are JSON. A request with a body sends Content-Type: application/json; other content types are rejected. Every response that carries data is JSON as well.
Request size
Section titled “Request size”A request body is capped at 4 MiB. The cap is applied to the raw body before it is parsed, so an oversized request is rejected with 413 PAYLOAD_TOO_LARGE and nothing in it takes effect.
Request IDs
Section titled “Request IDs”Every response carries an X-Request-Id header identifying that one request, and every error body repeats the same value as request_id. Success or failure, the ID is there.
Log it. When something goes wrong, quoting the request ID lets support find the exact request in seconds instead of asking you to reproduce it.
Errors
Section titled “Errors”Every failure is a Platform Error: a JSON body with a stable code to branch on, a human-readable message that may change between revisions, and the request_id. Some codes add fields of their own.
Branch on code, never on message. A code your client does not recognize should fall back on the HTTP status. The complete catalog, the extra fields each code carries, and the retry rules are on Errors.
Dates and times
Section titled “Dates and times”Every date and time on the wire is an ISO 8601 string in UTC:
2026-12-02T00:00:00.000ZThat holds in both directions. Send UTC, and convert to a local time zone only for display.
Identifiers
Section titled “Identifiers”The API uses public IDs — the same IDs you see in the app and in form URLs. Database IDs never appear in a request or a response.
| Resource | Length |
|---|---|
| Form | 10 characters |
| Page | 4 characters |
| Node | 4 characters |
| Submission | 22 characters |
| Organization | 10 characters |
| Workspace | 4 characters |
Public IDs are opaque strings. Store them as text at their stated length, never parse meaning out of them, and never assume they sort in any useful order. Form Blueprint covers the alphabet they use and how a form’s page and node IDs are minted.
Not a browser API
Section titled “Not a browser API”Budgeted Platform API operations are server-to-server routes. They do not return browser CORS headers, so call them from a server, a script, or a command line. The unauthenticated Public OpenAPI Document is the only route that sends a wildcard CORS header.
This is deliberate. A Personal Access Token acts as a member of your organization, and shipping one to a browser would hand it to everyone who opens the page. Keep the token on your side and let your own backend expose only what your frontend needs.
To try calls in a browser, use the API Explorer: it runs on the same origin as the API, so it needs no CORS headers.
Tolerant clients
Section titled “Tolerant clients”The server is strict about what it accepts: an unknown key, node type, or enum value is rejected with a 400 INVALID_INPUT that names the offending path. Your client should be the opposite — tolerant about what it receives.
- Ignore response fields you do not recognize instead of failing on them.
- Keep unknown fields when you read a document, change part of it, and write it back.
- Treat an unknown node type or enum value as something to pass through, not an error.
Within /v1, anything unfamiliar is a later addition rather than a mistake. The API changelog records each one.
Lists and single resources
Section titled “Lists and single resources”A single resource is returned as a bare object — the resource itself, with no wrapper.
List endpoints use cursor pagination and a consistent envelope:
{ "data": [ { "public_id": "Sb01EventReg2026Ab3cDf", "form_public_id": "Ev3ntReg01", "status": "partial", "published_version": 3, "created_at": "2026-10-01T09:00:00.000Z", "updated_at": "2026-10-01T09:00:00.000Z", "answers": { "nNam": "Grace Hopper" }, "hidden_values": { "utm_source": "partner" } }, { "public_id": "Sb01EventReg2026Ab3cDe", "form_public_id": "Ev3ntReg01", "status": "completed", "published_version": 3, "created_at": "2026-10-01T09:00:00.000Z", "updated_at": "2026-10-01T09:00:00.000Z", "answers": { "nNam": "Ada Lovelace", }, "hidden_values": { "utm_source": "conference-newsletter" } } ], "next_cursor": "eyJpZCI6IlNiMDFFdmVudFJlZzIwMjZBYjNjRGUiLCJrIjoiMjAyNi0xMC0wMVQwOTowMDowMC4wMDAwMDBaIiwibyI6ImRlc2MiLCJzIjoiY3JlYXRlZF9hdCJ9"}Read a page, and if next_cursor is not null, pass it back to fetch the next one. A null cursor means you have reached the end. Cursors are opaque: pass them back exactly as received and do not construct one yourself.
Every list takes the same two paging parameters:
| Parameter | Default | Range |
|---|---|---|
limit | 25 | 1 to 100 |
cursor | — | A next_cursor from the previous page |
A cursor carries the sort key of the last row on the page and that row’s public ID, so rows sharing a sort value still fall on one side of the page boundary each: none is skipped and none arrives twice. A cursor also remembers the sort and order it was minted under. Replaying it with a different ordering is refused with 400 INVALID_INPUT at path cursor, so a mid-run change of sort fails loudly instead of quietly reshuffling the page. Any cursor the server cannot read fails the same way.
There are no page numbers and no total count. Follow the cursor until it comes back null.
Rate limits
Section titled “Rate limits”Rate limits are applied per organization, not per token, so several tokens belonging to the same organization share one budget. See Rate limits and plans for the numbers, the RateLimit headers and the two throttled responses.