Skip to content
AXOWLDevelopers
Quickstart

Database Management (Strix)

Database Management is how application data — not just identity events — comes under Axowl’s integrity guarantee. An app group’s database is one of two kinds:

  1. Strix — sealed Postgres (Pro and up — not available on Free) — Axowl provisions the database: plain PostgreSQL, standard SQL, every driver and BI tool you already use. It is sealed from the first row: every change is sealed inside the database at the moment it is written, and a write made from outside your app is recorded on the spot — see The write-path principle. Your deployed app connects to it directly with the connection URI from the dashboard — see How a deployed app connects to its database. Because the data lives in the plane Axowl operates, behavioral analytics work out of the box.
  2. A database you already run — register your own PostgreSQL database with an app group. Axowl reads it for the admin console, changes it only through approval actions, and records hashes of the changes your app reports — see The adoption ladder.

On Enterprise, a database you run is sealed by key-loan: the data stays in your instance, in your network, under your compliance regime. Axowl never sees the contents: on every write your side sends a hash, Axowl signs it with a key you never hold, timestamps it, and anchors it append-only. Nobody — including the database owner — can silently rewrite history, because re-sealing produces a new sequence position and that is the detection. See Seals & integrity.

Authentication analytics (logins, sign-ups, MAU) work on every plan whichever database you use, because Axowl is the identity provider either way.

An app group holds up to 10 connections of either kind. One of them is the Default database — the one the console and admin boards read. The first connection becomes the default automatically; POST /{id}/primary moves it.

Integrity does not come from owning the database. It comes from two things:

  • Write-path exclusivity — a record is sealed when it enters through a write path Axowl controls.
  • Key custody — the sealing key is never in the hands of whoever holds the data.

These two properties are what both kinds of database are built on, and key-loan works even though Enterprise data never leaves the customer’s network — the same principle applied from the other end.

On a Strix database the seal sits inside the database itself. Axowl adds three columns to every table in the public schema — _axowl_seal, _axowl_by and _axowl_at — and fills them on every INSERT and UPDATE: the seal covers the whole row together with the database role that wrote it and the time. So every change is sealed as it is written, whichever client made it, and your application code does not change. Tables you add later are sealed on the next pass. A write from any role other than the database’s own app role is recorded on the spot and raised as an integrity alert — it is recorded, not blocked, because a direct fix by an administrator can be legitimate, and that is yours to judge. Removing the seal from a table is detected and raised the same way. Sealing and recording happen immediately, inside the database; what differs is how often Axowl collects the record and tells you — every few hours on a Serverless database, every few minutes on Always-On.

Be precise about what the seal is not: the connection you get from the dashboard is an ordinary PostgreSQL role that can read and write, and writes made through it go straight to Postgres — sealed as they land, but without an approval step. The one write path that runs through Axowl’s approval gate is an admin-board action. Which writes pass the gate, and which do not, is spelled out in How a deployed app connects to its database.

  • New project? There is no ladder for you — create a Strix database for your app group and point your app at its connection URI. Nothing to migrate.
  • Existing database? You climb the ladder below, one rung at a time, and migration is the last step — taken only if and when you choose it.

Every organization declares a home region when it is created — it appears as the badge in the console header, and everything you create afterward follows it by default. When you create a Strix database you can keep the organization default or pick a different data region per app group; the region you choose is where that database physically lives.

Region codes are Axowl’s own (kr, sg, au, eu-de, uk, us-e) and stay stable even as the underlying infrastructure evolves — the code you picked at creation never changes meaning underneath you. Each database records both its Axowl region and the physical provider region it was provisioned in at creation time, so residency answers come from the record, not from today’s mapping.

One note on residency: the Asia-Pacific data plane runs from Singapore, so kr data is near Korea, not inside it. If your compliance requires data on soil you control, that is the Enterprise isolated-instance track — not a region choice.

Nobody migrates a production database on day one, so Database Management is built as a ladder — every rung pays for itself, and no rung requires the next:

