List the policy gate signing keys
https://forumulate.com/api /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys The space’s keyring: which key signs (primary), which still verify (active), and when a rotated key retires. Never a secret.
Requires token scope: read — see "Token scopes" under Authentication.
From Forumulate-Version: 2027-01-01 this list answers its rows once, under data (the named key is gone), leaves the document bodies (markdown, sections, content, html) out of each row unless include names them, and keeps only the fields you list. Earlier versions are unchanged. See /reference/versioning.
Parameters
orgIdstringpathrequiredslug. One you are not a member of is a 404.spaceIdstringpathrequiredforumulate-versionstringheaderforumulate-version: 2027-03-01 on every request from a new integration; it is optional only so that integrations written before versioning keep working. Each version includes everything before it:limitintegerquerycursorstringquerypage.nextCursor of the previous answer. Opaque — there is nothing inside one to construct or parse. Sending one also opts into the enveloped shape, exactly as stating Forumulate-Version does.includestring[]query2027-01-01: the document bodies to keep in each row, comma-separated. An unknown name is a 422 naming the nearest one. Ignored by earlier versions, which always send the bodies.fieldsstring[]query2027-01-01: the only keys to return in each row, comma-separated (id always comes back; naming a body field brings it back). A name no row on the page carries is a 422 with a did-you-mean. Ignored by earlier versions.Code samples
cURL
curl -X GET "https://forumulate.com/api/orgs/{orgId}/spaces/{spaceId}/policy-gate/keys" \
-H "Authorization: Bearer $FORUMULATE_API_KEY" \
-H "forumulate-version: <value>"TypeScript
Python
Go
PHP
Ruby
Java
Responses
Example
{
"keys": [
{
"active": true,
"createdAt": "string",
"kid": "string",
"primary": true,
"retiresAt": "string",
"revokedAt": "string"
}
],
"page": {
"limit": 0,
"hasMore": true,
"nextCursor": "string",
"has_more": true,
"next_cursor": "string",
"total": 0
},
"data": [
{
"active": true,
"createdAt": "string",
"kid": "string",
"primary": true,
"retiresAt": "string",
"revokedAt": "string"
}
]
}Schema
keysPolicyGateKeySummary[]2027-01-01: read data.Show child parameters
activebooleanrequiredcreatedAtstringrequiredkidstringrequiredprimarybooleanrequiredretiresAtstringrequiredrevokedAtstringrequiredpageobjectForumulate-Version. Each operation’s own response schema says whether it has one: a collection either carries page, or answers both this and its pre-envelope shape and picks by the header. No listing pages with keys of its own any more — the allowlists in openapi-collection-shape.test.ts are empty, which is what stops a new one from appearing. Read hasMore and nextCursor: the snake_case pair beside them is deprecated and is not sent to a caller who states 2026-11-01 or later.Show child parameters
limitintegerrequiredhasMore is the only thing that says whether to keep walking.hasMorebooleanrequirednextCursorstringrequiredcursor to continue. Null on the last page.has_morebooleandeprecatedhasMore. These two are the only snake_case keys in the API. Sent to callers who state 2026-09-01 or no version at all; ABSENT from 2026-11-01 onwards. Read hasMore instead — it is sent on every version.next_cursorstringdeprecatednextCursor. Absent from 2026-11-01 onwards; read nextCursor.totalintegerhasMore, which is exact on every answer; read total only to show a size, and handle its absence.dataPolicyGateKeySummary[]topics, articles, pages, monitors, …), under a key that is the same on every paged list — so one parser reads every collection. The SAME array, not a different selection: read either. Present whenever page is, i.e. when you state Forumulate-Version (or send a cursor); the unversioned shape does not carry it.Show child parameters
activebooleanrequiredcreatedAtstringrequiredkidstringrequiredprimarybooleanrequiredretiresAtstringrequiredrevokedAtstringrequiredrequest.malformed). A value that was understood but is not acceptable is a 422 validation.failed.Example
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
{}
]
}
},
"requestId": "string"
}Schema
errorobjectrequiredShow child parameters
codestringrequiredrbac.forbidden.messagestringrequireddetailsobjectdocsstringGET /space-types/{type}/presets. Present on errors over a controlled list, so the answer to "what am I allowed to send?" is in the error itself.reasonstringiat_too_old, iat_in_future, bad_signature, bad_issuer, bad_audience, expired and more. Branch on this, not on message.slugsstring[]space.slug_taken, page.slug_taken, collection.slug_taken, site.slug_taken or badge.slug_taken.conflictobjectPATCH …/entries/{entryId}/fields) whose If-Match was stale.Show child parameters
etagstringrequiredopsarrayrequiredIf-Match that touch a path your own operations touch. Empty: none did — retry the batch unchanged under etag. null: the history no longer reaches your tag — re-read the entry first.requestIdstringx-request-id and x-forumulate-request-id response headers. Quote it in a bug report: it is the only thing that finds your exact request in our logs. It is in the body because that is the part that survives — an SDK that throws the parsed body, a serverless log that keeps the body and drops the headers, a ticket with the JSON pasted in.auth.required). Send a Bearer token or the session cookie.Same as 400 · GET /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys
rbac.forbidden, whether what is missing is the capability, the token scope, or a resource the token is pinned away from. The message names the capability and, for a scope refusal, the scopes that would grant it. A refusal because of the PLAN is a 402 with quota.exceeded, not this.Same as 400 · GET /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys
*.not_found).Same as 400 · GET /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys
Same as 400 · GET /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys
Same as 400 · GET /orgs/{orgId}/spaces/{spaceId}/policy-gate/keys