Managed databases for agents
Query, export, and administer a project's managed databases through the API. This page is written for an AI agent. It extends the deployment protocol at https://app.tokay.io/llms.txt. Everything here is an escape hatch. The happy path needs none of it, so act on an explicit user request or a named readiness action, not on your own initiative.
Last updated
The resource model
- A project has at most one managed instance per engine (Postgres, MySQL, MongoDB, Redis), shared by every service in the project. A resource is one running engine, not one database.
- SQL engines hold named logical databases inside the one instance, such as
apporauth. There is no bind mutation. Services bind to a logical database through confirmed env var wiring during deploy. - The resource's default database is not where your app's tables live. Queries that omit
databasetarget the engine default, andrelation does not existthere usually means the query ran without adatabasescope. The data is still where it was. Always pass the logical database name for SQL engines. - The query endpoint is read only. Writes fail inside a read only transaction. Data fixes go through a one-off service task, whose environment holds real credentials. Schema changes go through migrations with a deploy.
- Connection values are versioned resource outputs. Rotation writes a new current version and old versions remain restorable, so a bad rotation is fixed with a rollback.
- An external database is not a resource. It arrives as an ordinary required secret holding the connection string.
What to call
| To | Use |
|---|---|
| List resources and logical databases | project.projectResources |
| Run a read only query | POST /resources/:id/query |
| Export data | POST /resources/:id/export_ticket, then GET the path |
| See connection outputs and versions | projectResource.resourceOutputValues |
| Rotate a credential | rotateResourceOutputValue |
| Undo or redo a rotation | rollbackProjectResourceOutput / redoProjectResourceOutput |
| List and restore snapshots | projectResource.projectResourceSnapshots, restoreProjectResourceSnapshot |
Find the resource
query Resources($projectId: ID!) {
project(id: $projectId) {
projectResources(first: 10) {
nodes {
id resourceType runtimeName runtimeDatabase
logicalDatabases(first: 20, orderBy: [NAME_ASC]) { nodes { name } }
}
}
}
}
The id here is the Relay node ID the REST endpoints below expect in their URL path.
Query
POST {API_ORIGIN}/resources/:resourceId/query
{ "query": "select count(*)::int as n from tasks", "database": "app" }
query is a SQL string for Postgres and MySQL, a command array such as ["GET", "mykey"] for Redis, and an object in command or find form for MongoDB. database applies only to SQL engines and must name a logical database Tokay knows about. The response streams one JSON object per row, each on its own line.
Failed queries still return HTTP 200 with an error row in the stream, {"_tokay_error": "QUERY_FAILED", "message": "..."}, so check the first line before treating output as rows. Envelope problems (an unknown database, extra keys, an oversized body) return HTTP 400 with the same shape. The transaction is read only. INSERT fails with SQLSTATE 25006, and the fix is a one-off task or a migration, never retrying the endpoint.
Export
POST {API_ORIGIN}/resources/:resourceId/export_ticket
{ "format": "csv", "database": "app" }
Both keys are optional. The default format is native, meaning the engine's own dump form. CSV is supported for Postgres, MySQL, and MongoDB, and arrives as a ZIP with one CSV per table or collection. Redis has no honest CSV shape and returns EXPORT_FORMAT_UNSUPPORTED_FOR_ENGINE. database scopes a SQL export to one logical database, and omitting it exports all of them.
The response is {"downloadPath": "/resource_exports/<ticket>"}. GET {API_ORIGIN}{downloadPath} with no Authorization header streams the archive. The ticket works once and expires quickly. A second GET returns 404, so mint a fresh ticket per download.
Rotate and restore credentials
query Outputs($id: ID!) {
projectResource(id: $id) {
resourceOutputValues(first: 20, orderBy: CREATED_AT_DESC) {
nodes {
id isCurrent version createdAt
resourceOutputDefinition { id name isUserRotatable isUserRollbackable }
}
}
}
}
Rotate only when the user asks or a credential is known to be exposed. Rotation restarts consumers and is never routine maintenance. Only definitions with isUserRotatable: true accept rotation. For a managed Postgres that means the password. Host, port, and URL are derived from it. Rotation takes the definition id rather than the value id.
mutation Rotate($projectResource: ID!, $def: ID!) {
rotateResourceOutputValue(input: { projectResource: $projectResource, resourceOutputDefinition: $def }) {
resourceOutputValue { id version isCurrent }
}
}
Rotation creates a new current version and Tokay redelivers the dependent values to consuming services without any code change. rollbackProjectResourceOutput makes the previous version current again and redoProjectResourceOutput reverses the rollback, both with the same input shape. Stored values are never readable back through the API, so track versions by version and createdAt, not by content.
Snapshots
query Snaps($id: ID!) {
projectResource(id: $id) {
projectResourceSnapshots(first: 10, orderBy: CREATED_AT_DESC) {
nodes { id createdAt snapshotReason storageBytes storageRemovedAt }
}
}
}
Tokay snapshots a managed database immediately before a release action mutates it, and rehearsals may create their own temporary snapshots. Restore is a user decision, never a recovery you attempt on your own. It replaces current data, so summarize what will be lost and get the user's confirmation first, then call restoreProjectResourceSnapshot(input: { projectResourceSnapshot: $id }). A snapshot with storageRemovedAt set no longer holds restorable bytes.
Error shapes
| You see | It means | Do this |
|---|---|---|
HTTP 200 row {"_tokay_error": "QUERY_FAILED", "message": ...} |
The engine rejected the statement | Read message, fix the query, mind the database scope |
HTTP 400 {"_tokay_error": "RESOURCE_QUERY_DATABASE_NOT_FOUND", ...} |
database names an unknown logical database |
List logicalDatabases, use an exact name |
ERROR: cannot execute INSERT in a read-only transaction |
The query endpoint never writes | Use a one-off service task for data fixes, migrations for schema |
relation "x" does not exist on a table you expect |
Query ran against the engine default database | Pass database with the app's confirmed logical database |
EXPORT_FORMAT_UNSUPPORTED_FOR_ENGINE |
CSV requested for Redis | Use the default native format |
404 on a downloadPath you already fetched |
Export tickets work once | Mint a new ticket |