API Authentication¶
Applies to: All subscriptions
Purpose¶
Show exactly what to send on each API surface, and how to tell one authentication failure from another. Most integration delays at this stage come from confusing "not authenticated" with "not permitted".
Audience¶
Integration developers.
Prerequisites¶
- Machine-to-Machine Authentication — the model
- API Conventions
- Credentials issued for your environment
Steps¶
1. Identify which credential your surface needs¶
| Surface | Credential |
|---|---|
| Ingest | Machine credential |
| Integration (write-back) | Machine credential |
| Mapping templates | User token |
| Administration | User token |
Machine credentials cannot call administrative endpoints, and user tokens are not intended for ingest. This separation is deliberate.
2. Present a machine credential¶
Either form is accepted on ingest and integration endpoints:
Identity-provider token (preferred):
Authorization: Bearer eyJ0eXAiOiJKV1Qi...
Content-Type: application/json
Static key (supported, primarily for migration):
X-API-Key: cfly_<company>_<gstin>_<suffix>
Content-Type: application/json
The static key may also be sent as a bearer token; the platform distinguishes them by prefix.
3. Obtain an identity-provider token¶
The ERP requests a token using the client-credentials flow against the customer's identity tenant. The token must:
| Requirement | Failure if missing |
|---|---|
| Be issued by the configured tenant | Rejected as unauthenticated |
| Carry an accepted audience | Rejected as unauthenticated |
| Be unexpired | Rejected, with an expiry indication |
| Carry the required application role | Rejected as forbidden — the token is valid, it simply lacks the granted role |
The last row is the most common OAuth stumble. A token that authenticates but is refused means the application role was never granted through admin consent — not that the credential is wrong.
Cache the token until shortly before expiry. Requesting a new token per call is unnecessary and will attract rate limiting.
4. Present a user token¶
User-token surfaces need both the token and the registration being worked on:
Authorization: Bearer <session token>
X-GSTIN: 29AAACW3775F000
Content-Type: application/json
The registration header exists for tenant isolation. Omitting it is refused for ordinary users.
5. Distinguish the failures¶
This table resolves most integration authentication problems:
| Response | Meaning | Fix |
|---|---|---|
401 TOKEN_MISSING |
No credential presented | Send the header |
401 TOKEN_INVALID |
Malformed, wrong audience, wrong issuer, or bad signature | Decode the token and compare against the configuration |
401 TOKEN_EXPIRED |
Expired, or the clocks disagree | Refresh. If it recurs, check time synchronisation on both hosts |
403 missing role |
Valid token, role not granted | Grant the application role through admin consent |
403 OAUTH_REQUIRED |
This client has been switched to identity-provider tokens only | Complete the migration; the static key is now refused |
403 GSTIN_NOT_PERMITTED |
Authenticated, but the document's supplier registration is outside the credential's scope | Widen the scope deliberately, or send from the correct credential |
401 means "I do not know who you are". 403 means "I know who you are, and no". Treat them differently in your error handling: a 401 warrants a credential refresh and one retry; a 403 never does.
6. Handle credential rotation¶
| Credential | Rotation |
|---|---|
| Identity-provider token | Refreshed automatically by your client before expiry |
| Static key | Issue the new key, migrate the caller, verify, then revoke the old one |
A static key is shown once when issued. Store it in a secret manager at that moment; it cannot be retrieved afterwards.
Validation¶
| Check | Method | Pass condition |
|---|---|---|
| Credential authenticates | Call a low-risk endpoint | Accepted |
| Token is cached | Inspect your client's behaviour | One token serves many calls |
| Refresh works | Let a token expire | Refreshed automatically, without a failed business call |
| Scope enforced | Submit for an out-of-scope registration | 403, distinguishable from 401 |
| Failures distinguished | Force each of 401 and 403 |
Your code takes different paths |
| Revocation works | Revoke and call again | Refused |
| No credential in source control | Review the repository | Absent |
| Clocks synchronised | Compare host times | Within a second |
Troubleshooting¶
| Symptom | Cause | Action |
|---|---|---|
401 with a token that looks valid |
Wrong audience or issuer | Decode the token; compare its claims with the configuration |
401 intermittently |
Clock drift on either host | Token validation is time-sensitive. Fix time synchronisation |
403 with a valid token |
Application role not granted | Grant it through admin consent |
403 on some documents only |
Those documents' supplier registrations are outside scope | Check which registration each document belongs to |
| Static key suddenly refused | The client has been marked as requiring identity-provider tokens | Complete the migration |
| Key lost | Keys are not retrievable after issue | Issue a new one, migrate, revoke the old |
| Token validation fails after months of success | Provider signing keys rotated and cannot be fetched | Confirm outbound access to the provider's key endpoint |
| Rate limited while authenticating | A token requested per call instead of cached | Cache until near expiry |
| All integrations fail after one change | A single credential shared across ERPs | Issue one per ERP |
| User-token call refused with a missing-header message | Registration header omitted | Send it |
Related Articles¶
- Machine-to-Machine Authentication — the security model
- API Conventions — headers and status codes
- Ingest API — the first endpoint to try
- Rate Limits and Errors — limits and backoff