API conventions

Embed
Preview of the card
<a href="https://forumulate.com/guides/api-conventions"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/e160d058-3a11-4cd2-9368-d87ffa7ecd4f/social-card.svg?v=3e7ae33eec8b" alt="API conventions" width="600" height="315"></a>

The things that are true across every endpoint, stated once. Several of these correct earlier pages that made a blanket claim where the truth is per-operation.

The base URL

https://forumulate.com/api

The OpenAPI document is at https://forumulate.com/api/openapi.json and needs no authentication.

(The spec's own info.description currently says "Download this spec at /openapi.json". That resolves against the host root and redirects. Use the /api/ form.)

curl -sS -A 'my-app/1.0' https://forumulate.com/api/openapi.json -o openapi.json

It is a large document — 976 paths and 1,225 operations at the time of writing. Any figure you read in the form "700+ paths" is out of date; count it yourself:

python3 -c "import json;d=json.load(open('openapi.json'));print(len(d['paths']))"

User-Agent

Set one. Any identifying string.

Path

Behaviour

/api/*

Accepts any User-Agent, including library defaults.

/api/auth/*

Bot-protected. Some library defaults — Python-urllib among them — get a Cloudflare 403 HTML page, not a JSON error.

Two earlier pages contradicted each other on this: one said there is no restriction at all, the other said every default agent is challenged. Neither is right. The table above is.

If you get an HTML body where you expected JSON, that is the WAF and not us.

Authentication

Authorization: Bearer ak_…

Three token prefixes, all sent the same way: ak_ (an Atlas-minted key), cft_ (a personal access token) and cfo_ (an OAuth access token). Browser calls use the session cookie instead.

Every operation in the reference states its coarse scope requirement — "Requires token scope: write". That is only half the story; a token needs a catalog scope as well. See API tokens, scopes and capabilities.

Public endpoints need no token

Everything under /public/ is anonymous. If a reference page for a /public/ or /auth/ route shows "Requires authentication" and a Bearer example, that page is stale — the current spec marks 181 of 182 /public/ operations as unsecured, and 240 operations overall.

The one genuine exception at the time of writing is GET /public/orgs/{orgId}/spaces/{spaceId}/forum/embed/resolve, which the spec still marks as secured. It should not be, and that is a bug on our side rather than a requirement on yours.

Organizations

/orgs/{orgId}/… accepts either the organization's uuid or the name in its slug:

GET https://forumulate.com/api/orgs/acme/spaces
GET https://forumulate.com/api/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces

Both forms address the same organization. A name that is not yours is a 404, never a 500 and never a 403 — we do not confirm that an organization exists to somebody who is not in it.

A route that needs an organization and cannot work out which one answers tenancy.no_org_context. In practice that means one of two things: the path has no {orgId} and your token is scoped to no organization, or you are calling an organization-level route with a session that belongs to several and named none. Put the organization in the path.

{spaceId} is the same: a uuid, or the space's slug within that organization. It is not decoration on a nested route — a page id further down the path that belongs to a different space is a 404, not a cross-space read.

Slugs

Every addressable thing has a slug, and a slug is unique within its scope: a page's within its space (per version and locale), a space's within its organization, a site's within its organization.

Slugs are normalised on the way in, not rejected. Launch Day is stored as launch-day: lower-cased, every run of non-alphanumeric characters folded to a single -, leading and trailing - trimmed, cut to 80 characters. Unicode letters and digits survive — Überblick is überblick, not berblick.

When we rewrite what you sent, we say so. The response carries an X-Field-Warnings header, and the write doors that answer a body also carry warnings[]:

X-Field-Warnings: tags: "Launch Day" is stored as "launch-day"

If you get a 2xx and that header is present, the stored value is not the value you sent. Read it the first time a value "does not save".

A taken slug: onSlugTaken

Both page doors — POST …/collections/{collectionId}/pages and PATCH …/pages/{pageId} — take onSlugTaken, with the same default on each:

Value

suffix

Land at the next free name — getting-started-2 — and report it.

fail

Refuse with 409 page.slug_taken and write nothing.

The default depends on your version. Leave onSlugTaken out and you get suffix if you state Forumulate-Version: 2026-09-01 or 2026-11-01, or no version at all; from 2026-12-01 you get fail — the safe default, and the one blog posts have always had. A page landing at a URL nobody wrote is worse than a 409 you can act on. Send the value explicitly and the version does not matter.

The two doors used to disagree, create suffixing and update failing, which cost a migration its writes. They now behave identically; fail is there for a pipeline that would rather hear about a collision than work around one.

On a suffix rename, read slug off the response — do not assume the slug you sent:

{
  "ok": true,
  "slug": "install-2",
  "slugChanged": { "asked": "install", "used": "install-2" },
  "warnings": ["slug: the slug asked for was taken; read `slug` on the response for the one used"],
}

The create door answers the same two keys beside its page/data.

The OpenAPI doors answer page.slug_taken too — POST …/docs/openapi/import with onExisting: "replace", …/openapi/generate and a spec resync — when a generated slug is held by a page in another collection that the spec does not own. The envelope's slugs lists every such page.

A reserved slug is a different refusal: space.slug_reserved and badge.slug_reserved (409) mean the name is one we route on ourselves — api and docs are reserved host names, and a handful of path segments are reserved under a site. Pick another; a suffix will not help.

The other taken-identifier codes follow the same rule — space.slug_taken, collection.slug_taken, site.slug_taken, badge.slug_taken, version.key_taken, name.taken — all 409, all over the identifier rather than the request.

A markdown body (a page body, a section, /docs/import, a sync) also reports what its directive door did not keep — an unknown :::directive, an unknown attribute, an out-of-range ::column{width} — as markdown … lines.

Errors

One shape, everywhere:

{
  "error": {
    "code": "auth.unauthenticated",
    "message": "Authentication required.",
  },
}

Field

code

Stable. Branch on this.

message

For a human. May change between releases.

details

Optional. Field-level problems, keyed by path.

docs

Optional. A link to the relevant page.

slugs

Optional. Identifiers the error refers to.

Common codes: auth.unauthenticated (401), rbac.forbidden (403), not_found (404), validation.failed (422), rate_limited (429).

Bodies are UTF-8

A JSON or form body is read as UTF-8 unless its Content-Type names another charset. Bytes that are not valid UTF-8 — most often Latin-1 text, where ñ is the single byte F1 instead of C3 B1 — are a 400 request.malformed and nothing is written. Nothing is ever stored with the unreadable characters replaced by �.

Unknown body keys

A key we do not recognise is dropped, and the response names what it dropped in an X-Unrecognized-Fields header. The call answers 200.

Check that header the first time a field "does not save". A silently ignored key has cost two customers a day each.

The one surface that refuses instead of dropping is the theme, because a misspelled theme key produces a page that renders and is simply the wrong colour. Those answer 422 with the path named.

The theme routes (PUT/PATCH …/theme, …/theme/draft, …/reader-themes), a site's config and config.theme, and a page's metadata.themePatch all take every documented theme key, including the round-5 keys (for example docs.blocks.prompt.icon, forum.topic.rail[].group, docs.api.titleSize, forum.front.filters.labels). A bad value is a 422 naming the exact path and what it accepts (forum.list.phone.avatarSize: Number must be less than or equal to 48), and an unknown key beside a known one is still a 422 naming it.

Creates: data is the key that is always there

A create answers 201 with the new resource under its collection's own word — page, collection, article, topic — and again under data:

{
  "page": { "id": "3f1e0b2c-…", "title": "Installing", "slug": "installing" },
  "data": { "id": "3f1e0b2c-…", "title": "Installing", "slug": "installing" },
}

data is a mirror, not a new shape. It is the same object, under a second key. Nothing was renamed and nothing was removed, so a client reading response.page is unaffected — and a client wrapping ninety endpoints can read response.data and stop writing a parser per route.

Read data ?? <the named key>. That one line works on every create in the API, including the ones that have no data (below), and it keeps working if a route grows one.

The 201s that carry no data

Most operations that answer 201 declare data. The rest do not, because a 201 is not a promise that something addressable was made. Each of them says which kind of answer it is, in its own 201 description, so you never have to guess. (How many of each there are is counted off the document and stated in the spec's own description — GET /api/openapi.json, the info section. It is not repeated here, because a count typed into a page beside a generated document is a count that goes stale the next time an endpoint is added.)

Kind

What you get

Why no data

result

An acknowledgement ({ ok: true }), a computed answer, a tally, or a one-time credential

Nothing addressable was created, so there is nothing to mirror and nothing to GET back

collection

The whole collection you added to — POST …/docs/locales answers every locale

There is no single created row to point at; find yours in the array

bare resource

The resource itself, at the top level, with its own id — POST …/events, POST /scim/v2/Users

There is no key beside it to put data on without sending the body twice

A bare resource is the one kind we would change if we could: keying it as { event: … } would break every caller parsing it today, so it waits for a dated version. POST /scim/v2/Users and POST /scim/v2/Groups will never change — RFC 7644 defines the SCIM response as the resource itself, and a data key there would make us non-conformant with the spec your IdP implements.

Two more keys show up on creates and are never the thing that was made: warnings (values we accepted and then rewrote — see A taken slug) and slugChanged. Neither is ever mirrored into data.

Pagination

Paging is version-negotiated, not per-endpoint. State Forumulate-Version: 2026-09-01 (or later) and every collection-shaped GET answers { <rows>, page } — page carrying limit, hasMore, nextCursor and total. Send no version and you get the pre-envelope shape that route has always answered, which for some is a bare array, along with Deprecation and Sunset headers saying so. limit and cursor are accepted either way; sending a cursor opts into the enveloped shape exactly as stating a version does.

This page used to say that only a handful of lists carried page and that the spec's claim to the contrary was wrong. The spec was right and this page was out of date: the envelope is now declared on every list in the published document, and a walk over that document fails the build if one is added without it. The rule replaced the tally on purpose — a tally of endpoints is wrong the first time somebody adds one.

The exception is a ranking rather than a collection: the reputation leaderboards, ?q= semantic search, and the search-gap and uncovered-thread reports recompute a score per request, so there is no row a cursor could resume after. They honour limit and say so on the operation.

A route that cannot change shape without breaking a caller answers both shapes and lets you choose, so the unversioned answer differs from route to route: some were a bare array, some had paging keys of their own. The operation's own response schema says which, and it — not either of these paragraphs — is the thing to code against.

Rate limits

Caller

Limit

Authenticated, free or starter plan

600 requests / minute

Authenticated, business plan

1,800 / minute

Authenticated, enterprise plan

6,000 / minute

Anonymous

120 / minute, on every plan

Authenticated limits are keyed per organization; anonymous limits per IP, which is why they do not vary by plan — a per-IP bucket is the wrong place to sell capacity.

Some individual endpoints are tighter — a handful of public ones are 60, 30 or even 5 a minute — and each declares its own budget. Read the headers rather than this table:

RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 41
RateLimit-Policy: 600;w=60

On a 429, read Retry-After and wait that many seconds. Do not implement a fixed backoff; the header is more accurate than a guess.

Retries

Two statuses are worth retrying and the rest are not.

429 — you were too fast. Wait Retry-After seconds and send the same request again.

503 service_unavailable — something we depend on is failing, including a 502 or 504 from an upstream we called on your behalf (a BYOK model provider, a mail relay, an identity provider). It is retryable and it carries Retry-After when we know how long. Back off exponentially from there, with jitter, and give up after a few attempts rather than holding a request open: the edge cuts a connection at 30 seconds whatever we are doing behind it.

Everything else is yours to fix, not to retry. A 422 will fail identically forever; a 409 means the state is not what you assumed, so re-read and decide; a 404 will not become a 200.

Retry a write with Idempotency-Key. Without one, a retried create that actually succeeded the first time makes a second thing — and a timeout you did not see is the normal way that happens. See Idempotency.

Preconditions

Two resources support optimistic concurrency with If-Match: a page and an item. Their reads send an ETag response header; send it back as If-Match:

# Read, and keep the tag.
curl -sS -D- -A 'my-app/1.0' -H "Authorization: Bearer $TOKEN" \
  'https://forumulate.com/api/orgs/acme/spaces/docs/pages/3f1e0b2c-8a44-4c2e-9f1a-2b7d5e6c0a11'
# ETag: "r1-Qm8x…"

# Write, but only if nobody else has.
curl -sS -X PATCH -A 'my-app/1.0' -H "Authorization: Bearer $TOKEN" \
  -H 'If-Match: "r1-Qm8x…"' -H 'Content-Type: application/json' \
  -d '{"title":"Installing"}' \
  'https://forumulate.com/api/orgs/acme/spaces/docs/pages/3f1e0b2c-8a44-4c2e-9f1a-2b7d5e6c0a11'

If the resource has moved on, the write is refused with 412 precondition.failed, naming the tag you sent and the one it is at. Re-read and retry. The tag a read gives you works on every write to that resource — its body, its metadata, its slug, its deletion.

The ETag is opaque and changes on ANY change to what the read returns — the body, but also the title, slug, declared locale, metadata or anything else in the representation. It is the right tag for If-None-Match (a 304 means the response you hold is still exactly current) and for If-Match (a write is refused if anything moved). Do not parse it or compare it to contentHash.

contentHash still changes only when the content does. A slug, language or metadata edit leaves it exactly where it was, so it remains the value to compare for drift between what you pushed and what is published. It is also still accepted as If-Match, as it was before the ETag covered the whole representation, and there it guards the content only: a concurrent rename does not refuse it. Send the ETag when a metadata race matters.

Where a write cannot evaluate the header it is refused with 428 rather than ignored. That is deliberate: a caller who sends If-Match and has it dropped believes they have optimistic concurrency and loses a write in the exact case the header exists to catch. The routes that answer 428 are the ones where a single tag could not mean anything — a bulk operation over many pages, or a resource with no content to hash. If-Match: * passes everywhere, since it only asserts the thing exists.

A docs change request and a theme draft have their own named concurrency control (theme_draft.stale, publish with force) rather than an ETag.

Deprecation

A response that is serving a shape with a removal date carries:

Deprecation: @1788220800
Sunset: Wed, 01 Sep 2027 00:00:00 GMT
Link: <https://forumulate.com/api/openapi.json>; rel="service-desc",
      <https://forumulate.com/reference/versioning>; rel="deprecation"
forumulate-version: legacy

Deprecation is an RFC 9745 structured date — an @ followed by Unix seconds. It is not Deprecation: true; three places in our own docs said it was. @1788220800 is 2026-09-01T00:00:00Z, the day the shape became deprecated. Sunset (RFC 8594) is the day it stops being served.

Which responses carry them

An operation sends them when, and only when, it negotiates its response shape — when it answers one body to a caller who states a Forumulate-Version and a different one to a caller who does not. These are collections that answer { items } to an unversioned caller and { items, page } to a versioned one: /orgs, /pats, /orgs/{orgId}/spaces, the blog, the changelog, the forum, the audit log, …/items and others.

Do not count them from this page and do not keep your own list. Read it off the spec, where it is generated from the same fact the server reads:

  • an operation that sends them declares Deprecation, Sunset and Link under responses.<2xx>.headers, with the date in the header's own description;

  • and its 2xx response schema is an anyOf of exactly two variants, the withdrawn one marked deprecated.

Those two always arrive together, on the same operations. A tally written in prose beside a generated set goes stale the first time a route gains or loses a second shape — this page used to carry one that no longer matched the spec, which is how you came to be reading this paragraph.

These headers used to go out on every unversioned response in the API. That was wrong and it has been fixed. Sunset means this resource stops responding, and GET /orgs is not stopping: an unversioned caller and a versioned caller get the identical body from it, today and after the sunset date. If you built anything on seeing Sunset everywhere, it was reading a bug.

What is, and is not, marked deprecated in the spec

These operations are marked deprecated: true at the operation level. They are genuinely going away:

  • GET /orgs/{orgId}/templates

  • POST /hooks/cookiemunch

The shape-negotiating operations are not, and that is deliberate. The route is not deprecated — there is no replacement for GET /orgs and you will be calling it forever. What is deprecated is the response body you get by stating no version, so that is what carries the mark: the non-enveloped half of the operation's response anyOf has deprecated: true and a description naming 2027-09-01. OpenAPI 3.1 allows deprecated on a Schema Object, which is exactly the granularity this needs.

Both halves are spelled out, on every one of these operations: the withdrawn one lists the collection key alone, and the one you should move to lists page as required. If you have been reading a single schema with an optional page and wondering which kind of optional it was — negotiated, or simply sometimes absent — that was the gap, and it is closed.

So when you are looking at one of these operations and deciding which half of its anyOf to write a parser against: the struck-through one is the one with the date on it. Write against the other, and send the version header.

After the date

On 2027-09-01 the pre-envelope shapes stop being served and a request that states no version receives the 2026-09-01 shape instead. Nothing else changes — not the paths, not the parameters, not the error envelope. Send Forumulate-Version: 2026-09-01 to move now, and these headers stop arriving.

Versioning

Send Forumulate-Version on every request. It is optional only so that integrations written before versioning keep working:

Forumulate-Version: 2026-12-01

Each version includes everything before it, and a version never changes once it is served. Start a new integration on the newest one, 2026-12-01.

Without it you get the legacy shape and the forumulate-version: legacy response header. Every response echoes the version it was served as.

What a dated version changes, for a caller who states it or later — everything else ships to every caller and needs no version:

Version

Changes

2026-09-01

Collections answer with the page envelope, and data beside the named rows key (the same rows).

2026-11-01

page drops the snake_case has_more / next_cursor (read hasMore / nextCursor).

2026-12-01

POST /orgs/{orgId}/spaces without siteId joins no site (announced 2026-10-14) — before this version a PUBLIC space joins the oldest site and warnings names it. Send siteId (or null) to choose on any version. An unset onSlugTaken on the page doors means fail (409) instead of suffix. An unrecognised request-body field is a 422 naming its path on every write (including inside a block document) instead of a 200 and X-Unrecognized-Fields.

The SDK cannot currently send this header, nor Idempotency-Key — it has no option for arbitrary request headers. Use fetch for calls that need either.

Idempotency

POST /orgs/{orgId}/spaces/{spaceId}/items accepts:

Idempotency-Key: <any string, up to 200 characters>

It is optional and inert without the header — a request that omits it behaves exactly as it always did. But send one: a retried create without a key makes a second page, and a retry is the normal outcome of a timeout you did not see.

A UUID per logical create is the easy choice.

This is currently the only operation in the API that declares the header.

Comma lists: fields and include

GET /orgs/{orgId}/spaces/{spaceId}/items?fields=id,title,slug,published&include=sections

Both take a comma-separated list. An unrecognised name is a 422 that enumerates the allowed set.

fields accepts: id, kind, title, slug, published, group, state, updatedAt, url, freshness, deleted, sections, markdown, contentIn, locale.

include accepts: sections, body.

A caveat about generated clients. The OpenAPI document currently declares both as single-valued enums rather than as exploded arrays. The server accepts the list; a strict generated client, or an OpenAPI-driven "try it" panel, may refuse to send one. Until that is corrected in the spec, build those two query strings yourself.

Item languages: locale

Every row of GET /orgs/{orgId}/spaces/{spaceId}/items carries locale: the language that piece is declared to be written in, as canonical BCP-47 — es, pt-BR, zh-Hant, es-419. Language lowercase, script title case, region uppercase; never an underscore form, whatever was stored.

locale is null when nobody declared a language for that piece. It is never filled in from the space's contentLanguage or the organization's: a space holding two languages is where a default is wrong for half the rows, and a substituted default cannot be told apart from a fact. If you want a fallback, read GET /public/orgs/{org}/spaces/{space}/seo yourself and apply it knowing it is one.

What counts as declared, per kind:

Kind

locale is

page (docs, FAQ answers, careers, legal, …)

what was declared for it; else, for a language twin, the twin's language; a source page is null until declared

topic (blog post, forum topic, changelog release, roadmap card)

what was declared for it, else null

incident

what was declared for it, else null

Language twins of the primary version (POST …/docs/locales) are listed as items of their own, with their own id, locale and url (the language's address, e.g. …/docs/es/webhooks). A topic's or an incident's translations are served under the piece's own id and are not separate items.

Correcting a language. PATCH /orgs/{orgId}/spaces/{spaceId}/items/{itemId} with { "locale": "es" } — or null to clear it — works on every kind. A language-only correction moves the item's updatedAt, so it comes back in ?updated_since=, and never changes contentHash: the hash is of what the piece says, and its language is not part of that. It does not move a page to another language's address.

Joining /items to the other routes. An item's id is the id its kind's own routes use. A page item's id is the page id — the same value as id in GET …/pages (PageListRow) and the {pageId} of every …/pages/{pageId} route, twins included. A topic item's id is the topic id of the blog, forum, changelog and roadmap routes (topics are not in …/pages). An incident item's id is the {incidentId} of …/status/incidents/{incidentId}.

Share tokens

A private space's content can be read with a rotating share token:

?share=<token>

It is accepted on the public reader routes and on the PDF routes. It is scoped to one space and it rotates, so it is safe to put in a review link and not safe to put in a public page.

Forum counts and fields that read one rule

Likes. Every like count in the forum API counts the same thing: a member's reaction on a visible post that is not 👎, from anyone but the post's author. That covers a topic list row's likeCount, each post's likeCount in a topic payload, the profile summary's topReplies[].likes, topTopics[].likes and topPosts[].likes, the profile's stats.likesReceived, and the leaderboard's likesReceived. So a member's item likes add up to their "likes received". voteCount is still the net score (👍 minus 👎) that ranks a question's answers. The published reactions map still lists every emoji, anonymous reactions included.

Fields the front-page bands read (all optional on older payloads):

Payload

Field

What it is

GET …/forum/categories/overview

categories[].topicsPerWeek

Published topics created in the last 7 days, own + subcategories.

same, ?latest=N

categories[].latestTopics[].lastPostedAt, .replyCount

When the newest visible post went up (ISO), and the reply count.

GET …/forum (topic list)

topics[].lastPostedAt

The same time for a list row.

GET …/forum/leaderboard

leaders[].handle, leaders[].displayName

The @handle (null when there is none) and the name the member goes by. username and name carry the same values.

OpenAPI import. An import's answer carries landing: false when it laid the tags at the collection's top level on its own (a fresh import with landing unset, see Import content). Absent means the landing you sent, or a landing page.

Categories. A category carries topicKind ("question", "discussion" or null), the owner's word for what it holds. It decides which topics count toward the automatic Q&A look; see Forum.

What is not in the spec

Routes under /internal and /platform are ours, and are stripped from the published document. If you find one in a reference page, that page predates the stripping — the live spec has none.

Two tag groups that are published and look internal are genuinely yours: Impersonation (a "view as" for staff with the capability) and QueueAdmin (your organization's own background job queues).

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go clients are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP call_api tool. Where a CLI command or MCP tool exists too, the last column names it. Operation names link to the API reference.

Escalation Providers

Operation

Request

What it does

Shortcuts

EscalationProviders_list

GET /orgs/{orgId}/escalation/providers

List providers in an organization (escalation)

client.escalationProviders.list()

EscalationProviders_save

PUT /orgs/{orgId}/escalation/providers/{provider}

Replace the provider

client.escalationProviders.save()

EscalationProviders_remove

DELETE /orgs/{orgId}/escalation/providers/{provider}

Delete the provider

client.escalationProviders.remove()

EscalationProviders_test

POST /orgs/{orgId}/escalation/providers/{provider}/test

Send a test call to an escalation provider

client.escalationProviders.test()

EscalationProviders_rotate

POST /orgs/{orgId}/escalation/providers/{provider}/rotate-inbound-secret

Rotate inbound secret in an organization

client.escalationProviders.rotate()

Escalation Reader

Operation

Request

What it does

Shortcuts

EscalationReader_list

GET /public/orgs/{orgId}/spaces/{spaceId}/forum-escalations/t/{slug}

Get the topic

client.escalationReader.list()

EscalationReader_escalate

POST /public/orgs/{orgId}/spaces/{spaceId}/forum-escalations/t/{slug}

Create the topic

client.escalationReader.escalate()

Form Insights

Operation

Request

What it does

Shortcuts

FormInsights_get

GET /orgs/{orgId}/form-insights/{kind}/{id}

Get the form insight

client.forms.insights.get()

FormInsights_deletePartial

DELETE /orgs/{orgId}/form-insights/{kind}/{id}/partials/{partialId}

Delete the partial

client.forms.insights.deletePartial()

Item

Operation

Request

What it does

Shortcuts

Item_list

GET /orgs/{orgId}/spaces/{spaceId}/items

List items in a space

forumulate items list<br>client.items.list()<br>MCP list_items

Item_create

POST /orgs/{orgId}/spaces/{spaceId}/items

Create an item in a space

forumulate items create<br>client.items.create()<br>MCP create_item

Item_read

GET /orgs/{orgId}/spaces/{spaceId}/items/{itemId}

Get the item (spaces)

forumulate items get<br>client.items.read()<br>client.items.get()<br>MCP get_item<br>MCP get_translation

Item_patch

PATCH /orgs/{orgId}/spaces/{spaceId}/items/{itemId}

Update the item (spaces)

forumulate items update<br>client.items.patch()<br>MCP put_translation<br>MCP set_item_locale

Item_remove

DELETE /orgs/{orgId}/spaces/{spaceId}/items/{itemId}

Delete the item (spaces)

forumulate items delete<br>client.items.remove()<br>MCP remove_translation

Item_getContent

GET /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/content

Get the content in a space

forumulate items content<br>client.items.getContent()

Item_putContent

PUT /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/content

Replace the content in a space (items)

forumulate items write<br>client.items.putContent()<br>client.items.setContent()<br>MCP put_translation

Item_chunks

GET /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/chunks

List chunks in a space

forumulate items chunks<br>client.items.chunks()

Item_verify

POST /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/verify

Verify the item

forumulate items verify<br>client.items.verify()

Item_publish

POST /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/publish

Publish the item (spaces)

forumulate items publish<br>client.items.publish()<br>MCP publish_item<br>MCP put_translation

Item_fields

GET /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/fields

List fields in a space

forumulate items fields<br>client.items.fields()<br>MCP set_item_fields

Item_setFields

PATCH /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/fields

Update fields in a space

forumulate items fields set<br>client.items.setFields()<br>MCP set_item_fields

Item_putSection

PUT /orgs/{orgId}/spaces/{spaceId}/items/{itemId}/sections/{key}

Replace the section (items)

forumulate items section<br>client.items.putSection()<br>client.items.setSection()<br>MCP update_section

Live Rooms

Operation

Request

What it does

Shortcuts

LiveRooms_list

GET /orgs/{orgId}/spaces/{spaceId}/live-rooms

List live rooms in a space

client.liveRooms.list()

LiveRooms_create

POST /orgs/{orgId}/spaces/{spaceId}/live-rooms

Create a live room in a space

client.liveRooms.create()

LiveRooms_update

PATCH /orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}

Update a live room

client.liveRooms.update()

LiveRooms_remove

DELETE /orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}

Delete the live room

client.liveRooms.remove()

LiveRooms_start

POST /orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/start

Start a live room

client.liveRooms.start()

LiveRooms_end

POST /orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/end

Create the end in a space

client.liveRooms.end()

Live Rooms Public

Operation

Request

What it does

Shortcuts

LiveRoomsPublic_list

GET /public/orgs/{orgId}/spaces/{spaceId}/live-rooms

List live rooms in a space (public reader)

client.public.liveRooms.list()

LiveRoomsPublic_one

GET /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}

Get a live room

client.public.liveRooms.one()

LiveRoomsPublic_join

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/join

Join a live room

client.public.liveRooms.join()

LiveRoomsPublic_stream

GET /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/stream

Get the stream in a space (live rooms)

client.public.liveRooms.stream()

LiveRoomsPublic_signal

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/signal

Create the signal in a space

client.public.liveRooms.signal()

LiveRoomsPublic_heartbeat

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/heartbeat

Create the heartbeat in a space (public reader live rooms)

client.public.liveRooms.heartbeat()

LiveRoomsPublic_leave

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/leave

Leave the live room

client.public.liveRooms.leave()

LiveRoomsPublic_role

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/role

Create the role in a space

client.public.liveRooms.role()

LiveRoomsPublic_end

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/end

Create the end in a space (public reader)

client.public.liveRooms.end()

LiveRoomsPublic_sfuRole

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/sfu-role

Create the sfu role in a space

client.public.liveRooms.sfuRole()

LiveRoomsPublic_sfuEnd

POST /public/orgs/{orgId}/spaces/{spaceId}/live-rooms/{roomId}/sfu-end

Create the sfu end in a space

client.public.liveRooms.sfuEnd()

Manifest

Operation

Request

What it does

Shortcuts

Manifest_apply

PUT /orgs/{orgId}/manifest

Apply a declarative site manifest (dry run by default)

client.orgs.manifest.apply()

Org orgs

Operation

Request

What it does

Shortcuts

Org_list

GET /orgs

List orgs

forumulate orgs list<br>client.orgs.list()

Org_create

POST /orgs

Create an org

forumulate orgs create<br>client.orgs.create()

Org_get

GET /orgs/{orgId}

Get the org

forumulate orgs get<br>client.orgs.get()

Org_update

PATCH /orgs/{orgId}

Update the org

forumulate orgs update<br>client.orgs.update()

Org_remove

DELETE /orgs/{orgId}

Delete the org

forumulate orgs delete<br>client.orgs.remove()

Phone Optins

Operation

Request

What it does

Shortcuts

PhoneOptins_options

GET /public/orgs/{orgId}/spaces/{spaceId}/phone-alerts

The text channels this space offers readers

client.phoneOptins.options()

PhoneOptins_start

POST /public/orgs/{orgId}/spaces/{spaceId}/phone-alerts

Opt a phone in for event texts; a code is texted to it

client.phoneOptins.start()

PhoneOptins_verify

POST /public/orgs/{orgId}/spaces/{spaceId}/phone-alerts/verify

Confirm a phone with the code texted to it

client.phoneOptins.verify()

PhoneOptins_unsubscribe

POST /public/orgs/{orgId}/spaces/{spaceId}/phone-alerts/unsubscribe

Stop the texts to a phone

client.phoneOptins.unsubscribe()

Problems

Operation

Request

What it does

Shortcuts

Problems_report

GET /orgs/{orgId}/health

Health of an organization

client.orgs.problems.report()

Public seo

Operation

Request

What it does

Shortcuts

Public_seo

GET /public/orgs/{orgId}/spaces/{spaceId}/seo

List SEO settings in a space (public reader)

client.public.seo()

Public Federation

Operation

Request

What it does

Shortcuts

PublicFederation_handle

GET /public/orgs/{orgId}/spaces/{spaceId}/forum-federation

Get the forum federation in a space

client.public.federation.handle()

Public Member Segments

Operation

Request

What it does

Shortcuts

PublicMemberSegments_membership

GET /public/orgs/{orgId}/segments/{segmentId}/membership

Get the membership in an organization

client.members.publicSegments.membership()

Public Network

Operation

Request

What it does

Shortcuts

PublicNetwork_check

GET /public/orgs/{orgId}/spaces/{spaceId}/network-check

Whether this reader may read the space from their network

client.public.network.check()

PublicNetwork_facts

GET /public/orgs/{orgId}/spaces/{spaceId}/network-check/facts

Whether this space is limited to some networks

client.public.network.facts()

Public Post Engagement

Operation

Request

What it does

Shortcuts

PublicPostEngagement_record

POST /public/orgs/{orgId}/spaces/{spaceId}/engagement

Record a reader event on a post or release

client.public.postEngagement.record()

Public Reader Marks

Operation

Request

What it does

Shortcuts

PublicReaderMarks_streak

GET /public/orgs/{orgId}/spaces/{spaceId}/marks/streak

Read the signed-in reader’s reading streak

client.readers.publicMarks.streak()

PublicReaderMarks_read

GET /public/orgs/{orgId}/spaces/{spaceId}/marks/{slug}

Read a post’s claps, top highlight and restacks

client.readers.publicMarks.read()

PublicReaderMarks_mark

POST /public/orgs/{orgId}/spaces/{spaceId}/marks/{slug}

Highlight, note or restack a post

client.readers.publicMarks.mark()

PublicReaderMarks_clap

POST /public/orgs/{orgId}/spaces/{spaceId}/marks/{slug}/clap

Clap for a post

client.readers.publicMarks.clap()

PublicReaderMarks_unmark

DELETE /public/orgs/{orgId}/spaces/{spaceId}/marks/{slug}/{markId}

Remove one of your own marks

client.readers.publicMarks.unmark()

Public Tags

Operation

Request

What it does

Shortcuts

PublicTags_tags

GET /public/orgs/{orgId}/spaces/{spaceId}/tags

List tags in a space (public reader)

client.public.tags.tags()

PublicTags_tagPages

GET /public/orgs/{orgId}/spaces/{spaceId}/tags/{tag}

Get the tag (public reader spaces)

client.public.tags.tagPages()

Reports

Operation

Request

What it does

Shortcuts

Reports_catalog

GET /orgs/{orgId}/reports

List reports in an organization

client.reports.catalog()

Reports_healthIndex

GET /orgs/{orgId}/reports/health

Community health report

client.reports.healthIndex()

Reports_membersCsv

GET /orgs/{orgId}/reports/members.csv

File of members (reports)

client.reports.membersCsv()

Reports_reportCsv

GET /orgs/{orgId}/reports/{report}/export.csv

File of export (reports)

client.reports.reportCsv()

Saved Queries

Operation

Request

What it does

Shortcuts

SavedQueries_list

GET /orgs/{orgId}/admin/saved-queries

List saved queries in an organization

client.savedQueries.list()

SavedQueries_create

POST /orgs/{orgId}/admin/saved-queries

Create a saved query in an organization

client.savedQueries.create()

SavedQueries_recipients

GET /orgs/{orgId}/admin/saved-queries/recipients

List recipients in an organization (admin saved queries)

client.savedQueries.recipients()

SavedQueries_preview

POST /orgs/{orgId}/admin/saved-queries/schedule-preview

Create the schedule preview in an organization

client.savedQueries.preview()

SavedQueries_get

GET /orgs/{orgId}/admin/saved-queries/{queryId}

Get the saved query

client.savedQueries.get()

SavedQueries_update

PATCH /orgs/{orgId}/admin/saved-queries/{queryId}

Update the saved query

client.savedQueries.update()

SavedQueries_remove

DELETE /orgs/{orgId}/admin/saved-queries/{queryId}

Delete the saved query

client.savedQueries.remove()

SavedQueries_run

POST /orgs/{orgId}/admin/saved-queries/{queryId}/run

Run the saved query

client.savedQueries.run()

SavedQueries_runs

GET /orgs/{orgId}/admin/saved-queries/{queryId}/runs

List runs in an organization (admin saved queries)

client.savedQueries.runs()

SavedQueries_getRun

GET /orgs/{orgId}/admin/saved-queries/{queryId}/runs/{runId}

Get the run

client.savedQueries.getRun()

SavedQueries_runCsv

GET /orgs/{orgId}/admin/saved-queries/{queryId}/runs/{runId}/export.csv

File of export (admin saved queries runs)

client.savedQueries.runCsv()

Scim

Operation

Request

What it does

Shortcuts

Scim_list

GET /scim/v2/Users

List users

client.scim.list()

Scim_create

POST /scim/v2/Users

Create an user

client.scim.create()

Scim_get

GET /scim/v2/Users/{id}

Get the user

client.scim.get()

Scim_patch

PATCH /scim/v2/Users/{id}

Update the user

client.scim.patch()

Scim_remove

DELETE /scim/v2/Users/{id}

Delete the user

client.scim.remove()

Scim Groups

Operation

Request

What it does

Shortcuts

ScimGroups_list

GET /scim/v2/Groups

List groups

client.scim.groups.list()

ScimGroups_create

POST /scim/v2/Groups

Create a group

client.scim.groups.create()

ScimGroups_get

GET /scim/v2/Groups/{id}

Get the group (SCIM v2 groups)

client.scim.groups.get()

ScimGroups_replace

PUT /scim/v2/Groups/{id}

Replace the group

client.scim.groups.replace()

ScimGroups_patch

PATCH /scim/v2/Groups/{id}

Update the group (SCIM v2 groups)

client.scim.groups.patch()

ScimGroups_remove

DELETE /scim/v2/Groups/{id}

Delete the group (SCIM v2 groups)

client.scim.groups.remove()

Security Policy

Operation

Request

What it does

Shortcuts

SecurityPolicy_twoFactor

GET /orgs/{orgId}/security/two-factor

My standing against the organization’s two-step-verification policy

client.securityPolicy.twoFactor()

SecurityPolicy_devices

GET /auth/login-devices

Devices my account has signed in from

client.securityPolicy.devices()

SecurityPolicy_forget

DELETE /auth/login-devices/{id}

Forget a remembered sign-in device

client.securityPolicy.forget()

Service Connectors

Operation

Request

What it does

Shortcuts

ServiceConnectors_catalog

GET /orgs/{orgId}/service-connectors/catalog

The service connectors on offer and their settings fields

client.serviceConnectors.catalog()

ServiceConnectors_list

GET /orgs/{orgId}/service-connectors

Every service connector and whether it is set up (never a secret)

client.serviceConnectors.list()

ServiceConnectors_get

GET /orgs/{orgId}/service-connectors/{provider}

One service connector (never a secret)

client.serviceConnectors.get()

ServiceConnectors_set

PUT /orgs/{orgId}/service-connectors/{provider}

Set up or change a service connector

client.serviceConnectors.set()

ServiceConnectors_remove

DELETE /orgs/{orgId}/service-connectors/{provider}

Remove a service connector and its stored credentials

client.serviceConnectors.remove()

ServiceConnectors_sync

POST /orgs/{orgId}/service-connectors/{provider}/sync

Pull the trust centre controls from Vanta or Drata now

client.serviceConnectors.sync()

ServiceConnectors_authorizeUrl

GET /orgs/{orgId}/service-connectors/{provider}/authorize-url

The consent URL that connects Zoom or Google Meet

client.serviceConnectors.authorizeUrl()

Template

Operation

Request

What it does

Shortcuts

Template_list

GET /orgs/{orgId}/templates

List the built-in space templates (deprecated)

client.orgs.templates.list()

Ticket Wallet

Operation

Request

What it does

Shortcuts

TicketWallet_apple

GET /public/orgs/{orgId}/event-tickets/{orderId}/wallet/apple

The ticket as an Apple Wallet pass (.pkpass)

client.ticketWallet.apple()

TicketWallet_google

GET /public/orgs/{orgId}/event-tickets/{orderId}/wallet/google

Send the ticket holder to Google Wallet to save the pass

client.ticketWallet.google()

The Forumulate teamUpdated