No skill selection required — just send text

A2A Agent

A thinking PostgreSQL agent for AI agents and developers. Describe what you want — the agent plans and executes all the steps internally. No orchestration from your side.

Default

Just send text — agent handles the rest

A2A v0.2 spec or flat — same agent
# One request. Agent provisions DB, creates schema, seeds data,
# generates TypeScript types, and answers your question.
curl -s -X POST https://api.dbaas.dev/a2a/tasks/send \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "x",
    "message": {"role":"user","parts":[{"text":
      "create a blog DB with users, posts, comments. seed 10 rows.
       generate TypeScript types. how many posts per user?"
    }]}
  }' | jq .task_id

Returns task_id in ~100ms. Poll until state=completed — result contains every step's output including connection string and generated code. Spec-compliant JSON-RPC equivalent below ↓

Two wire formats — both accepted

A2A v0.2 spec

The same agent answers both shapes. Spec-compliant clients use JSON-RPC 2.0 at POST /a2a. Custom REST clients keep using the flat POST /a2a/tasks/send shape — unchanged. The wire format dictates response shape, not the URL.

A2A specPOST /a2a
curl -X POST https://api.dbaas.dev/a2a \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tasks/send",
    "params": {
      "id": "client-1",
      "message": {"role":"user","parts":[
        {"text":"create a blog DB and seed it"}
      ]}
    }
  }'

# 202 + JSON-RPC envelope:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "<task-uuid>",
    "status": {"state":"submitted"}
  }
}

# Poll via tasks/get (same envelope):
curl -X POST https://api.dbaas.dev/a2a \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tasks/get",
       "params":{"id":"<task-uuid>"}}'
LegacyPOST /a2a/tasks/send
curl -X POST https://api.dbaas.dev/a2a/tasks/send \
  -H 'Content-Type: application/json' \
  -d '{
    "id": "client-1",
    "message": {
      "role": "user",
      "parts": [{"text":"create a blog DB and seed it"}]
    }
  }'

# 202 + flat shape (unchanged):
{
  "task_id": "<task-uuid>",
  "skill": "workflow",
  "status": {"state":"submitted"},
  "poll_url": "/a2a/tasks/<task-uuid>",
  "created_at": "2026-..."
}

# Poll via REST GET (unchanged):
curl https://api.dbaas.dev/a2a/tasks/<task-uuid>
Methods (JSON-RPC)

tasks/send · tasks/get. tasks/cancel + tasks/sendSubscribe return -32601 (not yet supported).

Error codes

-32700 parse · -32600 invalid request · -32601 method not found · -32602 invalid params · -32603 internal · -32001 task not found.

id round-trip

The JSON-RPC id (string · number · null) is echoed back verbatim — type preserved.

How it works

A2A (Agent-to-Agent) is a protocol for AI agents to call other AI agents over HTTP. dbaas.dev is a thinking agent — it holds the plan, sequences skills internally, and returns a complete result. You don't orchestrate individual steps.

1. Discover
/.well-known/agent-card.json

A2A v0.2 card. Advertises protocol, protocolVersion, capabilities (incl. stateTransitionHistory), authentication.schemes:["Bearer"], interfaces[] (json-rpc + rest-legacy), defaultSkill: "workflow", full limits object, and skills with JSON schemas.

2. Submit
POST /a2a or /a2a/tasks/send

Returns 202 + task id instantly. JSON-RPC at /a2a, flat shape at /a2a/tasks/send. Work runs in a detached goroutine.

3. Poll
tasks/get or GET /a2a/tasks/{id}

submitted → working → completed | failed. Result includes all step outputs. Credentials on first poll only.

  Your agent                       dbaas.dev A2A                Internal planner
  ──────────                       ─────────────────            ────────────────
  POST /a2a (json-rpc)        OR
  POST /a2a/tasks/send (flat)  ──→  202 + task_id          ──→  LLM plans:
  "create blog DB                   { state: submitted }            provision →
   seed 10 rows                                                     create-schema →
   give me types"                   { state: working }              load-sample-data →
                                                                    generate-orm-models

                                    { state: completed }
  POST /a2a tasks/get          OR                          ──→  result: {
  GET /a2a/tasks/{id}          ──→                                connection_string,
                                                                  tables, code, ... }

Try it in 30 seconds

no API keyflat shape

