Data model
Shipped
Every Turbobase instance gives you a single, canonical data model that mirrors the records from each connected source. You read it over a normal Postgres connection or the auto-generated PostgREST API — the same surface your Turbobase app, Pilot (your AI assistant), and the MCP server all read through — and write it through a single validated write boundary that checks each change before it lands.
One canonical shape across sources
Section titled “One canonical shape across sources”Records flow in from each connected source (Oracle NetSuite, Stripe, QuickBooks, Shopify, and others) and are normalised into a shared, source-agnostic shape. A customer looks the same whether it arrived from NetSuite or Stripe, so you query one table rather than learning each source’s schema.
Each row carries the source it came from and the source-side identifier, so you can always trace a canonical row back to its origin. When the same real-world entity appears in more than one source, Atlas consolidates them — and your apps can read that consolidated identity directly, asking by any one of the source records and getting back every record behind it, a page at a time. See reading the identity graph from an app.
Raw and promoted tiers
Section titled “Raw and promoted tiers”Each record is stored in two complementary tiers:
- Raw — the full source payload is preserved on every row, so nothing the source sent is ever lost, even fields Turbobase doesn’t model.
- Promoted — the fields Turbobase canonicalises are surfaced as first-class typed columns you can query, filter, and join on directly.
Custom fields discovered in a source are promoted on top of this into their own cf_* columns, so a source-specific field becomes a real, queryable column rather than something you have to dig out of the raw payload.
Bidirectional vs read-only
Section titled “Bidirectional vs read-only”Record types fall into two classes:
- Bidirectional — record types you can create and edit from your instance. Writes never hit the tables directly: a raw PostgREST
INSERT/UPDATE/DELETEas an ordinary user is refused, and every change goes through a single validated write boundary that checks it first. Whether those writes are pushed back to the originating source is gated per source — write-back ships off and is turned on for a source on your instance when you’re ready (Stripe is the exception, always on). Until it’s on, a bidirectional type is effectively read-only upstream: your edits live in your canonical mirror but aren’t sent to the source. Once it’s on, a write you make is written back; if the source rejects it, you see the rejection as a sync failure rather than a silent drop. - Read-only — record types that mirror inbound source data without a write-back path.
Row-level security
Section titled “Row-level security”Access is enforced in the database by row-level security (RLS), not just in the app. RLS works on two axes, and one grant that runs the other way:
- Capabilities — a verb × record-type vocabulary like
read:sales_orderorwrite:invoicethat gates which operations a user may perform on which record types. - Scopes — generic dimensional restrictions (for example, by subsidiary or location) that limit which rows a user can see and touch.
- Content grants — the reverse of a scope, for message history: a role reads a channel’s history only when it names that channel exactly (see Content access).
Administrators bypass these checks; everyone else sees and writes only what their role and permissions allow. The same RLS fires no matter how the request arrives — the app, PostgREST, or MCP — so there is no privileged back door around it.