Schema reference
The tables and columns behind the freeform query tools — schema families, the canonical column pattern, and how PII redaction works per permission level.
This is the data model the freeform query tools (execute_query, build_query) read. It documents the schema families and the shape of the core tables. For exact, live column lists, use list_tables and get_table_schema against your own studio — they are the source of truth, and this page mirrors them.
Every studio's database carries the same shape, populated from whichever connectors it has. A table is empty, not absent, if you haven't connected a source that feeds it.
How the model is organised
Tables live under a small set of schemas, all on the default search_path:
people
Members, staff, companies, locations, identity links
commerce
Sales, payments, refunds, plans, products
bookings
Class sessions, attendance, appointments, facility entries
accounting
Chart of accounts, journals, entries, invoice lines
marketing
Ad accounts, campaigns, daily spend, attribution links
comms
Messages and attachments
graph
Relationship edges/nodes, affinity, member & location resilience, vulnerability quadrants, at-risk signals (derived)
compliance
Compliance records
ingest
raw_record — the verbatim landing zone before transform
mcp
The AI-collaboration layer (skills, memory, knowledge, artifacts, business context, ignition state) and ingest bookkeeping
The canonical column pattern
Most fact tables are source-stamped and event-sourced, so they share a common envelope of columns:
id
Canonical UUID for the row.
source
The connector that produced it (e.g. com.stripe, com.wix).
source_external_id
The provider's own id for the record.
site_id
The location/sub-tenant within the studio, if any.
occurred_at
When the event actually happened (not when we received it).
source_extras
jsonb — provider fields we kept verbatim but didn't promote to columns.
source_modified_at
When the provider last changed it.
created_at / updated_at
When Kula first/last wrote the row.
ingest_event_id
The idempotency key from the ingest envelope.
This means you can always trace a canonical row back to the exact provider record it came from.
PII redaction and the _guarded companions
_guarded companionsMany base tables have a _guarded companion (for example, commerce.sale and commerce.sale_guarded). The guarded view applies redaction based on your token's permission level — hiding member names, contact details, or payment fragments you aren't entitled to. Redaction happens server-side, so a lower level can't be tricked into revealing more.
Prefer the guarded companion (or the purpose-built read tools like get_member_payments and get_member_plan_status, which honour scoping automatically) when you want redaction handled for you.
Hide the books. A connector can also be set to withhold the whole
accountingschema — chart of accounts, journals, entries, invoice lines — regardless of its permission level. A query against anyaccountingtable (including its_guardedview) then returns a clear denial, whilecommerce(sales, payments, refunds) stays readable. This is the orthogonal "hide the books" restriction — see Permission levels.
Core tables
people.member
people.memberOne row per member. Key columns:
id, first_name, last_name, display_name, email, mobile_phone, status, membership_type, plan_name, current_plan_id, is_booking_suspended, suspension_start_date, suspension_end_date, member_since, first_session_date, home_location_id, home_location_name — plus the canonical envelope columns.
mobile_phone, and the suspension dates redact below theadminlevel; names redact belowoperations. See permission levels.
commerce.sale
commerce.saleOne row per sale line (refunds are negative companions in commerce.refund). Key columns:
id, occurred_at, amount, tax, total, discount, currency, quantity, item_type, item_id, item_name, item_category, description, member_id, staff_id, location_id, payment_method, payment_status, payment_provider, provider_transaction_id, transaction_id, transaction_status, payment_last4, reason_code, billing_email, is_recurring, installments — plus the envelope columns.
Amounts and classification are visible at
operations;billing_emailatadmin;payment_last4and provider transaction ids only atfull.
bookings.attendance
bookings.attendanceOne row per booking/visit. Key columns:
id, class_session_id, member_id, occurred_at, booking_status, booked_at, checked_in_at, cancelled_at, cancellation_window_minutes, sale_source_external_id, plan_source_external_id — plus the envelope columns.
sale_source_external_idandplan_source_external_idare what the diagnostic uses to connect each attendance to the plan and sale behind it.
The graph schema (derived)
graph schema (derived)The graph.* tables are a derived layer, rebuilt from people, bookings and commerce — they hold no source data of their own. See Graph & relationship tools for what the model means; the tables below are what you query directly. Person/location keys are loose foreign keys to people.member.id / people.location.id.
graph.member_resilience
graph.member_resilienceOne row per (member, location). The retention model.
person_id, location_id, w_ml, w_peers, max_w_ms, resilience, activity_affinity, contract_commitment, resilience_constrained, vuln_quadrant (resilient_enthusiast | constrained_captive | flexible_explorer | specialist_nomad), computed_at.
resilience_constrainedis the headline score;vuln_quadrantis the activity-affinity × contract-commitment taxonomy.
graph.member_affinity
graph.member_affinityOne row per member — the routing rollup.
person_id, primary_staff_id, top3_staff_ids, staff_concentration_pct, primary_edge_weight, primary_edge_tier, computed_at.
graph.staff_concentration
graph.staff_concentrationOne row per staff member. primary_member_count is both routing value and defection risk.
staff_id, primary_member_count, total_member_edges, avg_edge_weight, computed_at.
graph.location_resilience
graph.location_resilienceOne row per location — operational resilience.
location_id, ops_resilience, cover_depth, staff_count, single_points_of_failure, computed_at.
graph.risk_signal
graph.risk_signalThe at-risk queue. The open queue is WHERE resolved_at IS NULL.
id, person_id, node_id, signal_type (drift | freq_softening | missed_second_visit | milestone | pause_drift | at_risk_threshold), severity (low | medium | high), detected_at, context (jsonb — supporting facts and routing), resolved_at, resolution.
graph.node and graph.edge
graph.node and graph.edgeThe underlying entities and relationships. graph.node carries one row per entity (entity_type, entity_id, display_label); graph.edge carries the weighted relationships (from_node_id, to_node_id, kind, edge_class MS/ML/SL/MM/SS, weight, tier, last_observed_at, status). Prefer the rollups above and get_entity_edges for most questions.
Getting the full, live picture
This page documents the shape; your studio's database is authoritative.
Because the guarded views and the read tools enforce scoping, you can query freely with execute_query (SELECT/WITH only) without having to track the column-level PII map yourself.
Last updated
Was this helpful?