Skip to Content
API ReferenceConnections

Connections

A connection records that one site may use one service — a database, a storage bucket or an auth app — at one level. It drives the variables the site’s containers receive, its managed platform key and, for a database, the network policy. See the Connections guide for the concept.

Nothing connects on its own. A connection exists because someone chose it: at create (connect_site_ids on databases, buckets and auth apps), when ghayma deploy asks, or through the routes below.

All endpoints require Authorization: Bearer TOKEN. :id is a project id or slug; :siteId is a site uuid of that project.

These routes take an account token (gh_…) or a dashboard session. A project API key (gsk_…) answers 403: a site’s managed key is minted from its own connections, so a key able to change them could widen what it may do.

Kinds and Levels

Every kind takes two levels, listed here weakest first.

kindlevelGrants
databaseread-onlyThe connection variables and the network path, with a credential that may only read
databaseconnectThe connection variables and the network path
bucketreadThe bucket’s S3 credentials, with a key that may only GET and LIST
bucketread-writeThe bucket’s S3 credentials
auth_appclientThe app id, both endpoints and the server key
auth_appadminThe above, plus auth on the site’s managed GHAYMA_API_KEY, narrowed to that app

Omit level on a write and the kind’s default is used — connect for a database, read-write for a bucket, client for an auth_app. A level the kind does not accept is a 400. Reads need the project read role; every mutation needs write.

Connecting a site to a service also creates a credential for that connection alone: a database role, or a MongoDB or Valkey user, named c_ followed by eight characters, or an S3 access key scoped to that one bucket. The site’s DATABASE_URL, MONGODB_URI, REDIS_URL and STORAGE_ACCESS_KEY/STORAGE_SECRET_KEY carry it instead of the service’s own credential, and disconnecting deletes that credential and nothing else. See Each app gets its own database credential and Each app gets its own storage key.

A connection that cannot get a credential of its own is not made. Two refusals say why, both on POST and PUT:

StatuscodeWhen
409not_runningThe database or bucket is stopped, or still being provisioned: “the service is not ready: start it (or let it finish provisioning), then try again”. Connect it once it runs
409credential_unsupportedThe service cannot give an app a credential of its own: “this service cannot give an app a credential of its own, so the connection was not made”

A Valkey that is not running answers 409 with code: database_not_running instead (see Valkey).

The level does not change env_names or the network path; it changes only what the credential may do. See Read-only connections.

Connection Object

Every endpoint returns this shape:

{ "site_id": "b0a1…", "site_slug": "admin", "kind": "database", "resource_id": "9f3c…", "resource_name": "conn-db", "level": "connect", "env_names": ["DATABASE_URL", "DATABASE_URL_CONN_DB"], "created_at": "2026-09-08T10:00:00Z" }

resource_name is the database’s or bucket’s name, or the auth app’s app_id.

env_names

env_names holds the exact variable names the platform injects into that site for that connection — names only, never values. Read the values with ghayma env pull.

Connectionenv_names
PostgreSQL database conn-db, the site’s only databaseDATABASE_URL, DATABASE_URL_CONN_DB
Valkey database cache, the site’s only ValkeyREDIS_URL, VALKEY_URL, REDIS_URL_CACHE, VALKEY_URL_CACHE
Bucket conn-filesthe five STORAGE_* names, and the same five as STORAGE_CONN_FILES_*
Auth app azeghjGHAYMA_AUTH_APP_ID, GHAYMA_AUTH_URL, GHAYMA_AUTH_PUBLIC_URL, GHAYMA_AUTH_SERVER_KEY, and the same four names again suffixed _AZEGHJ

The suffix is the service’s name upper-cased, with every character that is not A–Z or 0–9 replaced by _. The first service of each kind the site was connected to also holds the plain, unsuffixed names; every service holds the suffixed ones. The full derivation is in Automatically injected variables.

List Project Connections

GET /api/v1/projects/:id/connections
QueryDescription
site_idOnly this site’s connections
kindOnly database, bucket or auth_app
resource_idOnly connections to this resource — with kind, this is “which apps use this service”

Response: 200

{ "connections": [ { "site_id": "b0a1…", "site_slug": "main", "kind": "database", "resource_id": "9f3c…", "resource_name": "conn-db", "level": "connect", "env_names": ["DATABASE_URL", "DATABASE_URL_CONN_DB"], "created_at": "2026-09-08T10:00:00Z" } ] }

A project with no connections returns { "connections": [] }.

List a Site’s Connections

GET /api/v1/projects/:id/sites/:siteId/connections

Returns what the site is connected to and what else it could be connected to, so a UI needs one call.

Response: 200

