Network & Access Control
AxiomDB uses CIDR-based IP allowlisting to control which IPs can connect to your databases. By default all projects start in "restricted" mode — no external connections are allowed until you add a rule.
Default policy
Every new project starts with network policy mode = restricted. No external IP can connect until you explicitly add an allowlist rule. This applies to both the runtime port (PgBouncer, 6432) and the direct port (PostgreSQL, 5432).
Network policy: restricted
└── No rules → all external connections refusedPorts
| Port | Purpose | Key |
|---|---|---|
6432 | PgBouncer connection pool — use for app/runtime traffic | runtime |
5432 | Direct PostgreSQL — use for migrations, prisma migrate, debug connections | direct |
Most rules should target both unless you need to restrict migration access separately.
Checking your current IP
GET /api/v1/network/my-ip
Authorization: Bearer <paseto-v4-token>Response:
{
"ip": "203.0.113.10",
"suggested_cidr": "203.0.113.10/32"
}Creating a network rule
POST /api/v1/projects/:project_id/network/rules
Authorization: Bearer <paseto-v4-token>
Content-Type: application/json
{
"cidr": "203.0.113.10/32",
"label": "My laptop",
"ports": "both",
"scope": "project",
"branch_id": null,
"expires_in": null
}Fields
| Field | Required | Description |
|---|---|---|
cidr | ✅ | IPv4 CIDR in a.b.c.d/prefix format. Single IPs use /32. |
label | ❌ | Human-readable description. Defaults to "manual allowlist rule". |
ports | ❌ | "runtime", "direct", or "both". Defaults to "both". |
scope | ❌ | "project" (applies to all branches) or "branch" (specific branch only). |
branch_id | ❌ | Required when scope = "branch". |
expires_in | ❌ | TTL string: "1h", "24h", "7d", "30d", "1y". Omit for permanent. |
Response (201 Created):
{
"rule": {
"id": "...",
"project_id": "...",
"branch_id": null,
"cidr": "203.0.113.10/32",
"label": "My laptop",
"ports": "both",
"scope": "project",
"expires_at": null,
"created_by": "...",
"source_ip": "203.0.113.20",
"created_at": "2025-07-02T00:00:00Z"
},
"applied": {
"cidr": "203.0.113.10/32",
"ports": "both",
"targets": [
{ "target": "runtime", "port": 6432, "output": "OK" },
{ "target": "direct", "port": 5432, "output": "OK" }
]
},
"reconcile_warning": null
}Deduplication: If an identical rule (same cidr, ports, scope, branch_id) already exists and is active, the gateway returns the existing rule instead of creating a duplicate and emits a network.rule.reused audit event.
Listing network rules
GET /api/v1/projects/:project_id/network/rules
Authorization: Bearer <paseto-v4-token>Optional query parameters:
branch_id=<uuid>— Filter to rules for a specific branchinclude_project=true— When filtering by branch, also include project-scoped rules
Response:
{
"project_id": "...",
"policy": {
"project_id": "...",
"mode": "restricted",
"revision": 1,
"last_applied_at": null,
"created_at": "...",
"updated_at": "..."
},
"rules": [
{
"id": "...",
"cidr": "203.0.113.10/32",
"label": "My laptop",
"ports": "both",
"scope": "project",
"branch_id": null,
"expires_at": null,
"created_at": "..."
}
]
}Updating a network rule
PATCH /api/v1/projects/:project_id/network/rules/:rule_id
Authorization: Bearer <paseto-v4-token>
Content-Type: application/json
{
"cidr": "203.0.113.20/32",
"label": "Updated label",
"ports": "runtime",
"expires_in": "30d"
}All fields are optional — only provided fields are updated. When cidr or ports changes, the old firewall rule is revoked and the new one applied immediately.
Deleting a network rule
DELETE /api/v1/projects/:project_id/network/rules/:rule_id
Authorization: Bearer <paseto-v4-token>Smart revocation: If another active rule covers the same CIDR+port combination, the firewall entry is not removed (only the logical rule record is soft-deleted). The underlying iptables/pg_hba.conf entry is only revoked when no remaining rules reference that CIDR+port.
Response:
{
"deleted": true,
"rule": { ... },
"revoked": {
"cidr": "203.0.113.10/32",
"ports": "both",
"targets": [
{ "target": "runtime", "port": 6432, "output": "OK" },
{ "target": "direct", "port": 5432, "output": "OK" }
]
}
}Public mode
For development or public-read APIs, you can open the runtime port to all IPs:
POST /api/v1/projects/:project_id/network/public-mode
Authorization: Bearer <paseto-v4-token>
Content-Type: application/json
{
"mode": "public_runtime",
"confirm": "make runtime public"
}Requires explicit confirmation. You must pass "confirm": "make runtime public" exactly. This opens PgBouncer (port 6432) to all IPs. The direct PostgreSQL port (5432) remains restricted.
To revert to restricted:
{ "mode": "restricted" }Policy modes
| Mode | Effect |
|---|---|
restricted | All external connections blocked. Only CIDR rules allow access. |
public_runtime | Port 6432 (PgBouncer) open to all IPs. Port 5432 still CIDR-restricted. |
Branch-scoped rules
Rules can be scoped to a specific branch rather than the entire project. This is useful for giving preview deploy services access only to their corresponding branch database.
{
"cidr": "10.0.0.5/32",
"label": "Preview deploy server",
"ports": "runtime",
"scope": "branch",
"branch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Branch-scoped rules are automatically created when you create a new branch (the creator's IP is whitelisted with label "auto: branch creator").
Reconciliation & failure handling
When a rule is created or updated, the gateway calls square-dbctl allow-pgbouncer-cidr and/or square-dbctl allow-cidr to update the firewall in real time. If the square-dbctl call fails (e.g. VPS connectivity issue):
- The rule is still saved to the control-plane database.
- The response includes
"reconcile_warning": "network access update failed — rule saved, firewall sync will retry". - The firewall sync will be retried on the next rule operation for that project.
Audit trail
Every network operation emits an audit event:
| Action | Trigger |
|---|---|
network.rule.created | New rule added |
network.rule.reused | Duplicate rule request (existing rule returned) |
network.rule.updated | CIDR, ports, label, or expiry changed |
network.rule.deleted | Rule soft-deleted |
network.public_mode.changed | Policy mode changed |
Data model
CREATE TABLE network_policies (
project_id UUID PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
mode TEXT NOT NULL DEFAULT 'restricted', -- restricted | public_runtime
revision BIGINT NOT NULL DEFAULT 1,
last_applied_at TIMESTAMPTZ,
tenant_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE network_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
branch_id UUID REFERENCES project_branches(id) ON DELETE CASCADE,
cidr TEXT NOT NULL,
label TEXT NOT NULL,
ports TEXT NOT NULL, -- runtime | direct | both
scope TEXT NOT NULL, -- project | branch
expires_at TIMESTAMPTZ,
created_by UUID REFERENCES users(id),
source_ip TEXT, -- IP that made the API request
source_user_agent TEXT,
deleted_at TIMESTAMPTZ,
tenant_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);How is this guide?
Branches
Database branches are independent copy-on-write clones of a parent PostgreSQL database. Each branch has its own connection credentials, network rules, metrics, and configurable lifespan — enabling safe schema experimentation, preview environments, and team isolation.
Authentication & Authorization
AxiomDB uses PASETO v4 public tokens issued by the Square Identity Provider (authlayer). All API routes require a valid Bearer token. Supported flows include Sign in with Square (OAuth2/OIDC), password login, magic links, CLI PKCE, token refresh, and two-factor authentication (TOTP and email OTP).
