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

# Authentication

> OAuth 2.0 client credentials, tokens and common auth errors

### How authentication works

The Zivio API uses the OAuth 2.0 **client credentials** grant. Your integration exchanges a client ID
and secret for a short-lived access token, then sends that token as a bearer token on every
request.

```
Authorization: Bearer <access_token>
```

The acting user is carried by the token.

### Identifying your organisation

The regional API endpoints (`api.zivio.net` and its regional variants) serve every Zivio
organisation, so each request must say which organisation it is for. Send your Zivio
organisation identifier in the `zivio-tenant-id` header:

```
Authorization: Bearer <access_token>
zivio-tenant-id: <your-tenant-id>
```

This applies to the token request as well as to API calls — the header is read before
authentication, so a request without it is rejected before your token is ever examined.

<Warning>
  A request to a regional endpoint without a valid `zivio-tenant-id` returns
  `404 Tenant Unknown`. If your first call fails this way, the header is missing or the
  identifier is wrong — it is not a problem with your token.
</Warning>

<Note>
  **Calling your own Zivio domain instead?** If you send requests to your organisation's own
  Zivio URL rather than a regional endpoint, the organisation is already identified by the
  hostname and the `zivio-tenant-id` header is not required. The regional endpoints are the
  documented, stable entry points and the ones the API Reference playground uses, so they are
  the recommended choice for new integrations.
</Note>

### Getting a token

<ParamField body="grant_type" type="string" required>
  Must be `client_credentials`.
</ParamField>

<ParamField body="client_id" type="string" required>
  Your application client ID.
</ParamField>

<ParamField body="client_secret" type="string" required>
  Your application client secret.
</ParamField>

<ParamField body="scope" type="string">
  Space-separated list of [scopes](/v4/scopes), for example
  `projects:read milestones:read invoices:read`.
</ParamField>

The request is form-encoded, not JSON:

```bash theme={null}
curl -X POST https://api.zivio.net/api/v4/oauth/token \
  -H "zivio-tenant-id: $ZIVIO_TENANT_ID" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=client_credentials \
  -d client_id=$ZIVIO_CLIENT_ID \
  -d client_secret=$ZIVIO_CLIENT_SECRET \
  -d scope="projects:read invoices:read"
```

<ResponseField name="access_token" type="string">
  The bearer token to send on subsequent requests.
</ResponseField>

<ResponseField name="token_type" type="string">
  Always `Bearer`.
</ResponseField>

<ResponseField name="expires_in" type="integer">
  Token lifetime in seconds.
</ResponseField>

<ResponseField name="scope" type="string">
  The scopes actually granted — the intersection of what you requested and what your client
  holds. Always read this rather than assuming you received everything you asked for.
</ResponseField>

### Token lifetime and refresh

Tokens expire — `expires_in` tells you when. The client credentials grant has no refresh
token: when a token expires, request a new one the same way you got the first.

<Tip>
  Cache the token for its lifetime rather than fetching one per request, and refresh slightly
  early so an in-flight request cannot straddle the expiry.
</Tip>

### Who am I?

`GET /welcome` returns the user the token acts as, together with the granted scopes. Call it
first in any new integration — it validates the token and tells you what you can do.

```bash theme={null}
curl https://api.zivio.net/api/v4/welcome \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "zivio-tenant-id: $ZIVIO_TENANT_ID"
```

```json theme={null}
{
  "message": "Welcome to Zivio",
  "owner": { "id": 12345, "email": "api-user@company.com" },
  "scopes": "welcome:read projects:read"
}
```

### Authentication errors

<AccordionGroup>
  <Accordion title="401 — invalid_token">
    The access token is missing, expired, revoked or malformed. Request a new token; if it
    still fails, confirm you are sending `Authorization: Bearer <token>` and not the raw
    client secret.

    ```json theme={null}
    {
      "error": "invalid_token",
      "error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
    }
    ```
  </Accordion>

  <Accordion title="403 — insufficient_scope">
    The token is valid but was not granted the scope this endpoint requires. The response
    names both the scope needed and the scopes you hold, so you can widen your token request.

    ```json theme={null}
    {
      "error": "insufficient_scope",
      "error_description": "The request requires higher privileges than provided by the access token",
      "required_scope": "projects:read",
      "provided_scopes": ["welcome:read"]
    }
    ```
  </Accordion>

  <Accordion title="404 — Tenant Unknown">
    The `zivio-tenant-id` header is missing or does not match a Zivio organisation. This is
    checked before authentication, so it is unrelated to your token or scopes. Confirm the
    identifier with your Zivio administrator.
  </Accordion>

  <Accordion title="400 — invalid_request or invalid_client">
    The token request itself was rejected. Check that the body is form-encoded
    (`application/x-www-form-urlencoded`, not JSON), that `grant_type` is exactly
    `client_credentials`, and that the client ID and secret are correct and not URL-escaped
    twice.
  </Accordion>
</AccordionGroup>

### Keeping credentials safe

* Treat the client secret like a password — environment variables or a secret manager, never
  source control.
* Request the narrowest set of [scopes](/v4/scopes) that does the job. A read-only integration
  should never hold a `:write` scope.
* Use separate clients for separate integrations so one can be revoked without disrupting
  the others.