RungWhat it does
ConnectRegister the PostgreSQL database you already run with an app group. It powers the admin console (table and column listing, list queries) and the coverage report.
Gated writesAn approval action taken on an admin board runs one guarded single-row UPDATE against your database, and Axowl keeps a record of the decision.
WitnessDetect changes made around Axowl. Default path: your app reports each row change to Axowl as a hash, and Axowl records it in the organization’s sealed audit chain — no agent, nothing installed, zero contact with your database. Enterprise path (opt-in): an agent runs inside your network, reads the change stream locally, and sends only hashes outbound. Pro and up — on Free, reported changes are refused with 402 { "error": "UPGRADE_REQUIRED", "feature": "witness" }. The first 1,000,000 events a month are included; past that they are billed at $29 per million, pro rata.
New data starts sealedAdd a Strix database to the same app group. New tables and features land there, sealed from the first row; existing data stays where it is.
Full migrationMove the rest when you choose — and seal a genesis marker into the chain: records before the import point are declared external history, not covered; everything after is sealed for life. The chain never claims more than it can prove.

Migration tooling is free on every plan.

GET /{id}/coverage shows how far the witness reaches: totalTables, coveredTables (tables that have reported at least one change), uncoveredTables, and unmatchedWitnessTables (reported table names that this database does not have). Tables are matched by name only (matchedByNameOnly: true), so two connected databases with the same table name are not told apart.

These are commitments, not caveats:

  • No live analytics queries against your primary. Analytics are served from snapshots and sealed events held on Axowl’s side — your primary is never a BI backend.
  • No broad credentials needed. Connect a role scoped to the tables Axowl should see. GET /{id}/schema-permission shows whether the connected role can change the schema.
  • No replication slot left to fill your disk. The Enterprise witness agent installs only on PostgreSQL 13+ with max_slot_wal_keep_size configured, so an abandoned slot cannot grow without bound. If the witness is ever interrupted, the gap itself is sealed as an explicit event rather than papered over.
  • No dual-writes, ever. A product whose ledger is the truth cannot run a migration mode where the truth exists in two places.
  • Contents stay yours. Witness and coverage work from table names and hashes, not row values.
  • Reads are read-only. Console list queries must be a SELECT; they run inside a read-only transaction, so a write hidden inside a query is rejected by PostgreSQL itself, and they stop after 15 seconds.
  • Writes are one row at a time. The only statement Axowl runs against your data is an admin-board action: a single UPDATE with a WHERE clause that names the row. DELETE, DROP, TRUNCATE, ALTER, GRANT and CREATE are refused.
  • Disconnecting and deleting never touch your database. Both change only Axowl’s record of the connection.
  • Passwords are stored encrypted and are never returned by the list call. Revealing one needs a personal seal and is recorded.

How a deployed app connects to its database

Section titled “How a deployed app connects to its database”

Your application’s runtime — the server you deploy, not the browser — talks to the database directly, over the standard PostgreSQL protocol. There is no Axowl SDK or proxy in that path.

  1. Get the connection details. Dashboard → your app group → Database → open the database’s connection settings → Show connection details. Your personal seal is required, and every reveal is recorded (appgroup.datasource.credential_revealed). You get the password and two ready-to-paste forms:

    FormShapeUse with
    URIpostgresql://user:password@host:5432/database?sslmode=requirepsql, libpq, Prisma, Drizzle, SQLAlchemy, most ORMs
    ADO.NETHost=…;Port=5432;Database=…;Username=…;Password=…;SSL Mode=RequireNpgsql / Entity Framework Core

    API callers use POST /api/org/{slug}/app-groups/{groupId}/data-sources/{id}/credentials with the same seal (body { assertionResponseJson }); it returns host, port, database, username, password, connectionUri and connectionString.

  2. Store it as a server-side secret — for example DATABASE_URL in your host’s secret settings. Never ship it to a browser or a mobile app: whoever holds it can read and write the database.

  3. Connect with your normal driver. Reads, writes, migrations and transactions are ordinary Postgres.

What does and does not pass through Axowl. Writes your app makes over that connection are not routed through Axowl and do not pass the approval gate; on a Strix database they are sealed inside the database as they are written. The write Axowl performs itself is the admin-board action: POST /api/org/{slug}/app-groups/{groupId}/schemas/{schemaKey}/rows/{rowId}/actions/{actionKey} runs the action’s single-row UPDATE (the row identifier is bound as a parameter in place of {id} — statements without a WHERE … {id} are refused) against the app group’s primary data source, and Axowl keeps a record of the decision — who decided, which row, which statement, how many rows changed. It is an approval step taken by a person in the dashboard, not a general-purpose write API for your application. Admin boards are enabled per organization; where they are not enabled, this route answers 404.

