Authentication
Itera has two credential types, and which one you use depends on who you are and what you’re doing:
- Authoring (write lessons) → an Itera Supabase user-JWT carrying an
authormembership. Used againstitera-mcp. RLS-scoped to your tenant. - Reading results (pull learners’ activity results / progress / artifacts) →
a per-tenant API key (
itera_<prefix>_<secret>). Used againstitera-api. Read-only.
The MCP never has a “god mode” view. Every authoring call runs as you under
Row-Level Security — there is no service_role in itera-mcp.
The per-audience matrix
Section titled “The per-audience matrix”| Audience / action | Identity | Credential | Surface |
|---|---|---|---|
| Internal DFL teacher — author | DFL SSO → Itera federation | DFL login → /v1/embed/session → Itera Supabase JWT (author role) | itera-mcp |
| DFL-as-consumer — read results | tenant (dfl-batch) | per-tenant API key itera_<prefix>_<secret> | itera-api /v1/* |
| DFL-as-consumer — embed player | DFL fellow (iframe) | DFL access token → /v1/embed/session | itera-api embed |
| Third-party instructor — author | Itera Supabase account (their tenant) | Itera Supabase JWT (author role) | itera-mcp |
| Third-party — read results | their tenant | per-tenant API key | itera-api /v1/* |
| Third-party — inject content only | Itera account | Itera JWT (author role) | itera-mcp |
Each row is expanded below. No credential values ever appear in these docs —
only the shape (itera_<prefix>_<secret>) and where to request one.
The authoring auth flow (itera-mcp)
Section titled “The authoring auth flow (itera-mcp)”-
Present a JWT. Your client sends
Authorization: Bearer <jwt>— an Itera Supabase access token — on every MCP request. -
HS256 verification. The server validates the token’s signature against the Itera project’s JWT secret. Wrong-project tokens are rejected (
-32001); anonymous calls are rejected (missing header). -
Per-request, JWT-scoped Supabase client. The validated JWT is attached to a
@supabase/supabase-jsclient, so Postgres seesauth.uid()= your token’ssub.itera.current_itera_user_id()maps that to youritera.users.id, anditera.current_author_tenant_ids()returns the tenants where you hold an authoring role. -
RLS decides writes.
create_* / update_* / delete_* / instantiate_programsucceed only when the target row’s tenant is in your author-tenant set — enforced by the*_author_insert/update/deletepolicies. Learners get tenant-scoped reads but are rejected on writes.
flowchart LR
jwt["Itera Supabase JWT<br/><small>author membership</small>"]
mcp["itera-mcp<br/><small>HS256 verify → per-request client</small>"]
rls["itera.* RLS<br/><small>current_author_tenant_ids()</small>"]
db[("Itera Supabase")]
jwt -->|Bearer| mcp --> rls --> db
Internal DFL teacher
Section titled “Internal DFL teacher”You already have a DevFellowship identity. Itera treats DFL as a federated identity provider: you exchange a verified DFL session for a freshly minted Itera session — no second password, no cross-Supabase foreign key.
-
Get your DFL access token (your normal DFL Supabase session — e.g. the one dfl-learn already holds).
-
Exchange it for an Itera session:
Terminal window curl -X POST https://api.iterahq.dev/v1/embed/session \-H "Content-Type: application/json" \-d '{ "dfl_token": "YOUR_DFL_ACCESS_TOKEN", "tenant": "dfl-batch" }'Response:
{"access_token": "<Itera access_token>","refresh_token": "<Itera refresh_token>","expires_in": 3600,"itera_user_id": "<uuid>"} -
Use
access_tokenas youritera-mcpbearer (see Getting started → step 2).
Third-party instructor
Section titled “Third-party instructor”Your company gets an Itera tenant and Itera Supabase accounts with an
author role for each instructor. This is the same authoring auth model as
internal DFL teachers — a user-JWT + author RLS — just sourced from your own
Itera accounts rather than DFL federation.
-
Your Itera admin provisions your tenant and creates author-role accounts for your instructors (email + password, or an SSO arrangement).
-
Sign in to the Itera Supabase project from your app / a script to get an
access_token+refresh_token:Terminal window curl -X POST "https://kjefmwlopzeaqgqtlbdn.supabase.co/auth/v1/token?grant_type=password" \-H "apikey: YOUR_ITERA_ANON_KEY" \-H "Content-Type: application/json" \-d '{ "email": "instructor@yourco.com", "password": "…" }'(Or use the
@supabase/supabase-jsclient with the Itera URL + anon key.) -
Use the
access_tokenas youritera-mcpbearer. RLS scopes every write to your tenant automatically.
Content-injection only
Section titled “Content-injection only”A partner that only wants to inject content (populate programs/units/lessons/
activities programmatically, without full instructor seats) uses the same
author role and the same itera-mcp create_* / instantiate_program tools —
content-injection is a use-case, not a separate permission. See
Recipes → Content-injection flow.
Reading results (per-tenant API key)
Section titled “Reading results (per-tenant API key)”To read learners’ activity results, progress, and artifacts back out of
Itera, use a per-tenant API key against itera-api — not a user-JWT, and
never for writes.
- Header:
Authorization: Bearer itera_<prefix>_<secret>(orX-Itera-Api-Key: …). - The key is sha256’d and looked up in
itera.api_keys; itstenant_idis the only authorization. Every query is scoped to that tenant. Unknown/revoked →401; a cross-tenant id fetch →404(no enumeration). - To obtain one: ask your Itera admin — they mint it with
npm run mint-key -- --tenant <slug> --name "…". The raw key is shown once and is unrecoverable (only a sha256 + prefix are stored).
Full endpoint reference: Reading results (itera-api).
How to obtain a token
Section titled “How to obtain a token”| You are… | Do this |
|---|---|
| An internal DFL teacher | Exchange your DFL session at /v1/embed/session, then have an admin grant you author. |
| A third-party instructor | Sign in to your Itera account → use the session access_token. |
| Reading results (DFL or third-party) | Ask your Itera admin for a per-tenant API key. |