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

# Credentials by journey

> Which credential a developer, admin, CI job or production agent uses today, what it can do, and how long it lives.

This page states what the code does today. Where a credential does not exist
in your auth mode, or an obvious expectation is not met, it says so.

> [!WARNING]
>
> **Auth mode decides which credentials exist.** `sk_` API tokens exist only in
> `local` auth mode. Minting one in any other mode returns `503`. An install
> that signs people in through an IdP (`oidc`) has no `sk_` tokens, so the
> developer and production-agent steps below that use one do not apply to it.
> This guide covers `local` and `oidc` installs. SAML sign-in is not covered here.

Session tokens are signed JWTs that name this install's tenant. An `sk_` token is
different: an opaque key whose hash the server looks up, with the tenant in its
prefix. A token that names another tenant is refused.

## Developer testing

| You want | Use | Lives | Mode |
| - | - | - | - |
| A session in the Console or CLI | Password sign-in (`grant_type=password`), or `sengol login --device` approved in the Console | 1 hour (password); device token lifetime not checked | `local` (password); device: not checked |
| A token for a script you run yourself | Mint an `sk_` token for your own user at `POST /v1/users/{your_user_id}/tokens` | `SENGOL_API_TOKEN_TTL_HOURS` hours, default 720 | `local` only |

