Skip to Content
Framework GuidesProject API Keys

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 :

FieldWhat it does
NameHow you recognize the key later, e.g. production server. Up to 100 characters.
PurposeApplication opens the auth apps, buckets and databases sections; CI/CD opens sites. It grants nothing by itself.
Auth apps, Buckets, Databases, SitesOne 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:

GrantToken scope
databasedatabases
auth_appauth-apps
bucketstorage
sitedeploy

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:

KindLevelThe key may
auth_appreadRead the app and its statistics; list and read its end users
auth_appadminThe 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
bucketreadRead the bucket; list, inspect and download objects; mint presigned download URLs
bucketread-writeThe above, plus upload and delete objects, including batch and prefix deletes, and mint presigned upload URLs
databasereadRead the database and its metrics
sitedeployUpload 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_id goes 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 read or read-write acts as that bucket level, a database connection at either level acts as database read, and an auth app connected at admin acts as auth_app admin. Any other bucket, database or auth app answers 403 with code not_connected, and an object write through a read bucket connection answers 403 with code read_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 list and 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 answers 403 with code not_connected. A key you create belongs to no site, so it gets 403 with code site_key_required on 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 toYouOne project
ReachesEvery project you can reach, plus your account routes — or, restricted, only the projects you nameThe resources its grants name, through that project’s allowlisted application routes
Created inSettings → API TokensProject → Settings → API keys
ExpiryOptional, chosen at create — 30 / 90 / 365 days or neverOptional, chosen at create
RotationRotate mints the replacement and kills the original in one transaction — no overlapRoll issues a second key and leaves the old one working until you revoke it
RevokeSoft — it stops working at once, its history staysSoft — the row is kept and shows as revoked
AuditPer-token Activity: every entry the token wrote, newest firstNot itemized per key
Use it forThe ghayma CLI, and any orchestrator or pipeline that spans projectsServer 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, plus domains if it manages domains. Leave out tokens, billing, teams and account unless 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:

BeforeAfter
storageAll buckets, read-write
databasesAll databases, read
deployAll sites, deploy
auth, restricted to some auth appsThose apps, admin
auth, unrestrictedAll 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.