Orion API
Query your governed metrics from anywhere โ a single REST endpoint takes a natural language question and returns a governed answer as prose plus structured JSON.
Why use it: Query your governed metrics from a Slack bot, a mobile app, an internal tool, or a Google Sheet โ without rebuilding your semantic layer anywhere else.
Where to find it: Generate keys in Lumen โ API Access. Call the API at
https://app.celiq.co/api/v1.Who can use it: Any workspace with an API key. Keys are workspace-scoped.
The Orion API turns Celiq into headless analytics. Instead of opening the app, you POST a question to a single endpoint and get back the same governed answer Orion would give you in chat โ grounded in your reveals, with provenance and freshness attached. One API call returns a natural language answer and the structured data behind it.
This is the same Orion you use inside Celiq. It respects the same governed reveals, the same metric definitions, and the same workspace memory. The difference is that the answer comes back as JSON you can parse, store, or forward.
How it differs from the REST API
Celiq has two APIs, and they serve different jobs:
| Orion API (this page) | REST API | |
|---|---|---|
| Base URL | https://app.celiq.co/api/v1 | https://api.celiq.co/v1 |
| Input | A natural language question | Structured query (node, dimensions, measures) |
| Output | Prose answer + data + provenance | Raw query results |
| Auth header | Authorization: Bearer celiq_sk_... | Authorization: Bearer <key> |
| Best for | Bots, embedded Q&A, Sheets, ad-hoc questions | Programmatic dashboards, model management, sync |
Use the Orion API when you want a governed answer to a question. Use the REST API when you already know exactly which fields you want and just need the rows.
Authentication
All Orion API requests require an API key in the Authorization header:
Authorization: Bearer celiq_sk_your_key_hereKeys are workspace-scoped โ a key only ever sees the reveals and data in the workspace it was created in. There is no separate IP allowlist; access is controlled entirely by the key.
Treat an API key like a password. Anyone with the key can query your governed metrics. Store keys in environment variables or a secret manager โ never commit them to source control or paste them into client-side code.
Creating a key
Open API Access in Lumen
Go to Lumen โ API Access. You'll see the list of existing keys (by prefix only) and a + Create API Key button.
Name the key
Give the key a descriptive name like production-key or slack-bot. The name only helps you identify it later โ it has no effect on permissions.
Copy the key once
Celiq shows the full key โ celiq_sk_... โ exactly once, with a Copy button and the warning "This key will not be shown again." Copy it immediately and store it somewhere safe.
Use the prefix to identify it later
After you close the dialog, only the prefix (e.g. celiq_sk_abc...) is ever shown again, alongside the last-used time. If you lose the full key, revoke it and create a new one.
Managing keys
The API Access panel lists every key with its prefix, last-used time, and a Revoke button. Revoking a key takes effect immediately โ the next request using it returns 401. Keys can also carry an optional expiry; an expired key behaves exactly like a revoked one.
Key management itself happens through your authenticated Lumen session (cookie auth), not through the Orion API. The Orion API endpoints (/api/v1/*) are reachable only with a celiq_sk_ key, and the key endpoints (/api/keys) are reachable only from a signed-in session.
Base URL
https://app.celiq.co/api/v1All endpoints below are relative to this base URL.
POST /api/v1/ask
Ask Orion a data question. This is the primary endpoint and the one you'll use most.
POST /api/v1/ask{
"question": "what was revenue last week?",
"domain": "ecommerce_bigquery"
}| Field | Type | Required | Description |
|---|---|---|---|
question | string | Yes | The question in plain English, exactly as you'd type it in Orion chat. |
domain | string | No | The domain to scope the question to (e.g. ecommerce_bigquery). If omitted, Orion picks the most relevant domain automatically. |
curl -X POST https://app.celiq.co/api/v1/ask \
-H "Authorization: Bearer celiq_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"question": "what was revenue last week?"}'
{
"answer": "Revenue last week was $284,000, up 11% from the prior week. The increase was driven mostly by the Home & Garden category.",
"data": {
"rows": [
{ "week": "2026-06-01", "revenue": 284000 },
{ "week": "2026-05-25", "revenue": 255800 }
],
"columns": ["week", "revenue"],
"metric": "revenue",
"value": 284000,
"period": "last_week"
},
"provenance": {
"source": "semantic layer",
"reveal": "orders_analysis",
"reveal_backed": true,
"freshness": "2026-06-12",
"confidence": "high"
},
"usage": {
"tokens_used": 847,
"latency_ms": 1240
}
}Response fields
| Field | Description |
|---|---|
answer | The natural language answer โ the same prose Orion shows in chat. |
data.rows | The raw result rows behind the answer. |
data.columns | Column names in the order they appear in each row. |
data.metric | The primary metric the question was about, if Orion identified one. |
data.value | The headline numeric value for that metric. |
data.period | The time period the answer covers (e.g. last_week, last_30_days). |
provenance.source | semantic layer when a governed reveal answered the question, or raw SQL when Orion fell back to ad-hoc SQL. |
provenance.reveal | The reveal that produced the answer (only when source is semantic layer). |
provenance.reveal_backed | true if the answer used a governed reveal; false for raw-SQL fallback. |
provenance.freshness | The date of the most recent data in the result. |
provenance.confidence | high for governed reveals, medium for raw-SQL approximations. |
usage.tokens_used | LLM tokens consumed by this request. |
usage.latency_ms | Server-side latency in milliseconds. |
Always check provenance.reveal_backed before trusting a number programmatically. true means the answer used a metric definition your team wrote in CML. false means Orion approximated the metric from your schema โ fine for exploration, but verify the definition before wiring it into a report.
GET /api/v1/reveals
List the reveals available in your workspace. Use this to discover what you can ask about before sending a question.
GET /api/v1/revealscurl https://app.celiq.co/api/v1/reveals \
-H "Authorization: Bearer celiq_sk_your_key_here"
{
"reveals": [
{
"name": "orders_analysis",
"domain": "ecommerce_bigquery",
"description": "Revenue, orders, and AOV by day, channel, and category.",
"metrics": ["revenue", "order_count", "aov"]
},
{
"name": "orders_customers",
"domain": "ecommerce_bigquery",
"description": "Customer acquisition and retention metrics.",
"metrics": ["cac", "ltv", "new_customers", "return_rate"]
}
]
}GET /api/v1/metrics/:reveal_name
Return the metric definitions for a single reveal. Use this to understand exactly how a metric is calculated before you ask about it.
GET /api/v1/metrics/orders_customerscurl https://app.celiq.co/api/v1/metrics/orders_customers \
-H "Authorization: Bearer celiq_sk_your_key_here"
{
"reveal": "orders_customers",
"domain": "ecommerce_bigquery",
"metrics": [
{
"name": "cac",
"label": "Customer Acquisition Cost",
"description": "Marketing spend divided by new customers acquired.",
"unit": "dollars"
},
{
"name": "return_rate",
"label": "Return Rate",
"description": "Returned orders as a share of total orders.",
"unit": "percent"
}
]
}Rate limits
Each API key is limited to 100 requests per hour by default. The limit is enforced per key, not per IP address โ so spreading calls across multiple machines using the same key does not raise the ceiling.
When you exceed the limit, the API returns 429 Too Many Requests. Wait until the start of the next hourly window and retry. If you need a higher limit, contact us.
Rate limits are per key. If you have separate keys for production and a Slack bot, each gets its own 100-requests-per-hour budget. This is a good reason to issue one key per integration rather than sharing a single key everywhere.
Errors
All errors are returned as JSON with an error field and the appropriate HTTP status code.
{
"error": "Invalid or revoked API key."
}| Code | Meaning |
|---|---|
400 | Bad request โ the body is missing question or is malformed JSON. |
401 | Missing, invalid, revoked, or expired API key. |
403 | The key is valid but does not have access to the requested resource. |
404 | Reveal not found (for /metrics/:reveal_name). |
429 | Rate limited โ more than 100 requests in the current hour for this key. |
500 | Internal error. Retry; if it persists, contact support. |
Code examples
curl
curl -X POST https://app.celiq.co/api/v1/ask \
-H "Authorization: Bearer celiq_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"question": "what was revenue last week?", "domain": "ecommerce_bigquery"}'
Python
import os
import requests
API_KEY = os.environ["CELIQ_API_KEY"]
resp = requests.post(
"https://app.celiq.co/api/v1/ask",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"question": "what was revenue last week?"},
timeout=30,
)
resp.raise_for_status()
result = resp.json()
print(result["answer"])
Only trust the number programmatically if it came from a governed reveal.
prov = result["provenance"]
if prov["reveal_backed"]:
print(f"{result['data']['metric']} = {result['data']['value']} "
f"(reveal: {prov['reveal']}, as of {prov['freshness']})")
else:
print("Warning: answer used raw SQL, not a governed reveal.")
JavaScript / Node.js
const API_KEY = process.env.CELIQ_API_KEY;
const resp = await fetch("https://app.celiq.co/api/v1/ask", {
method: "POST",
headers: {
"Authorization": Bearer ${API_KEY},
"Content-Type": "application/json",
},
body: JSON.stringify({ question: "what was revenue last week?" }),
});
if (!resp.ok) {
throw new Error(Celiq API error ${resp.status}: ${await resp.text()});
}
const result = await resp.json();
console.log(result.answer);
if (result.provenance.reveal_backed) {
console.log(${result.data.metric} = ${result.data.value});
}
Google Sheets (Apps Script)
Add a custom =ASK_ORION(...) function to any sheet. Open Extensions โ Apps Script, paste the code below, then store your key in Project Settings โ Script Properties as CELIQ_API_KEY.
/**
* Ask Orion a question and return the headline value.
* Usage in a cell: =ASK_ORION("what was revenue last week?")
*/
function ASK_ORION(question) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty("CELIQ_API_KEY");
const resp = UrlFetchApp.fetch("https://app.celiq.co/api/v1/ask", {
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer " + apiKey },
payload: JSON.stringify({ question: question }),
muteHttpExceptions: true,
});
const result = JSON.parse(resp.getContentText());
if (resp.getResponseCode() !== 200) {
return "Error: " + (result.error || resp.getResponseCode());
}
// Return the numeric value if there is one, otherwise the prose answer.
return result.data && result.data.value != null
? result.data.value
: result.answer;
}
For a richer, no-code Sheets experience, see the Celiq for Sheets add-on, which wraps this API in a sidebar.
Best practices
- One key per integration. Issue a separate key for each app, bot, or environment so you can revoke one without breaking the others โ and so each gets its own rate-limit budget.
- Store keys in secrets, not code. Use environment variables or a secret manager. Never embed a key in client-side JavaScript or a public repository.
- Check
reveal_backedbefore trusting numbers. Amedium-confidence, raw-SQL answer is fine for exploration but should be verified before it drives a report or alert. - Scope with
domainwhen you can. Passing thedomainmakes routing faster and more accurate, especially in workspaces with many reveals. - Cache where it makes sense. If many users ask the same question, cache the response for a few minutes rather than burning rate-limit budget on identical calls.
Limitations
- Read-only. The Orion API answers questions and lists reveals and metrics. It cannot create reveals, edit your model, or write data.
- Reveal-first, with raw-SQL fallback. When no governed reveal matches, Orion may answer with raw SQL at
mediumconfidence. Checkprovenanceto tell the difference. - English answers. Questions can be asked in any language, but
answerprose is returned in English. - No streaming. Responses are returned as a single JSON payload once the answer is complete, not as a token stream.
- Rate limited. 100 requests per hour per key by default.