PlanetScale
PlanetScale runs a managed database with schema branches and deploy requests. Here the database is /v1/provisioning (28) and the branch, the review and the deploy are /v1/git (44), where the migration already lives.
PlanetScale gives you a managed database, a branch of its schema, and a deploy
request that puts the branch back onto production. Two capabilities answer that.
/v1/provisioning (28 operations) makes the database — POST /v1/provisioning/sql
launches your org's own PostgreSQL instance on port 5432 and answers with a
postgres:// DSN. /v1/git (44) carries the other half, because the schema change
is a file in a repo and that repo already has a branch, a proposal and a merge.
The structural difference is that one. PlanetScale hands you a second branch — inside the database, with its own host, its own password and its own deploy request number — that has to be kept in step with the git branch holding the same migration. Here there is one branch.
Start here
Mint a key, create the database, read it back until it says ready.
# 1. mint a key — sk- belongs on a server, pk- is safe in a browser
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
-H "Authorization: Bearer $HANZO_SESSION" \
-H 'Content-Type: application/json' \
-d '{"type":"secret"}'
# 2. launch your org's own PostgreSQL instance — the DSN is in this answer only
curl -sS -X POST https://api.hanzo.ai/v1/provisioning/sql \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"name":"orders"}'
# 3. read it back until status goes from provisioning to ready
curl -sS https://api.hanzo.ai/v1/provisioning/sql/orders \
-H "Authorization: Bearer $HANZO_API_KEY"Step 2 answers 201 with connectionString while the instance is still booting;
step 3 reconciles status against the operator's live view, so ready there means
psql will connect. Neither path carries an organization slug — the key does.
Core capabilities
| Capability | What it does | Operations |
|---|---|---|
/v1/provisioning | Makes the database. Seven kinds share one body and one result shape: sql, datastore, docdb, kv, search, vector, s3 | 28 |
/v1/git | The branch, the pull request and the merge that carry the migration file. POST /v1/git/repos/{name}/pulls/{number}/merge is the deploy request | 44 |
/v1/o11y | What Insights showed: POST /v1/o11y/service/top_operations for latency, call count and errors per operation | 381 |
Nouns
The database
| PlanetScale | Hanzo |
|---|---|
| Organization | Your org, taken from the validated key. Never a field in the request |
| Database | POST /v1/provisioning/sql — your org's own PostgreSQL instance, port 5432 |
| List databases | GET /v1/provisioning/sql |
| Database detail | GET /v1/provisioning/sql/{name} — host, port, username, status, never the password |
| Delete a database | DELETE /v1/provisioning/sql/{name} — a bound app reverts to Base first, then the instance goes |
| Connection string | connectionString on the create result, returned once |
| Password with a role, per branch | One admin credential per instance, minted at create and sealed in /v1/kms (5) |
| Putting the credential in your app | instance in the create body — the DSN lands in that app's addons Secret |
The pscale connect proxy | Nothing in between. The DSN is what psql and your driver take |
Six other kinds sit on the same two routes: datastore for analytics,
docdb on the MongoDB wire protocol, kv, search, vector and s3. Same
body, same result shape, same one-name-per-resource rule.
The schema change
| PlanetScale | Hanzo |
|---|---|
| Branch of a database | A branch of the repo the migration lives in — GET /v1/git/repos/{name}/refs |
pscale branch push, schema applied to a branch | POST /v1/git/repos/{name}/push — files land as one commit, and it fires what a real push fires |
| Deploy request | A pull request — POST /v1/git/repos/{name}/pulls |
| Open deploy requests | GET /v1/git/repos/{name}/pulls?state=open |
| Deploying it | POST /v1/git/repos/{name}/pulls/{number}/merge |
| What landed, and when | GET /v1/git/repos/{name}/commits |
| Running the migration | The build the merge fires, or POST /v1/platform/projects/{project}/apps/{app}/deploy |
| Build history | GET /v1/platform/builds — part of /v1/platform (37) |
Insights, and the rest
| PlanetScale | Hanzo |
|---|---|
| Insights, per-statement p50 p95 p99 | POST /v1/o11y/service/top_operations — latency, call count and error count per operation |
| Where one slow statement sits among its peers | POST /v1/o11y/span_percentile |
| Slow query log | GET /v1/o11y/logs |
| Alert when a threshold is crossed | /v1/o11y/rules |
| Insights dashboards | /v1/o11y/dashboards — part of /v1/o11y (381) |
| Service tokens, OAuth applications, members | /v1/iam (159) — /v1/iam/service-accounts and /v1/iam/keys |
| Audit log | GET /v1/iam/audit-logs, and GET /v1/audit (1) for the org trail |
| Database webhooks | /v1/webhook (8) — singular, with GET /v1/webhook/{id}/deliveries |
| Invoices and usage | /v1/billing (45) and /v1/usage (5) |
The call
PlanetScale, from nothing to a deployed schema change:
curl -sS -X POST https://api.planetscale.com/v1/organizations/acme/databases \
-H "Authorization: $PLANETSCALE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"orders","region":"us-east"}'
curl -sS -X POST \
https://api.planetscale.com/v1/organizations/acme/databases/orders/branches \
-H "Authorization: $PLANETSCALE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"add-line-items","parent_branch":"main"}'
curl -sS -X POST \
https://api.planetscale.com/v1/organizations/acme/databases/orders/branches/add-line-items/passwords \
-H "Authorization: $PLANETSCALE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"app","role":"readwriter"}'
curl -sS -X POST \
https://api.planetscale.com/v1/organizations/acme/databases/orders/deploy-requests \
-H "Authorization: $PLANETSCALE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"branch":"add-line-items","into_branch":"main"}'
curl -sS -X POST \
https://api.planetscale.com/v1/organizations/acme/databases/orders/deploy-requests/12/deploy \
-H "Authorization: $PLANETSCALE_TOKEN"Five calls, and five identifiers that must agree: acme, orders,
add-line-items, main, 12. A sixth is the git branch carrying the same
migration, which this API never sees and cannot check.
Hanzo. The database, once:
curl -sS -X POST https://api.hanzo.ai/v1/provisioning/sql \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"name":"orders","instance":"checkout"}'{
"id": "rs_7c1f",
"kind": "sql",
"name": "orders",
"port": 5432,
"status": "provisioning",
"connectionString": "postgres://..."
}There is no acme in that path. The store root and the instance both come from
the org claim in the key, so a caller has no way to write another tenant's name
down. instance binds the result: the checkout app's addons Secret receives
SQL_URL, so the DSN reaches the process that needs it without a copy step.
Omit instance and you keep the DSN yourself.
Then the schema change, on the branch you already pushed:
curl -sS -X POST https://api.hanzo.ai/v1/git/repos/orders/pulls \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"head":"add-line-items","base":"main","title":"add line items"}'
curl -sS -X POST https://api.hanzo.ai/v1/git/repos/orders/pulls/4/merge \
-H "Authorization: Bearer $HANZO_API_KEY"The merge is a fast-forward or nothing. It moves base only when base is
already an ancestor of head — the case where nothing is lost and nothing is
invented — and answers 409 when base has moved on independently, saying so.
A deploy request is a state machine you poll through queued and in-progress; this
answers with the revision base now points at. The build it fires is the same one
a git push of those bytes fires, so merging is not a second road into
production with its own rules. Proposing the same head into the same base twice
while the first is open is a 409, so a retried run leaves one thing to review.
What does not carry
No database branch. PlanetScale forks the schema, and on a data branch the
rows, into a copy with its own host and password. A name here is one instance and
one DSN. A staging database is a second POST /v1/provisioning/sql under a second
name, seeded by the same migrations rather than forked from production.
No deploy request, no schema diff, no revert window. PlanetScale reads your DDL, shows you the diff, applies it outside the request path and holds a window in which you can put it back. Nothing here reads your DDL. The merge moves a ref and the build runs your migration, so a bad migration is undone by another migration.
No backup or restore route. The API document has zero paths matching backup,
restore or point-in-time. Restoring last night into a fresh branch, which is one
of the better things PlanetScale sells, has no equivalent call. Schedule a dump
on /v1/tasks (5) and land it in /v1/s3 (6).
No region, no size, no read replica. provisionRequest has exactly two
fields, name and instance. There is nowhere to write us-east, ask for more
memory, or add a read-only region. The instance lands in your org's tenant
namespace and scales up rather than out.
No Vitess. No keyspaces, no vindexes, no horizontal sharding, and no online
DDL running a ghost table behind your statement. The engine is PostgreSQL, so
MySQL dialect, inline ENUM columns and SHOW statements go with it. If you are
already on PlanetScale for Postgres, only the branch model changes.
The credential is returned once. connectionString and password come back
at create and no read carries them again — GET /v1/provisioning/sql/{name}
answers host, port, username and status, never the password — and there is no
rotate route beside it. PlanetScale mints a new password whenever you ask. Lose
this one and you provision again.
How is this guide?
Firebase
Firebase is six products behind one project id — sign-in is /v1/iam (159 operations), the data is /v1/kv (6) and /v1/provisioning (28), files are /v1/s3 (6), code is /v1/functions (11), hosting is /v1/projects (27), and the project id stops being something you carry.
Upstash
Upstash sells Redis, Kafka, QStash and Vector as four products behind four hostnames and ten credential values; here that is /v1/kv (6), /v1/mq (15), /v1/webhook (8) and /v1/index (17) on one hostname with one bearer.