Data
Data lets a deployed agent provision a time-limited PostgreSQL database and use it through a standard Postgres client. Agent steps call the typed ctx.sapiom.database capability; Sapiom supplies the authenticated cloud connection when the deployed agent runs.
This is a provisioning capability, not a SQL query API. database.create returns connection credentials; the agent project supplies the database client and runs its own SQL.
Choose the database operation
Section titled “Choose the database operation”| What the agent needs | Recommended operation |
|---|---|
| A new ephemeral Postgres database | database.create({ duration }) |
| A stable key for a database reused by later steps | Create with handle, then database.get(handle) |
| Active and provisioning databases | database.list() |
| Early cleanup before automatic expiry | database.delete(idOrHandle) |
| Queries, migrations, or transactions | A standard Postgres client using connectionString |
Choose a lifetime from "15m", "1h", "4h", "24h", or "7d". Use the shortest lifetime that safely contains the work.
Provision, query, and clean up
Section titled “Provision, query, and clean up”This example assumes the agent project includes pg:
import { Client } from "pg";
const db = await ctx.sapiom.database.create({ duration: "1h", name: "report-build",});
if (db.status !== "active" || !db.connection) { throw new Error(`Database ${db.id} is ${db.status}`);}
const client = new Client({ connectionString: db.connection.connectionString,});
try { await client.connect(); await client.query( "create table report_rows (label text not null, value integer not null)", ); await client.query( "insert into report_rows (label, value) values ($1, $2)", [input.label, input.value], );
const result = await client.query( "select label, value from report_rows order by label", ); return { rows: result.rows };} finally { await client.end().catch(() => {}); await ctx.sapiom.database.delete(db.id).catch((error) => { ctx.logger.warn("database cleanup failed", { databaseId: db.id, error: String(error), }); });}Use parameterized queries for values. Never construct SQL by interpolating caller input, and never return or log connectionString, username, or password.
Reuse a database deliberately
Section titled “Reuse a database deliberately”A handle is a tenant-scoped, human-friendly lookup key. Use one only when another step or run must find the same database before it expires:
const db = await ctx.sapiom.database.get(input.databaseHandle);
if (db.status !== "active" || !db.connection) { throw new Error(`Database ${input.databaseHandle} is ${db.status}`);}Pass the handle or database ID between steps, not the connection credentials. A database can be provisioning with connection: null, and it can expire between lookup and connection, so handle both conditions.
Test the behavior locally
Section titled “Test the behavior locally”Local Run replaces ctx.sapiom.database calls with deterministic records. It does not provision Postgres. The built-in connection points at fixture localhost credentials; a direct pg client remains real and can attempt a local connection.
Test provisioning and lifecycle branches with exact capability fixtures, and put SQL access behind an injectable adapter when the Local Run must exercise query-dependent logic:
{ "version": 1, "steps": { "prepare-data": { "database.create": { "id": "db-local", "handle": null, "name": "report-build", "description": null, "status": "active", "region": "us-east-1", "pgVersion": 17, "duration": "1h", "connection": { "connectionString": "postgresql://fixture.invalid/report" }, "expiresAt": "2099-01-01T00:00:00.000Z", "createdAt": "2098-12-31T23:00:00.000Z" } } }}The override is returned verbatim. Supply the shape the step consumes, assert the terminal output, and require both unusedStubs and stubWarnings to be empty. Use a small production run to verify real provisioning, client connectivity, SQL behavior, and cleanup.
Manage lifecycle and usage
Section titled “Manage lifecycle and usage”Automatic expiry is the final cleanup boundary, not a reason to leave scratch databases running. Delete a database when the work finishes, use list() to find resources left by an interrupted run, and make retry behavior explicit when a stable handle might already exist.
Use the signed-in capability catalog for current regions, limits, and pricing. Database usage depends on the selected lifetime and live resource lifecycle; do not copy a rate into agent logic.
© 2026 Sapiom, Inc.