Cloudflare Pages. If a client in the app group deploys to a connected Cloudflare Pages project, POST …/data-sources/{id}/pages-secret (seal required, plus the org.deployment.update permission) writes the connection URI to that project as the DATABASE_URL secret, so you do not copy it by hand. Without a connected project it answers 409 NO_PAGES_TARGET.

When the password changes, update the secret. POST …/data-sources/{id}/rotate-password (seal required) reissues a Strix password and the previous connection string stops working immediately. A database suspended for non-payment has its password rotated too (see below) — reconnecting it gives you new connection details to copy into your app.

Strix is a paid-plan feature. The plan opens the gate and usage pays: storage and compute are metered with no included amount, and witness events are billed past the included million. The pricing page is the live schedule.

PlanDatabase connections (whole organization)Strix
Free1 (an existing database you run)Not available — create, reconnect and Always-On answer 403 STRIX_PLAN_REQUIRED
Pro and upNo organization limit (10 per app group)Available

A Free organization that already has a Strix database keeps it: it can still be disconnected, deleted and billed as before (including the $5 monthly minimum), and turned off Always-On — only creating, reconnecting and turning Always-On on need a paid plan.

Always-On is $15 per database per month on top of usage. It is charged when you turn it on: the rest of the current month by remaining days (counting today), taken from Axowl Credit before the database switches; from the next month the full $15. Turning it off is scheduled for the end of the month: the database stays Always-On until then — that month is already paid — and goes back to Serverless on the 1st (UTC). Until then you can cancel the scheduled turn-off at no charge. The month already paid is not refunded. If the switch fails, the charge is refunded. Because an Always-On database never sleeps, its compute is at least 0.25 CU around the clock (about $19 a month at the current rate), billed by actual use. The dashboard shows these amounts and asks before turning it on; if the balance is too low the API answers 402 CREDIT_REQUIRED with the amount, and the dashboard opens a top-up and then retries.

Base: /api/org/{slug}/app-groups/{groupId}/data-sources (authenticated; the organization must own the app group).

Method · RoutePurpose
GET /List connections — passwords are never returned, only hasPassword. The primary source (the Default database) sorts first. Each row carries isManaged (a Strix database), isPrimary, isAlwaysOn, alwaysOnDowngradedAt, purpose (production or staging), region, the last test result, and this month’s metered usage for a Strix database (usageCuHours, usageGbMonths, usageStorageUsd, usageComputeUsd, usageTotalUsd)
POST /testTest a connection before saving it
POST /Register (accepts a full connection string or individual fields; a failed test still registers, recorded as unreachable). 403 PLAN_DB_LIMIT with { limit, requiredPlan } when the organization’s plan connection limit is reached (Free: 1); 400 when the app group already has 10 connections; 409 when the name is already used in the group
POST /{id}/testRe-test with stored credentials — audited as its own event, since it exercises the stored password
POST /strixCreate a managed Strix database (requires a paid plan — 403 STRIX_PLAN_REQUIRED on Free — and the Strix integration to be active, 403 STRIX_NOT_ENABLED otherwise). Body: { name, region?, alwaysOn? } — region falls back to the organization’s home region. Returns the new connection (id, name, region, connected), listed with isManaged: true and sealed from the first row. Also 403 PLAN_DB_LIMIT as for POST /
PATCH /{id}Rename a connection, mark it production or staging (purpose — one staging database per app group, and the Default database cannot be staging), or, for a database you run, change its host, port, database, user, password or SSL setting. Changed connection details are re-tested. Returns { id, name, changed, connected, error }
GET /{id}/schema-permissionWhether the connected role can change the schema: { canChangeSchema, detail }
GET /{id}/coverageWitness coverage by table — see the adoption ladder
POST /{id}/credentialsReveal the connection details (personal seal) — see How a deployed app connects to its database
POST /{id}/rotate-passwordReissue a Strix database’s password (personal seal). 400 NOT_MANAGED for a database you run
POST /{id}/pages-secretWrite the connection URI to a connected Cloudflare Pages project as DATABASE_URL (personal seal)
POST /{id}/primaryMark the source the console and admin boards read from (one per group)
DELETE /{id}Disconnect — nothing is deleted. The row is kept with isActive: false; when it was connected and disconnected is part of the record. A Strix database keeps existing (and metering) until you delete it
POST /{id}/reconnectReconnect a disconnected row. Re-tests with the stored credentials and returns { reconnected, connected, error }. 409 NAME_TAKEN if an active connection took the same name meanwhile. Counts toward the plan connection limit (403 PLAN_DB_LIMIT); a Strix row needs a paid plan (403 STRIX_PLAN_REQUIRED)
POST /{id}/tierTurn Always-On on or off for a Strix database. Body: { alwaysOn }. Turning on needs a paid plan (403 STRIX_PLAN_REQUIRED), charges this month’s share right away (402 CREDIT_REQUIRED with required when the balance is short — nothing is charged), and refunds it if the switch fails; turning off is always allowed and is scheduled: alwaysOn: false answers { alwaysOn: true, endsAt, scheduled: true } and the database switches at endsAt (the 1st of next month, UTC); alwaysOn: true while a turn-off is scheduled cancels it ({ alwaysOn: true, scheduleCancelled: true }, no charge). Turning on returns { alwaysOn, alwaysOnCount, charged }. Rows in GET / carry alwaysOnEndsAt
POST /{id}/deletePermanent delete. Body: { assertionResponseJson, confirmName } — a personal seal bound to this source and the delete action, plus the connection’s exact name. Only a disconnected row can be deleted (400 STILL_ACTIVE otherwise). For a Strix database the provider project is destroyed first — all data is gone, no undo — and the row is soft-deleted only after that succeeds (502 PROVIDER_DELETE_FAILED leaves the row so you can retry). For an external connection only Axowl’s record is removed; your database is never touched

