Skip to content

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 author membership. Used against itera-mcp. RLS-scoped to your tenant.
  • Reading results (pull learners’ activity results / progress / artifacts) → a per-tenant API key (itera_<prefix>_<secret>). Used against itera-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.

Audience / actionIdentityCredentialSurface
Internal DFL teacher — authorDFL SSO → Itera federationDFL login → /v1/embed/sessionItera Supabase JWT (author role)itera-mcp
DFL-as-consumer — read resultstenant (dfl-batch)per-tenant API key itera_<prefix>_<secret>itera-api /v1/*
DFL-as-consumer — embed playerDFL fellow (iframe)DFL access token → /v1/embed/sessionitera-api embed
Third-party instructor — authorItera Supabase account (their tenant)Itera Supabase JWT (author role)itera-mcp
Third-party — read resultstheir tenantper-tenant API keyitera-api /v1/*
Third-party — inject content onlyItera accountItera 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.

  1. Present a JWT. Your client sends Authorization: Bearer <jwt> — an Itera Supabase access token — on every MCP request.

  2. 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).

  3. Per-request, JWT-scoped Supabase client. The validated JWT is attached to a @supabase/supabase-js client, so Postgres sees auth.uid() = your token’s sub. itera.current_itera_user_id() maps that to your itera.users.id, and itera.current_author_tenant_ids() returns the tenants where you hold an authoring role.

  4. RLS decides writes. create_* / update_* / delete_* / instantiate_program succeed only when the target row’s tenant is in your author-tenant set — enforced by the *_author_insert/update/delete policies. 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

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.

  1. Get your DFL access token (your normal DFL Supabase session — e.g. the one dfl-learn already holds).

  2. 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>"
    }
  3. Use access_token as your itera-mcp bearer (see Getting started → step 2).

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.

  1. Your Itera admin provisions your tenant and creates author-role accounts for your instructors (email + password, or an SSO arrangement).

  2. 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-js client with the Itera URL + anon key.)

  3. Use the access_token as your itera-mcp bearer. RLS scopes every write to your tenant automatically.

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.

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> (or X-Itera-Api-Key: …).
  • The key is sha256’d and looked up in itera.api_keys; its tenant_id is 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).

You are…Do this
An internal DFL teacherExchange your DFL session at /v1/embed/session, then have an admin grant you author.
A third-party instructorSign 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.