API keys
An API key lets a backend call Gate's own /api/v1 endpoints without a user session. It is the
right credential for reading configuration at boot, recording usage, checking entitlements, or
running checkout from a worker.
It is the wrong credential for representing a user — for that, register an OAuth client. An API key also never administers Gate itself: creating keys, changing project settings, and every organization route need a person.
Key format
gate_sk_<12 alphanumeric>_<43 base64url>
└── prefix ──┘ └─── secret ───┘The prefix is stored in the clear and shown in the dashboard, so you can tell keys apart. The whole key is hashed with HMAC-SHA256 and compared in constant time. Gate never stores the key itself, so it cannot be recovered — only replaced.
Create one
Open the project's API keys tab and choose Create API key. Give it a name, optionally limit it to one environment, and pick its scopes. Owners and admins can create keys.
The same call over HTTP requires a user session with credentials:manage:
const response = await fetch(
`https://evonia-gate.annatarhe.com/api/v1/projects/${projectId}/api-keys`,
{
method: "POST",
headers: { "content-type": "application/json", cookie: request.headers.get("cookie")! },
body: JSON.stringify({
name: "ShellTime API",
description: "Reads configuration at boot and records usage",
environmentId, // omit to allow every environment
scopes: ["config:read", "usage:write"],
expiresAt: "2027-01-01T00:00:00.000Z", // optional
}),
},
);
const { data } = await response.json();
// data.secret exists only in THIS response.
await secretStore.write("GATE_API_KEY", data.secret);curl -sS -X POST \
"https://evonia-gate.annatarhe.com/api/v1/projects/$PROJECT_ID/api-keys" \
-H "content-type: application/json" \
-b "better-auth.session_token=$SESSION" \
-d '{
"name": "ShellTime API",
"description": "Reads configuration at boot and records usage",
"scopes": ["config:read", "usage:write"]
}'Constraints:
nameis 2–80 characters;descriptionis optional, up to 500.scopesholds at least one of the API key scopes.environmentId, when given, must belong to the project.expiresAtis an optional ISO-8601 timestamp.
Use it
Send the key as X-API-Key. Do not put it in a query string.
const response = await fetch(
`https://evonia-gate.annatarhe.com/api/v1/environments/${environmentId}/configs`,
{
headers: {
"x-api-key": process.env.GATE_API_KEY!,
"x-request-id": crypto.randomUUID(),
},
},
);curl -sS "https://evonia-gate.annatarhe.com/api/v1/environments/$ENVIRONMENT_ID/configs" \
-H "x-api-key: $GATE_API_KEY"req.Header.Set("X-API-Key", os.Getenv("GATE_API_KEY"))request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")Scopes
Every key can read its own project and environments. Everything else needs a scope:
| Scope | Allows |
|---|---|
config:read | Read configuration. API keys also receive secret values. |
config:write | Create, update, archive, and restore configuration. |
usage:read | Read usage analytics. |
usage:write | Record usage events. |
billing:read | Read Stripe settings, plans, subscriptions, and entitlements. |
billing:write | Manage Stripe settings and plans, start checkout, cancel, and grant entitlements. |
users:write | Remove, restore, or delete a product's end users. |
* | Every API key scope. |
Grant the narrowest set that works:
| Job | Scopes |
|---|---|
| Read configuration at boot | config:read |
| Record usage events | usage:write |
| Read analytics | usage:read, billing:read |
| Check entitlements | billing:read |
| Run checkout and grants | billing:write |
| Remove or delete end users | users:write |
* grants every scope above, and nothing more: it never lets a key change project settings, manage
credentials, or reach another project.
Environments
A key with an environment set is confined to it. Any request touching a different one fails with
403 API key is limited to another environment — before the scope check runs.
Limit production keys to production. A key for all environments that leaks from a staging host can read production secrets.
Rotation
There is no rotate endpoint. Roll forward:
-
Create a second key with the same scopes.
-
Deploy the new key to every consumer.
-
Confirm the old key is idle — its last use stops advancing.
-
Revoke the old key from the API keys tab, or with
POST /projects/{projectId}/api-keys/{keyId}/revoke.
Revoking takes effect immediately and cannot be undone. Set expiresAt on creation if you want a
deadline that enforces itself.