centic

REST API

Read-only access to the Centic data lake for API clients. Same query pipeline as the MCP server: PII is auto-obfuscated, only SELECT/WITH queries are permitted, and every request is logged to the restaurant's query log.

Machine-readable version: /docs/api.md · LLM index: /llms.txt

Authentication

All endpoints require an API key sent as a Bearer token in the Authorization header. Ask a Centic admin to issue one for you via /admin/api-keys.

Authorization: Bearer oh_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Keys are issued with either global scope (access to every restaurant, including any created later) or restaurant scope (access to an explicit list of restaurants). Requests to a restaurant outside the key's scope return 403.

Endpoints

MethodPathPurpose
GET/api/v1/restaurantsList restaurants the key can access.
GET/api/v1/restaurants/:id/schema/tablesList public-schema tables with row estimates.
GET/api/v1/restaurants/:id/schema/tables/:name/columnsColumn names, types, PII flag, and BSM label/description if available.
GET/api/v1/restaurants/:id/schema/bsmBrand Semantic Map: every annotated field with label/description/unit/source.
POST/api/v1/restaurants/:id/queryRun a SELECT. Body {"sql": "..."}. Results capped at 10,000 rows; 30s timeout.
POST/api/v1/restaurants/:id/query/validateValidate a SELECT without executing (via EXPLAIN).

Read-only guarantees

Four layers stop writes:

  1. SQL validator: rejects anything but a single SELECT/WITH.
  2. Postgres role: the connection uses a read-only DB user.
  3. Per-transaction SET LOCAL default_transaction_read_only = on.
  4. Statement timeout of 30 seconds.

Query response shape

{
  "columns": ["order_id", "guest_email", "total"],
  "rows": [
    ["4a1c...", "j***@e.com", 42.10],
    ["9b2d...", "s***@m.com", 88.00]
  ],
  "row_count": 2,
  "truncated": false,
  "obfuscated_fields": ["guest_email"],
  "duration_ms": 74
}

Rows are returned as column-ordered arrays (not objects). Look up positions in columns. Any field annotated as PII in the restaurant's bsm_metadata is auto-masked and listed in obfuscated_fields.

Example: run a query

curl -X POST https://centic.ai/api/v1/restaurants/cmmnwg8wf0000e8sx27g8m2hi/query \
  -H "Authorization: Bearer oh_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT business_date, SUM(total) FROM brink_orders GROUP BY 1 ORDER BY 1 DESC LIMIT 7"}'

Error format

{ "error": "restaurant_not_in_scope", "message": "API key does not have access to this restaurant" }

Common codes: missing_bearer_token, invalid_key, revoked_key, expired_key, restaurant_not_in_scope, invalid_query, query_timeout, query_failed, no_data_lake.