The extension API

Embed
Preview of the card
<a href="https://forumulate.com/guides/extensions"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/dd5c03d7-6dec-4de0-8b38-d76067716246/social-card.svg?v=3e7ae33eec8b" alt="The extension API" width="600" height="315"></a>

An extension is a remote app installed into an organization. Once installed it calls back into Forumulate through a small, scoped API under /ext/v1. It is authenticated by the per-install access token, not by a user session, and every route checks that the install was granted the scope it needs. This page lists those routes and what each scope allows.

Send the token as a bearer header. A missing or disabled token is a 401; a token without the needed scope is a 403 naming the scope. The API is throttled to 120 requests per minute.

GET /ext/v1/me
Authorization: Bearer <install access token>

/ext/v1/me needs no scope. It returns installId, organizationId and the scopes this install was granted, which is the quickest way to see why a call was refused.

Scopes

Scope

Routes

read:pages

GET /ext/v1/pages/{pageId}, GET /ext/v1/spaces/{spaceId}/pages

write:pages

POST /ext/v1/pages/{pageId}/draft

read:comments

GET /ext/v1/pages/{pageId}/comments

write:comments

POST /ext/v1/pages/{pageId}/comments

read:members

GET /ext/v1/members

read:analytics

GET /ext/v1/spaces/{spaceId}/analytics

Request only the scopes the extension needs; they are granted at install time.

Read pages

GET /ext/v1/pages/{pageId} returns the published page. GET /ext/v1/spaces/{spaceId}/pages lists a space's page summaries. Both answer { page } and { pages }.

Save a draft

POST /ext/v1/pages/{pageId}/draft
If-Match: "<page etag>"
{ "title": "Rewritten intro", "content": { "type": "doc", "content": [] } }

title is 1 to 300 characters and content is a block-model document. The draft goes through the same save as the console, so it does not publish anything; the response is { "revisionId": "…" }. Send If-Match with the page's current tag to avoid overwriting an author's save made a moment earlier.

Comments, members and analytics

POST /ext/v1/pages/{pageId}/comments
{ "body": "Link checker found 2 dead links.", "authorName": "Link checker" }

body is 1 to 5000 characters; authorName is optional (up to 120). The response is { comment }. GET .../comments lists a page's comments, GET /ext/v1/members lists the organization's members, and GET /ext/v1/spaces/{spaceId}/analytics returns the space's analytics summary.

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.

Ext Api

Operation

Request

What it does

Shortcuts

ExtApi_me

GET /ext/v1/me

The signed-in account, for an extension

client.extensions.api.me()

ExtApi_page

GET /ext/v1/pages/{pageId}

Get the page (ext v1)

client.extensions.api.page()

ExtApi_spacePages

GET /ext/v1/spaces/{spaceId}/pages

List pages in a space (ext v1)

client.extensions.api.spacePages()

ExtApi_saveDraft

POST /ext/v1/pages/{pageId}/draft

List draft pages

client.extensions.api.saveDraft()

ExtApi_comments

GET /ext/v1/pages/{pageId}/comments

List comments

client.extensions.api.comments()

ExtApi_postComment

POST /ext/v1/pages/{pageId}/comments

Create a comment

client.extensions.api.postComment()

ExtApi_members

GET /ext/v1/members

List members

client.extensions.api.members()

ExtApi_analytics

GET /ext/v1/spaces/{spaceId}/analytics

List analytics in a space (ext v1)

client.extensions.api.analytics()

The Forumulate teamUpdated