One plain-text request. Flat shape below — for the JSON-RPC equivalent see the "Two wire formats" section above. Both produce identical work.

  1. 1 · Submit (no skill needed)
    TASK=$(curl -s -X POST https://api.dbaas.dev/a2a/tasks/send \
      -H 'Content-Type: application/json' \
      -d '{
        "id":"client-1",
        "message":{"role":"user","parts":[{
          "text":"create a database for a blog with users, posts, comments and seed 10 sample rows"
        }]}
      }' | jq -r .task_id)
    echo "task: $TASK"
  2. 2 · Poll until completed (~2–3 min)
    while true; do
      R=$(curl -s https://api.dbaas.dev/a2a/tasks/$TASK)
      S=$(echo "$R" | jq -r .status.state)
      echo "[$(date +%H:%M:%S)] $S"
      [ "$S" = "completed" ] || [ "$S" = "failed" ] && echo "$R" | jq && break
      sleep 5
    done
  3. 3 · Connect (credentials on first poll only)
    psql "postgresql://user:[email protected]:port/db?sslmode=require"
    \dt        # 3 tables: users, posts, comments
    SELECT count(*) FROM posts;   # 10

Skills

workflow is the default — send plain text, agent picks and sequences skills internally. Direct-access skills are available when you need deterministic, single-step control: send metadata.skill: <id> (flat) or params.message.metadata.skill: <id> (JSON-RPC). Same skill IDs in either shape.

Primary interface

workflow

DEFAULT — send any goal as plain text. Agent plans + executes all steps internally. No skill selection needed.

Lifecycle (direct)

provision-postgres

Spin up a 1–60 minute Postgres database

get-status

Check DB status + remaining TTL + connection string

extend-ttl

Bump TTL on a running DB (capped at 90 min total by default)

delete-db

Tear down a DB immediately (frees IP slot)

list-my-dbs

List all DBs owned by your token/IP

SQL (direct)

exec-sql

Run any SQL (filter rejects COPY + dangerous fns)

generate-query

NL → SQL. Optional execute=true to run it

chat-with-data

NL question → SELECT → execute → NL answer

Schema (direct)

describe-schema

Tables + columns + indexes + FKs as JSON

create-schema

Generate full schema from NL description

migrate-schema

Incremental ALTER from NL change request

reset-schema

DROP all tables (CASCADE) — requires confirm=true

Data (direct)

load-sample-data

Generate + insert realistic sample data

export-data

Export rows as JSON (capped per table)

Code (direct)

add-procedure

Generate + install a PL/pgSQL stored procedure

generate-orm-models

Schema → TS / Pydantic / SQLAlchemy / Prisma / Go / Rust

Common patterns

Convert any flat example below to JSON-RPC

Wrap the flat body in params and add the JSON-RPC envelope. Skill metadata, message parts, ownership token — all stay in the same place inside params.message.

# Flat (POST /a2a/tasks/send):
{ "id": "x", "message": { ... } }

# JSON-RPC equivalent (POST /a2a):
{ "jsonrpc": "2.0", "id": 1, "method": "tasks/send",
  "params": { "id": "x", "message": { ... } } }

# Poll equivalent:
GET /a2a/tasks/<task-uuid>             ←→   POST /a2a {"jsonrpc":"2.0","id":2,
                                                       "method":"tasks/get",
                                                       "params":{"id":"<task-uuid>"}}
Recommended
End-to-end in one request

Describe the full goal. Agent plans and runs all steps.

Flat — POST /a2a/tasks/send
{
  "id": "client-1",
  "message": {
    "role": "user",
    "parts": [{"text":
      "create a credit scoring database, fill it
       with 5 sample applicants, add a stored
       procedure that calculates credit score
       from income and debts, then generate
       Pydantic models for FastAPI"
    }]
  }
}
JSON-RPC — POST /a2a
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tasks/send",
  "params": {
    "id": "client-1",
    "message": {
      "role": "user",
      "parts": [{"text":
        "create a credit scoring database, fill it
         with 5 sample applicants, add a stored
         procedure ... generate Pydantic models"
      }]
    }
  }
}

Agent plans: provision → create-schema → load-sample-data → add-procedure → generate-orm-models. Single submit. Poll for full result.

New
Workflow on an existing database

