API Conventions¶
Applies to: All subscriptions
Purpose¶
Establish the conventions that apply across every Complifly API surface, so each endpoint page can describe only what is specific to it.
Audience¶
Integration developers.
Prerequisites¶
Reference¶
Transport¶
| Property | Value |
|---|---|
| Protocol | HTTPS only |
| Format | JSON, request and response |
| Encoding | UTF-8 |
| Method semantics | POST creates or submits; GET reads; PUT and PATCH update; DELETE removes |
Common headers¶
| Header | Direction | Purpose |
|---|---|---|
Content-Type: application/json |
Request | Required on any request with a body |
Authorization: Bearer <token> |
Request | Machine token, or a user session token |
X-API-Key: cfly_... |
Request | Static machine credential, as an alternative |
X-GSTIN |
Request | The registration being worked on, on user-token surfaces |
RateLimit-* |
Response | Standard rate-limit headers |
Status codes¶
| Code | Meaning | Retry? |
|---|---|---|
200 |
Success | — |
201 |
Created | — |
400 |
Malformed request | No — fix the request |
401 |
Not authenticated | Once, after refreshing the credential |
403 |
Authenticated but not permitted — usually a GSTIN scope mismatch | No — fix the scope |
404 |
Not found, or no subscription exists | No |
409 |
Duplicate | No — terminal and permanent |
422 |
Validation failure | No — fix the data |
429 |
Rate limited | Yes, after backing off |
500 |
Server error | Yes, with backoff |
The distinction that matters most: 4xx other than 429 should never be retried automatically. Retrying a validation failure or a duplicate cannot succeed and consumes rate-limit headroom that genuine traffic needs.
Response shapes¶
Successful ingest responses carry a status field with the outcome, the document identifiers, and a message:
{
"status": "ACCEPTED",
"invHdrId": 12345,
"docNo": "INV-2026-0001",
"gstin": "29AAACW3775F000",
"fiscalYear": "2026-27",
"itemsIngested": 3,
"message": "Invoice accepted."
}
Failure responses carry the same identity fields — so a rejection can be correlated with the document that caused it — plus the reason and a request identifier:
{
"status": "REJECTED",
"docNo": "INV-2026-0001",
"docType": "INV",
"errors": [
{ "Level": "HEADER", "InvoiceNo": "INV-2026-0001",
"Field": "BillTo_Pos", "Message": "Place of Supply is required",
"Severity": "ERROR" }
],
"requestId": "..."
}
Every refusal is recorded, including those rejected at the door before the document is understood. A rejected document is not silently discarded, so a document that "vanished" can always be accounted for.
The validation error shape¶
Validation errors use a consistent five-field structure:
| Field | Meaning |
|---|---|
Level |
HEADER or ITEM |
InvoiceNo |
The document number |
Field |
The canonical field name, as the registration portal names it — for example BillTo_Pos, Item_HsnCd |
Message |
Human-readable description |
Severity |
ERROR or WARNING |
Field is the key to look up in the Validation Error Codes reference and in the in-product help. Surface it to your users rather than only the message — it is the stable identifier.
Request identifiers¶
Every response carries a request identifier, and it appears in the server logs for the same request. Capture and store it. A support conversation that begins with a request identifier is resolved far faster than one that begins with "an invoice failed yesterday".
Identifiers¶
| Identifier | Meaning | Stability |
|---|---|---|
invHdrId |
Complifly's internal document identifier | Stable for the document's life |
docNo |
Your document number | Yours; must be unique per registration per year |
irn |
The 64-character reference issued by the registration portal | Immutable once issued |
event_id |
A write-back event identifier | Stable; use it for idempotency |
event_pk |
A monotonically increasing sequence for the feed | Use as a cursor, not as an identity |
Dates and numbers¶
| Item | Convention |
|---|---|
| Document dates in the platform envelope | DD/MM/YYYY |
| Your ERP's own format | Declared in the mapping template and converted on ingest |
| Timestamps in responses | ISO 8601, UTC |
| Amounts | Numbers, not strings. Do not send formatted values with separators |
Idempotency¶
| Surface | Mechanism |
|---|---|
| Ingest | The document number itself. Re-sending yields 409 DUPLICATE, which is the safety net — not a mechanism to rely on |
| Write-back | event_id. Delivery is at-least-once, so your consumer must deduplicate on it |
Design for a timeout: if an ingest call times out you do not know whether it succeeded. Query the document's status before resending, rather than resending blindly.
Batch semantics¶
Batch endpoints accept an array and return a per-item result. A batch is not a transaction: some items may be accepted while others are rejected. Read every result rather than the overall status code, or partial failures will be silently lost.
Validation¶
| Check | Method | Pass condition |
|---|---|---|
| Correct content type sent | Inspect the request | application/json |
| Status codes handled distinctly | Force each code in the sandbox | Each takes its own path; 4xx other than 429 never retried |
| Request identifiers stored | Inspect your own logs | Present against every call |
| Validation fields surfaced | Force a validation failure | The Field value reaches a human, not just the message |
| Dates convert correctly | Send a document with an ambiguous date, such as the fifth of March | Stored as the intended date, not transposed |
| Amounts sent as numbers | Inspect the payload | No thousands separators, no currency symbols |
| Batch results read individually | Send a batch with one bad item | The bad item is detected and the good ones are not resent |
| Idempotency works | Deliver the same event twice | Processed once |
| Timeout handling works | Simulate a timeout | Status is queried before any resend |
The ambiguous-date test is worth doing deliberately. A transposed day and month passes validation, produces a legally wrong document, and is discovered weeks later.
Troubleshooting¶
| Symptom | Cause | Action |
|---|---|---|
| HTTP 400 on a request that looks correct | Malformed JSON, or wrong content type | Validate the payload independently before sending |
| Dates off by a month | Format token in the mapping template does not match the ERP's real format | Correct the template and re-test with an unambiguous date |
| Amounts rejected | Sent as formatted strings | Send numbers |
| Batch appears successful, some documents missing | Only the overall status was read | Read every per-item result |
| Duplicates created after a timeout | Blind resend on timeout | Query status before resending |
| Support cannot trace a failure | Request identifier not captured | Store it against every call |
| The same event processed twice | Consumer not idempotent | Deduplicate on event_id |
| Validation messages unhelpful to users | Only the message surfaced, not the field | Surface Field as well; it is the stable identifier |
Related Articles¶
- API Authentication — presenting credentials
- Ingest API — the main integration surface
- Rate Limits and Errors — limits and retry policy
- Validation Error Codes — interpreting the
Fieldvalue