Guide
Authentication
Bearer API keys, the two scopes, and what happens when a key is revoked mid-request.
Every route except GET /health requires a bearer API key:
Authorization: Bearer <key>Keys are verified with HMAC-SHA-256 against authoritative control-plane records on every request. There is no cached validation and no token exchange, so revocation and expiry take effect immediately.
Getting a key
Section titled “Getting a key”Keys are issued by an operator. There is no signup, no dashboard and no key management API yet — during the controlled staging phase, a key is created for a specific workspace with a specific scope and a short expiry, and revoked when the work it was issued for is done.
If you have a key, you were given the workspace and connection identifiers with it. You cannot discover them through the API.
Scopes
Section titled “Scopes”Two scopes exist, and they are genuinely separate — one does not imply the other.
| Scope | Grants |
|---|---|
query:read | POST /v1/query, GET /v1/metrics/ga4.sessions, POST /mcp, GET /v1/pilot/schedules/{scheduleId} |
sync:write | POST /v1/syncs, GET /v1/syncs/{runId}, all four POST /v1/pilot/... operations |
A sync:write key can trigger extraction but cannot read the resulting data. A
query:read key can read published data but cannot cause a provider call. Issue the
narrower one: a key that only ever reads should not be able to spend provider quota.
Using a key outside its scope returns 403 forbidden, not 401 — the key is valid,
the operation is not permitted.
Workspace binding
Section titled “Workspace binding”The key determines the workspace. workspaceId in a request body must equal the
key’s own workspace; a mismatch is 403 forbidden, never a cross-tenant read.
Workspace headers are ignored entirely. There is no header, parameter or body field that lets a caller act on a workspace other than its key’s.
Where authentication sits in the request
Section titled “Where authentication sits in the request”The two surfaces differ, which matters when you are reading a status code.
On the /v1 routes, the route match, the method check and the query-delimiter check
all run first. GET /v1/query is 405 and POST /v1/query?x=1 is 400, whether or
not your key is valid.
On /mcp, the pinned URL, Host and optional Origin are checked first, before
the authorization store is touched. A disallowed origin is therefore 403 even
when the request also carries a bad key. Authentication follows for requests that
reach that private boundary.
Revocation during a request
Section titled “Revocation during a request”Worth knowing if you are reasoning about what a leaked key could reach: authorization is checked before the backend read and again before the response is released.
If a key is revoked, expires, or is reassigned to another workspace while a query is
in flight, the request returns 401 and the data is discarded. A read that began
while the key was valid does not complete just because it started in time. The same
recheck applies to sync responses and to MCP tool results.
Pilot requests are authenticated again by the private sync Worker, then rechecked
before the gateway releases either a result or a scoped error. Pilot request bodies
do not accept workspaceId; the current key supplies it.
An enabled, versioned schedule and active source/destination grants authorize background work. The runtime does not save or replay the configuring API key. Disabling a schedule invalidates unfinished source and export ownership; requests already sent to Google cannot be recalled. See Pilot automation.
If Google returns a valid replacement refresh token immediately before that schedule authority is lost, the broker retains the encrypted replacement under its separate exact credential lease. It still discards the data response and does not start a later Google request for the denied schedule.
When authentication itself is unavailable
Section titled “When authentication itself is unavailable”If the authorization store cannot be reached, the original REST and MCP routes return
503 auth_unavailable; pilot routes return 503 automation_unavailable. An
unreachable key store never results in an unauthenticated read. Retry with backoff.
What errors do not tell you
Section titled “What errors do not tell you”Authentication failures return fixed messages. unauthorized does not distinguish
between a key that never existed, one that expired, and one that was revoked, and
error bodies never carry SQL, provider text, ciphertext or reflected header values.
This is deliberate, and it means the API cannot be used to probe which keys or workspaces exist. When debugging, work from the code and your own records rather than expecting the message to narrow it down.
Transport
Section titled “Transport”REST bodies must be application/json, at most 4 KiB, unencoded (Content-Encoding
other than identity is rejected).
Any URL query delimiter, including a bare ?, is rejected on every /v1 route
and on /mcp — keys and query payloads do not belong in the URL. Run and schedule identifiers do appear in
documented resource paths.
/health is the exception in mechanism rather than spirit: it matches only an exact
GET with no query delimiter; a delimiter or another method still matches that
deliberately opaque route and returns 404 not_found rather than 400 invalid_query.
Responses are no-store, nosniff and noindex, nofollow, and there is no CORS
policy: this is not a browser-callable API.