Skip to main content

Authentication

Two credentials share Authorization: Bearer.

Service keys​

Prefix esk_, then token_urlsafe(32). Stored as a 12-character prefix and a SHA-256 hash.

A key carries:

  • service (for example identity)
  • allowed_lanes
  • allowed_template_prefixes (a template key must start with one of them)
  • optional allowed_company_ids (empty means every company)

Issue a key as staff:

POST /api/v1/service-clients

or:

uv run python manage.py create_service_key --service identity --lanes auth,transactional --prefixes identity.

The command and the POST response print the key once. Callers set EMAIL_SERVICE_API_KEY.

Identity JWTs​

Admin routes verify RS256 tokens with the same JWKS client as storage-service and hosting-service.

SettingRole
IDENTITY_JWKS or IDENTITY_JWKS_FILEPinned public keys. Preferred in production.
IDENTITY_SERVICE_URLDev base URL. JWKS is {url}/.well-known/jwks.json.
IDENTITY_JWKS_URLExplicit JWKS URL.
IDENTITY_ISSUERRequired when DEBUG=false. Match identity JWT_ISSUER.
IDENTITY_AUDIENCERequired when DEBUG=false. Match identity JWT_AUDIENCE.
JWT_HS256_FALLBACK_SECRETLocal only, when identity is in DEBUG with HS256.

The principal needs is_staff or is_company_owner. If the token has company_id, it must equal the requested company unless the caller is staff. A mismatch is 403 company_mismatch.

GET /api/v1/metrics follows storage-service: staff, or the pat_agm claim, sees every company. A company owner sees their own company. The endpoint is not anonymous.

Health​

GET /api/v1/health does not require a credential.