Project API Keys
A project API key belongs to a project, not to a person. It reaches only the resources you grant it — this bucket, that auth app — so a leaked key exposes those, not everything you can reach.
What they are
- Project-owned. A key is created inside one project and never moves. It reaches that project’s resources and nothing else.
- Prefixed
gsk_. A key is 60 characters:gsk_, a random body, and a checksum that lets the API reject a truncated or mistyped key before it looks anything up. Lists show only the first 12 characters. - One per server or pipeline. Name a key after where it lives —
production server,github actions— so revoking it never takes down more than the one thing you meant. - Shown once. The key is displayed when you create it and never again. If you lose it, roll it.
On Ghayma or outside it
Code running on Ghayma already has a key: its site’s
GHAYMA_API_KEY, limited to what the site is connected to.
Connect the site to the bucket, database or auth app it uses, and the key
follows.
Create a key here only for code outside Ghayma: an external server, an admin script, a CI pipeline.
Create one
Open Project → Settings → API keys in the console :
| Field | What it does |
|---|---|
| Name | How you recognize the key later, e.g. production server. Up to 100 characters. |
| Purpose | Application opens the auth apps, buckets and databases sections; CI/CD opens sites. It grants nothing by itself. |
| Auth apps, Buckets, Databases, Sites | One section per kind: None (the default), Specific — tick resources and pick a level for each — or All, including future ones. |
| Advanced → Expires in (days) | Optional. Empty means the key never expires. |
Create key stays disabled until the key holds at least one grant. Copy the value straight away — it is not shown again. To create keys over the API, see API Keys.
Anyone with the read role on the project can see the list. Creating, editing,
rolling and revoking need admin or owner. A project API key can never manage
keys, including itself: those routes answer 403 for a key.
Over the API, a key can hold no more than your token. Each grant kind needs the token scope that opens its own routes:
| Grant | Token scope |
|---|---|
database | databases |
auth_app | auth-apps |
bucket | storage |
site | deploy |
Creating, editing or rolling a key with a grant your token’s scopes don’t cover
answers 403 insufficient token scope, with the missing scopes in required.
A dashboard session and a full token always pass.
What a key can reach
A key holds grants. Each grant names a kind, one resource of that kind or all of them, and a level:
| Kind | Level | The key may |
|---|---|---|
auth_app | read | Read the app and its statistics; list and read its end users |
auth_app | admin | The above, plus disable, enable and delete a user, reset a user’s 2FA, set a user’s app_metadata, and mint a password-reset link |
bucket | read | Read the bucket; list, inspect and download objects; mint presigned download URLs |
bucket | read-write | The above, plus upload and delete objects, including batch and prefix deletes, and mint presigned upload URLs |
database | read | Read the database and its metrics |
site | deploy | Upload a deploy to the site; read the site, its deployments and their logs; roll it back. Any site grant also lets the key read the project |
- All, including future ones. A grant without a resource covers every resource of its kind, including ones created later. A specific grant and an “all” grant on the same kind resolve to the stronger level.
- Specific grants name resources by id. A database deleted and recreated under the same name is not granted again. A grant on a deleted resource reaches nothing, and the console marks it deleted.
- Lists show only what the key holds. Listing auth apps, buckets, databases, a project’s sites or its deployments returns the granted ones.
- Some site routes need every site. A project’s combined logs, and a
deployment made before the project had sites, need the all-sites grant. A
deploy upload without
site_idgoes to the project’s default site and needs a grant on it; if the project has no site yet, or several and no default, only an all-sites key may deploy. - No database passwords. A database’s password comes from a site’s connection, never from a key you create — see Managed keys.
What it cannot
Everything that creates, destroys or reconfigures infrastructure stays with a
human, in the console or the ghayma CLI:
- Create, update or delete an auth app, rotate its signing keys, or touch its server key.
- Create, edit, roll or revoke API keys.
- Create, delete, expose or unexpose a bucket, rotate an app’s key for it, or edit its CORS rules.
- Create, delete, stop, start, resize or retier a database, rotate an app’s credential for it, change which sites reach it, or take and restore backups.
- Reach a database through the API’s data plane — collections, documents, queries, tables. An app talks to its database over the wire with the credentials, not through this API.
- Billing, account, teams, account tokens and domains. A key is never granted those scopes.
Another project’s resource answers 404, and a route outside the
allowlist answers 403. A resource of the key’s own project that it holds no
grant on answers 403 with code not_granted, and an action above its level —
an upload through a read grant — answers 403 with code read_only_grant.
Every code and its fix is listed under Refusals.
Edit a key’s access
Edit on a key’s row opens the same form, pre-filled. Save changes
replaces the key’s grants; the key itself does not change, so code using it
keeps working with the new access, which applies at once. Each edit is audited
as project_api_key.grants_updated, with the grants before and after.
A key holding an “all” grant also shows Narrow this key, which opens the
same form, to trade “all” for the resources the key actually uses. Over the
API, an edit is a PATCH.
Managed keys
You do not create every key. The platform mints one for each site connected to
a storage bucket, a database, or an auth app at level admin, and injects it
into that site’s container as GHAYMA_API_KEY on every deploy — the hand-copy
step disappears.
- Named
site:<site>. One per site, listed alongside the keys you created and marked managed. - Its reach follows the connections. A managed key holds no grants. It
reaches the buckets, databases and auth apps its site is
connected to, and nothing else in the project: a bucket
connection at
readorread-writeacts as that bucket level, a database connection at either level acts as databaseread, and an auth app connected atadminacts asauth_appadmin. Any other bucket, database or auth app answers403with codenot_connected, and an object write through areadbucket connection answers403with coderead_only_connection. Connect or disconnect a service and the key is re-minted to match; disconnect the site’s last one and the key is revoked with it. A change reaches the running pods without a redeploy. - Not yours to edit, roll or revoke. Those answer
409(managed by connections) for a managed key: rolling one by hand would leave the running app holding a dead credential, and the next deploy would mint a replacement beside it anyway. Change the site’s connections instead. - Revoked with its site. Deleting the site revokes its managed key in the same step; it stops working within 30 seconds and the revoke is audited. A managed key whose site is already gone can also be revoked by hand.
- Not stored as an env var. Like the database and storage variables, it is
derived at deploy time, so it never appears in
ghayma env listand there is nothing to edit. - Credentials are its site’s own. Reading a database’s or a bucket’s
credentials (
GET /databases/:id/credentials,GET /storage/:id/credentials) with a managed key returns the site’s own connection credential — the one in its connection variables. A service the site isn’t connected to answers403with codenot_connected. A key you create belongs to no site, so it gets403with codesite_key_requiredon both routes.
A managed key administers an auth app’s end users only when its site is
connected to that app at admin. At client — where a new connection starts —
it cannot touch them (403 read_only_connection): raise the connection to
admin.
Use it
On your server, hand the key to the SDK:
import { Ghayma } from "@ghayma/sdk";
const ghayma = new Ghayma({ apiKey: process.env.GHAYMA_API_KEY });Zero-argument new Ghayma() reads GHAYMA_API_KEY from the environment, so a
site that already has the variable needs no argument at all — and a site
connected to a bucket, a database, or an auth app at admin already has it.
When you set GHAYMA_API_KEY yourself. For a server hosted outside
Ghayma, create a key with the grants it needs and set the variable in that
server’s environment.
CLI support for project keys is not shipped yet. A key with a site grant
authenticates the deploy API routes today, but ghayma deploy with a project
key comes in a later CLI release. Run your pipeline on an account
token meanwhile — scoped to projects,deploy, with an expiry
and a rotation schedule.
Roll and revoke
Roll issues a fresh key with the same name (suffixed (rolled)), the same
grants and the same remaining lifetime. The old key keeps working until you
revoke it, which is the point: deploy the new value, confirm it works, then
revoke the old one. No downtime, no window where nothing authenticates.
To change what a key reaches, edit it instead: rolling changes the secret, not the access.
Revoke takes a key out of service. The row is kept for audit and shows as
revoked in the list. Keys are validated against a 30-second cache, so a
revoke takes effect everywhere within 30 seconds.
Both refuse a managed key with a 409 while its site exists —
it is retired by disconnecting the site, not by hand. Deleting the site revokes
it; a managed key left over from a deleted site can be revoked here.
A password reset or change leaves project keys alone. It signs out your sessions and revokes your account tokens, but a key belongs to its project, so it keeps working. If you think a key leaked, roll and revoke it here.
Account tokens vs project keys
Account token (gh_…) | Project API key (gsk_…) | |
|---|---|---|
| Belongs to | You | One project |
| Reaches | Every project you can reach, plus your account routes — or, restricted, only the projects you name | The resources its grants name, through that project’s allowlisted application routes |
| Created in | Settings → API Tokens | Project → Settings → API keys |
| Expiry | Optional, chosen at create — 30 / 90 / 365 days or never | Optional, chosen at create |
| Rotation | Rotate mints the replacement and kills the original in one transaction — no overlap | Roll issues a second key and leaves the old one working until you revoke it |
| Revoke | Soft — it stops working at once, its history stays | Soft — the row is kept and shows as revoked |
| Audit | Per-token Activity: every entry the token wrote, newest first | Not itemized per key |
| Use it for | The ghayma CLI, and any orchestrator or pipeline that spans projects | Server code and CI outside Ghayma, inside one project |
Both are first-class and neither is being retired. Pick by blast radius: a project key is the smaller credential, so an app’s server code should hold one — but a system that drives several projects, or calls a route no key is granted (teams, billing, domains, token management), needs a token, and that is a supported thing to do.
An account token still authenticates @ghayma/sdk and logs one warning per
process suggesting a project key, because for a single app’s server code a key
is the narrower choice. The call works either way.
Running a token like a credential
For an orchestrator, a CI pipeline, or anything long-running that acts as you:
- One token per system, named after where it lives — revoking one then takes down nothing else.
- Only its projects — if it works on a few projects, restrict it to them.
- Minimal scopes —
projects,deploy,databases,storage,auth-apps, plusdomainsif it manages domains. Leave outtokens,billing,teamsandaccountunless the system drives them. - Set an expiry so a forgotten credential lapses by itself.
- Rotate on a schedule — one call mints the replacement and revokes the original.
- Read its Activity when you want to know what one system did, without wading through the whole account’s trail.
The routes behind all of that are on the Account Tokens API page.
Keys made before grants
Keys used to be limited by type — auth, storage, databases, deploy —
plus an optional list of auth apps. Existing keys were given equivalent access
when this release shipped, so none stopped working:
| Before | After |
|---|---|
storage | All buckets, read-write |
databases | All databases, read |
deploy | All sites, deploy |
auth, restricted to some auth apps | Those apps, admin |
auth, unrestricted | All auth apps, admin |
A key that came through with an “all” grant shows Narrow this key: edit it down to the resources it really uses.