Authentication
Authenticate to the Builder API with an agency key (ak_) or viewer key (sk_), and use share tokens for public embeds.
Keys and roles
The Builder API uses two key types, both resolved server-side against Supabase. Keys match the pattern ^(ak_|sk_)[a-f0-9]{32,64}$.
| Key | Role | Access |
|---|---|---|
ak_... | agency | Full read/write (CRUD) across every location linked to the agency. Required for all write operations. |
sk_... | viewer | Read-only, scoped to the single location the key belongs to. |
Write endpoints (POST, PATCH, DELETE) require an agency key and reject viewer keys with 403. Read endpoints accept either.
Passing the key
Send the key as a bearer token.
Bearer token. Format: Bearer ak_... or Bearer sk_....
The one exception is the embed page: GET /builder?ak=ak_... takes the key in the query string, because a document request cannot send an Authorization header. Adding an agency-linked location key as a second parameter — GET /builder?ak=ak_...&sk=sk_... — scopes the embedded workspace to that location. Every JSON endpoint below requires the header.
Location scoping
A viewer key is bound to one location, so requests are automatically scoped. An agency key spans many locations, so location-scoped endpoints require a location_id query parameter identifying which linked location to act on.
The client location to scope the request to. Required with an agency key on location-scoped endpoints; the location must be linked to that agency. Ignored with a viewer key.
Validate a key
GET /validate returns the role and what the key can reach. Use it to bootstrap a session.
curl "https://mythic-analytics.gulp.workers.dev/builder/validate" \
-H "Authorization: Bearer ak_00000000000000000000000000000000"
An agency key returns its linked locations:
{
"role": "agency",
"agencyId": "8f3c…",
"agencyName": "Vision Labs",
"locations": [
{ "id": "acme-retail", "name": "Acme Retail", "domain": "acme-retail.com", "business_name": "Acme Retail Co.", "status": true, "api_metadata": {} }
],
"locationId": null,
"locationName": null
}
A viewer key returns its single location:
{
"role": "viewer",
"locationId": "acme-retail",
"locationName": "Acme Retail",
"apiMetadata": {}
}
List agency locations
GET /locations lists the locations linked to an agency key. It requires an agency key (403 otherwise). Secret keys in the response are masked (sk_xxxx…xxxx) — the Builder runs in the browser, and a sk_ key grants person-level reads on the data API, so full keys are only issued over server-side channels (the admin API or a rotate-secret endpoint).
{
"data": [
{ "id": "acme-retail", "name": "Acme Retail", "domain": "acme-retail.com", "business_name": "Acme Retail Co.", "status": true, "secret_key": "sk_a1b2…9f0e" }
]
}
Public embeds (share tokens)
POST /public/query runs before key auth and takes no ak_/sk_. Instead it accepts a share token that resolves to a saved insight; the server generates and executes the insight's SQL itself (raw SQL is not accepted on this endpoint):
A dashboard's public_token. The dashboard must have is_public = true.
An insight's embed_token. The insight must have is_embeddable = true.
Never expose an agency key (ak_) in client-side code, public repositories, or logs. For client-facing dashboards, publish the dashboard or insight and use its share token with POST /public/query.