---
description: >-
  OAuth 2.0 client-credentials authentication for the Inyo KYC API via POST /oauth/token — the sessions and verifications scopes, token lifetime and refresh, and the RFC 6749 error responses.
---

# Authentication

The KYC API uses the **OAuth 2.0 client-credentials grant** (RFC 6749 §4.4). Your backend exchanges a `client_id` and `client_secret` for a short-lived Bearer token, then presents that token on every `/v1/*` call.

> Credentials are server-side only. Never ship a `client_secret` — or an access token — to a browser or mobile app. Your customers interact with the [widget](widget-delivery.md), never with the API.

***

### Request a Token

**Endpoint:** `POST /oauth/token`\
**Content-Type:** `application/x-www-form-urlencoded`

```bash
curl --request POST \
  --url https://{FQDN}/oauth/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data grant_type=client_credentials \
  --data client_id=$KYC_CLIENT_ID \
  --data client_secret=$KYC_CLIENT_SECRET
```

Credentials may also be sent as HTTP Basic authentication (`client_secret_basic`) instead of in the body:

```bash
curl --request POST \
  --url https://{FQDN}/oauth/token \
  --user "$KYC_CLIENT_ID:$KYC_CLIENT_SECRET" \
  --data grant_type=client_credentials
```

**Response:**

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "sessions verifications"
}
```

`grant_type` must be `client_credentials` — it is the only grant this endpoint supports.

***

### Scopes

Each endpoint requires a specific scope. Tokens are issued with the scopes granted to your client; request a narrower set with an optional `scope` parameter when a component of your system only needs one of them.

| Scope | Grants access to |
| ----- | ---------------- |
| `sessions` | `POST /v1/sessions`, `GET /v1/sessions/{sessionId}` |
| `verifications` | `POST /v1/verifications` |

```bash
--data scope=sessions
```

Requesting an unknown scope is rejected rather than silently ignored. A token that lacks the scope an endpoint requires returns `403`, not `401` — the token is valid, it just is not authorized for that call.

***

### Using the Token

Present the token as a Bearer credential:

```bash
curl --request POST \
  --url https://{FQDN}/v1/sessions \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"userRef": "user-123"}'
```

***

### Token Lifetime and Refresh

Tokens are RS256-signed JWTs and expire — `expires_in` tells you when, in seconds. Cache the token and refresh it before expiry; do not request a new token per API call, and do not hardcode a token anywhere.

A robust client:

1. Caches the token in memory with its expiry timestamp.
2. Refreshes when the remaining lifetime drops below a small margin (30-60 seconds).
3. Retries once on a `401`, in case the token expired between the check and the call.

***

### Error Responses

Token endpoint errors follow RFC 6749 §5.2 — an `error` code with a human-readable `error_description`:

| Status | `error` | Cause |
| ------ | ------- | ----- |
| `400` | `unsupported_grant_type` | `grant_type` was absent or not `client_credentials` |
| `400` | `invalid_scope` | The requested `scope` contains an unknown value |
| `401` | `invalid_client` | Missing, unknown, or incorrect client credentials. The response carries a `WWW-Authenticate: Basic` challenge |

```json
{
  "error": "invalid_client",
  "error_description": "Missing client credentials"
}
```

Errors on `/v1/*` calls:

| Status | Cause | How to fix |
| ------ | ----- | ---------- |
| `401` | No `Authorization: Bearer` header | Send the header; obtain a token at `POST /oauth/token` |
| `401` | Token expired | Refresh the token and retry |
| `401` | Token invalid or unverifiable | Confirm you are using the token verbatim, and against the matching environment |
| `403` | Token is valid but lacks the endpoint's scope | Request a token carrying the required scope |

The `403` response includes `WWW-Authenticate: Bearer error="insufficient_scope"` naming the scope that was required.

***

### Environment Isolation

Sandbox and production issue separate credentials, and each token is valid only in the environment that issued it. Sessions are strictly scoped to the tenant that created them: a token can never read another tenant's session, and a request for one returns `404`.

***

### Rotating Your Secret

Client secrets are rotated by Inyo on request — contact your Inyo representative rather than attempting rotation through the API. Rotate immediately if a secret may have been exposed.

***

### Next Steps

* [Getting Started](getting-started.md) — run a full verification with your new token
* [Verification Sessions](sessions.md) — every field on `POST /v1/sessions`
