Authentication
One bearer key, which resolves to one organization and carries the permissions that decide what it may read.
Every request carries a Subcore API key as a bearer token:
Authorization: Bearer sk_live_...There is nothing else to send. No organization id, no tenant header, no signed request. The key resolves to exactly one organization, and that organization scopes every result.
What the key decides
A key carries a permissions object. Two of its fields govern reads.
permissions.telemetry
Must be exactly true for any of the read endpoints to answer. A key
without it authenticates fine and is then refused with 403:
{
"error": "This API key cannot read telemetry",
"code": "FORBIDDEN",
"timestamp": 1788566031707
}Reading is stricter than writing. Ingestion accepts any truthy telemetry
value, because that is the check it has always run; reading requires the literal
true. A key that ships telemetry today is therefore not guaranteed to read it
back. If a key ingests but gets a 403 on a read, this is why.
permissions.cores
Which agents the key may see.
| Value | Effect |
|---|---|
["*"] | Every core in the organization. This is the default |
| Field absent | Every core in the organization |
["support-agent", "sales-agent"] | Only those two cores |
[] | No core is readable. Unscoped lists come back empty, naming a core is refused with 403, and a session or run you cannot reach is 404 |
A restricted key behaves differently depending on whether you name a core:
- Asking for a core on its list — filtered to that core, as you would expect.
- Asking for a core not on its list — refused with
403, not silently narrowed. An empty answer to "show me core b" would read as "core b had no traffic", which is a different and much worse thing to be told.
{
"error": "This API key cannot read core \"billing-agent\"",
"code": "FORBIDDEN",
"timestamp": 1788566031707
}- Asking for nothing in particular — confined to its list.
permissions.cores only takes a coreSlug argument on the two endpoints that
have one: /telemetry/sessions and /telemetry/events. The single-object
endpoints (/telemetry/sessions/{sid} and /telemetry/runs/{runId}) have no
core parameter, so a restricted key asking for an object outside its cores gets
a 404, not a 403 — the object is simply not visible to it.
Status codes
| Code | Meaning |
|---|---|
401 | No Authorization header, or the key is invalid or expired |
403 | The key is valid but lacks permissions.telemetry, or was refused a core outside permissions.cores |
404 | No such session or run in this organization — or none this key may read |
Both 401 bodies are distinct, which is useful when debugging a client:
{ "error": "Missing Authorization header", "code": "AUTHENTICATION_ERROR", "timestamp": 1788566031707 }{ "error": "Invalid or expired API key", "code": "AUTHENTICATION_ERROR", "timestamp": 1788566031707 }Checking a key
curl -i -H "Authorization: Bearer $SUBCORE_API_KEY" \
"https://api.subcore.ai/telemetry/sessions?limit=1"A 200 means the key authenticates and carries permissions.telemetry.
There is also a GET /telemetry/health endpoint, but read its answer carefully:
it validates the key and then checks the ingest permission, not the read
one. A 200 from it does not prove the key can read telemetry back. For that,
call one of the read endpoints as above.