API Reference
The Overscore API powers query execution and dashboard deployment. All endpoints are served from overscore.dev.
Authentication
Credentials are read from request headers. Nothing is passed in the request body.
Authorization: Bearer <key>— a project API key (prefixos_). This is a local development detail.overscore devmints a short-lived key and injects it into the dev server for you. You never create, copy or paste one.X-OS-Access-Token— used in production. The Cloudflare worker that serves a deployed dashboard sends this header. The token comes from the signedos_accesscookie the Hub sets after verifying the viewer's session, so access follows the viewer's identity. A deployed bundle contains no key of its own.- Session cookie — set automatically when you sign in to the Hub via Google OAuth. Used by the Hub UI.
API keys are scoped to a project. A valid API key can execute any registered query within that project.
POST /api/query
Execute a registered query against BigQuery and return the results. This is the endpoint that the useQuery hook calls under the hood.
Request
POST https://overscore.dev/api/query
Content-Type: application/json
Authorization: Bearer os_abc123...
Body:
{
"project_slug": "acme",
"query_name": "revenue_by_month"
}
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| project_slug | string | Yes | Your project's unique slug |
| query_name | string | Yes | The registered query name to execute |
| force_refresh | boolean | No | Skip the cache and re-run the query against BigQuery |
| accept_arrow | boolean | No | Return uncached results as Arrow IPC instead of JSON |
Field names are snake_case. Sending camelCase keys returns a 400.
Response
Success (200):
{
"data": [
{ "month": "2025-01", "total_revenue": 52400 },
{ "month": "2025-02", "total_revenue": 61300 },
{ "month": "2025-03", "total_revenue": 58900 }
],
"query_name": "revenue_by_month",
"row_count": 3,
"trace_id": "b1e6c2a4-0f3d-4f6e-9a11-7c2d8e5f0a33"
}
| Field | Type | Description |
|-------|------|-------------|
| data | array | Array of row objects. Each key is a column name. |
| query_name | string | The query that was executed |
| row_count | number | Number of rows returned |
| trace_id | string | Request identifier, useful when reporting a problem |
When caching is enabled and the cache is fresh, the response has a different
shape: there is no data array. You get a pointer to the cached result and
fetch it yourself.
{
"cached": true,
"cache_url": "https://overscore.dev/cache/acme/sales-dashboard/revenue_by_month.arrow",
"query_name": "revenue_by_month",
"cached_at": "2025-06-15T12:00:00Z",
"cache_ttl_seconds": 3600,
"trace_id": "b1e6c2a4-0f3d-4f6e-9a11-7c2d8e5f0a33"
}
| Field | Type | Description |
|-------|------|-------------|
| cached | boolean | Always true on this branch |
| cache_url | string | Where to fetch the cached result |
| query_name | string | The query that was executed |
| cached_at | string | ISO 8601 timestamp of when the cache was written |
| cache_ttl_seconds | number | How long the cache stays fresh |
| trace_id | string | Request identifier |
@overscore/client handles both branches for you, so you only need this
distinction if you are calling the API directly.
Error responses
401 Unauthorized — the credential in the header was invalid:
{
"error": "Invalid API key"
}
When no credential is sent at all:
{
"error": "Not authenticated. Provide a Bearer token or sign in."
}
404 Not Found — query not registered:
{
"error": "Query \"revenue_by_month\" not found. Has it been registered?"
}
500 Internal Server Error — BigQuery execution failure:
{
"error": "Query execution failed",
"detail": "BigQuery error: Table not found: my-project.analytics.orders"
}
POST /api/deploy
Deploy a built dashboard to Overscore. This endpoint accepts a multipart upload containing the build artifacts.
Request
POST https://overscore.dev/api/deploy
Content-Type: multipart/form-data
Authorization: Bearer <device-token>
Form fields:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| dashboard_slug | string | Yes | The dashboard slug to deploy to |
| commit_message | string | No | Description of this deploy, shown in version history |
Build output is not sent as a single archive. Each file is its own form entry,
keyed by its path within dist/. The project is derived from your credentials,
not from a form field.
The Authorization header carries the device token saved by npx @overscore/cli auth login. The CLI handles this automatically.
Response
Success (200):
{
"success": true,
"dashboard_slug": "sales-dashboard",
"project_slug": "acme",
"file_count": 42,
"total_size_bytes": 245760,
"source_uploaded": true,
"url": "https://acme.overscore.dev/sales-dashboard/"
}
| Field | Type | Description |
|-------|------|-------------|
| success | boolean | Whether the deployment succeeded |
| dashboard_slug | string | The dashboard that was deployed |
| project_slug | string | The project it belongs to |
| file_count | number | Number of build files uploaded |
| total_size_bytes | number | Total size of the upload |
| source_uploaded | boolean | Whether source was uploaded alongside the build |
| url | string | The live URL of the deployed dashboard |
Error responses
401 Unauthorized — missing or expired credentials:
{
"error": "Authentication required. Run 'npx @overscore/cli auth login'."
}
403 Forbidden — the user is not an owner or editor of the project:
{
"error": "Editor role required"
}
400 Bad Request — a file is too large, or the upload exceeds the total limit:
{
"error": "File too large (>50MB): assets/report.parquet"
}
{
"error": "Total upload size exceeds 200MB limit"
}
Next steps
- Authentication — how CLI login and dashboard access tokens work in detail
- Data Caching — Parquet caching and DuckDB-WASM
- CLI Reference — the CLI commands that call these endpoints