Authentication 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 | | OAuth 2.0 (authorization_code) | Multi-user apps with a login flow; the Bonusly MCP server | 2-hour access tokens with refresh | 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 Company → Integrations → API & Tokens. 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. ## OAuth 2.0 If you're building a multi-user app — anything where end users log in with their own Bonusly account — use OAuth's authorization_code grant instead of a PAT. The same flow powers Bonusly's MCP server for AI agents. Bonusly only accepts the authorization_code grant (implicit and password are not supported), and PKCE is required for every authorization — confidential and public clients alike. Authorization requests without a code_challenge are rejected. ### The flow 1. Register an OAuth application at Company → Integrations → OAuth Applications. Save the client_id; if you're building a confidential server-side client, save the client_secret shown once at creation time. 2. Generate a per-request PKCE pair and a CSRF state. - code_verifier: 43-128 chars of random URL-safe alphabet. - code_challenge: base64url(SHA256(code_verifier)). - state: an unguessable random string. Store the verifier and the state server-side (or in a same-site, http-only cookie) keyed off the user's session. 3. Redirect the user to the authorization endpoint with the challenge and the state: https://bonus.ly/oauth/authorize ?client_id=<your-client-id> &redirect_uri=<your-redirect-uri> &response_type=code &scope=<space-separated scopes> &state=<random-state> &code_challenge=<base64url-sha256-of-verifier> &code_challenge_method=S256 4. On callback, validate state first. If the returned state doesn't match the value you stored for this user's session, abort — that's the signal of a CSRF attempt. Only after state matches should you read the code. 5. Exchange the code at POST https://bonus.ly/oauth/token, passing back the code_verifier you stored in step 2: grant_type=authorization_code code=<code-from-callback> code_verifier=<verifier-from-step-2> client_id=<your-client-id> redirect_uri=<your-redirect-uri> # plus client_secret if you have one Doorkeeper returns an access token (2-hour lifetime) and a refresh token if you requested the offline scope. 6. Send the access token as Authorization: Bearer … on every API request — exactly like a PAT. When the access token expires, use the refresh token to exchange for a fresh access token without prompting the user again. The same scope catalog applies to OAuth tokens as to PATs. ## 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 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. ## See also - Getting started — from zero to your first API call. - The API reference in the sidebar — every endpoint shows the scope it requires.
authorization_code) | Multi-user apps with a login flow; the Bonusly MCP server | 2-hour access tokens with refresh | 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 Company → Integrations → API & Tokens. 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. ## OAuth 2.0 If you're building a multi-user app — anything where end users log in with their own Bonusly account — use OAuth's authorization_code grant instead of a PAT. The same flow powers Bonusly's MCP server for AI agents. Bonusly only accepts the authorization_code grant (implicit and password are not supported), and PKCE is required for every authorization — confidential and public clients alike. Authorization requests without a code_challenge are rejected. ### The flow 1. Register an OAuth application at Company → Integrations → OAuth Applications. Save the client_id; if you're building a confidential server-side client, save the client_secret shown once at creation time. 2. Generate a per-request PKCE pair and a CSRF state. - code_verifier: 43-128 chars of random URL-safe alphabet. - code_challenge: base64url(SHA256(code_verifier)). - state: an unguessable random string. Store the verifier and the state server-side (or in a same-site, http-only cookie) keyed off the user's session. 3. Redirect the user to the authorization endpoint with the challenge and the state: https://bonus.ly/oauth/authorize ?client_id=<your-client-id> &redirect_uri=<your-redirect-uri> &response_type=code &scope=<space-separated scopes> &state=<random-state> &code_challenge=<base64url-sha256-of-verifier> &code_challenge_method=S256 4. On callback, validate state first. If the returned state doesn't match the value you stored for this user's session, abort — that's the signal of a CSRF attempt. Only after state matches should you read the code. 5. Exchange the code at POST https://bonus.ly/oauth/token, passing back the code_verifier you stored in step 2: grant_type=authorization_code code=<code-from-callback> code_verifier=<verifier-from-step-2> client_id=<your-client-id> redirect_uri=<your-redirect-uri> # plus client_secret if you have one Doorkeeper returns an access token (2-hour lifetime) and a refresh token if you requested the offline scope. 6. Send the access token as Authorization: Bearer … on every API request — exactly like a PAT. When the access token expires, use the refresh token to exchange for a fresh access token without prompting the user again. The same scope catalog applies to OAuth tokens as to PATs. ## 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 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. ## See also - Getting started — from zero to your first API call. - The API reference in the sidebar — every endpoint shows the scope it requires.Log in to see your API keys
| API Key | Label | Last Used | |
|---|---|---|---|