Obtaining tokens
Every request to an ARC endpoint carries an OAuth2 bearer access token. Tokens are issued by Osigu's SSO server via the client-credentials grant — no user login flow, no refresh tokens, no PKCE. You have a client_id, a client_secret, and you exchange them for an access token you cache and reuse.
The one endpoint you'll call
POST /v1/oauth/token on Osigu's SSO server. Same shape in both environments — only the host differs:
| Environment | Host |
|---|---|
| Sandbox | https://sandbox.osigu.com |
| Production | https://api.osigu.com |
Full URL for sandbox: https://sandbox.osigu.com/v1/oauth/token.
Request
Standard OAuth2 client-credentials request. Send client_id / client_secret as HTTP Basic auth (RFC 6749 §2.3.1) — do not put them in the body.
curl -X POST https://sandbox.osigu.com/v1/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"
That's the entire request. No scope parameter is needed — the scopes are pre-configured server-side per client_id and returned in the response.
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "arc:read arc:write dispensing:read dispensing:write ehr:read ehr:write"
}
access_token— the JWT you attach to every subsequent request.token_type— alwaysbearer.expires_in— token lifetime in seconds. Default is 3600 (1 hour).scope— space-delimited scopes the token carries. Depends on yourclient_idconfiguration.
Attaching the token
Authorization: Bearer <access_token> on every ARC / Dispensing / EHR call:
curl https://sandbox.osigu.com/arc/v1/authorization-requests/areq_... \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
Cache aggressively
The token endpoint is rate-limited to 10 requests per minute per client_id. Do not fetch a fresh token per API call.
- Cache the token in your service (in-memory is fine).
- Refresh 50 minutes after issuance (leaves 10 minutes of slack for clock skew and slow refresh).
- Serialise refresh calls — under load, multiple concurrent workers should reuse a single refresh, not stampede the SSO server.
A common pattern:
if cached_token and cached_token.expiry > now + 5min:
return cached_token
with lock:
if cached_token and cached_token.expiry > now + 5min:
return cached_token
cached_token = fetch_new_token()
cached_token.expiry = now + response.expires_in
return cached_token
What the token carries
The JWT payload includes:
sub— yourclient_id.provider_slug— a custom claim identifying which provider this token acts as. This is how the ARC and ePrescription APIs derive the provider without you passingprovider_idon every call.scope— the space-delimited scope list.exp/iat— expiration and issued-at.
Osigu's APIs validate the signature against the SSO server's JWKS. Don't try to forge the JWT or edit its claims — signature verification will fail.
Errors
| Status | Cause | Fix |
|---|---|---|
401 Unauthorized | Bad client_id / client_secret. | Check credentials; note sandbox vs. production credentials are different. |
400 Bad Request — invalid_grant | Missing or malformed grant_type. | Send grant_type=client_credentials in the body as form-urlencoded. |
429 Too Many Requests | You're refreshing the token too often. | Cache. |
403 Forbidden on downstream API with a valid token | Token doesn't carry the scope needed for that endpoint. | Contact Osigu support to widen the scope. |
Secret rotation
If your client_secret leaks, email security@osigu.com immediately. Osigu can invalidate the old secret and issue a new one in a few minutes. Tokens signed under the old secret continue to work until they expire (max 1 hour), so you have a small overlap window to roll the new secret across your workers.
Related concepts
- OTP flow — an additional challenge on top of the access token for sensitive operations.
- API Reference — Authentication & OTP — the two OTP endpoints exposed publicly.