Skip to content
Discord Get Started

Provisioning

DB9 databases are created synchronously and return a usable connection string in under a second. This makes DB9 suitable for workflows where databases are created on demand — per agent, per user, per task, or per CI run.

This page explains the provisioning model: how databases are created, what metadata they carry, how their lifecycle works, and how to manage fleets of databases programmatically.

A DB9 database is an isolated tenant backed by a dedicated TiKV keyspace. Creating a database provisions the keyspace, bootstraps an admin user, installs default extensions (http and pg_cron), and returns connection credentials — all in a single synchronous API call.

Each database is identified by a 12-character opaque ID (e.g., t1a2b3c4d5e6) and an optional human-readable name. The ID is permanent. The name must be unique within your account.

Terminal
# Minimal — auto-generates a name like "brave-tiger-42"
db9 create
# Named
db9 create --name my-agent-db
# With region hint
db9 create --name my-agent-db --region us-east
# Show connection string in output
db9 create --name my-agent-db --show-connection-string

Available flags:

FlagDescription
--name <NAME>Human-readable name (auto-generated if omitted)
--region <REGION>Region hint (stored as metadata)
--password <PASSWORD>Set a specific admin password (random if omitted)
--show-passwordPrint admin password in output
--show-connection-stringPrint the full PostgreSQL DSN
--show-secretsShorthand for both --show-password and --show-connection-string
--output jsonMachine-readable JSON output

The SDK offers two creation patterns:

instantDatabase() — idempotent, agent-friendly:

TypeScript
import { instantDatabase } from 'get-db9';
const db = await instantDatabase({
name: 'agent-workspace',
seed: `
CREATE TABLE context (id SERIAL, key TEXT, value JSONB);
CREATE TABLE artifacts (id SERIAL, path TEXT, content TEXT);
`,
});
console.log(db.databaseId); // "t1a2b3c4d5e6"
console.log(db.connectionString); // "postgresql://..."
console.log(db.adminUser); // "admin"

instantDatabase() checks for an existing database with the same name and returns it if found. If no match exists, it creates a new one. This makes agent restarts safe — the same code path won’t duplicate databases.

Note: The seed SQL only runs when a new database is created. If an existing database is returned, the seed is skipped.

Options:

