> This is the Markdown of https://backed.giving/developers/guides/authentication, a page of the Backed developer documentation. Every page is listed at https://backed.giving/developers/llms.txt.

# Authentication

Get a 5-day Bearer token from a credential with the OAuth 2.0 client-credentials grant, send it on every request, and choose the scopes a credential holds.

Every request to the API is made with an access token, and every token comes from an API credential. The API uses the OAuth 2.0 client-credentials grant: your system sends the credential’s Client ID and Client Secret to the token endpoint and gets back a Bearer token.

## Get a credential

An Administrator of your organisation creates the credential under `Settings > Profile and terms`, on the `API Access` tab, and chooses its scopes. The Client Secret is shown once. See [API access](https://admin.backed.giving/help/integrations/api-access) in the Help Centre.

## Ask for a token

Send the pair to `POST /oauth/token` on your organisation’s own address, form-encoded, with `grant_type=client_credentials`. You may send the Client ID and Client Secret in the form body, or with HTTP Basic authentication instead.

Request:

```bash
curl -X POST "https://your-org.backed.giving/oauth/token" \
  -d grant_type=client_credentials \
  -d client_id=your-client-id \
  -d client_secret=your-client-secret

# or, with HTTP Basic authentication:
curl -X POST "https://your-org.backed.giving/oauth/token" \
  -u your-client-id:your-client-secret \
  -d grant_type=client_credentials
```

Response 200:

```json
{
  "access_token": "q3Vx9LmT2sKp4Rz7Wn1YeB8cD5fG6hJ0kN_aQ-uIoPr",
  "token_type": "Bearer",
  "expires_in": 432000,
  "created_at": 1762650000
}
```

- `expires_in` is 432000 seconds: a token lasts 5 days.
- There is no refresh token. Each successful request issues a new token; when a request answers `401 invalid_token`, ask for a new one.
- The answer carries `Cache-Control: no-store`. Keep the token as you keep the secret.
- The token endpoint takes 30 requests a minute per `client_id`, counted before the secret is checked. Ask for a token when you need one, not before every call.

## Send the token

Send the token on every request in the `Authorization` header:

```bash
curl "https://your-org.backed.giving/api/v2/funding_rounds" \
  -H "Authorization: Bearer $BACKED_TOKEN"
```

Node.js 18 or later:

```js
const host = 'https://your-org.backed.giving';

async function token() {
  const response = await fetch(`${host}/oauth/token`, {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.BACKED_CLIENT_ID,
      client_secret: process.env.BACKED_CLIENT_SECRET,
    }),
  });
  if (!response.ok) throw new Error(`token: ${response.status}`);
  return (await response.json()).access_token;
}

const rounds = await fetch(`${host}/api/v2/funding_rounds`, {
  headers: { Authorization: `Bearer ${await token()}` },
}).then((response) => response.json());
```

A missing, invalid, expired or revoked token answers `401` with `invalid_token` and `WWW-Authenticate: Bearer error="invalid_token"`. Every request must use HTTPS: one sent over plain HTTP answers `403 https_required` and is not processed. See [Errors](https://backed.giving/developers/guides/errors.md).

## Scopes

A credential holds the scopes an Administrator ticked when they created or edited it. Every credential holds `read`. A write needs its own write scope, and a credential without it is answered `403 insufficient_scope`.

Scopes:

| Scope | What it allows |
| --- | --- |
| `read` | Read every /api/v2 resource. Every credential holds it. |
| `read:reporting_feed` | Read the OData v4 reporting feed at /odata/v4. |
| `write:registrations` | Create and edit registration entities and invite their users. |
| `write:applications` | Move funding applications, add notes and replace tags. |
| `write:assessments` | Assign Assessors to applications in an assessment round. |
| `write:post_award` | Create and approve milestones, mark payment batches paid, record contracts. |
| `write:files` | Upload files to deliverables, application notes and registration entities. |

## Who a write acts as

A credential with a write scope acts as one Administrator, chosen under `Acts as`. Every write runs with that person’s permissions, Data Access Rules and approval limits, so it can do nothing they could not, and the audit log records the credential and the Administrator together. A credential with no enabled Administrator to act as is answered `403 run_as_disabled` on every write. The write routes are described in [Write API: registrations, applications, notes, tags and assignments](https://admin.backed.giving/help/integrations/api-c02-write-api-part-one-registrations-applications-no) and [Write API: milestones, payments, contracts and files](https://admin.backed.giving/help/integrations/api-c03-write-api-part-two-milestones-payments-contracts).

## Rate limits

A credential may make 120 requests a minute, the API and the OData feed together. Past that, the API answers `429 rate_limited` with a `Retry-After` header: wait that many seconds, then carry on.