GET / returns active connections by default; pass ?includeInactive=true to also get disconnected rows (each row carries isActive).

Usage is billed per compute-hour and per GB, so every managed database is created with fixed limits and Axowl re-checks them once a day, correcting any that drifted:

SettingStandardAlways-On
Suspend when idleafter 5 minutes (compute billing stops; data stays)never
Compute range0.25 – 1 CU0.25 CU
Change history kept1 day1 day

Change history is what point-in-time recovery and branching work from, and it is billed as storage — it is set explicitly rather than left to the provider’s default, which differs by plan.

Strix has no free tier, and the API enforces it: POST /strix and POST /{id}/reconnect (managed rows only) return 402 { code: "CREDIT_REQUIRED", balance, required } unless the paying organization holds at least required ($5) of Axowl Credit, has auto top-up enabled, or has a Korean recurring-billing card registered — month-end usage is then charged to that card, the same way auto top-up covers it. The dashboard opens a top-up dialog in place (a card-registration step for Korean-language organizations, since won cannot be topped up); API callers top up via Billing, or register a card via POST /api/org/{slug}/billing/krw/billing-key/issue, and retry.

If a monthly charge goes unpaid, the ordinary delinquency clock applies (3-day grace, then the plan drops to Free). At the end of that grace period every Strix database of the organization is suspended: the row is disconnected (isActive: false, lastTestError: "Suspended for non-payment") and the database role password is rotated, so existing connection strings stop working and compute idles to zero. Data is kept. Each suspended database is deleted 30 days later unless it has been reconnected (which requires passing the credit gate). Warnings go out by in-app notification and email to the organization’s owners and to whoever created each database: at suspension, 7 days before deletion, and 1 day before deletion, each stating the exact deletion time (UTC). Deletion emits appgroup.datasource.deleted with pd: true.

Always-On databases are checked every few hours as well: when the credit balance cannot cover the next day for the organization’s Always-On databases and auto top-up is off, each is switched back to a standard database that suspends when idle — the data is kept. The row then carries alwaysOnDowngradedAt; turn Always-On on again after topping up.

Every mutation publishes a sealed audit event (appgroup.datasource.connected / disconnected / reconnected / deleted / tested / primary_changed / updated / password_rotated / credential_revealed). Credentials never appear in events; deleted records whether the provider-side delete was confirmed (pd).