* An `sk_` token carries your whole role unless you bind it to one agent (see
  [Bound tokens](#bound-tokens) below).
* The lifetime is an install-wide setting. `0` or `none` makes new tokens
  non-expiring. A token can ask for a shorter life (see below).
* The plaintext is shown once. Only its hash is stored.
* A token minted this way cannot approve intakes, certify agents or activate
  obligations: those routes need an interactive sign-in.

### Bound tokens

> [!NOTE]
>
> Agent binding, per-token lifetime and live revocation below arrived with the
> 2026-10-07 sengol release. Earlier releases ignore `agent_id` and
> `expires_in_hours`, mint an unbound token, and let an exchanged session run to
> its `exp`. The Console form warns when the server did not apply either field.

`POST /v1/users/{user_id}/tokens` also takes:

* `agent_id` (up to 256 characters): binds the token to one agent. The
  exchanged session holds `audit:write` only, and its agent is the token's,
  whatever `agent_id` the client sends at exchange. The exchange returns `403`
  if the user's role does not hold `audit:write`.
* `expires_in_hours` (1 to 87600): a shorter life for this token. With an
  install-wide cap set, the token never outlives it.

A bound session reaches only `POST /v1/audit/ingest`, `POST /v1/traces/otlp` and
`/v1/capture/*`. Every other route returns `403`. Audit ingest refuses with
`403`, before writing anything, a record that names another agent. Use a bound
token for the audit sink, not for the other SDK clients.

The Console mints one from Settings, Access, Users, with an optional agent id
and lifetime, and lists each token's agent, expiry (UTC) and status.

Every session exchanged from an `sk_` token ends when the token is revoked or
expires, or its user is disabled: the next request that reaches the revocation
check returns `401`. A request the rate limiter stops first returns `429`. An
exchanged session never outlives its token, and lasts at most one hour. A
session exchanged before an upgrade carries no token reference and runs to its
own expiry.

## Admin

* The first admin is seeded from `SENGOL_BOOTSTRAP_ADMIN_EMAIL` at boot.
  `grant_type=console_admin` exchanges the install's bootstrap secret
  (`SENGOL_CONSOLE_ADMIN_SECRET`) for a one-hour admin token. That token is not an
  interactive sign-in: it cannot approve intakes, certify agents or activate
  obligations. Use a password, SSO or `sengol login --device` session for those.
* In `local` mode an admin can mint an `sk_` token for any user the admin
  outranks (`POST /v1/users/{user_id}/tokens`). The token is that user's: it
  carries that user's role and subject, not the admin's. The change is logged
  as `api_token.create` with the admin as actor.

## CI/CD

CI exchanges the runner's OIDC token (`grant_type=github_oidc`, or
`ci_oidc` for another issuer in `SENGOL_CI_OIDC_ISSUERS`) for a one-hour token.

> [!NOTE]
>
> The repository allowlist, the CI principal marker and the `403` on audit ingest
> and OTLP below arrived with the 2026-10-07 sengol release. Earlier releases do
> not apply them.

* **Allowlist.** Only repositories named in
  `SENGOL_CI_OIDC_ALLOWED_REPOSITORIES` can exchange: `owner/repo`, `owner/*`
  or a numeric project id. An unset or empty list admits nobody. An entry
  without a prefix applies to the GitHub Actions issuer only. For a `ci_oidc`
  issuer, prefix the entry with that issuer, as in
  `https://issuer.example=group/project`; without the prefix that issuer gets
  `401`. An issuer whose token has no repository claim is matched on the token's
  `sub`: exact, case-sensitive, with no `owner/*` or numeric id.
* **Scope.** The token carries `audit:write` and is marked as a CI principal.
  It is never an interactive sign-in, so it cannot approve, certify or
  activate.
* **Refused routes.** `POST /v1/audit/ingest` and `POST /v1/traces/otlp` return
  `403` to a CI token. CI tokens still reach the routes that write their own
  records, such as gates.
* **Identity.** The token's `agent_id` is the repository path (`owner/repo`),
  or, for a `ci_oidc` issuer whose token has no repository claim, the token's
  verified `sub`. It is not a registered agent id. No branch, environment or workflow restriction is
  applied; those claims are recorded in the workflow audit log only.

## Production agent

| Credential | How | Token lives | Mode |
| - | - | - | - |
| Registration secret | `grant_type=registration_secret` with the agent id and secret | 1 hour | any |
| Bound IdP identity | Bind a service principal with `POST /v1/agents/{agent_id}/identity-binding`, then sign in with `grant_type=agent_oidc` | 1 hour | `oidc` connection required |

Both give the same token: `audit:write` only, bound to one agent, never
interactive. Revoking the registration secret stops its tokens.

> [!NOTE]
>
> The SDK sinks read `SENGOL_API_TOKEN`, or do the CI exchange. They do not read
> `SENGOL_REGISTRATION_SECRET`, and neither `registration_secret` nor
> `agent_oidc` is wired into the SDK today. An agent running through the SDK
> therefore holds a token you supply, typically an `sk_` token in `local` mode.
> Unless you bind it to the agent (see [Bound tokens](#bound-tokens)), it
> carries that user's whole role.

The one-hour figure is the issued token's life, not the source credential's.
A registration secret is revocable and can carry an expiry. Rotating it keeps
the old secrets working for `grace_days` (default 7, range 1 to 30) unless you
choose `revoke_now` or `keep`. Revoking or emergency-revoking a secret stops
tokens already issued from it on their next request. A bound IdP identity is
not tied to the token's one-hour life.

## Entra and other IdP sign-in

`grant_type=oidc` verifies the IdP token against the install's SSO connection,
maps the token's roles or groups to a Sengol role, and issues a one-hour token
marked interactive. The IdP's `amr` and `acr` claims are copied into it.

* Step-up (`mfa` in `amr`) is checked only for CRITICAL-tier transitions, and
  only when `SENGOL_SOD_REQUIRE_STEP_UP` is set to `true`.
* The grant does not check what kind of token it was given. Do not map an
  app role or group to a Sengol role unless every holder is a person; an
  Entra service principal carries app roles in `roles` and would be treated as
  a signed-in person. Use `agent_oidc` for a non-human identity.
* A synced directory account used as a service account arrives like any user
  and is indistinguishable from a person.
* A user disabled through SCIM cannot exchange a new token. A token already
  issued stays valid until it expires, up to one hour. A user with no SCIM
  record is stopped only when the IdP stops issuing tokens.


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