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.
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.
kind | level | Grants |
|---|---|---|
database | read-only | The connection variables and the network path, with a credential that may only read |
database | connect | The connection variables and the network path |
bucket | read | The bucket’s S3 credentials, with a key that may only GET and LIST |
bucket | read-write | The bucket’s S3 credentials |
auth_app | client | The app id, both endpoints and the server key |
auth_app | admin | The 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 MongoDB user named c_ followed by eight characters,
or an S3 access key scoped to that one bucket. The site’s DATABASE_URL,
MONGODB_URI and STORAGE_ACCESS_KEY/STORAGE_SECRET_KEY carry it instead of
the service’s own credential, and disconnecting deletes that credential and
nothing else. Standalone MongoDB is the exception — it still hands out the shared
user. See Each app gets its own database
credential and
Each app gets its own storage
key.
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.
| Connection | env_names |
|---|---|
PostgreSQL database conn-db, the site’s only database | DATABASE_URL, DATABASE_URL_CONN_DB |
Bucket conn-files | the five STORAGE_* names, and the same five as STORAGE_CONN_FILES_* |
Auth app azeghj | ESPACETECH_AUTH_APP_ID, ESPACETECH_AUTH_URL, ESPACETECH_AUTH_PUBLIC_URL, GHAYMA_AUTH_APP_ID, ESPACETECH_AUTH_SERVER_KEY, and the four ESPACETECH_AUTH_* 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| Query | Description |
|---|---|
site_id | Only this site’s connections |
kind | Only database, bucket or auth_app |
resource_id | Only 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/connectionsReturns 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": ["ESPACETECH_AUTH_APP_ID", "ESPACETECH_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.
Replace a Site’s Connections
PUT /api/v1/projects/:id/sites/:siteId/connectionsBody — 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/connectionsConnects 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/:resourceIdResponse: 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/rotateGives 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 shared credential. kind is database or bucket — an
auth_app has no per-connection credential.
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.
Beyond the errors below, this route answers 409 when the connection is served
by the service’s shared credential (a standalone MongoDB, or a connection
made while the database was stopped) — retrying cannot help, so rotate that
credential on the service itself — and 503 when the engine could not be
reached, in which case nothing changed and the same call works later.
Errors
| Code | When |
|---|---|
400 | Unknown kind, a level that kind does not accept, or a resource that belongs to another project |
403 | The caller’s project role is too low — or the caller is a project API key |
404 | No 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 |
409 | Rotate only: the connection is served by the service’s shared credential, so it has none of its own to rotate |
503 | POST, 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.