> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Clerk

> Frontend API, Backend API, OAuth and OIDC, organizations, sessions, JWT templates.

Clerk authentication and user management emulation with the Frontend API that `@clerk/clerk-js` (and `@clerk/nextjs` and `@clerk/clerk-react`) signs users up and in through, an OAuth 2.0 / OIDC provider, RS256 session tokens, and the Clerk Backend API for users, email addresses, organizations, memberships, invitations, and sessions.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/clerk` |
| Port | `4010` in the default set, `4000` when started alone |
| Config key | `clerk` |
| HTTP operations | 106, browsable at `http://localhost:3999/docs/clerk` while it runs ([API reference](/api-reference.md)) |
| Vendor spec | [clerk/openapi-specs](https://github.com/clerk/openapi-specs) at `22d08a2` |

```bash CLI theme={null}
npx @deploybaseline/cli start --service clerk
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const clerk = await createEmulator({ service: "clerk" });
// clerk.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "clerk": {
    "users": [
      {
        "first_name": "Test",
        "last_name": "User",
        "email_addresses": ["test@example.com"],
        "password": "clerk_test_password"
      }
    ],
    "organizations": [
      {
        "name": "My Company",
        "slug": "my-company",
        "members": [
          {
            "email": "test@example.com",
            "role": "admin"
          }
        ]
      }
    ],
    "oauth_applications": [
      {
        "client_id": "clerk_baseline_client",
        "client_secret": "clerk_baseline_secret",
        "name": "Baseline App",
        "redirect_uris": ["http://localhost:3000/api/auth/callback/clerk"]
      }
    ]
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service clerk
```

Clerk listens on `http://localhost:4010` when all services start, or on `http://localhost:4000` when it is the only service.

Programmatically:

```typescript theme={null}
import { createEmulator } from '@deploybaseline/cli'

const clerk = await createEmulator({ service: 'clerk', port: 4010 })
// clerk.url === 'http://localhost:4010'
```

When the store is empty, the emulator creates a user `Test User` with primary email `test@example.com` and password `test_password`, and an OAuth application `Baseline App` with `client_id` `clerk_baseline_client`, `client_secret` `clerk_baseline_secret`, and redirect URI `http://localhost:3000/api/auth/callback/clerk`. Because an OAuth application always exists, `/oauth/authorize` and `/oauth/token` always validate `client_id`, `redirect_uri`, and (for non-public applications) `client_secret`.

### @clerk/backend

```typescript theme={null}
import { createClerkClient } from '@clerk/backend'

const clerk = createClerkClient({
  secretKey: 'sk_test_baseline',
  apiUrl: process.env.CLERK_EMULATOR_URL, // e.g. http://localhost:4010
})

const { data: users } = await clerk.users.getUserList()
```

The SDK appends `/v1` to `apiUrl`, which matches the emulator routes.

### @clerk/nextjs, @clerk/clerk-react, and clerk-js

```bash theme={null}
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_YmFzZWxpbmUuY2xlcmsuYWNjb3VudHMuZGV2JA
NEXT_PUBLIC_CLERK_PROXY_URL=http://localhost:4010
CLERK_SECRET_KEY=sk_test_baseline
CLERK_API_URL=http://localhost:4010
```

The publishable key encodes `clerk.instance.frontend_api` (default `baseline.clerk.accounts.dev`); `GET /_clerk/keys` prints it for a custom host. The browser SDK sends Frontend API calls to the proxy URL, and the server SDK's `authenticateRequest` (`clerkMiddleware` in Next.js) verifies `__session` against the emulator's JWKS and runs the handshake through it. With clerk-js directly:

```typescript theme={null}
import { Clerk } from '@clerk/clerk-js'

const clerk = new Clerk(publishableKey, { proxyUrl: 'http://localhost:4010' })
await clerk.load()
const signIn = await clerk.client.signIn.create({ identifier: 'test@example.com', password: 'test_password' })
await clerk.setActive({ session: signIn.createdSessionId })
```

### OIDC clients

Point any OIDC client at the emulator URL as the issuer. Discovery advertises `/oauth/authorize`, `/oauth/token`, `/oauth/userinfo`, `/oauth/token_info`, `/oauth/token/revoke`, `/v1/jwks`, the `authorization_code` and `refresh_token` grants, and the `offline_access` scope.

```typescript theme={null}
// Auth.js generic OIDC provider
{
  id: 'clerk',
  name: 'Clerk',
  type: 'oidc',
  issuer: process.env.CLERK_EMULATOR_URL,
  clientId: 'clerk_baseline_client',
  clientSecret: 'clerk_baseline_secret',
}
```

The callback URL `http://localhost:3000/api/auth/callback/clerk` matches the default OAuth application.

## Auth

Backend API routes (`/v1/users`, `/v1/organizations`, `/v1/sessions`, and so on) require an `Authorization: Bearer <secret key>` header. When started from the CLI, any non-empty bearer token is accepted, so a placeholder such as `sk_test_baseline` works. OAuth access and refresh tokens (`oat_...`, `ort_...`) are not secret keys and get `401`. Errors use the Clerk shape: `{ "errors": [{ "code", "message", "long_message", "meta" }] }`.

Error codes are lowercase, as Clerk sends them: `resource_not_found`, `form_identifier_exists`, `form_param_missing`, `form_param_value_invalid`, `authentication_invalid`, and so on. Timestamps (`created_at`, `updated_at`, `last_active_at`, `expire_at`, and the rest) are Unix milliseconds.

List responses use offset pagination (`limit` defaults to 10, maximum 500). `GET /v1/users`, `GET /v1/sessions`, and `GET /v1/jwt_templates` return a plain array, as Clerk does; add `paginated=true` for `{ data, total_count }`. Every other list returns `{ data, total_count }`.

## Frontend API

The emulator also serves Clerk's Frontend API, which `@clerk/clerk-js` calls from the browser (and so `@clerk/nextjs`, `@clerk/clerk-react`, and the other SDKs built on it). Point the SDK at the emulator with `proxyUrl` and a publishable key that encodes the instance's Frontend API host. `GET /_clerk/keys` returns both:

```json theme={null}
{
  "frontend_api": "baseline.clerk.accounts.dev",
  "proxy_url": "http://localhost:4010",
  "publishable_key": "pk_test_YmFzZWxpbmUuY2xlcmsuYWNjb3VudHMuZGV2JA",
  "publishable_key_live": "pk_live_YmFzZWxpbmUuY2xlcmsuYWNjb3VudHMuZGV2JA"
}
```

Routes, with Clerk's `{ response, client }` envelope and `{ errors: [{ message, long_message, code, meta }], clerk_trace_id }` errors. Writes may be sent as `POST` with `_method`, as clerk-js does, and bodies may be form encoded or JSON:

* `POST /v1/dev_browser` — Create dev browser token (`Clerk-Db-Jwt` header). Development instances carry the client in `__clerk_db_jwt`; an unknown token is `401 dev_browser_unauthenticated`.
* `GET` and `PATCH /v1/environment`: auth, display, user, and organization settings from `clerk.instance`.
* `GET`, `PUT`, `POST`, and `DELETE /v1/client`; `DELETE /v1/client/sessions`.
* `GET /v1/client/sessions/:id`, `POST .../touch` (`active_organization_id` sets the active organization), `.../end`, `.../remove`, `.../tokens` (`organization_id` picks the organization, `""` the personal account), and `.../tokens/:template`.
* Sign-ins: `POST /v1/client/sign_ins` (identifier and password, `email_code`, `reset_password_email_code`, `oauth_*`, `transfer`), `GET .../:id`, `.../prepare_first_factor`, `.../attempt_first_factor`, `.../reset_password`.
* Sign-ups: `POST /v1/client/sign_ups`, `GET` and `PATCH .../:id`, `.../prepare_verification`, `.../attempt_verification` (email codes).
* `/v1/me`: `GET`, `PATCH`, `DELETE`, `PATCH /v1/me/metadata`, `POST /v1/me/change_password`, email addresses (add, read, verify, delete), `GET /v1/me/organization_memberships`, and `DELETE /v1/me/organization_memberships/:organizationId`.
* `GET /v1/client/handshake` and the Backend API's `GET /v1/clients/handshake_payload`, so `authenticateRequest` can refresh a missing or expired `__session` cookie.
* Social sign-in: an `oauth_*` strategy returns an `external_verification_redirect_url` to the emulator's account chooser at `/_clerk/oauth/:strategy`, which returns through `/v1/oauth_callback` to the attempt's `redirect_url`. A new account on sign-in (or a taken one on sign-up) becomes `transferable`, as on Clerk.

Behavior to know:

* Session tokens (60 seconds) are v2: `sid`, `sub`, `azp` (the request's origin), `iss` (`https://<frontend_api>`), `nbf`, `exp`, `iat`, `v: 2`, `fva`, and with an active organization `o: { id, rol, slg, per, fpm }`. `verifyToken` and `authenticateRequest` from `@clerk/backend` accept them against the emulator's JWKS.
* Passwords are checked for length (`form_password_length_too_short`, minimum `password_min_length`) and against a list of breached passwords (`form_password_pwned`, off with `hibp: false`). A taken email or username is `form_identifier_exists`, an unknown one at sign-in `form_identifier_not_found`, and a wrong password `form_password_incorrect`.
* Codes are not emailed. `GET /_clerk/emails?email_address=` lists the codes sent, `GET /_clerk/simulate/verification-code?email_address=` returns the latest, and the inspector's Emails tab shows them. Addresses with `+clerk_test` take `424242` and get no code, while `test_mode` is on (the default on development instances).
* Single session mode is on by default: a second sign-in on a signed-in client is `session_exists`. Seed `single_session_mode: false` for multi-session apps.
* Sessions made here are Backend API sessions: `GET /v1/sessions` lists them, and revoking one through the Backend API signs the browser out.
* `GET /npm/*` redirects clerk-js and `@clerk/ui` bundle requests to the public npm CDN, so the SDK's script loader works through `proxyUrl`. Offline, serve the bundle yourself (`clerkJSUrl`).

## OAuth and OIDC

* `GET /.well-known/openid-configuration` — OIDC discovery
* `GET /v1/jwks` — JSON Web Key Set (JWKS)
* `GET /oauth/authorize` — Authorize: shows the user picker
* `POST /oauth/authorize/callback` — Submit user picker: redirects to `redirect_uri` with `code` and `state`
* `POST /oauth/token` — Exchange token (`authorization_code` and `refresh_token` grants)
* `GET /oauth/userinfo` — Get user info: for an OAuth access token
* `POST /oauth/token_info` — Introspect token (RFC 7662): for the client the token was issued to
* `POST /oauth/token/revoke` — Revoke token (RFC 7009); revoking a refresh token also ends the access tokens of its grant
* `GET /oauth/authorize/deny` — Deny consent: the consent screen's Deny link, redirects with `error=access_denied`

### Authorization code flow

1. Redirect the user to `/oauth/authorize` with `client_id`, `redirect_uri`, `scope`, `state`, and optionally `nonce`, `code_challenge`, and `code_challenge_method`
2. The emulator renders a user picker with every seeded user
3. On selection, the emulator redirects to `redirect_uri` with `code` and `state`
4. Exchange the code via `POST /oauth/token` with `grant_type=authorization_code`

Only `response_type=code` is supported. Authorization codes expire after 10 minutes, work once, and only for the client they were issued to. Client credentials can be sent in the body or as `Authorization: Basic`, for both grants. PKCE supports `plain` and `S256`; any other `code_challenge_method` redirects back with `error=invalid_request`.

The token response contains an opaque `access_token` (`oat_...`), `token_type`, `expires_in` (3600), and `scope`. It adds an RS256 `id_token` when `openid` was granted, and a `refresh_token` (`ort_...`) when `offline_access` was granted. The `id_token` carries `iss` (the emulator URL), `sub` (the Clerk user ID), `aud` (the client ID), `sid`, `nonce` (when the authorize request sent one), `email`, `email_verified`, and `name`. Each code exchange creates an active session.

`grant_type=refresh_token` rotates the refresh token: the presented one stops working and a new set is returned. It must be sent by the same client and its session must still be active. A `scope` parameter may narrow the grant; asking for a scope that was not granted returns `invalid_scope`. Revoking the session ends the refresh token and its access tokens.

`/oauth/userinfo` accepts only a live OAuth access token and answers `401` with `WWW-Authenticate: Bearer error="invalid_token"` otherwise. It returns `sub`, plus `name`, `given_name`, `family_name`, `preferred_username`, and `picture` for `profile`, and `email` and `email_verified` for `email`.

Introspection takes the token and the client's credentials (`client_secret_basic` or `client_secret_post`); public clients cannot introspect. Revocation also accepts a public client's `client_id` alone. Another client's or an unknown token is `{"active": false}` on introspection and a quiet `200` on revocation.

### Consent screen

Seed an OAuth application with `consent_screen_enabled: true` to show Clerk's consent screen after the user picks an account: it lists the requested scopes with **Allow** and **Deny**. Approval is remembered per user and application, so the screen comes back only for new scopes or when the client sends `prompt=consent`. Deny redirects with `error=access_denied` and the `state`. Applications have no consent screen unless seeded with it.

### Signing key

ID tokens and session tokens are signed with a key stored in emulator state (`kid`: `baseline-clerk-1`), so they stay valid across snapshot restores and persisted restarts. Set `clerk.signing_key.private_jwk` to an RSA private JWK to pin the key. See [OIDC signing keys](/configuration.md#oidc-signing-keys).

## Users

* `GET /v1/users` — List users (`limit`, `offset`, `query`, `email_address`, `external_id`, `username`, `user_id`, `order_by`). Filters other than `query` may repeat.
* `GET /v1/users/count` — Count users (same filters)
* `GET /v1/users/:userId` — Get user
* `POST /v1/users` — Create user (`422 form_identifier_exists` for an email address or username already in use, `422 form_param_format_invalid` for a malformed email)
* `PATCH /v1/users/:userId` — Update user
* `DELETE /v1/users/:userId` — Delete user and its email addresses, memberships, and sessions
* `POST /v1/users/:userId/ban` — Ban user
* `POST /v1/users/:userId/unban` — Unban user
* `POST /v1/users/:userId/lock` — Lock user
* `POST /v1/users/:userId/unlock` — Unlock user
* `PATCH /v1/users/:userId/metadata` — Merge user metadata: deep merge public, private, and unsafe metadata (`null` removes a key)
* `PUT /v1/users/:userId/metadata` — Replace user metadata: replaces each metadata field sent; fields left out are kept
* `GET /v1/users/:userId/organization_memberships` — List user memberships (`limit`, `offset`, `role`, `order_by`)
* `POST /v1/users/:userId/verify_password` — Verify password (`{ "verified": true }`, or `422 form_password_incorrect`)

## Email addresses

* `GET /v1/email_addresses/:emailId` — Get email address
* `POST /v1/email_addresses` — Create email address
* `PATCH /v1/email_addresses/:emailId` — Update email address
* `DELETE /v1/email_addresses/:emailId` — Delete email address

## Organizations

* `GET /v1/organizations` — List organizations (`limit`, `offset`, `query`)
* `GET /v1/organizations/:orgId` — Get organization by ID or slug
* `POST /v1/organizations` — Create organization (`created_by` adds that user as `org:admin`; a slug already in use, ignoring case, is `422 form_identifier_exists`)
* `PATCH /v1/organizations/:orgId` — Update organization (the same slug check)
* `DELETE /v1/organizations/:orgId` — Delete organization
* `PATCH /v1/organizations/:orgId/metadata` — Merge organization metadata: deep merge (`null` removes a key)
* `PUT /v1/organizations/:orgId/metadata` — Replace organization metadata: replaces each metadata field sent

## Memberships

* `GET /v1/organization_memberships` — List all memberships: across every organization (`limit`, `offset`, `user_id`, `role`, `order_by`; filters may repeat)
* `GET /v1/organizations/:orgId/memberships` — List members (`limit`, `offset`, `role`)
* `POST /v1/organizations/:orgId/memberships` — Add member
* `PATCH /v1/organizations/:orgId/memberships/:userId` — Update member role
* `DELETE /v1/organizations/:orgId/memberships/:userId` — Remove member (returns the removed membership)
* `PATCH /v1/organizations/:orgId/memberships/:userId/metadata` — Merge membership metadata: deep merge

## Invitations

* `GET /v1/organizations/:orgId/invitations` — List invitations (`limit`, `offset`, `status`, which may repeat)
* `GET /v1/organizations/:orgId/invitations/:invitationId` — Get invitation
* `POST /v1/organizations/:orgId/invitations` — Create invitation (`email_address`, `role`, `inviter_user_id`, `public_metadata`, `private_metadata`, `expires_in_days` from 1 to 365)
* `POST /v1/organizations/:orgId/invitations/bulk` — Create invitations in bulk: from an array of invitation bodies; returns `{ data, total_count }`
* `POST /v1/organizations/:orgId/invitations/:invitationId/revoke` — Revoke invitation: a pending one

## Sessions

* `GET /v1/sessions` — List sessions (`limit`, `offset`, `user_id`, `status` such as `active` or `revoked`)
* `GET /v1/sessions/:sessionId` — Get session
* `POST /v1/sessions` — Create session: for a `user_id`
* `POST /v1/sessions/:sessionId/revoke` — Revoke session
* `POST /v1/sessions/:sessionId/tokens` — Create session token (JWT)
* `POST /v1/sessions/:sessionId/tokens/:template` — Create session token from template (`404` when no template has that name)

Both token routes accept an optional `expires_in_seconds` (30 to 315360000).

### Session tokens

`POST /v1/sessions/:sessionId/tokens` returns `{ "object": "token", "jwt": "..." }`. The JWT is signed with the same key as the JWKS (`kid`: `baseline-clerk-1`) and contains `iss`, `sub`, `sid`, `iat`, `nbf`, a one hour `exp`, `metadata` when the user has public metadata, and `org_id`, `org_role`, `org_slug`, and `org_permissions` from the user's first organization membership. Revoked sessions return `session_not_active`.

`POST /v1/sessions/:sessionId/tokens/:template` renders the named JWT template instead. The token always has `iss`, `sid`, `sub`, `iat`, `jti`, `nbf` (`iat` minus `allowed_clock_skew`), and `exp` (`iat` plus `expires_in_seconds` or the template `lifetime`), and the rendered claims are merged in. The reserved claims `azp`, `exp`, `iat`, `iss`, `jti`, and `nbf` cannot be set by a template.

Claim values may use Clerk shortcodes: `{{user.id}}`, `{{user.external_id}}`, `{{user.first_name}}`, `{{user.last_name}}`, `{{user.full_name}}`, `{{user.username}}`, `{{user.image_url}}`, `{{user.has_image}}`, `{{user.primary_email_address}}`, `{{user.email_verified}}`, `{{user.primary_phone_address}}`, `{{user.phone_number_verified}}`, `{{user.two_factor_enabled}}`, `{{user.created_at}}`, `{{user.updated_at}}`, `{{user.public_metadata}}` (and `{{user.public_metadata.some.path}}`), `{{user.unsafe_metadata}}`, `{{org.id}}`, `{{org.name}}`, `{{org.slug}}`, `{{org.image_url}}`, `{{org.role}}`, `{{org.public_metadata}}`, `{{session.id}}`, and `{{session.actor}}`. A value that is a single shortcode keeps its type (a boolean stays a boolean); shortcodes inside a longer string are interpolated. `||` gives fallbacks, for example `{{user.username || user.id}}` or `{{org.role || 'none'}}`. Organization shortcodes come from the user's first membership.

Templates are signed with the instance key unless `custom_signing_key` is true, in which case `signing_algorithm` (`HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`) and `signing_key` (a shared secret for HMAC, a PKCS#8 PEM otherwise) are used. That covers integrations such as Supabase, which verify an HS256 token with their JWT secret. `lifetime` is 30 to 315360000 seconds (default 60) and `allowed_clock_skew` is 0 to 300 seconds (default 5).

Seed templates with `jwt_templates`:

```json theme={null}
{
  "clerk": {
    "jwt_templates": [
      {
        "name": "supabase",
        "lifetime": 3600,
        "claims": {
          "aud": "authenticated",
          "role": "authenticated",
          "email": "{{user.primary_email_address}}"
        },
        "custom_signing_key": true,
        "signing_algorithm": "HS256",
        "signing_key": "super-secret-jwt-token-with-at-least-32-characters"
      }
    ]
  }
}
```

## JWT templates

* `GET /v1/jwt_templates` — List templates (a plain array, or `{ data, total_count }` with `paginated=true`)
* `GET /v1/jwt_templates/:templateId` — Get template
* `POST /v1/jwt_templates` — Create template (`name`, `claims`, `lifetime`, `allowed_clock_skew`, `custom_signing_key`, `signing_algorithm`, `signing_key`)
* `PATCH /v1/jwt_templates/:templateId` — Update template
* `DELETE /v1/jwt_templates/:templateId` — Delete template

## Instance

* `GET /v1/instance` — Get instance (`clerk.instance.get()`): `id`, `environment_type`, `allowed_origins`, and the subdomain settings
* `PATCH /v1/instance` — Update instance: settings such as `allowed_origins` (an empty list clears it; `null` leaves a setting unchanged), `support_email`, `test_mode`, `hibp`; `204`

As on Clerk, the environment comes from the secret key: an `sk_live_` key reports `production`, any other key `development`. Subdomain settings are reported only on production. Unknown settings are `422 form_param_unknown`, and an allowed origin must be `scheme://host` with no path.

## Seed config

```json theme={null}
{
  "clerk": {
    "instance": {
      "allowed_origins": ["chrome-extension://abcdef"],
      "application_name": "My App",
      "single_session_mode": true,
      "attributes": {
        "username": "required",
        "first_name": "on",
        "last_name": "on",
        "password": "required"
      },
      "password_min_length": 8,
      "social_providers": ["oauth_google", "oauth_github"]
    },
    "users": [
      {
        "first_name": "Jane",
        "last_name": "Doe",
        "username": "jane",
        "email_addresses": ["jane@example.com"],
        "password": "jane_password",
        "public_metadata": {
          "plan": "pro"
        }
      }
    ],
    "organizations": [
      {
        "name": "My Company",
        "slug": "my-company",
        "max_allowed_memberships": 10,
        "members": [
          {
            "email": "jane@example.com",
            "role": "admin"
          }
        ]
      }
    ],
    "oauth_applications": [
      {
        "client_id": "clerk_baseline_client",
        "client_secret": "clerk_baseline_secret",
        "name": "Baseline App",
        "redirect_uris": ["http://localhost:3000/api/auth/callback/clerk"],
        "scopes": ["openid", "profile", "email"]
      }
    ]
  }
}
```

* `users`: `email_addresses` (required, the first is primary and all are verified), `clerk_id`, `first_name`, `last_name`, `username`, `password`, `external_id`, `public_metadata`, `private_metadata`, `unsafe_metadata`. A user whose first email already exists (such as the default `test@example.com`) is updated with the fields the entry sets, and keeps its ID.
* `organizations`: `name` (required), `clerk_id`, `slug` (derived from `name` when omitted), `max_allowed_memberships`, `public_metadata`, `private_metadata`, `members`. Each member references a seeded user by `email`; `role` gets an `org:` prefix when missing.
* `instance`: `id` (defaults to `ins_baseline00000000000000000`) and `allowed_origins` for `GET /v1/instance`, and the Frontend API settings: `frontend_api` (the host the publishable keys encode, default `baseline.clerk.accounts.dev`), `environment_type` (`development` or `production`), `application_name`, `single_session_mode` (default `true`), `attributes` (`username`, `first_name`, `last_name`, `password` as `on`, `off`, or `required`; email is always required), `password_min_length` (default 8), `social_providers` (default `oauth_google`, `oauth_github`), `organizations_enabled` (default `true`), `hibp` (breached password check, default on), `test_mode` (the `424242` code for `+clerk_test` addresses, default on in development), and `support_email`.
* `oauth_applications`: `client_id`, `name`, and `redirect_uris` (required), `client_secret`, `scopes` (defaults to `openid profile email`), `public` (skips the client secret check), `consent_screen_enabled` (see [Consent screen](#consent-screen)). An entry for an existing `client_id`, such as the default `clerk_baseline_client`, updates it.
* `jwt_templates`: `name` and `claims` (required), `lifetime`, `allowed_clock_skew`, `custom_signing_key`, `signing_algorithm`, `signing_key`. A template whose name already exists is skipped. See [Session tokens](#session-tokens).

## Inspector

* `GET /_inspector` shows users, sessions and clients, sign-in and sign-up attempts, sent codes, and the publishable keys.

## Limits

Not built: phone numbers, second factors (TOTP, backup codes), passkeys, web3 wallets, email links, enterprise SSO, captcha, creating organizations from the Frontend API, and the hosted Account Portal pages (their URLs in `/v1/environment` point at your app).

## Conformance

| | |
| - | - |
| Routes | 106 supported or partial, 0 planned |
| Route gate | fail |
| Spec gate | fail, 11 allowed ([clerk/openapi-specs](https://github.com/clerk/openapi-specs) at `22d08a2`) |
| SDKs tested | `@clerk/backend@3.25.0`, `@clerk/clerk-js@6.39.1`, `jose@6.2.12`, `openid-client@6.8.8` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.