Appearance
Data Relationships
See also: Identity and Access, Model Routing and API Behavior, Provider API Compatibility, Budgets and Spending, Observability and Request Logs, ADR: Identity Foundation for Users, Teams, and API Key Ownership, ADR: Route-Level Provider API Compatibility Profiles
This document is schema-oriented. It describes the persistent relationships that are hard to infer from a single file, but it does not try to restate every runtime rule owned by neighboring docs.
Source of Truth
- Migrations:
- Core types:
- Runtime behavior:
Core Entity Graph
teams1..Nteam_membershipsusers0..1team_membershipsapi_keysbelongs to exactly one ownerapi_keysN..Ngateway_modelsthroughapi_key_model_grants- Optional restriction overlays:
user_model_allowlistteam_model_allowlist
user_budgetsandteam_budgetseach allow one active budget per ownerusage_cost_eventsrecords request ownership, model attribution, pricing status, and computed costrequest_logsrecords the final user-visible request outcomerequest_log_payloadsstores sanitized request and response bodies separately from the summary rowpricing_catalog_cachestores normalized pricing snapshots used by runtime pricing resolutionmodel_pricingstores effective-dated pricing rows used for historical chargingusage_cost_event_duplicates_archivepreserves duplicate-ledger migration/archive context
Table Catalog
Foundation Tables
providers: upstream provider config and secret referencesgateway_models: gateway model registry; rows can be provider-backed or alias-backedmodel_routes: execution targets for provider-backed models onlyapi_key_model_grants: model grants attached to an API keyaudit_logs: control-plane audit baseline
model_routes stores two distinct route execution metadata documents:
capabilities_jsoncontrols whether the route may execute a requestcompatibility_jsoncontrols declared provider API compatibility transforms after route selection
capabilities_json includes API-family gates such as chat_completions, responses, and embeddings.
Compatibility metadata is not a provider config fallback and is not an extra_body convention.
Identity and Access Tables
teams- Key columns:
team_id,team_key,status,model_access_mode - Notes:
team_keyis the durable identifier;model_access_modeisall|restricted
- Key columns:
users- Key columns:
user_id,email,global_role,auth_mode,request_logging_enabled,model_access_mode - Notes: case-insensitive uniqueness is enforced through
email_normalized
- Key columns:
team_memberships- Key columns:
team_id,user_id,role - Notes: one-team-per-user is enforced by a unique
user_id
- Key columns:
oidc_providers- Key columns:
oidc_provider_id,provider_key,provider_type,issuer_url,client_id,enabled - Notes: the current schema supports
okta|generic_oidc
- Key columns:
user_password_auth- Key columns:
user_id,password_hash,password_updated_at
- Key columns:
user_oidc_auth- Key columns:
user_id,oidc_provider_id,subject,email_claim - Notes: unique
(oidc_provider_id, subject)
- Key columns:
user_oauth_auth- Key columns:
user_id,oauth_provider,subject
- Key columns:
user_oidc_links- Purpose: pre-provisioned relationship between a user and the OIDC provider they are allowed to activate against
Authorization Overlay Tables
user_model_allowlist- Relationship:
user_id+model_id
- Relationship:
team_model_allowlist- Relationship:
team_id+model_id
- Relationship:
Ownership, Accounting, and Logging Tables
api_keys- Key columns:
id,public_id,secret_hash,owner_kind,owner_user_id,owner_team_id - Constraint: exactly one owner column must be set consistently with
owner_kind - Reserved ownership: seeded system-owned keys use the reserved
system-legacyteam
- Key columns:
user_budgets- Key columns:
user_budget_id,user_id,cadence,amount_10000,hard_limit,timezone,is_active - Constraint: one active user budget per user
- Key columns:
team_budgets- Key columns:
team_budget_id,team_id,cadence,amount_10000,hard_limit,timezone,is_active - Constraint: one active team budget per team
- Key columns:
usage_cost_events- Key columns:
usage_event_id,request_id,ownership_scope_key,api_key_id,user_id,team_id,actor_user_id,model_id,provider_key,upstream_model,pricing_status,unpriced_reason,pricing_row_id,pricing_provider_id,computed_cost_10000,provider_usage,occurred_at - Notes: this is the canonical spend ledger used for enforcement and reporting
- Key columns:
request_logs- Key columns:
request_log_id,request_id,api_key_id,user_id,team_id,model_key,resolved_model_key,provider_key,caller_service,caller_component,caller_env,status_code,metadata_json,occurred_at - Notes: one summary row per final request outcome;
metadata_json.payload_policyrecords the capture mode and limits used for the row when request logging is enabled
- Key columns:
request_log_payloads- Key columns:
request_log_id,request_json,response_json - Notes: summary and payload are intentionally split; rows exist only when the payload policy captures redacted payloads
- Key columns:
request_log_tags- Key columns:
request_log_id,tag_key,tag_value - Notes: bounded bespoke caller tags for request-log filtering and attribution
- Key columns:
Pricing Catalog Cache
pricing_catalog_cache- Key columns:
catalog_key,source,etag,fetched_at,snapshot_json - Notes: runtime uses the cached snapshot together with the vendored fallback in the repo
- Key columns:
model_pricing- Key columns:
model_pricing_id,pricing_provider_id,pricing_model_id,effective_start_at,effective_end_at - Notes: effective-dated pricing rows are the durable historical charging source
- Key columns:
usage_cost_event_duplicates_archive- Purpose: preserves duplicate-ledger rows during pricing/ledger migration cleanup and audit backfill flows
Authorization Semantics
Effective model access is the intersection of:
- API key grants from
api_key_model_grants - Team allowlist, only when
teams.model_access_mode='restricted' - User allowlist, only when
users.model_access_mode='restricted'
If neither the team nor the user is restricted, grants remain unchanged.
Budget and Pricing Notes
- Pricing lookup comes from the internal pricing catalog layer, not from provider
/v1/modelsresponses - Supported pricing source ids in this slice are
openai,google-vertex, andgoogle-vertex-anthropic openai_compatproviders must declarepricing_provider_idgcp_vertexderives pricing source from theupstream_modelpublisher prefix- Pricing is exact-only in this slice; unsupported billing modifiers, unsupported publisher/location combinations, and unknown model ids resolve as
unpriced - Chargeable requests write usage ledger rows and participate in budget enforcement
- Unpriced requests are not charged and must not be budget-blocked
- Supported budget cadence values are
daily|weekly|monthly - Current UTC window semantics:
- Daily windows start at
00:00:00 UTC - Weekly windows start at
Monday 00:00:00 UTC - Monthly windows start at the first day of the month at
00:00:00 UTC Sunday 23:59:59 UTCremains in the previous weekly window
- Daily windows start at
- Budget threshold alerts persist audit rows plus per-recipient delivery rows and currently deliver owner-only email alerts when remaining budget crosses to
20%or less - Team attribution remains
actor:nonetoday:- team key + acting user context attribution is still deferred
- team key without acting user context is attributed to team only
Requested vs Resolved Model Identity
gateway_modelscan either point directly to provider routes or alias another modelrequest_logs.model_keystores the requested gateway modelrequest_logs.resolved_model_keystores the canonical execution model after alias resolution
This distinction matters for admin-facing observability and historical debugging. See model-routing-and-api-behavior.md.
Route Viability Note
Schema alone does not determine whether a model can execute.
Operational viability also depends on:
- provider existence
- route
enabledstate - positive route weights
- capability filtering
Those rules are owned by configuration-reference.md and model-routing-and-api-behavior.md.
Ownership Notes
- User-owned and team-owned API keys share the same
api_keystable - Team-owned usage and request logs can exist without an acting user
- Current team spend attribution remains
actor:noneat the ownership-scope level
That ownership model is explained operationally in identity-and-access.md and budgets-and-spending.md.
PostgreSQL and libsql Parity
Both runtime backends are expected to stay logically aligned for:
- schema shape
- migrations
- seed behavior
- aliases and request-log model identity
- spend ledger behavior
- request-log summary and payload persistence
See ../crates/gateway-store/README.md for the storage-layer overview.