chatformdocs

Scopes

What a key is allowed to do, and how to give it as little as possible.

A key carries scopes as resource: [actions]. They are checked on every request, and a missing one is a 403 naming exactly what was needed:

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key lacks the session:create scope",
    "required": "session:create"
  }
}

The vocabulary

Every scope below gates at least one endpoint. Which one a given endpoint needs is on its reference page, and in the spec as x-required-scope.

ResourceActionsWhat it covers
formread, write, publishReading forms, editing documents, publishing versions
responseread, write, exportCreating and reading responses, and requesting exports
sessioncreate, write, readConversations
webhookread, writeDelivery endpoints
fileread, writeUploads and downloads
analyticsreadAggregates
aigenerateGenerating or editing a document with a model
paymentread, writeConnected payment gateway accounts (Cashfree, Razorpay, Stripe)

ai:generate is the only scope that costs money to use. A generation spends your plan's ai_generations allowance and the tokens it takes, so it is a separate resource rather than part of form:write: a key that writes documents your own code composed should not also be able to start spending your model budget. It is not in the agent preset for the same reason — ask for it deliberately.

Two more names exist in the vocabulary that GET /api/keys/scopes reports, and no endpoint requires either: response:read_partial and response:delete. Granting them changes nothing. They are there because the dashboard's own roles are written in the same vocabulary — a viewer who may read completed responses but not unfinished ones is a real distinction inside the product, and it has no API surface yet.

Reading unfinished responses is gated by your plan, not by a scope: GET /v1/forms/{id}/responses?status=partial answers 402 on a plan without partial responses, whatever the key holds.

Defaults

A new secret key gets form:read, response:read and all of session — enough for most integrations, and nothing destructive. Ask for more explicitly.

Publishable keys have a ceiling, not a default: form:read, session:* and file:write, and no amount of asking will widen it. That is what makes them safe to ship in a page.

Because the ceiling is a hard one, an endpoint guarded by anything outside it is secret-key-only in fact, so each reference page lists only the key types that can really reach it. Most of the /v1 surface needs sk_; what a publishable key can reach is sessions, blocks, a published form's config, and uploading a file. The reference page for an endpoint is the authority on which key types it accepts.

What no key can do

Some things are unreachable by any key, whatever its scopes say: minting keys, changing your plan, reading the audit log, managing team members. Those need a signed-in person with the right role.

This is deny-by-default — a capability is unreachable until it is deliberately mapped — so a new dashboard feature is never accidentally exposed to every existing key.

Pinning a key to specific forms

A key can be restricted to a list of forms. Anything outside that list returns 404, not 403, so a restricted key cannot be used to discover which form ids exist.

Useful when you are handing a key to a contractor, a client, or a single deployment that only has business with one form.

Least privilege in practice

  • A script that exports responses nightly: response:read, response:export.
  • A signup page: a publishable key, pinned to the signup form.
  • A form-authoring pipeline: form:read, form:write, form:publish — and nothing that can read anyone's answers.

On this page