OAuth endpoints
OAuth 2.0 authorization code flow with stateless JWT access + refresh tokens.
OAuth 2.0 authorization code flow with stateless JWT access + refresh tokens.
Every route takes Authorization: Bearer <ACCESS_TOKEN> unless the auth line says otherwise. Ids are numbers. Errors use the shape on the errors page.
| Route | Scope | What it does |
|---|---|---|
GET /integrations/install/{client_id} | none | Start the OAuth authorization code flow. |
POST /v1/oauth/token | none | Exchange an authorization code for an access token and refresh token. |
POST /v1/oauth/token | none | Use the refresh token to obtain a new access token. |
Send the user to the install URL
GET https://app.bluereacher.com/integrations/install/{client_id}?redirect_uri={redirect_uri}&state={state}Auth: none. The user's browser opens this URL.
Start the OAuth authorization code flow.
This is a dashboard URL (https://app.bluereacher.com), not an API-host endpoint — you redirect the user's browser here. They see a consent page listing the scopes your app requests. If they are not logged in, Blue Reacher redirects them to the login page first and returns them here after sign-in. When they click Install, the browser is redirected to your redirect_uri with a single-use ?code= query parameter, plus state echoed verbatim when you sent one. If they click Cancel, the browser is redirected to your redirect_uri with ?error=access_denied (and the same state) and no code is issued.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string | yes | The public client_id of your app (issued when your app is registered). |
redirect_uri | query | string | yes | Where Blue Reacher should send the user after consent. Must match the redirect_uri registered on the app. |
state | query | string | no | Opaque value generated by your app (CSRF token). It is not interpreted by Blue Reacher and is returned unchanged, URL-encoded, as state on both the success and the cancel redirect. Verify it on your callback before exchanging the code. |
Request
https://app.bluereacher.com/integrations/install/<CLIENT_ID>?redirect_uri=<YOUR_CALLBACK>&state=<RANDOM>Responses
A 302 reply (On Install: redirect to your redirect_uri with ?code=<32-char hex> (and your state)):
{
"Location": "https://your-app.com/oauth/callback?code=4f8a7e1d2c9b3a5f6e0d1c2b7a8f9e6d&state=xyz123"
}| Status | When | Message |
|---|---|---|
| 302 | On Install: redirect to your redirect_uri with ?code=<32-char hex> (and your state) | |
| 302 | On Cancel: redirect to your redirect_uri with error=access_denied (and your state); no code is issued |
Exchange code for tokens
POST https://api.bluereacher.com/v1/oauth/tokenAuth: client_id and client_secret in the JSON body. No Bearer token.
Exchange an authorization code for an access token and refresh token.
Server-to-server. Send the code your callback received along with your client_id + client_secret. Returns both tokens — store them in your database, keyed by the installing user. Codes are single-use and expire 10 minutes after consent: a second exchange with the same code returns an error indicating that the code has already been used. If the exchange times out or returns a 5xx, retry it once; if the retry reports the code as already used, the first attempt was processed and no tokens can be recovered — send the user through the install URL again. The token endpoints are not covered by the per-installation API rate limits and do not return 429.
Request
curl -X POST "https://api.bluereacher.com/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "4f8a7e1d2c9b3a5f6e0d1c2b7a8f9e6d",
"client_id": "<YOUR CLIENT_ID>",
"client_secret": "<YOUR CLIENT_SECRET>",
"redirect_uri": "https://your-app.com/oauth/callback"
}'Responses
A 200 reply (Success — tokens issued):
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"scopes": [
"contacts.read",
"contacts.write"
]
}
}| Status | When | Message |
|---|---|---|
| 200 | Success — tokens issued | |
| 400 | Invalid or expired authorization code | Invalid or expired authorization code |
| 400 | Code already used (single-use enforcement) | Authorization code has already been used. Re-start the install to get a fresh code. |
Refresh tokens
POST https://api.bluereacher.com/v1/oauth/tokenAuth: client_id and client_secret in the JSON body. No Bearer token.
Use the refresh token to obtain a new access token.
When you get a 401 from the API ("Invalid or expired access token"), call this endpoint with your stored refresh_token to get a fresh pair. Replace both stored tokens with the new values. Access tokens expire after 24 hours (expires_in); refresh tokens after 1 year. Every refresh returns a new refresh token, but tokens are stateless signed JWTs: the previous refresh token is NOT invalidated by rotation and stays usable until its own expiry or until the installation is suspended. Keep only the newest pair. Because the old token stays valid, a refresh that times out or returns a 5xx is safe to retry with exponential backoff (for example 1 s, 2 s, 4 s). The token endpoints are not covered by the per-installation API rate limits and do not return 429.
Request
curl -X POST "https://api.bluereacher.com/v1/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "<YOUR_REFRESH_TOKEN>",
"client_id": "<YOUR CLIENT_ID>",
"client_secret": "<YOUR CLIENT_SECRET>"
}'Responses
A 200 reply (Fresh tokens issued):
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"scopes": [
"contacts.read",
"contacts.write"
]
}
}| Status | When | Message |
|---|---|---|
| 200 | Fresh tokens issued | |
| 401 | Refresh token invalid or revoked | Invalid or expired refresh_token |