Pass metadata.database_id to operate on a DB you already provisioned. The agent introspects the current schema and plans accordingly — no fresh DB created.

Flat
# Migrate:
{
  "id": "t1",
  "message": {
    "metadata": { "database_id": "<id>" },
    "parts": [{"text":
      "add a deleted_at TIMESTAMPTZ column
       to users and an index on email"
    }]
  }
}
# Plan: migrate-schema (no provision)

# Q&A:
{
  "id": "t2",
  "message": {
    "metadata": { "database_id": "<id>" },
    "parts": [{"text":
      "top 5 customers by total order value"
    }]
  }
}
# Plan: chat-with-data → SELECT → answer
JSON-RPC
# Migrate:
{
  "jsonrpc": "2.0", "id": 1,
  "method": "tasks/send",
  "params": {
    "id": "t1",
    "message": {
      "metadata": { "database_id": "<id>" },
      "parts": [{"text":
        "add a deleted_at column to users"
      }]
    }
  }
}

# Q&A:
{
  "jsonrpc": "2.0", "id": 2,
  "method": "tasks/send",
  "params": {
    "id": "t2",
    "message": {
      "metadata": { "database_id": "<id>" },
      "parts": [{"text":
        "top 5 customers by order value"
      }]
    }
  }
}
Direct skill (deterministic, single-step)

Set metadata.skill to bypass the planner entirely. Free-form text in parts is ignored when skill is explicit. Use when you want a single skill invoked exactly as specified.

Flat
# Run a specific SQL — no LLM in the loop:
{
  "id": "t3",
  "message": {
    "metadata": {
      "skill": "exec-sql",
      "database_id": "<id>",
      "sql": "SELECT count(*) FROM users
              WHERE deleted_at IS NULL"
    }
  }
}
JSON-RPC
{
  "jsonrpc": "2.0", "id": 1,
  "method": "tasks/send",
  "params": {
    "id": "t3",
    "message": {
      "metadata": {
        "skill": "exec-sql",
        "database_id": "<id>",
        "sql": "SELECT count(*) FROM users
                WHERE deleted_at IS NULL"
      }
    }
  }
}
# Available metadata.skill values:
provision-postgres, get-status, exec-sql, list-my-dbs, extend-ttl,
delete-db, create-schema, migrate-schema, load-sample-data, add-procedure,
describe-schema, generate-orm-models, generate-query, chat-with-data,
reset-schema, export-data
Schema-first prototyping (chained direct skills)

Provision → design schema → generate types. Explicit skill control. Each call is a separate submit; capture database_id from step 1's task result.

Flat — message body only
# 1. provision
{ "id":"s1", "message": { "metadata": {
    "skill": "provision-postgres",
    "ttl_minutes": 5
} } }

# 2. create schema
{ "id":"s2", "message": { "metadata": {
    "skill": "create-schema",
    "database_id": "<id from step 1>",
    "description": "e-commerce: users,
       products, categories, orders,
       order_items"
} } }

# 3. generate TypeScript interfaces
{ "id":"s3", "message": { "metadata": {
    "skill": "generate-orm-models",
    "database_id": "<id>",
    "language": "typescript"
} } }
JSON-RPC — full envelope
# 1. provision
{ "jsonrpc":"2.0", "id":1, "method":"tasks/send",
  "params": { "id":"s1", "message": { "metadata": {
    "skill": "provision-postgres",
    "ttl_minutes": 5
  } } } }

# 2. create schema
{ "jsonrpc":"2.0", "id":2, "method":"tasks/send",
  "params": { "id":"s2", "message": { "metadata": {
    "skill": "create-schema",
    "database_id": "<id from step 1>",
    "description": "e-commerce schema..."
  } } } }

# 3. generate TypeScript interfaces
{ "jsonrpc":"2.0", "id":3, "method":"tasks/send",
  "params": { "id":"s3", "message": { "metadata": {
    "skill": "generate-orm-models",
    "database_id": "<id>",
    "language": "typescript"
  } } } }
NL Q&A over data

Ask questions in plain English; get rows + a written answer.

Flat
# Via workflow:
{ "id":"q1", "message": { "parts":[{"text":
    "create an orders DB, seed 20 rows,
     what is the average order value?"
}]}}

