Branches
Connectable Postgres databases with independent credentials, metrics, and configurable lifespans.
Branches
A branch is a connectable Postgres database instance within a project. Each branch has its own database, its own credentials, its own metrics, and its own lifespan. Branches are the fundamental unit of work in AxiomDB — every connection your application makes targets a specific branch.
The main branch is created automatically when you create a project. It is protected, has an infinite lifespan, and cannot be deleted. All other branches are ephemeral: they are created from main (or from another branch), inherit the source schema, and are automatically cleaned up when they expire.
Branch Anatomy
Every branch consists of:
┌─────────────────────────────────────────────────────┐
│ Branch │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Database │ │ Roles │ │
│ │ │ │ │ │
│ │ sq_<key>_<e> │ │ *_owner (CDb) │ │
│ │ _br_<slug> │ │ *_rw │ │
│ │ │ │ *_ro │ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Credentials │ │ Metrics │ │
│ │ │ │ │ │
│ │ DATABASE_URL │ │ connections │ │
│ │ DIRECT_URL │ │ storage_bytes │ │
│ │ │ │ cpu_seconds │ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Lifespan │ │ Status │ │
│ │ │ │ │ │
│ │ 7d / 1m / │ │ active / │ │
│ │ 6m / 1y / │ │ expired / │ │
│ │ forever │ │ deleting / │ │
│ │ │ │ deleted / │ │
│ │ │ │ failed │ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Metadata │ │
│ │ │ │
│ │ id, slug, source_branch_id, │ │
│ │ created_by, created_at, │ │
│ │ expires_at, protected │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘Database Naming Convention
AxiomDB derives database and role names from the project's app_key and env, plus the branch's slug.
Main Branch
Database: sq_<app_key>_<env>
Example: sq_payments_prodChild Branches
Database: sq_<app_key>_<env>_br_<branch_slug>
Example: sq_payments_prod_br_fix-checkout-bugRoles
| Role | Pattern | Example | Privileges |
|---|---|---|---|
| Owner | <app_key>_<env>_owner | payments_prod_owner | CREATEDB, full DDL + DML |
| Read-Write | <app_key>_<env>_rw | payments_prod_rw | SELECT, INSERT, UPDATE, DELETE |
| Read-Only | <app_key>_<env>_ro | payments_prod_ro | SELECT |
Role sharing
Roles are shared across all branches in a project. The payments_prod_rw role exists once in the Postgres cluster and can access any branch database within the project.
Lifespans
Every branch has a lifespan — a duration after which it is automatically expired and deleted.
Available Lifespans
| Lifespan | Duration | Typical Use |
|---|---|---|
7d | 7 days | Feature branches, quick experiments |
1m | 30 days | Sprint-length work, QA cycles |
6m | 180 days | Long-running staging environments |
1y | 365 days | Annual environments, compliance holds |
forever | No expiry | Production (main branch only) |
Lifespan Rules
- The
mainbranch always has lifespanforever. This cannot be changed. - Child branches can use any lifespan except
forever(unless explicitly authorized). - The default lifespan is determined by the project's
env:dev→7dstaging→1mprod→1y
- You can extend a branch's lifespan before it expires:
curl -X PATCH https://gateway.axiomdb.io/v1/branches/br_x1y2z3 \
-H "Authorization: Bearer paseto_v4_..." \
-H "Content-Type: application/json" \
-d '{ "lifespan": "6m" }'- You can shorten a branch's lifespan to force earlier cleanup:
curl -X PATCH https://gateway.axiomdb.io/v1/branches/br_x1y2z3 \
-H "Authorization: Bearer paseto_v4_..." \
-H "Content-Type: application/json" \
-d '{ "lifespan": "7d" }'Expiration Timeline
Created Extended to 6m Original expiry (7d) New expiry (6m)
│ │ │ │
▼ ▼ ▼ ▼
───┬─────────────────┬───────────────────────┬───────────────────────┬───
│ │ │ │
│ Branch is │ Extension applied; │ Would have expired │
│ active │ new expiry set │ here without ext. │
│ │ │ │
Branch expires
and is deletedProtected Branch Rules
The main branch is protected. The following restrictions apply:
| Operation | main | Child Branches |
|---|---|---|
| Create | Auto-created with project | Manual creation |
| Delete | ❌ Forbidden | ✅ Allowed |
| Change lifespan | ❌ Locked to forever | ✅ Allowed |
| Rename slug | ❌ Locked to main | ✅ Allowed |
| Schema changes | ✅ Allowed | ✅ Allowed |
| Data mutations | ✅ Allowed | ✅ Allowed |
Attempting to delete the main branch returns 403 Forbidden with error code PROTECTED_BRANCH.
Branch Status Values
┌──────────┐
│ creating │
└────┬─────┘
│
▼
┌──────────┐
┌────►│ active │◄────┐
│ └────┬─────┘ │
│ │ │
│ │ (expires) │ (extend)
│ ▼ │
│ ┌──────────┐ │
│ │ expired │─────┘
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ deleting │
│ └────┬─────┘
│ │
│ ├──────────────┐
│ ▼ ▼
│ ┌──────────┐ ┌──────────┐
└─────│ deleted │ │ failed │
└──────────┘ └──────────┘| Status | Description |
|---|---|
creating | The branch database is being provisioned. Schema is being copied from the source. |
active | The branch is fully operational and accepting connections. |
expired | The lifespan has elapsed. The branch is read-only; no new connections are accepted. Existing connections are drained with a 5-minute grace period. |
deleting | The branch is being torn down. Database and roles are being dropped. |
deleted | The branch has been fully removed. |
failed | An error occurred during creation or deletion. Manual intervention may be required. |
Expired branches
Expired branches enter a read-only state for 5 minutes before being hard-deleted. During this window, you can extend the lifespan to reactivate the branch. After hard deletion, data is unrecoverable.
Branch Creation Flow
When you call POST /v1/projects/:project_id/branches:
curl -X POST https://gateway.axiomdb.io/v1/projects/proj_a1b2c3d4/branches \
-H "Authorization: Bearer paseto_v4_..." \
-H "Content-Type: application/json" \
-d '{
"slug": "fix-checkout-bug",
"lifespan": "7d",
"source_branch_id": "br_main"
}'Internal Sequence
Client Gateway (4060) Control Plane square-dbctl Postgres
│ │ │ │ │
│ POST /branches │ │ │ │
│ ────────────────────►│ │ │ │
│ │ Auth (PASETO v4) │ │ │
│ │ Validate payload │ │ │
│ │ Check branch limit │ │ │
│ │ ─────────────────────►│ │ │
│ │ │ Enqueue command │ │
│ │ │ CREATE_BRANCH ───►│ │
│ │ │ │ │
│ │ │ │ 1. CREATE DATABASE │
│ │ │ │ sq_payments_prod │
│ │ │ │ _br_fix-checkout │
│ │ │ │ ──────────────────►│
│ │ │ │ │
│ │ │ │ 2. Copy schema │
│ │ │ │ from source │
│ │ │ │ ──────────────────►│
│ │ │ │ │
│ │ │ │ 3. Grant roles │
│ │ │ │ ──────────────────►│
│ │ │ │ │
│ │ │ 4. Generate creds │ │
│ │ │ 5. Write audit │ │
│ │ │ 6. Set expiry │ │
│ │ │ │ │
│ ◄───────────────────│◄──────────────────────│ │ │
│ 201 Created │ │ │ │Schema Copying
When creating a branch from a source, AxiomDB copies the schema only, not the data. The process:
pg_dump --schema-onlyfrom the source database.- Apply the SQL to the new database.
- Verify table count matches.
-- Verification query run after schema copy
SELECT COUNT(*) AS table_count
FROM information_schema.tables
WHERE table_schema = 'public';If the table count does not match, the branch is marked as failed and an alert is raised.
Branch Credentials
Each branch exposes two connection URLs:
| URL | Port | Layer | Purpose |
|---|---|---|---|
DATABASE_URL | 6432 | PgBouncer (session mode) | Application runtime. Prisma-compatible. |
DIRECT_URL | 5432 | Direct Postgres | Migrations, admin tasks. |
Credential Structure
{
"branch_id": "br_x1y2z3",
"slug": "fix-checkout-bug",
"database_url": "postgresql://payments_prod_rw:<password>@gateway.axiomdb.io:6432/sq_payments_prod_br_fix-checkout-bug?sslmode=require",
"direct_url": "postgresql://payments_prod_owner:<password>@gateway.axiomdb.io:5432/sq_payments_prod_br_fix-checkout-bug?sslmode=require",
"role": "payments_prod_rw",
"database_name": "sq_payments_prod_br_fix-checkout-bug"
}See Connections for URL anatomy and framework integration.
Branch Metrics
Each branch reports metrics collected from the Postgres cluster:
{
"branch_id": "br_x1y2z3",
"metrics": {
"storage_bytes": 188743680,
"storage_gib": 0.176,
"active_connections": 4,
"total_connections": 127,
"cpu_seconds": 42.7,
"rows_inserted": 150000,
"rows_updated": 32000,
"rows_deleted": 500,
"cache_hit_ratio": 0.97,
"transactions_committed": 890000,
"transactions_rolled_back": 120
},
"sampled_at": "2025-03-20T14:00:00Z"
}Storage Measurement Query
The storage metric is derived from:
SELECT pg_database_size(current_database()) AS storage_bytes;Cache Hit Ratio
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;Metrics refresh
Metrics are sampled every 60 seconds and cached. The API returns the most recent sample. Historical metrics are retained for 30 days.
Branch Limits
| Limit | Value | Notes |
|---|---|---|
| Active branches per project | 10 | Includes main. Cannot be exceeded. |
| Max slug length | 48 characters | Lowercase alphanumeric + hyphens. |
| Min slug length | 3 characters | — |
| Max database name length | 63 characters | Postgres limit. Derived from app_key + env + slug. |
| Schema copy timeout | 5 minutes | Branch is marked failed if exceeded. |
Checking Branch Count
Before creating a branch, the gateway checks:
SELECT COUNT(*) AS active_branches
FROM branches
WHERE project_id = $1 AND status IN ('creating', 'active', 'expired');If active_branches >= max_branches, the API returns 429 Too Many Requests with error code BRANCH_LIMIT_REACHED.
Source Branch Inheritance
A child branch can be created from any existing branch, not just main. This enables branch stacking:
main (protected, forever)
├── feat-payments (1m)
│ ├── fix-payment-bug (7d) ← branched from feat-payments
│ └── payment-tests (7d) ← branched from feat-payments
└── feat-users (1m)
└── user-schema-v2 (7d) ← branched from feat-usersWhen branching from a non-main source, the schema copy pulls from the source branch's current schema state, including any migrations that have been applied to it.
API Reference
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/projects/:pid/branches | Create a new branch. |
GET | /v1/projects/:pid/branches | List all branches. |
GET | /v1/branches/:bid | Get a single branch. |
PATCH | /v1/branches/:bid | Update branch (lifespan, slug). |
DELETE | /v1/branches/:bid | Delete a branch. |
GET | /v1/branches/:bid/credentials | Get connection URLs. |
GET | /v1/branches/:bid/metrics | Get branch metrics. |
POST | /v1/branches/:bid/extend | Extend branch lifespan. |