Skip to content

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