# Or direct skill on existing DB:
{ "id":"q2", "message": { "metadata": {
    "skill": "chat-with-data",
    "database_id": "<id>",
    "question": "average order value
                 per customer last month?"
} } }
JSON-RPC
# Via workflow:
{ "jsonrpc":"2.0", "id":1, "method":"tasks/send",
  "params": { "id":"q1", "message": { "parts":[{"text":
    "create an orders DB, seed 20 rows,
     what is the average order value?"
}]}}}

# Or direct skill on existing DB:
{ "jsonrpc":"2.0", "id":2, "method":"tasks/send",
  "params": { "id":"q2", "message": { "metadata": {
    "skill": "chat-with-data",
    "database_id": "<id>",
    "question": "average order value
                 per customer last month?"
} } } }

Result (either shape): { "sql": "...", "rows": [...], "answer": "Average order value was $87.40..." }

Iterative schema evolution

Apply incremental migrations without dropping tables.

Flat
{ "id":"m1", "message": { "metadata": {
    "skill": "migrate-schema",
    "database_id": "<id>",
    "description":
      "add a status column to orders with
       values pending, paid, shipped"
} } }
JSON-RPC
{ "jsonrpc":"2.0", "id":1, "method":"tasks/send",
  "params": { "id":"m1", "message": { "metadata": {
    "skill": "migrate-schema",
    "database_id": "<id>",
    "description":
      "add a status column to orders with
       values pending, paid, shipped"
  } } } }

Ownership tokens (recommended)

Pass a 32+ char random token at submit time via the Authorization: Bearer header (works on both wire formats — header is below the body shape). Your task and any provisioned DBs are bound to that token — only callers presenting it can poll the task or use the DB.

Flat
TOKEN=$(openssl rand -hex 24)

curl -X POST https://api.dbaas.dev/a2a/tasks/send \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"id":"x","message":{"parts":[
    {"text":"create a blog DB and seed it"}
  ]}}'

# poll
curl https://api.dbaas.dev/a2a/tasks/$TASK \
  -H "Authorization: Bearer $TOKEN"
JSON-RPC
TOKEN=$(openssl rand -hex 24)

curl -X POST https://api.dbaas.dev/a2a \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tasks/send",
       "params":{"id":"x","message":{"parts":[
         {"text":"create a blog DB and seed it"}
       ]}}}'

# poll
curl -X POST https://api.dbaas.dev/a2a \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tasks/get",
       "params":{"id":"<task-uuid>"}}'
Token storage

Server stores SHA-256 hash only. Constant-time compare on every check.

Read-once credentials

First poll returns connection_string. Subsequent polls return [redacted] — capture on first read.

Authentication

Card advertises authentication.schemes: ["Bearer"] with required: false. Three places the token can live (in priority order):

1 · Authorization header
Authorization: Bearer <token>

Standard A2A spec auth. Recommended.

2 · Custom header
X-Client-Token: <token>

Useful when SDK can't set Authorization.

3 · In-body metadata
metadata.client_token

Inside message.metadata (flat) or params.message.metadata (RPC).

Token format: 32–128 char opaque random string. Server stores SHA-256 hash; constant-time compare on every poll. Operators can flip A2A_REQUIRE_CLIENT_TOKEN=true on the backend to make submission without a token return 401; the card's authentication.required field will reflect this.

Error response shapes

Both wire formats use the same numeric error codes (per JSON-RPC 2.0). Only the envelope differs.

JSON-RPC envelope
# Unknown method:
HTTP/1.1 400 Bad Request
{
  "jsonrpc": "2.0",
  "id": 99,
  "error": {
    "code": -32601,
    "message": "Method not found: frobnicate"
  }
}

# Task not found (auth-fail looks the
# same — deliberate, hides existence):
HTTP/1.1 404 Not Found
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32001,
             "message": "Task not found" }
}
Flat envelope
# Skill not determined:
HTTP/1.1 400 Bad Request
{
  "error": {
    "code": -32602,
    "message": "Could not determine skill..."
  }
}

# Task not found:
HTTP/1.1 404 Not Found
{
  "error": { "code": -32001,
             "message": "Task not found" }
}

The id field round-trips verbatim in JSON-RPC errors (string · number · null preserved). HTTP status code mirrors the JSON-RPC error: 400 for client errors, 401 for missing-required-token, 404 for not-found, 413 for oversized input, 500 for internal, 501 for not-implemented methods (cancel, sendSubscribe), 429 for rate-limit.

