OverviewAuthentication

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}$.

KeyRoleAccess
ak_...agencyFull read/write (CRUD) across every location linked to the agency. Required for all write operations.
sk_...viewerRead-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.

header
Authorizationstring

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.

query
location_idstring

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):

header
X-Dashboard-Tokenstring

A dashboard's public_token. The dashboard must have is_public = true.

header
X-Embed-Tokenstring

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.