{ "connections": [ { "site_id": "b0a1…", "site_slug": "admin", "kind": "auth_app", "resource_id": "7d21…", "resource_name": "main-auth", "level": "client", "env_names": ["GHAYMA_AUTH_APP_ID", "GHAYMA_AUTH_URL", "…"], "created_at": "2026-09-08T10:00:00Z" } ], "available": [ { "kind": "bucket", "resource_id": "4e88…", "resource_name": "conn-files", "levels": ["read", "read-write"], "env_names": [ "STORAGE_ENDPOINT", "STORAGE_ACCESS_KEY", "STORAGE_SECRET_KEY", "STORAGE_BUCKET", "STORAGE_REGION", "STORAGE_CONN_FILES_ENDPOINT", "STORAGE_CONN_FILES_ACCESS_KEY", "STORAGE_CONN_FILES_SECRET_KEY", "STORAGE_CONN_FILES_BUCKET", "STORAGE_CONN_FILES_REGION" ] } ] }

available lists the project’s services this site does not hold, each with the levels it accepts, weakest first — ["read-only", "connect"] for a database, ["read", "read-write"] for a bucket, ["client", "admin"] for an auth app. A deleted database and a disabled auth app never appear. An available entry carries env_names too — there it is what connecting the service would add.

List Unconnected Services

GET /api/v1/projects/:id/connections/unconnected

The project’s databases, buckets and auth apps that no site is connected to — what the console’s banner names and what ghayma deploy offers.

Response: 200

{ "unconnected": [ { "kind": "database", "resource_id": "9f3c…", "resource_name": "shop-db", "default_level": "connect" } ] }

default_level is the level a connection made without one gets. A project whose services are all in use returns { "unconnected": [] }.

Replace a Site’s Connections

PUT /api/v1/projects/:id/sites/:siteId/connections

Body — the full desired set (an empty array disconnects everything):

{ "connections": [ { "kind": "database", "resource_id": "9f3c…", "level": "connect" }, { "kind": "auth_app", "resource_id": "7d21…", "level": "admin" } ] }

Response: 200 — the site’s connections after the write, in the connection object shape. Only what actually moved is re-derived, so saving an unchanged set restarts nothing.

Connect a Service

POST /api/v1/projects/:id/sites/:siteId/connections

Connects one service, or changes the level of a connection that already exists.

Body:

{ "kind": "auth_app", "resource_id": "7d21…", "level": "admin" }

Response: 201 — the stored connection object.

Disconnect a Service

DELETE /api/v1/projects/:id/sites/:siteId/connections/:kind/:resourceId

Response: 200

{ "removed": true }

removed is false when there was no such connection. Disconnecting twice is not an error, so a retry after a dropped response is safe.

Rotate a Connection’s Credential

POST /api/v1/projects/:id/sites/:siteId/connections/:kind/:resourceId/rotate

Gives that one connection’s credential a new secret. Nothing else moves: not the level, not the env_names, not another site’s credential, and never the service’s own credential. kind is database or bucket — an auth_app has no per-connection credential, and rotating one answers 409 with code: nothing_to_rotate: “this kind of connection carries no credential of its own”.

Response: 204 — no body. The new secret is never returned; it reaches the app through the live patch this fires and through the next deploy’s derivation. See Rotating Credentials.

A 409 means there is nothing to rotate right now: the connection has no credential of its own yet, or the service is stopped. With no code, the connection is still waiting for its own credential — “this connection has no credential of its own yet, so there is nothing to rotate — the platform gives it one as soon as the service can (it retries every ten minutes); retry later, or reconnect it”. With code: not_running, the database or bucket is stopped or still being provisioned: start it, then rotate. A Valkey that is not running answers code: database_not_running instead (see Valkey). Branch on code first. A 503 is an engine that could not be reached: nothing changed, and the same call works later.

Errors

CodeWhen
400Unknown kind, a level that kind does not accept, or a resource that belongs to another project
403The caller’s project role is too low — or the caller is a project API key
404No such project, :siteId is not a site of it, :siteId is not a uuid, or — on a rotate — the service is not connected to that site
409Rotate, with no code: the connection has no credential of its own yet. The platform gives it one as soon as the service can (it retries every ten minutes); retry later, or reconnect it
409With code: not_running (POST, PUT and rotate): the database or bucket is stopped or still being provisioned. Start it, then try again
409With code: credential_unsupported (POST, PUT): the service cannot give an app a credential of its own, so the connection was not made
409With code: nothing_to_rotate (rotate): an auth app connection, which carries no credential of its own
409With code: database_not_running (POST, PUT and rotate): the database is a Valkey that is not running yet. Connect it, change its level or rotate it once it runs
503POST, PUT and rotate: the service could not prepare or replace the site’s own credential — a database that was unreachable at that moment, for example. Retrying later works

The per-service routes GET/PUT /databases/:id/sites and GET/PUT /storage/:id/sites keep working. They read and write these same connections, one service at a time. A PUT needs write on every site whose access it changes.