Skip to content

Authentication

The API uses the OAuth 2.0 client credentials grant (RFC 6749 section 4.4). Your integration exchanges a client_id and client_secret for a short-lived access token, then sends that token on every request.

POST /oauth/token with grant_type=client_credentials. Send the credentials with HTTP Basic (recommended) or in the form body.

Terminal window
curl -X POST https://api.uniqueonthego.com/oauth/token \
-u "$UNIQUE_CLIENT_ID:$UNIQUE_CLIENT_SECRET" \
-d grant_type=client_credentials
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDkifQ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "tasks:read locations:read"
}

Send it in the Authorization header:

GET /v1/tasks?from=2026-09-26T00:00:00Z&to=2026-09-27T00:00:00Z HTTP/1.1
Host: api.uniqueonthego.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDkifQ...

Tokens are never accepted as query parameters.

Scope Grants
tasks:read GET /v1/tasks and GET /v1/tasks/{id}
locations:read GET /v1/locations

By default a token receives every scope granted to the client. Pass scope=tasks:read to request a narrower token.

  • Tokens are valid for expires_in seconds (one hour).
  • Cache the token and reuse it for every request until it is about to expire. Refresh it about five minutes early to avoid races.
  • The token endpoint has its own, lower rate limit. Requesting a token per API call will get you throttled. See Rate limits.
  • There are no refresh tokens. When a token expires, request a new one with your credentials.
let cached = null;
async function getToken() {
if (cached && Date.now() < cached.expiresAt - 5 * 60 * 1000) {
return cached.token;
}
const response = await fetch("https://api.uniqueonthego.com/oauth/token", {
method: "POST",
headers: {
Authorization:
"Basic " + btoa(`${process.env.UNIQUE_CLIENT_ID}:${process.env.UNIQUE_CLIENT_SECRET}`),
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials",
});
if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
const body = await response.json();
cached = { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 };
return cached.token;
}

The token endpoint follows the OAuth error format from RFC 6749 section 5.2:

Status error Meaning
400 invalid_request A required parameter is missing or duplicated.
400 unsupported_grant_type Only client_credentials is supported.
400 invalid_scope The requested scope is not granted to this client.
401 invalid_client Wrong, revoked or expired credentials.

Every other endpoint uses the standard error format. A missing or expired token returns 401 unauthorized; a token without the required scope returns 403 forbidden.