OptionTypeDescription
namestringDatabase name (default: 'default')
seedstringSQL to execute after creation
seedFilestringSQL file content to execute after creation
baseUrlstringAPI base URL (default: https://api.db9.ai)
timeoutnumberRequest timeout in milliseconds
maxRetriesnumberRetry count for transient failures

databases.create() — direct API call:

TypeScript
import { createDb9Client } from 'get-db9';
const client = createDb9Client();
const db = await client.databases.create({ name: 'my-agent-db' });

The create request accepts name (required), region (optional), and admin_password (optional).

Terminal
curl -X POST https://api.db9.ai/customer/databases \
-H "Authorization: Bearer $DB9_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "my-agent-db"}'

Response (HTTP 201):

JSON
{
"id": "t1a2b3c4d5e6",
"name": "my-agent-db",
"state": "ACTIVE",
"admin_user": "admin",
"admin_password": "generated-password",
"created_at": "2025-01-15T10:30:00Z",
"connection_string": "postgresql://t1a2b3c4d5e6.admin@sql.db9.ai:5432/postgres"
}

Every database carries these fields:

FieldDescription
id12-character opaque identifier, permanent
nameHuman-readable name, unique per account
stateCurrent lifecycle state (see below)
state_reasonError message when state is not ACTIVE
admin_userAlways "admin"
admin_passwordPlaintext password (only returned at create time or after reset)
connection_stringPostgreSQL DSN
regionRegion hint as supplied at creation
endpointsHost, port, and type for each endpoint (only on single-database queries)
created_atRFC 3339 timestamp
parent_database_idSource database ID (branches only)
snapshot_atSnapshot timestamp (branches only)

Connection string format: postgresql://{id}.admin@{host}:{port}/postgres

The database ID is embedded in the username ({id}.admin), which is how the server routes connections to the correct tenant.

A database transitions through these states:

StateMeaningTerminal?
CREATINGKeyspace being provisionedNo — transitions to ACTIVE or CREATE_FAILED
CLONINGBranch copy in progressNo — transitions to ACTIVE or CREATE_FAILED
ACTIVENormal operating stateNo
DISABLINGDelete in progressNo — transitions to DISABLED
DISABLEDDeletedYes
CREATE_FAILEDProvisioning failedYes
SUSPENDEDOperator-imposed suspensionYes (requires operator action to resume)

Normal lifecycle: CREATING → ACTIVE → DISABLING → DISABLED

Branch lifecycle: CREATING → CLONING → ACTIVE → DISABLING → DISABLED

Recovery: A background reconciler recovers databases stuck in CREATING or DISABLING for more than 10 minutes. If recovery fails, the database moves to CREATE_FAILED.

DISABLED databases are excluded from list responses.

DB9 supports a trial-first model where databases can be created without signing up.

Anonymous accounts:

  • Created automatically on first db9 create when no credentials are present
  • Limited to 5 databases (including branches)
  • Receive a 90-day bearer token
  • Can be refreshed using the stored anonymous credentials

Claiming an account:

  • Run db9 claim to upgrade via Auth0 SSO
  • The database limit is removed after claiming
  • All existing databases are retained under the claimed account

If an anonymous account hits the 5-database limit, the CLI returns an error directing you to run db9 claim.

Deeper guide: Anonymous and Claimed Databases (coming soon)

Terminal
# Table output
db9 list
# JSON output
db9 list --json

Table columns: ID, NAME, STATE, REGION, CREATED.

TypeScript
const databases = await client.databases.list();
// Returns DatabaseResponse[] — excludes DISABLED databases
Terminal
curl https://api.db9.ai/customer/databases \
-H "Authorization: Bearer $DB9_TOKEN"

A single-database query returns the full metadata including endpoints and connection string:

Terminal
db9 db status my-agent-db

Or via the SDK:

TypeScript
const db = await client.databases.get('t1a2b3c4d5e6');
console.log(db.endpoints); // [{ host, port, type, enabled }]
console.log(db.connectionString);
Terminal
# Interactive confirmation
db9 delete my-agent-db
# Skip confirmation
db9 delete my-agent-db --yes
TypeScript
await client.databases.delete('t1a2b3c4d5e6');

Deletion transitions the database through DISABLING → DISABLED. Once disabled, the database is excluded from list results and its TiKV keyspace is cleaned up.

Password reset:

Terminal
db9 db reset-password my-agent-db

Returns a new random password and updated connection string.

Connect tokens (short-lived, for sharing or CI):

Terminal
db9 db connect-token my-agent-db

Returns a token usable as PGPASSWORD with psql. Connect tokens expire in 10 minutes by default.

SDK equivalents:

TypeScript
const creds = await client.databases.credentials('t1a2b3c4d5e6');
const token = await client.databases.connectToken('t1a2b3c4d5e6');

When managing many databases programmatically, consider these patterns:

Each agent instance creates its own database on startup and uses it for the duration of its lifecycle. Use instantDatabase() with a deterministic name derived from the agent’s identity.

TypeScript
const db = await instantDatabase({
name: `agent-${agentId}`,
seed: 'CREATE TABLE state (key TEXT PRIMARY KEY, value JSONB)',
});

Multi-tenant applications create a database for each end user. Name databases with a user identifier to make them idempotent.

TypeScript
const db = await instantDatabase({ name: `user-${userId}` });

CI pipelines or one-shot tasks create a database, run their work, and delete it:

Terminal
ID=$(db9 create --name "ci-run-$BUILD_ID" --json | jq -r '.id')
# ... run tests against the database ...
db9 delete "$ID" --yes

Use the SDK or CLI to list and clean up databases that match a naming pattern:

TypeScript
const all = await client.databases.list();
const stale = all.filter(db =>
db.name.startsWith('ci-run-') &&
new Date(db.created_at) < oneDayAgo
);
for (const db of stale) {
await client.databases.delete(db.id);
}
  • Name uniqueness: Database names must be unique within an account. Duplicate names return HTTP 409.
  • Anonymous limit: 5 databases per anonymous account (including branches). Claim to remove the limit.
  • No rename: Database names cannot be changed after creation.
  • No region migration: The region hint is metadata only — databases cannot be moved between regions after creation.
  • Synchronous creation: Database creation blocks until the keyspace is provisioned. This typically completes in under a second, but can take longer under load.
  • State recovery: Databases stuck in CREATING or DISABLING for more than 10 minutes are automatically recovered by a background reconciler.