# Machine to machine
Use this integration when your backend calls the Done OS API on its own behalf and no user signs in. It uses the OAuth 2.0 client credentials flow.

Before you start, get a client ID and a client secret. See [Request a client](/request-a-client).

## Get an access token

Send a `POST` request to the token endpoint of the environment:

| Environment | Token endpoint |
|---|---|
| Sandbox | `https://id.sandbox.done.ai/realms/done/protocol/openid-connect/token` |
| Production | `https://id.done.ai/realms/done/protocol/openid-connect/token` |

Use the header `Content-Type: application/x-www-form-urlencoded` and these body parameters:

| Parameter | Value |
|---|---|
| `grant_type` | `client_credentials` |
| `client_id` | Your client ID |
| `client_secret` | Your client secret |

Example for sandbox:

```bash
curl -X POST https://id.sandbox.done.ai/realms/done/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret"
```

Done ID returns `200 OK` and a JSON body:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 300
}
```

| Field | Description |
|---|---|
| `access_token` | The token that you send to the Done OS API. |
| `token_type` | Always `Bearer`. |
| `expires_in` | The lifetime of the token in seconds. |

The response also contains standard OAuth 2.0 fields, for example `scope`, `refresh_expires_in`, and `not-before-policy`. You can ignore them for this flow.

## Call the API

Send the access token in the `Authorization` header:

```bash
curl https://api.sandbox.done.ai/<path> \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The API reference for each environment is in the external OpenAPI file, for example `https://api.sandbox.done.ai/openapi/external_v1.json`.

## Renew the token

The client credentials flow gives no refresh token. When the token expires, request a new one with the same request.

1. Keep the access token in memory and use it for all calls until it expires.
2. Request a new token a short time before `expires_in` ends.
3. If the API returns `401 Unauthorized`, request a new token and send the call again one time.

Do not request a new token for each API call.

## Errors

If the request fails, Done ID returns `400 Bad Request` or `401 Unauthorized` with a JSON body:

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

| Error | Cause |
|---|---|
| `invalid_client` | The client ID or the client secret is wrong. |
| `invalid_request` | A parameter is missing or incorrect. |
| `unauthorized_client` | The client cannot use this grant type. You possibly use a login client for a machine-to-machine call. |
| `unsupported_grant_type` | The value of `grant_type` is incorrect. |

If you contact the Done team about an error, send the client ID, the error message, and the time of the request. Never send the client secret.

## Token contents

The access token identifies your client and your company. See [Tokens](/tokens#machine-to-machine-tokens).

## See also

- [Done ID for partner apps](/)
