Silicon IAMDocs

Authentication

Seven transports, each with one job. Picking the wrong one is the most common integration mistake, so this page starts with the table and then explains when each applies.

ModeTransportUsed for
Publicno credential Signup, login initiation and verification, availability probes, provider callbacks
IAM bearerAuthorization: Bearer … Carbon or Silicon API access
Browser sessionsecure iam_session cookie Interactive application login and SSO navigation
ApplicationHTTP Basic, app ID and app secret Token exchange, introspection, revocation, and OBO
Platform adminIAM bearer whose Carbon holds a current grant /api/v1/admin/*
Step-upX-Step-Up-Token, in addition to a bearer Ownership, credentials, SSO, deletion, privileged grants
WorkOSverified WorkOS-Signature The WorkOS webhook receiver

Credential lifetimes

These are exact. Startup rejects a deployment that overrides any of them.

Credential or stateLifetime
Signup session48 hours
Email or phone OTP10 minutes
IAM and application access token30 minutes
IAM session refresh family (Carbon or Silicon)900 days, absolute
Application OAuth refresh family900 days, absolute
Short-lived login token2 minutes, single use
Step-up token5 minutes, bound to one action on one resource
Carbon invitation48 hours
WorkOS setup link5 minutes
OBO proof60 seconds maximum, single use
One-time secret replay envelope10 minutes

Refresh tokens rotate, and reuse is fatal

The family boundary matters. An IAM refresh family belongs to one Carbon device session or one Silicon session; compromise invalidates that session and authority descended from it, not the principal's other device sessions. An OAuth refresh family belongs to one parent IAM session and one client Application; compromise revokes that Application family and its access tokens without revoking the parent IAM session or another Application's tokens.

This is deliberate: it is what makes a stolen refresh token detectable. It also means a client must serialise refresh per family. Two browser tabs or worker replicas starting separate refresh operations with the same token will compromise that family. An uncertain transport retry is safe only when it repeats the identical request with the original Idempotency-Key.

Concretely, a correct client:

Access and refresh tokens are opaque 256-bit random values. There is no signing-key discovery surface and nothing to parse: when an application needs to know whether a token is still good right now, it calls introspection.

Carbon login

Passwordless, in two calls. POST /api/v1/login/challenges takes exactly one of email, phone_number or carbon_id; POST /api/v1/login/challenges/{session_id}/verify exchanges the six-digit code for a credential pair.

A carbon_id challenge dispatches a code to both verified channels, and either one satisfies the verification. Say so in your interface — a user who checks only their inbox will otherwise sit waiting for an SMS that already arrived.

Email codes are generated and digest-verified by IAM. Phone codes are generated, routed and validated by Twilio Verify; IAM stores only the provider attempt identifier and its own challenge lifecycle state. Phone numbers must be supplied in E.164 form.

Login initiation answers 404 when no active Carbon owns the submitted identity. That is a documented, deliberate disclosure: an identity provider that pretends to send a code to a non-existent account teaches people to ignore a missing message.

Silicon login

POST /api/v1/silicon-auth/token takes the global Silicon ID as the username and the stk-… token as the password, and returns the same credential pair a Carbon gets. Silicon tokens are 128 bits, formatted as stk- plus 32 lowercase hexadecimal characters, and are stored only as a keyed digest — a lost token can be replaced, never recovered.

Step-up

Some actions need proof that the person at the keyboard is still the account holder. Step-up is a second factor over an already-authenticated session, and the resulting token is bound to one action on one resource: a token minted for application.client_secret.rotate or application.webhook_secret.rotate or application.webhook.approve on application A is rejected for application B.

  1. POST /api/v1/step-up/challenges with the action, the resource ID and a channel.
  2. POST /api/v1/step-up/challenges/{session_id}/verify with the code.
  3. Send the resulting sup_… value as X-Step-Up-Token alongside your bearer, within five minutes.

Actions that require it:

Do not cache a step-up token. It buys at most one prompt and leaves a live credential in memory between unrelated operations.

Verification-code protection

Every IAM-managed OTP is six digits and lives ten minutes. Signup, Carbon login, invitation-join and verified-channel step-up challenges each allow ten failed verifications; the tenth failure starts a sixty-second cooldown, after which the still-unexpired code gets a fresh ten-attempt window.

The partial failure count and any active cooldown carry into a replacement code. Resending cannot reset either, and a cooldown never extends the original ten-minute expiry. Interfaces should say so, or users will resend repeatedly in the belief that it helps.

The browser session

A successful Carbon login also sets iam_session: host-only, Secure, HttpOnly, SameSite=Lax, and signed. It exists for the two flows that are navigations rather than API calls — application login and SSO — and it is bound to the same refresh family, so revoking the session revokes the cookie.

IAM recognizes an existing session and shows Application validation followed by the current permission list with descriptions and critical labels, then its organization picker. Explicit permission consent precedes organization selection when consent_required is true. The user must choose at least one organization. Apps cannot supply org_id. GET navigation never mints a token; the trusted IAM interface submits explicit org_ids, the complete approved_scopes, and the exact scope_version returned by the choices API, using a direct IAM bearer. A scope change requires fresh consent.

Application scopes and credentials

A registration's app_scope declares permitted IAM data and external endpoints. Identity and profile are defaults; critical permissions require reviewer approval. The application receives only the scope intersection currently approved and consented by the user for selected active memberships. webhook_scope controls event subscriptions independently. A direct IAM bearer and an application OAuth bearer are distinct credentials with different authority.

IAM Carbon and Silicon sessions can read login choices and submit explicit consent. Application secrets and app-issued bearers cannot create or expand that consent. The only login handoff to an app is an app-bound, two-minute single-use SLT, exchanged on that app's backend with its own secret. Read Applications for the complete request shapes.

Batch and bundle login

/login?app_ids=tos%3Ebriefcase,tos%3Edm authenticates once for 1–100 distinct applications. IAM collects each app's permissions and organization choices, then creates all SLTs atomically. Direct IAM clients use GET /api/v1/app-auth/batch/organizations?app_ids=... followed by POST /api/v1/app-auth/batch/short-lived-tokens. Each element of applications contains app_id, org_ids, approved_scopes, and scope_version.

/login?bundle_id=tos%3Esuite displays one bundle identity with combined member permissions and a shared organization picker. The bundle routes validate the current exact member set. A bundle has no client secret, and every SLT remains bound to an individual application.

Both callbacks receive #slts= with a URL-encoded JSON array containing app_id, slt, expires_in, expires_at, and request_id. Read the fragment in browser JavaScript, verify login state and expected app IDs, clear it, and send each token to its own application server. See Batch login and Bundles.