Authentication

The OneFinOps API uses OAuth 2.0 client credentials. You exchange a client id and secret for a short-lived bearer token, then pass that token on every API call.

Issue an OAuth client

OAuth clients are managed from the Developer hub in your OneFinOps dashboard.

  • The Live tab issues production clients with id prefix ofin_live_*.
  • The Sandbox tab issues sandbox clients with id prefix ofin_test_*.

When you create a client we return the client_id (visible forever) and client_secret (shown once). Store the secret in a secrets manager — we cannot recover it; if you lose it, rotate.

The scope onefinops.api is attached automatically. You don't pick per-action scopes and you don't pass scope=... on the token request.

Token endpoint

Production and sandbox share the same Keycloak realm. The token endpoint is the same for both environments:

https://login.onefinops.com/realms/onefinops/protocol/openid-connect/token

Which environment the token runs against is decided by the API host you call:

EnvironmentAPI hostClient prefix
Productionhttps://api.in.onefinops.comofin_live_*
Sandboxhttps://sandbox-api.in.onefinops.comofin_test_*

Request a token

curl -X POST 'https://login.onefinops.com/realms/onefinops/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=ofin_test_<your-client-id>' \
  -d 'client_secret=<your-client-secret>'

Response:

{
  "access_token": "eyJhbGciOi...",
  "expires_in": 900,
  "token_type": "Bearer",
  "scope": "onefinops.api"
}

The token is a JWT — your code doesn't need to parse it, but it does carry:

  • aud: onefinops.api (the API audience)
  • organization_id: pins the token to your tenant; you cannot override this in the request body or path.
  • exp: token expiry (15 minutes from issue).

Pass the token on every call as Authorization: Bearer <access_token>.

Token lifetime + caching

Tokens are valid for 15 minutes. Cache them — the token endpoint is rate-limited and minting one per request will eventually return 429.

A simple cache rule: mint a new token when the current one has < 60 seconds left. Most OAuth client libraries handle this automatically.

Permissions

Per-action OAuth scopes (einvoice:generate etc.) do not exist on this API. What your token can do is governed by:

  • Your plan / entitlement. If your plan includes only e-invoicing, a call to /v1/ewaybills/* will authenticate successfully but be rejected at the entitlement layer.
  • Per-client rate limits. Applied per OAuth client per minute, regardless of which endpoint is hit. See Rate limits.

We chose this over per-scope authorization deliberately: you don't have to maintain a scope catalogue in client config, and we can enable or disable capabilities on your account without forcing you to rotate keys.

Rotating secrets

Rotate a client's secret from the Developer hub at any time. Rotation issues a new secret and starts a 24-hour overlap window during which both the old and new secrets work — long enough to roll your deployment without downtime. After 24 hours, the old secret is revoked.

Common mistakes

  • Using the token endpoint as the API base URL. The token endpoint is on login.onefinops.com; the API itself is on api.in.onefinops.com (production) or sandbox-api.in.onefinops.com (sandbox).
  • Calling sandbox endpoints with a production client. Use the ofin_test_* client for sandbox and the ofin_live_* client for production. Mixing them returns 401 because the client's allowed audience doesn't match the API host.
  • Embedding the client secret in browser or mobile code. Client credentials is a server-to-server flow. A secret shipped to a public client can be extracted.
  • Minting a token per request. Cache and reuse. The token endpoint will rate-limit aggressive churn.
  • Sending scope=einvoice:generate or similar. No per-action scopes exist. Omit the scope parameter entirely.

Where to next