Limits

Mirrored verbatim by the agent card's limits object — clients can pre-flight without trial-and-error.

  • No API key required — public agent. Rate limit 5 submits / 10 min / IP (limits.submitsPerTenMinutes: 5). Returns 429 when exceeded.
  • Polling rate limit 120 req / min / IP (limits.pollsPerMinute: 120).
  • Per-IP cap of 2 concurrent active ephemeral databases (limits.concurrentDatabasesPerIP: 2).
  • Database TTL 1–60 min (default 60). Extend up to 90 min total via extend-ttl (limits.databaseTTLMinutes: {min:1, max:60, default:60, extendCapMinutes:90}).
  • Input length caps: 4 KB per free-form text field (limits.maxTextLengthBytes: 4096), 32 KB for raw SQL (limits.maxSQLLengthBytes: 32768). Returns 413 when exceeded.
  • LLM-generated SQL filtered (rejects COPY, pg_read_file, ALTER SYSTEM, shell metacharacters).

Agent Card

Machine-readable A2A v0.2 card — every spec-required field plus dbaas extensions. Cached public, max-age=3600. Both /.well-known/agent.json (legacy) and /.well-known/agent-card.json (A2A v0.2 canonical) return identical content — pick whichever your spec client fetches.

{
  // ─── A2A v0.2 spec-required ──────────────────────────────────────────
  "name": "dbaas.dev",
  "description": "PostgreSQL agent that plans and executes ...",
  "url": "https://api.dbaas.dev/a2a",
  "version": "2.4.0",
  "documentationUrl": "https://dbaas.dev/integrations/a2a",
  "provider": { "organization": "dbaas.dev", "url": "https://dbaas.dev" },
  "capabilities": {
    "streaming": false,                  // tasks/sendSubscribe (SSE) not implemented
    "pushNotifications": false,          // no webhook delivery yet
    "stateTransitionHistory": true,      // submitted → working → completed/failed persisted
    "async": true,                       // (extension)
    "taskPolling": true                  // (extension)
  },
  "authentication": {
    "schemes": ["Bearer"],
    "required": false,                   // optional unless A2A_REQUIRE_CLIENT_TOKEN=true
    "description": "Optional Authorization: Bearer <32-128 char client_token>..."
  },
  "defaultInputModes":  ["application/json", "text/plain"],
  "defaultOutputModes": ["application/json"],

  // ─── A2A spec extensions (dbaas-specific, namespaced) ───────────────
  "protocol": "a2a",
  "protocolVersion": "0.2",
  "defaultSkill": "workflow",
  "primaryInterface": "natural-language",
  "interfaces": [
    {
      "type": "json-rpc",
      "url": "https://api.dbaas.dev/a2a",
      "contentType": "application/json",
      "methods": ["tasks/send", "tasks/get"],
      "unsupportedMethods": ["tasks/cancel", "tasks/sendSubscribe"],
      "errorCodes": {
        "-32700": "Parse error",
        "-32600": "Invalid Request",
        "-32601": "Method not found",
        "-32602": "Invalid params",
        "-32603": "Internal error",
        "-32001": "Task not found"
      }
    },
    {
      "type": "rest-legacy",
      "url": "https://api.dbaas.dev/a2a/tasks/send",
      "contentType": "application/json",
      "body": "flat: {id, message{role, parts[], metadata}}",
      "poll": "GET /a2a/tasks/{task_id}"
    }
  ],
  "endpoints": {
    "submit":     "POST /a2a/tasks/send",       // legacy flat
    "poll":       "GET /a2a/tasks/{task_id}",   // legacy poll
    "jsonrpc":    "POST /a2a",                  // canonical A2A v0.2
    "jsonrpcGet": "POST /a2a method=\"tasks/get\""
  },
  "limits": {
    "submitsPerTenMinutes": 5,
    "pollsPerMinute": 120,
    "maxTextLengthBytes": 4096,
    "maxSQLLengthBytes": 32768,
    "databaseTTLMinutes": { "min": 1, "max": 60, "default": 60, "extendCapMinutes": 90 },
    "concurrentDatabasesPerIP": 2
  },

  "skills": [ /* workflow + 16 direct-access, each with id/name/description/tags/examples/inputSchema */ ]
}