Managed databases for agents
This reference extends https://app.tokay.io/llms.txt with managed database operations for AI agents. Use these operations only when the user explicitly asks or a named readiness action requires them. Never rotate credentials, restore a snapshot, or replace data without user approval.
Last updated
AI agent? Start with llms.txt.
The resource and connection model
- A project has at most one managed instance per engine (Postgres, MySQL, MongoDB, Redis), shared by its services. A resource is one physical engine, not one database.
- Postgres, MySQL, and MongoDB resources contain logical databases Tokay knows about, such as
appandauth. Each logical database has its own generated application username and versioned password. Changing only the database name in a connection string does not grant access to another logical database. - One
serviceResourceConnectiongroups all env vars for one application connection and binds them to one logical database. An app that needs two databases receives two independent sets of env vars. - Redis credentials stay scoped to the whole resource because its numbered databases are not authorization boundaries.
- Admin and read only platform credentials are never delivered to apps. The query endpoint uses the read only principal, while app tasks and migrations receive the service's ordinary scoped connection.
- The query endpoint is read only. Data writes belong in a service task the user requested, and schema changes belong in release migrations.
- A MySQL app that uses one login for SQL across schemas must split that access into separate connections. Static evidence blocks deploy with
MYSQL_CROSS_DATABASE_CONNECTION_UNSUPPORTED, and MySQL 1044/1142 failures that correlate with that topology receive the same diagnosis.
Tokay still creates infrastructure automatically. Confirming a connection records the choice, but the engine is physically provisioned only during deploy.
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 |
| Stage an import upload | POST /projects/:id/resource_import_blobs |
| Import while binding a connection | setServiceResourceConnectionLogicalDatabaseWithImport |
| Import into an existing logical database | createProjectResourceImport |
| Poll, confirm, cancel, or retarget an import | projectResourceImport, confirmProjectResourceImport, cancelProjectResourceImport, retargetProjectResourceImport |
| Rotate an application password | rotateProjectResourceLogicalDatabasePassword |
| Undo or redo a rotation | rollbackProjectResourceLogicalDatabasePassword / redoProjectResourceLogicalDatabasePassword |
| List and restore snapshots | projectResource.projectResourceSnapshots, restoreProjectResourceSnapshot |
Postgres extensions
Tokay's managed Postgres runs PostgreSQL 18.3. Normal migrations can install the extensions Postgres marks trusted, including common options such as pg_trgm, hstore, uuid-ossp, pgcrypto, ltree, and citext. Tokay also supplies and permits vector from pgvector 0.8.5 and postgis from PostGIS 3.6.4.
Extensions belong to one logical database. Add ordinary SQL to the migration that runs against the intended database:
create extension if not exists "vector";
Use the extension's exact name. The default target schema is public. Run the migration through the service's confirmed connection for that logical database so the extension installs where the app uses it. The resource query endpoint can inspect pg_catalog.pg_extension, but it is read only and is never an installation path. Other extensions that require superuser authority remain blocked.
Find resources and logical databases
query Resources($projectId: ID!) {
project(id: $projectId) {
projectResources(first: 10) {
nodes {
id
resourceType
runtimeName
logicalDatabases(first: 100, orderBy: [NAME_ASC]) {
nodes {
id
name
runtimeUsername
passwordVersions(first: 20, orderBy: [VERSION_DESC]) {
nodes {
id
version
isCurrent
reconciledAt
failedAt
failureMessage
createdAt
}
}
}
}
}
}
}
}
GraphQL IDs are Relay node IDs. Use projectResource.id in REST resource paths, logicalDatabases.nodes[].id for password actions and imports that target an existing logical database, and a setup blocker's serviceResourceConnection.id when binding an application connection.
Query
POST {API_ORIGIN}/resources/:resourceId/query
Content-Type: application/json
{ "query": "select count(*)::int as n from tasks", "database": "app" }
query is a SQL string for Postgres and MySQL, a Redis command array such as ["GET", "mykey"], or a MongoDB command or find object. database is optional for Postgres, MySQL, and MongoDB and must name a logical database Tokay knows about. Omitting it targets the contract default. Redis rejects database targeting.
The response streams one JSON object per line. Engine failures still return HTTP 200 with an error row, {"_tokay_error":"QUERY_FAILED","message":"..."}, so inspect the first line. Envelope and target errors return HTTP 400 in the same NDJSON shape. Writes fail because the endpoint uses a read only transaction or principal.
Export
POST {API_ORIGIN}/resources/:resourceId/export_ticket
Content-Type: application/json
{ "format": "native", "database": "app" }
Both keys are optional. database scopes Postgres, MySQL, or MongoDB to one logical database Tokay knows about. Omitting it exports all known logical databases. Exports authenticate with each selected logical database's own credential, not a root secret. Redis rejects database targeting.
native preserves the engine backup shape and is the format imports accept back. CSV is supported for Postgres, MySQL, and MongoDB and returns a ZIP with one CSV per table or collection. Redis returns EXPORT_FORMAT_UNSUPPORTED_FOR_ENGINE.
The response is {"downloadPath":"/resource_exports/<ticket>"}. Fetch that path once without an Authorization header. The ticket expires quickly and works once.
Import an existing database
Imports load an existing dump into one logical database per operation:
- Postgres plain SQL,
pg_dumpcustom format, or a Tokay native ZIP containing one database. - MySQL logical SQL or a Tokay native ZIP containing one database.
- MongoDB
mongodump --archive, compressed or uncompressed. - Redis is unsupported.
Dumps that span a whole cluster, define global roles, or contain several databases are rejected. Uploaded bytes never run through a managed admin connection. Tokay restores them in an isolated sandbox that runs the same engine and holds no secrets, normalizes the result, and proves a fresh restore under the application role before touching the managed engine.
Import step 1: stage the raw bytes
POST {API_ORIGIN}/projects/:projectId/resource_import_blobs
Authorization: Bearer <token>
Content-Type: application/octet-stream
<raw dump bytes>
The project path uses a Relay node ID. The HTTP 202 response contains stagedImportHandle, sha256, sizeBytes, and expiresAt. Staging only holds the bytes. It is not yet an import, and an unused blob expires after one hour.
Import step 2a: bind during onboarding
Resolve the normal UNCONFIRMED_LOGICAL_DATABASE blocker and attach the handle in the same mutation:
mutation BindDatabaseWithImport(
$connection: ID!
$database: String!
$handle: String!
) {
setServiceResourceConnectionLogicalDatabaseWithImport(input: {
connection: $connection
logicalDatabaseName: $database
stagedImportHandle: $handle
}) {
projectResourceImport {
id
importState
logicalDatabase { id name }
}
}
}
This transaction makes the connection and the import reference the same logical database. No later confirmation can send the app to a different one.
Import step 2b: target an existing logical database
mutation ImportExisting($database: ID!, $handle: String!) {
createProjectResourceImport(input: {
logicalDatabase: $database
stagedImportHandle: $handle
}) {
projectResourceImport { id importState }
}
}
Import step 3: poll the durable import
query DatabaseImport($id: ID!) {
projectResourceImport(id: $id) {
id
importState
importStep
sourceFormat
sourceSizeBytes
normalizedFormat
normalizedSizeBytes
expandedSizeBytes
errorCode
errorMessage
logicalDatabase { id name }
projectResource { id resourceType runtimeName }
}
}
| State | Action |
|---|---|
PENDING_VALIDATION, VALIDATING |
Poll. Tokay is validating and normalizing without changing active data. |
AWAITING_DEPLOY |
Resume ordinary deploy readiness. Restore runs before release actions and app startup. |
AWAITING_CONFIRMATION |
Existing data will be replaced. Explain the target and downtime, obtain explicit user approval, then confirm. |
EXECUTING |
Poll. Tokay owns the maintenance window, rollback, and app recovery. |
SUCCEEDED |
Done. |
FAILED |
Stop and surface errorCode plus errorMessage. A corrected dump needs a new upload. |
CANCELED, EXPIRED |
Stop. A new import needs a new upload. |
Confirm only with explicit destructive approval:
mutation ConfirmImport($id: ID!) {
confirmProjectResourceImport(input: { projectResourceImport: $id }) {
projectResourceImport { id importState importStep }
}
}
cancelProjectResourceImport uses the same projectResourceImport input and is valid only before destructive execution. retargetProjectResourceImport takes projectResourceImport plus a destination logicalDatabase. Earlier normalization work may be reused when the new target runs the same engine, but target and capacity checks run again.
Validation enforces independent limits on upload size, expanded data, runtime, and disk capacity. Replacement blocks other maintenance on the resource while it runs, but stops only the services credentialed for the target logical database. The target keeps its stable name. Rollback protection lasts only until the old or new application is healthy again, and Tokay does not offer a delayed rollback that discards new writes.
Rotate and restore application passwords
Postgres, MySQL, and MongoDB application passwords belong to logical databases. Rotate only on user request or known exposure:
mutation RotateDatabasePassword($database: ID!) {
rotateProjectResourceLogicalDatabasePassword(input: {
logicalDatabase: $database
}) {
logicalDatabasePassword {
id
version
isCurrent
reconciledAt
failedAt
failureMessage
}
}
}
Rotation updates the engine first, then recreates or rechecks only the consumers of that logical database. reconciledAt means both sides converged. If app recovery fails after engine rotation, Tokay automatically restores the latest previously reconciled version.
rollbackProjectResourceLogicalDatabasePassword and redoProjectResourceLogicalDatabasePassword both take { logicalDatabase: <id> }. To restore a particular historical row, call restoreProjectResourceLogicalDatabasePassword with { password: <password version id> }. Plaintext is never readable from the API. Redis password rotation continues through the resource output mutations because Redis credentials stay scoped to the whole resource.
Snapshots
query Snapshots($id: ID!) {
projectResource(id: $id) {
projectResourceSnapshots(first: 10, orderBy: [CREATED_AT_DESC]) {
nodes { id createdAt snapshotReason storageBytes storageRemovedAt }
}
}
}
Release snapshots cover the managed resource. Import replacement uses its own rollback protection scoped to the target logical database during the maintenance window. Snapshot restore is a destructive user decision. Explain what current data will be lost and obtain confirmation before calling restoreProjectResourceSnapshot.
Error shapes
| You see | It means | Do this |
|---|---|---|
RESOURCE_QUERY_DATABASE_NOT_FOUND / EXPORT_DATABASE_NOT_FOUND |
database names an unknown logical database |
List logicalDatabases, use an exact name |
RESOURCE_QUERY_DATABASE_TARGET_UNSUPPORTED / EXPORT_DATABASE_TARGET_UNSUPPORTED |
database was sent for Redis |
Remove the target |
PROJECT_RESOURCE_MAINTENANCE_ACTIVE |
An import replacement is running on that resource | Poll the import, hold other maintenance until it finishes |
DATABASE_IMPORT_UNSUPPORTED_ENGINE |
Redis import requested | Stop. Redis import is not supported |
DATABASE_IMPORT_*LIMIT* or HTTP 413 |
An upload or expanded data limit was exceeded | Produce a smaller supported logical dump |
DATABASE_IMPORT_SCHEMA_REMAP_UNSAFE |
A MySQL stored program uses the source database name ambiguously | Import into that database name, or remove or rename the qualified reference or alias |
DATABASE_IMPORT_FORMAT_UNSUPPORTED, DATABASE_IMPORT_ARCHIVE_*, or DATABASE_IMPORT_RESTORE_FAILED |
The restore sandbox rejected the input | Show errorMessage, create a corrected dump |
MYSQL_CROSS_DATABASE_CONNECTION_UNSUPPORTED |
One MySQL connection crosses schemas | Split the code or config into separate connection env vars |
HTTP 200 row with _tokay_error: QUERY_FAILED |
The engine rejected a read only query | Fix the statement or the database target |
404 on an already fetched downloadPath |
Export tickets work once | Mint a new ticket |