Use casesData teamsSelf-serve data07 / 12
How it fits together

Each team finds what it can use, and nothing else on the database

The data team publishes saved queries into collections. Each team's role grants its collections, read-only, with its own rate limit, so a key reaches exactly those queries and never the tables behind them. /catalog shows each team what it can call and on what terms, and a query filed into a collection is there on the next call.

0database passwords handed out
1role per team
1call to see what you can use
Your teams
The governed core
Collections↗
Your warehouse
Your data team↗
saves queries into collections
↗ Finance· BI tool key finance-reporting 200 · /catalog: 2 queries
↗ Marketing· people jwt:maria@example.com
→ marketing
403 connection_forbidden
↗ Ops· dashboard key ops-dashboard 403 connection_forbidden
The governed coreGET /catalog · GET /q/<name>↗

01 · Caller

A key, or a person's token. Either way, a team's role.

finance-reporting · finance 401

02 · Reach

Only the queries in the role's collections. Read-only, never ad-hoc SQL.

collection finance ✓ 403 connection_forbidden

03 · Terms

The role's rate limit and quota, and the query's cache.

600/hour · cache 300 s · MISS 429 rate_limited
↗ Who ran what
14:05 finance-reporting revenue_by_region 14:03 ops-dashboard open_tickets 14:02 jwt:maria@example.com campaign_spend
finance revenue_by_region · cache 300 s margin_by_product granted: role finance
marketing campaign_spend signups_by_channel granted: role marketing
ops open_tickets sla_breaches granted: role ops
↗

PostgreSQL

warehouse
orders products campaigns tickets payroll raw_events
On the same connection, in no published query: no team reaches them. orders.amt: Net revenue in EUR, refunds excluded source: databaseimported from the database's comments
What finance's /catalog saysonly what this key can run
{"queries": [{"name": "revenue_by_region", "collection": "finance", "cache_ttl": 300, "can_write": false}, {"name": "margin_by_product", "collection": "finance", "cache_ttl": null, "can_write": false}], "caller": {"name": "finance-reporting", "rate_limit": "600/hour"}}
yours, outside QueryAPIGate yours served refused A key made from a role copies it once; people who sign in use the role live.
Your teamsA key per team, or people signing inEach one a team's role: its collections, rate limit and quota.
The governed coreCaller → reach → termsOnly the queries in the role's collections, read-only. Anything else, a query outside them or ad-hoc SQL, is 403.
CollectionsWhat the data team publishedFiling a query into a collection puts it in every granted team's catalog at once, and the move is audited.
Your warehouseReached only through published queriesTables in none of them stay out of reach on the same connection.
/catalogWhat can I use, and on what termsEach query's description, parameters and cache, and the caller's own rate limit.

The same governed core as the full architecture.

For data and analytics leads

Self-serve data for every team, without handing out the database

Ten teams want data from the warehouse. Shared database passwords leak, and routing every request through the data team as a ticket doesn't scale. Publish saved queries in collections instead, give each team a role, and let people find what they can use.

✓A role per team: the collections it may run, its rate limit and quota. Keys are made from the role, or people sign in through your identity provider and are mapped to it.
✓A collection groups saved queries. Granting one is read-only and reaches exactly the queries in it, nothing else on the connection.
✓GET /catalog shows each caller what it can call and on what terms: description, parameters, whether it is cached or writable, and its own rate limit.
✓Descriptions imported from database comments say what each column means, and every run is attributed to the team that made it.
Self-serve data
# a role for the finance team: one collection, read-only, a budget
curl -X POST https://data.example.com/api/v1/roles \
  -H 'X-API-Key: <admin>' -H 'Content-Type: application/json' -d '{
  "name": "finance", "connections": [],
  "collections": ["finance"], "rate_limit": "600/hour"
}'

# what a finance key finds
GET /catalog
{"queries": [{"name": "revenue_by_region", "collection": "finance",
              "description": "Net revenue by region and month",
              "cache_ttl": 300, "can_write": false}, ...],
 "caller": {"name": "finance-reporting", "rate_limit": "600/hour", ...}}
More use casesAll twelve →

Not sure which fits?

The example APIs ship with a generated database and one scoped key per scenario, so you can try each of these in a few minutes without connecting anything of your own.

Run the examplesCompare alternatives