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

# Authentication

> Create an API client and exchange its credentials for a bearer token.

The public API uses the OAuth 2.0 client credentials grant. You create an API client in the
dashboard, exchange its credentials for a short-lived bearer token, and send that token on every
subsequent request. There is no refresh token: when the access token expires, ask for another one.

A token issued this way represents your organization, not a person. It carries only the scopes you
delegated to the client, and it is accepted only on the endpoints documented here.

## Create an API client

In the dashboard, open **Organization** and then **API**.

* The **client ID** is prefixed `ti_client_` and is safe to log.
* The **client secret** is shown once, at creation time, and cannot be retrieved afterwards. To
  replace a secret, create a new client and delete the old one.
* Creating a client requires an active subscription on the **Starter** plan or above.
* A client can be **blocked** and unblocked from the same screen. A blocked client cannot obtain new
  tokens, and answers exactly like an unknown one so a caller cannot tell the two apart.

## Get a token

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.thingidentity.com/oauth/token \
    -u "$TI_CLIENT_ID:$TI_CLIENT_SECRET" \
    -H "Accept: application/vnd.thingidentity.public.v1+json" \
    -d "grant_type=client_credentials"
  ```

  ```ts TypeScript theme={null}
  const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64")

  const response = await fetch("https://api.thingidentity.com/oauth/token", {
    method: "POST",
    headers: {
      Authorization: `Basic ${basic}`,
      Accept: "application/vnd.thingidentity.public.v1+json",
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({ grant_type: "client_credentials" }),
  })

  const { access_token, expires_in } = await response.json()
  ```

  ```python Python theme={null}
  import base64, requests

  basic = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()

  response = requests.post(
      "https://api.thingidentity.com/oauth/token",
      headers={
          "Authorization": f"Basic {basic}",
          "Accept": "application/vnd.thingidentity.public.v1+json",
      },
      data={"grant_type": "client_credentials"},
  )

  token = response.json()["access_token"]
  ```
</CodeGroup>

```json theme={null}
{
  "access_token": "eyJ0eXAiOiJ0aS1hcGkrand0...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

Send it on every other request as `Authorization: Bearer <access_token>`. The full request and
response reference, along with the failure codes, lives on
[`POST /oauth/token`](/api-reference/endpoints/get-an-access-token).

<Tip>
  Cache the token for its lifetime and refresh it shortly before expiry rather than requesting one
  per call. Expect `401` at any time regardless, and re-authenticate once on that signal.
</Tip>

## Scopes

You choose a client's scopes when you create it, in the dashboard.

| Scope         | Grants                                                                                                          |
| ------------- | --------------------------------------------------------------------------------------------------------------- |
| `write:codes` | Generate GS1 codes and Digital Link URIs. Required by [`POST /codes`](/api-reference/endpoints/generate-codes). |

Grant the narrowest set that does the job. A token missing the scope an endpoint requires answers
`403 Forbidden`.
