# Authentication

Details on how to authenticate with the Bonusly API

The Bonusly API accepts two credential types. You'll almost
always want a **Personal Access Token (PAT)**. OAuth exists for
multi-user apps with a login flow (and for agentic integrations
via the Bonusly MCP server).

| Credential                      | Use case                                                                 | Lifespan                                    | Scopes                    |
| ------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------- | ------------------------- |
| **Personal Access Token (PAT)** | Scripts, server-to-server jobs, SCIM, Digital Signage, most integrations | Up to 365 days; you choose at creation time | Fine-grained per resource |

## Personal Access Tokens

PATs are bearer tokens created by a company admin from the
**Company → Integrations → API & Tokens** page.

### Creating a token

1. Sign in as a **Global** or **Tech** admin.
2. Go to **Profile Settings →&#x20;**<Anchor target="_blank" href="https://app.bonus.ly/user/settings/services">**API Tab**</Anchor> o&#x72;**&#x20;Company → Integrations →&#x20;**[**API & Tokens**](https://app.bonus.ly/company/integrations/personal-access-tokens) (if you're a Bonusly admin).
3. Click **Create token**.
4. Fill in:
   * **Name** — anything memorable; this is how you'll
     identify the token in the listing later.
   * **Expiration** — number of days, up to 365. Hard maximum,
     no exceptions.
   * **Scopes** — pick the minimum set the integration needs.
     Bonusly hides scopes your admin role can't grant.
5. Click **Create**. The token string is shown **once** —
   copy it before closing the dialog. We store only a hash, so a
   lost token can't be recovered; you'd have to create a new one.

> **Tip**: create one PAT per integration. If a vendor changes
> or a token leaks, you can revoke that one token without
> disrupting anything else.

### Sending a PAT on a request

Pass the token in the standard `Authorization: Bearer …` header:

```bash
curl https://bonus.ly/api/public/whoami \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

If your integration only supports an `access_token=…` query
parameter, that works too — but we recommend the header form
because query parameters can leak into HTTP server logs.

```bash
curl "https://bonus.ly/api/public/whoami?access_token=YOUR_TOKEN_HERE"
```

### Scopes

Scopes are the read/write/administer permissions you ask for at
creation time. They're grouped by resource family (users,
recognitions, awards, finance, company, analytics, uploads,
etc.) with a `read`, `write`, or `administer` flavor depending
on what you need. Pick the minimum set the integration actually
needs — adding the `administer` flavor to a resource that only
needs `read` is unnecessary blast radius if the token ever
leaks.

The set of scopes you can grant is gated by your admin role.
Global admins can grant any scope; Tech admins can grant a
subset. **The full, authoritative list is shown to you in the
create-token modal** with anything your role can't grant
already filtered out — you can't accidentally request a scope
the API will then refuse.

At request time, the API rejects calls whose token doesn't
include the scope the endpoint requires.

> **Finding the scope an endpoint needs**: every endpoint's
> documentation page in the API reference lists the scope (or
> scopes) it requires under the **Authorization** heading. If
> you're not sure what to ask for when creating a token, open
> the docs for the endpoints your integration will call and
> collect the union of their required scopes.

### Expiration and renewal

Every PAT expires — there is no perpetual token. We email the
token owner twice before expiration:

* **30 days out** — heads-up, plan a renewal.
* **7 days out** — final reminder.

When a token expires it stops working. To renew:

1. Create a fresh token from the **API & Tokens** page with the
   same scopes (use the old token's name as a reference).
2. Update the integration's stored credential with the new
   token.
3. Revoke the old, now-expired row from the table to keep things
   tidy.

There's no auto-rotation; renewals are a deliberate human
action.

### Revoking a token

Revocation is immediate. Find the row in the **API & Tokens**
table and click **Revoke**. The next request bearing that
token gets a `401 Unauthorized`.

Bonusly will also revoke PATs automatically in one situation:
**7 days after a company subscription is canceled**, if the
cancellation hasn't been reversed. If you resubscribe inside that
7-day window, nothing happens to your tokens. If you resubscribe
after the auto-revocation, you'll need to create fresh tokens.

<br />

## Sample request

A canonical authenticated call:

```bash
curl https://bonus.ly/api/public/whoami \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Accept: application/json"
```

Response (truncated):

```json
{
  "success": true,
  "message": "authenticated",
  "company": { "id": "abc123", "name": "Acme Corp" },
  "application": { "id": "def456", "name": "My Integration" },
  "scopes": ["user:read", "awards:read"],
  "user": null
}
```

## Errors you might see

| Status                  | What it means                                                             | What to do                                                                                                                                                                             |
| ----------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized`      | The bearer is missing, malformed, or unknown.                             | Confirm the `Authorization` header includes `Bearer ` and a non-empty token.                                                                                                           |
| `401 Unauthorized`      | The bearer is expired, revoked, or the owner was deactivated.             | Create a new token.                                                                                                                                                                    |
| `403 Forbidden`         | The bearer is valid but doesn't carry the scope required by the endpoint. | Create a new token with the right scope, or use a different endpoint.                                                                                                                  |
| `429 Too Many Requests` | The company has exceeded the rate limit for this operation.               | Honor the `Retry-After` header before retrying. See [Rate limits](https://docs.bonus.ly/docs/getting-started#rate-limits) on the Getting started page; limits vary by operation and can be raised on request. |

For the full picture of error envelopes and rate-limit headers,
see [Getting started](https://docs.bonus.ly/docs/getting-started).

## See also

* [Getting started](https://docs.bonus.ly/docs/getting-started) — from zero to your
  first API call.
* The **API reference** in the sidebar — every endpoint shows the
  scope it requires.

<br />