Skip to Content
FeaturesAPI Keys

API Keys

API keys let your applications connect to Foir’s public API. Foir provides two types of keys — public and secret — distinguished by their prefix, giving you fine-grained control over access and permissions.

Overview

Every request to the Foir API requires an API key. Keys are scoped to a project and carry specific permissions that determine what operations the caller can perform.

Key Types

The key’s prefix tells you its type — there is nothing else to choose. Keys look like pk_<hex> and sk_<hex>.

TypePrefixUse ForCapabilities
Publicpk_Frontend, client-side codeRead-only, published content only
Secretsk_Backend, server-side codeFull read/write; can hold additional scopes

Public keys are safe to include in browser code, mobile apps, and any client-side application. They are read-only and can read only published content — they cannot be granted drafts:read or any secret-only scope (the API rejects such scopes on a pk_ key at creation time).

Secret keys must never be exposed to browsers or client-side code. They can create, update, delete, and publish content, and can be granted additional scopes (including drafts:read). The API rejects a sk_ key on any request that carries a browser Origin / Sec-Fetch-Site.

Tenant keys (pk_t_ / sk_t_) are multi-project variants: one key reaches an explicit allow-list of projects within the same tenant, and each request names the target project with an X-Project-Id header. Use them when a single deploy serves several projects (for example a storefront fleet). They behave like their pk_ / sk_ siblings otherwise. See Tenant Keys.

Reading draft content

There is no separate “test” key for previewing unpublished work. To read draft / unpublished content, use a secret key that holds the drafts:read scope and pass preview: true on the query (you can alternatively select the draft schema/design-tokens channel with the X-Foir-Schema-Channel: draft request header). Without drafts:read, preview: true is rejected with a permission error. See Preview Mode Access.

In the Admin

Creating a Key

  1. Open API keys in the sidebar (under Settings)
  2. Click Create API Key
  3. Fill in the form:
    • Name — something descriptive (e.g., “Production Frontend”, “Staging Backend”)
    • Key Type — PUBLIC (pk_) or SECRET (sk_)
    • Permissions — select the scopes this key needs
  4. Click Create
  5. Copy the key immediately — it is only shown once

Scopes and Permissions

Public Key Scopes

ScopeGrants Access To
records:readFetch published records
files:readFetch a file by ID
files:listList files (own uploads + shared library media)
search:readSemantic search (searchRecords, per-model search<Model>s)
search:semantic:readRead embedding coverage / similar-record queries
schemas:readRead project models / schemas
notifications:readRead customer notifications + preferences
schedules:readRead schedules
customers:readList / read customers and invitations
operations:readRead operation executions

Secret Key Scopes

All public scopes, plus:

ScopeGrants Access To
records:writeCreate and update records
records:publishPublish records
records:deleteDelete records
drafts:readRead draft content via preview: true (SECRET keys only)
files:writeUpload files
files:deleteDelete files
files:list.allList every customer’s files, unfiltered (SECRET keys only)
operations:executeExecute all operations (execute<Op>, cancel executions) — see Specific operations only to grant individual operations instead
search:semantic:writeGenerate / write / delete embeddings
notifications:writeSend notifications, register device tokens
customers:writeSet passwords, manage customer invitations
secrets:read.projectRead project-scoped secret plaintext via getSecret (SECRET keys only)
secrets:put.projectWrite project-scoped secrets via putSecret (SECRET keys only)
secrets:delete.projectSoft-delete project-scoped secrets via deleteSecret (SECRET keys only)

The drafts:read, files:list.all, and secrets:*.project scopes are restricted to secret keys at key-creation time — public keys (pk_*) are designed to be embeddable in browser bundles and would leak unpublished or privileged content. See Preview Mode Access.

Per-model record scopes (records:read:<model>, records:write:<model>, etc.) are reserved for scoped Ed25519 tokens and are rejected on API keys at creation time — keys must use the unscoped records:read / records:write / records:delete / records:publish forms. To bound an API key to specific models, use the model allow-list instead.

Restricting a Key’s Reach

Scopes control what kind of access a key has (read, write, execute). Two further selectors in the key form control which resources that access reaches. Both follow the same pattern: All (the default — no restriction) or Specific, with a picker. Use them to mint least-privilege keys for a frontend: the key exposes exactly the fields that application uses and nothing else.

Specific operations only

