Auth & RBAC
SemiLayer uses a layered security model: API keys authenticate the tenant (which environment you're operating against), and optional user JWTs enforce per-row access rules. Together they support everything from a simple public read to complex row-level security.
Easiest path: mint, list, revoke, and rotate keys from console.semilayer.com → your env → API Keys. Plaintext is shown exactly once on creation, then only the prefix + last 4 chars survive — copy it to your secret store immediately. The CLI mirrors the same surface for scripted setups.
API Key Types
Every key is prefixed so you can spot the type at a glance and grep for accidental leaks. The general format for env-scoped keys is <prefix>_<env-slug>_<random> (e.g. pk_production_a1b2…); runner keys are rk_<org-slug>_<random>.
| Prefix | Scope | Can do |
|---|---|---|
sk_<env>_… | Environment (server-side) | Full read + write across the env. Bypasses lens-level rules. Backend only — never ship to a browser. |
pk_<env>_… | Environment (browser-safe) | Subject to lens-level access rules. Safe to bundle in frontends. |
ik_<env>_… | Environment (write-only) | Ingest webhook only (POST /v1/ingest/:lens). Cannot query, search, similar, or stream. Auto-created per env. |
rk_<org>_… | Organization (runner) | Used only by the self-hosted runner to authenticate its outbound WebSocket to runner.semilayer.com. Not a query credential — issuing a request with one will be rejected. |
Secret keys (sk_) bypass all lens-level access rules. Use them server-to-server. Never put them in a browser bundle.
Public keys (pk_) enforce every access rule declared on the lens. They're the right choice for frontend code, customer-facing widgets, and anywhere the user-side JWT (X-User-Token) is also being passed for row-level security.
Ingest keys (ik_) are write-only and limited to the ingest webhook. Use them in upstream systems that fan out change events to SemiLayer.
Runner keys (rk_) are org-scoped and live only on the runner machine. Hashed (SHA-256 with a per-token-class pepper) on our side; rotation takes effect at the next runner heartbeat (~25s).
All four types are managed from Console → API Keys (env-scoped) and Console → Runners (rk_), or via the CLI:
Access Rules
Access rules are declared per-Lens in sl.config.ts under grants. They gate search,
similar, query, and subscribe operations.
Rule Types
'public' — No authentication required. Requests with pk_ keys and no user token
are allowed.
'authenticated' — A valid user JWT (X-User-Token header or ?userToken=) is required.
The JWT is validated against your JWKS endpoint.
ClaimCheck — The user JWT must contain specific claims.
Function rule — Arbitrary logic. Returns true (allow), false (deny), or
{ filter: {...} } (allow with a metadata filter applied to results).
Function rules are serialized and stored in the database. They are evaluated server-side on every request. Only use serializable logic (no closures over external variables).
Per-Operation Rules
You can set different rules for different operations:
query is disabled by default and must be explicitly enabled. All other operations
default to 'public' when no grant is set.
Dual-Token Pattern
SemiLayer uses a dual-token pattern that separates the tenant identity (API key) from the end-user identity (JWT). This lets you use a single API key per environment while enforcing per-user access rules.
Your auth provider issues the user JWT (Auth0, Clerk, Supabase Auth, your own system — anything that implements OIDC). Configure the JWKS URL once per Environment, and SemiLayer validates every user token against it.
Configure JWKS
In sl.config.ts:
BeamClient — Attaching a User Token
When using the generated Beam client, call withUser on the BeamClient instance inside
Beam, then pass it to a fresh Beam construction. Or use BeamClient directly:
Row-Level Security
Access rule functions can return a filter object that is merged into every query's
metadata WHERE clause. This restricts results to rows the user is allowed to see.
Now any search request returns only records where metadata.ownerId === claims.sub.
The filter is applied server-side and cannot be bypassed by the client.
Combined with the query operation:
RBAC Decision Flow
Frontend Safety Checklist
- Use
pk_keys in browser-side code, neversk_ - Declare explicit
grantsfor every operation you expose publicly - Set
minScoreon search to prune low-relevance results client-side - Use
ik_keys in ingest webhook callers — they cannot query your data - Never log or expose
sk_orik_keys in client-side bundles