Reader keys and audiences
A publish config's audience decides who, if anyone, needs a credential to read it. A reader key is the long-lived, SDK-holdable credential a restricted publication's readers use.
The four audiences
| Audience | Anonymous reads? | Grantable to |
|---|---|---|
public |
Yes — no credential at all. | — |
people |
No. | person principals (WorkOS user ids) |
agents |
No. | agent principals (vault agent-token slugs) |
people_agents |
No. | either kind |
For a restricted audience, a credential alone isn't enough — the resolved
principal must also hold an explicit read grant on that publish config
(vault_publish_grant, set via the publish config's grants array). A
wrong-kind credential (e.g. a person token against an agents-only audience)
is rejected even with a grant, because the grant itself is stored per
(principal_kind, principal_id).
Credentials the pub API accepts
Every non-public request is a Authorization: Bearer <credential>. The pub
API resolves the credential in this order:
- Reader key (
solr_<key_id>_<secret>) — a person principal, minted via the flow below. - Agent token — the same HS256 vault JWT
/api/mcpaccepts. It must be cloud-scoped and match the vault's currentagent_token_epoch(a rotated/revoked token stops working immediately). Because every vault currently issues one shared agent token with slug*, the token's ownvaultIdclaim is also checked against the publish's vault — an agent token minted for vault A can never unlock vault B's restricted publication, even if*is granted there too. - First-party session JWT (RS256, 1h) — the credential native Solomon clients hold.
Anything else (missing bearer, malformed token, expired/revoked token) is treated as anonymous, so a restricted publication just doesn't resolve for that caller.
Bearer-only, no cookies. The pub API deliberately never honors the
AuthKit session cookie, because it also sends
Access-Control-Allow-Origin: *. Since no ambient cookie authority ever
rides these requests, a malicious site fetching them cross-origin can only
see what a bare curl could — it can never piggyback a signed-in user's
session.
Minting a reader key
GET/POST /api/v1/auth/device— bootstraps an OAuth 2.0 Device Authorization Grant (RFC 8628) against Solomon's AuthKit: shows the caller auser_code+verification_urito open in a browser, then pollstoken_endpointuntil approved. Returns503withdevice_flow_unconfiguredif the deployment hasn't finished its AuthKit domain cutover — SDKs should surface "browser sign-in unavailable".- Trade the resulting WorkOS access token (or an existing first-party
session JWT) at
POST /api/v1/auth/keysfor a reader key. The full key (solr_<key_id>_<secret>) is returned exactly once — it's stored hash-only (SHA-256 of the secret), so losing it means minting a new one. Reader keys and agent tokens are themselves rejected at this endpoint (401): a read credential can never mint another credential. GET /api/v1/auth/keyslists your keys' metadata (id, prefix, label, timestamps) — never the secret.DELETE /api/v1/auth/keys/{keyId}soft-revokes one (revoked_atis stamped; the row stays listed).
A reader key currently only mints person principals — agents already carry their own vault-scoped agent token and use that directly against the pub API.
Fail-closed, always 404
Every failure mode on the read routes (/api/v1/pub/{slug},
.../notes/{path}, .../search) — unknown slug, disabled publish, missing
credential, invalid/expired/revoked credential, a valid credential with no
grant, or a valid credential of the wrong principal kind — returns the exact
same 404 {"error": "not_found"}. This is deliberate: a 403 would tell a
prober that a restricted publication exists at that slug even without
access. Only the auth-management routes (/api/v1/auth/*) return a normal
401 (with WWW-Authenticate), since those aren't guarding content
existence.
See also
- [[Publish a folder or vault]] — scopes, audiences, and the entitlement gate.
- [[The published API and SDKs]] — the endpoints these credentials unlock.
- [[TypeScript SDK quickstart]] — where a reader key plugs in as a
generated client's
apiKey.