Authentication
Provenance authenticates every request (except /health) with an API key.
Key format
Keys are prefixed by environment:
prv_live_<32 base62>
prv_test_<32 base62>
We store only a SHA-256 hash of the key, its 8-character display prefix, and its last 4 characters, so the dashboard can show prv_test_…a1B2 and you can tell keys apart (keys created before last-4 display existed show the prefix only). The raw key is shown once, at creation. If you lose it, revoke and issue a new one.
Passing your key
Either header works:
curl $PROVENANCE_API/v1/claims \
-H "Authorization: Bearer $PRV_KEY"
# or
curl $PROVENANCE_API/v1/claims \
-H "X-API-Key: $PRV_KEY"
A missing key returns authentication_required; a bad one, invalid_api_key.
Sandbox keys
Three keys are pre-provisioned in the sandbox and safe to publish — they only ever touch sandbox data:
| Key | Scopes |
| --- | --- |
| prv_test_full_K7mN3xQp9RbT2vWm8sYd4cFjLhXn6gAe | all |
| prv_test_verify_M3xQp9RbT2vWmK7n8sYd4cFjLhXe6gA | verify |
| prv_test_attest_p9RbT2vWmK7n3xQ8sYd4cFjLhXe6gAM | attest |
Scopes
Each route requires one scope: verify, attest, escalate, admin, intent, or screen. A key missing the required scope gets insufficient_scope (403). Grant least privilege — a verify-only integration should carry only verify.
Self-serve Authorize keys also carry a plan (developer, growth, or enterprise) that governs metering and the monthly cap — see Billing.
Rotation & lost keys
Create a replacement key, deploy it, then revoke the old one. Revoked keys return api_key_revoked; expired keys, api_key_expired. Because we never store the raw key, "recovering" a lost key is impossible by design — rotate instead.
Sandbox vs live
Sandbox and live are fully isolated at the query level. A sandbox key can never read or write live data; crossing environments returns environment_mismatch. See Environments.
Dashboard login vs. API keys
There are two ways to authenticate, for two different callers:
- API keys (this page) authenticate programmatic access — the SDK, scripts, and every
/v1/*data endpoint. Passed as a Bearer token orX-API-Keyheader. This is unchanged. - Human login authenticates a person in the dashboard. It's passwordless: enter your email, click the magic link, and the browser gets an HttpOnly session cookie. No password, and the session never touches your API keys. During the private beta a deployment can restrict who may sign in (
DASHBOARD_LOGIN_ALLOWLIST); anyone else getsbeta_access_required(403) and a pointer to the waitlist.
Account-management endpoints accept either. /v1/policy* and /v1/account/* work with an account-bearing API key or a dashboard session, so anything you do in the UI you can also script. Cookie-authenticated mutations additionally require a CSRF header (the dashboard handles this); API-key calls are exempt. Product-section visibility in the dashboard is driven by the account's products entitlement — a UX convenience only; real authorization is still enforced per API scope and per-account policy on the server.
Magic-link tokens and session secrets are stored only as hashes (like API keys), tokens are single-use with a short TTL, and in sandbox the sign-in link is echoed in the response so you can log in without an inbox.
Teams, roles & SSO (Enterprise)
An account is an organization; users are members with a role. Magic-link stays for self-serve — teams and SSO are additive (a single-user account behaves exactly as before).
- Roles (v1, fixed):
owner,admin(keys/policies/webhooks/team),approver(step-up approvals),analyst(read-only claims/analytics),developer(keys + docs). Role-gated routes return403 insufficient_rolewhen the acting user lacks the role. API keys are org-scoped machine identity and act with full (owner) authority for their scopes. - Invite a member:
POST /v1/org/users/invite { email, role }(owner/admin). They accept viaPOST /v1/org/invites/accept { token }, which issues a role-scoped session.prov.org.invite(...)in the SDK. - OIDC SSO (Okta / Entra): configure per-org with
PUT /v1/auth/sso/config { issuer, client_id, client_secret, domain }. Login is the standard authorization-code flow with state + nonce + PKCE:GET /v1/auth/sso/start?domain=…→ the IdP →POST /v1/auth/sso/callback { code, state }verifies the id_token (RS256/ES256) against the IdP's JWKS and issues a session. Users are JIT-provisioned (least-privilegeanalyst). - Deprovisioning (SCIM-lite):
DELETE /v1/org/users/:idmarks a user deprovisioned; their sessions are invalidated on the next request.
SSO, teams, and roles beyond owner are an Enterprise feature (requirePlan(enterprise)).