API keys & security
Every project has two keys. The publishable key is safe to ship in your client app; the secret key is for your own servers and bypasses all security policies. Which key a request carries decides the database role it runs as — but the real security boundary is your policies, not key secrecy.
The two keys
Open your project dashboard to find both. Each is a long token with a visible prefix so you can tell them apart at a glance (and spot “did I just commit the admin key” in code review).
| Publishable / anon | Secret / service |
|---|---|
| ich_pub_… | ich_admin_… |
Role anon (or authenticated once a user logs in) | Role service_role |
| RLS / policies enforced | RLS / policies bypassed |
| Safe to ship in client apps | Server-side only — never ship to a client |
Used via @ichibase/client | Used via the JSR SDKs (@ichibase/postgrest, …) |
ich_pub_ in a browser, mobile, or native app is expected and fine, as long as the tables it can reach have policies. The ich_admin_ key is the opposite: it skips every policy, so a leak is a full data breach.How a request is authenticated
A request carries the key in an Authorization header. The key is a wrapped JWT; the ich_pub_ / ich_admin_ prefix is a visible class marker and is not part of the JWT. The gateway strips the prefix and forwards the bare token, which encodes the role claim:
Client sends → Database runs as
Authorization: Bearer ich_pub_<jwt> → role anon
Authorization: Bearer ich_admin_<jwt> → role service_role
Authorization: Bearer <user access_token> → role authenticated (auth.uid() = that user)At issuance the prefix and the role claim are locked together: a ich_pub_ key must claim role: anon and a ich_admin_ key must claim role: service_role— a mismatched token is rejected, so a leaked signing-key half can't be used to forge an admin token from a public one.
After a user logs in, your client keeps sending the project key as the apikey andadditionally sends that user's short-lived access token (a JWT) as the Bearer. The request then runs as authenticated with their identity, so auth.uid() is populated inside your policies. No extra call is needed — the SDK attaches it automatically.
Where each key goes
Client apps use the anon key through @ichibase/client. That SDK is built for untrusted environments, so by design it refuses an ich_admin_ key — a guard rail against accidentally shipping the secret key in a bundle.
Servers, edge / functions code, and admin tools use the secret key through the JSR SDKs (@ichibase/postgrest and friends). Because service_role bypasses RLS, run these only where the key stays private.
// CLIENT app — anon key via @ichibase/client.
// Access is controlled by your RLS policies, not by hiding the key.
import { createClient } from '@ichibase/client';
const ichi = createClient(
process.env.NEXT_PUBLIC_ICHIBASE_URL!, // https://<project>.ichibase.net
process.env.NEXT_PUBLIC_ICHIBASE_ANON_KEY!, // ich_pub_…
);
// Passing an ich_admin_ key here throws — @ichibase/client refuses it.
const { data } = await ichi.from('posts').select('*');
// Server-side instead? Use the JSR service SDK with the ich_admin_ key:
// import { PostgREST } from '@ichibase/postgrest'; // bypasses RLS — keep secretThe real boundary is your policies
Key secrecy is not what protects your data — your access policies are. The anon key is meant to be public, so a table it can reach is only as safe as the policies on it. Get those right before you ship:
- Postgres projects: Row-Level Security — enable RLS and write policies on every table the anon key can touch.
- Mongo projects: Mongo policies — the equivalent allow / deny layer for collections.
service_role (secret key) bypasses all of this. That's exactly why it's server-only — and why you should test your policies with the anon key or a real user token, never the admin key.Rotating keys
The dashboard regenerates a project's keys. Rotation issues fresh tokens and invalidates the old ones, so anything still presenting an old key starts failing authentication. Plan for that before you rotate:
- Every place that holds the old key must be updated — deployed client builds, server env vars, CI secrets, edge functions.
- Rotating the underlying signing key also invalidates currently-issued user access tokens, so logged-in users will need to re-authenticate.
- Rotation is the fastest way to contain a leaked
ich_admin_key — do it immediately, then redeploy your servers with the new value.
Do / don't checklist
| Do | Don't |
|---|---|
| Enable RLS / policies on every table the anon key can reach | Rely on key secrecy instead of policies |
Ship the ich_pub_anon key in client apps — that's fine | Ship the ich_admin_ key anywhere a user can read it |
Load keys from env vars (e.g. .env) | Commit the admin key to git or paste it in client code |
Use @ichibase/client for clients, JSR SDKs for servers | Use the service key just to skip writing a policy |