When a key holds operations:execute, an Operation Access selector appears:

  • All operations — the key can execute every operation in the project (today’s default, and the behavior of every key minted before this selector existed).
  • Specific operations only — pick the operations this key may execute. Under the hood the key’s operations:execute scope is replaced with one operations:execute:<operation-key> entry per selected operation.

An operation left off the list isn’t just denied — its execute<Op> mutation disappears from the schema the key sees, including introspection, so codegen against the key’s schema only ever sees what the key can call.

Two details to know:

  • The selector covers mutation-shaped operations (the execute<Op> surface). Query- and field-shaped operations are read surface and aren’t restricted per key.
  • cancelOperationExecution requires the unrestricted operations:execute scope — a key limited to specific operations can execute them but not cancel executions.

Specific models only

When a key holds any records scope, a Model Access selector appears (this is about API key reach — not to be confused with a model’s access config, which declares record ownership):

  • All models — the key reaches every model its scopes allow.
  • Specific models only — pick the models. The key’s record reads/writes and per-model search are bounded to that list: typed fields for other models disappear from the key’s schema and are denied if called.

A model-restricted key deliberately loses the generic record fields — record, records, recordVersions, searchRecords, and batchRecordOperations — because those can reach any model. Use the typed per-model fields (page, createPage, searchPages, …) instead; a restricted frontend shouldn’t need the generic escape hatches.

One caveat: the semantic-search scopes (search:semantic:read / search:semantic:write) are not model-bounded. If a restricted key must not surface other models’ content at all, don’t grant it semantic search.

Rotating a Key

If a key is compromised or needs to be changed:

  1. Open API keys in the sidebar (under Settings)
  2. Find the key and click Rotate
  3. Copy the new key
  4. Update your application
  5. The old key stops working immediately

Revoking a Key

To permanently disable a key:

  1. Open API keys in the sidebar (under Settings)
  2. Find the key and click Revoke

Revoked keys cannot be restored. Create a new key if needed.

Using Keys in Requests

Include the key in the x-api-key header:

curl -X POST https://api.foir.dev/graphql \ -H "Content-Type: application/json" \ -H "x-api-key: pk_..." \ -d '{"query": "{ pages(first: 10) { edges { node { _id } } } }"}'

In JavaScript:

const response = await fetch('https://api.foir.dev/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.FOIR_API_KEY, }, body: JSON.stringify({ query: `{ pages(first: 10) { edges { node { _id } } } }` }), });

Environment Variables

Store keys in environment variables, never in code:

# .env.local (do not commit this file) FOIR_PUBLIC_KEY=pk_... FOIR_SECRET_KEY=sk_...

The Foir CLI also uses the FOIR_API_KEY environment variable for headless authentication:

export FOIR_API_KEY=sk_... foir records list page --json

Via the CLI

# List all API keys for the current project foir api-keys list # Create a new API key (scopes are comma-separated) foir api-keys create \ --name "Production Frontend" \ --scopes records:read,files:read,search:read # Restrict execution to specific operations with per-operation scopes foir api-keys create \ --name "Checkout Frontend" \ --scopes records:read,operations:execute:checkout,operations:execute:enrich_url # Rotate a key (generates a new key, invalidates the old one) foir api-keys rotate <id> # Revoke a key (permanently disables it) foir api-keys revoke <id>

Rate Limits

Each API key has its own per-hour request budget. When exceeded, the API returns 429 Too Many Requests and the response carries a Retry-After header telling you how many seconds to wait.

  • Limits are configured per key in the dashboard; the default for new keys is 10,000 requests per hour.
  • The window is a sliding 60-minute window — bursts are flattened across the hour rather than refilling on a fixed cadence.
  • Every successful response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers so you can pace your client.
  • Enterprise plans support higher per-key limits — contact support to raise yours.

See Errors & Rate Limits for the full header reference and a recommended client retry/backoff pattern.

Best Practices

Do:

  • Use public keys for all client-side code (browsers, mobile apps).
  • Store secret keys in environment variables on your server.
  • Grant only the minimum scopes each key needs — and use the operation and model selectors to bound a frontend key to exactly the fields it uses.
  • Use descriptive names so you can identify which application uses each key.
  • Rotate keys on a regular schedule and immediately if compromised.
  • Use a secret key with drafts:read (plus preview: true) when a server-rendered preview environment needs unpublished content.

Do not:

  • Commit keys to version control.
  • Use secret keys in browser or client-side code.
  • Use one key everywhere — create separate keys per application and environment.
  • Grant more permissions than necessary.
Last updated on