Skip to main content

Hosted Databases

Provision and manage project-scoped PostgreSQL databases directly from the Syntropy platform. Each database runs with PgBouncer connection pooling and supports automated backups and credential rotation.

Endpoints

GET    /api/organizations/{organizationId}/projects/{projectId}/databases
POST /api/organizations/{organizationId}/projects/{projectId}/databases
GET /api/organizations/{organizationId}/projects/{projectId}/databases/{databaseId}
DELETE /api/organizations/{organizationId}/projects/{projectId}/databases/{databaseId}
GET /api/organizations/{organizationId}/projects/{projectId}/databases/{databaseId}/connection-string
GET /api/organizations/{organizationId}/projects/{projectId}/databases/{databaseId}/credentials
POST /api/organizations/{organizationId}/projects/{projectId}/databases/{databaseId}/credentials

Authentication and Authorization

All database endpoints support:

  • authenticated dashboard session, or
  • project API key with the appropriate scope
ScopeGrants
databases:readList databases, get status, read connection string, list credentials
databases:writeCreate databases
databases:adminDelete databases, rotate credentials

Database Lifecycle

provisioning → ready → deleting → deleted

Creation and deletion are async. Both operations enqueue a background job and return a jobId you can poll via the Queue Worker API.

List Databases

GET /databases

Returns all databases for the project, ordered by creation date descending.

Response

{
"databases": [
{
"id": "uuid",
"name": "My Database",
"databaseName": "my_db",
"plan": "shared",
"engine": "postgres",
"engineVersion": "16",
"status": "ready",
"requestedStorageGb": 10,
"latestBackupAt": "2026-05-01T03:00:00.000Z",
"provisionedAt": "2026-04-15T12:00:00.000Z",
"createdAt": "2026-04-15T11:58:00.000Z",
"updatedAt": "2026-05-01T03:00:00.000Z"
}
]
}

Create Database

POST /databases

Provisions a new PostgreSQL database. Returns immediately with status: "provisioning" and a jobId to track progress.

Request Body

FieldTypeRequiredDescription
namestringyesDisplay name (1–255 characters)
databaseNamestringyesPostgreSQL database name. Must start with a letter; only lowercase letters, digits, and underscores; max 63 characters.
requestedStorageGbintegernoRequested disk size in GB (1–100, default 10)

Example

{
"name": "Production DB",
"databaseName": "prod_db",
"requestedStorageGb": 20
}

Response — 201 Created

{
"database": {
"id": "uuid",
"name": "Production DB",
"databaseName": "prod_db",
"plan": "shared",
"status": "provisioning",
"createdAt": "2026-05-08T10:00:00.000Z"
},
"jobId": "job-uuid"
}

status will transition to ready once provisioning completes (poll via /queue/jobs/{jobId}).

Errors

StatusReason
400Invalid request body
409A database with that databaseName already exists in this organization

Get Database

GET /databases/{databaseId}

Returns full database details. When status is ready, the response includes a redacted connection string (password replaced with ***) for display purposes. Use the connection-string endpoint to retrieve the live credential.

Response

{
"database": {
"id": "uuid",
"name": "Production DB",
"databaseName": "prod_db",
"ownerRoleName": "prod_db_owner",
"plan": "shared",
"engine": "postgres",
"engineVersion": "16",
"status": "ready",
"requestedStorageGb": 20,
"backupPolicy": "daily",
"latestBackupAt": "2026-05-07T03:00:00.000Z",
"provisionedAt": "2026-05-08T10:05:00.000Z",
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:05:00.000Z",
"connectionString": "postgresql://prod_db_owner:***@pool.host:5432/prod_db"
}
}

Delete Database

DELETE /databases/{databaseId} — requires databases:admin

Enqueues a destroy job. Returns immediately with status: "deleting".

Response

{
"jobId": "job-uuid",
"status": "deleting"
}

Errors

StatusReason
404Database not found
409Database is already being deleted

Get Connection String

GET /databases/{databaseId}/connection-string — requires databases:read

Returns the live connection string including the plaintext password. Only available when status is ready. Store this securely — treat it like a secret.

The connection string points to the PgBouncer pooler endpoint, not the Postgres host directly.

Response

{
"connectionString": "postgresql://prod_db_owner:s3cr3tp4ss@pool.host:5432/prod_db"
}

Errors

StatusReason
409Database is not ready
503Connection details not yet available (provisioning still in progress)

List Credentials

GET /databases/{databaseId}/credentials — requires databases:read

Returns all credentials associated with the database, ordered by creation date descending.

Response

{
"credentials": [
{
"id": "uuid",
"name": "owner",
"roleName": "prod_db_owner",
"status": "active",
"scopes": ["read", "write"],
"lastUsedAt": null,
"rotatedAt": null,
"expiresAt": null,
"revokedAt": null,
"createdAt": "2026-05-08T10:05:00.000Z",
"updatedAt": "2026-05-08T10:05:00.000Z"
}
]
}

Rotate Credentials

POST /databases/{databaseId}/credentials — requires databases:admin

Enqueues a credential rotation job for a specific credential. The old password remains valid until the job completes.

Request Body

FieldTypeRequiredDescription
credentialIdstring (UUID)yesThe credential to rotate

Response

{
"jobId": "job-uuid",
"status": "rotating"
}

Errors

StatusReason
400credentialId missing
404Database or credential not found
409Database is not ready, or credential is not active

Example cURL

# Create a database
curl -X POST \
"https://your-domain.com/api/organizations/{orgId}/projects/{projectId}/databases" \
-H "Authorization: Bearer syn_sk_xxx" \
-H "Content-Type: application/json" \
-d '{"name": "Production DB", "databaseName": "prod_db", "requestedStorageGb": 20}'

# Get the live connection string once ready
curl \
"https://your-domain.com/api/organizations/{orgId}/projects/{projectId}/databases/{databaseId}/connection-string" \
-H "Authorization: Bearer syn_sk_xxx"
  • Project API Keysdatabases:read, databases:write, databases:admin scopes
  • Queue Worker — track provisioning and rotation jobs via jobId