Skip to content

Ingest API

Applies to: e-Invoice · e-Way Bill · TDS · Which modules do I have?

Purpose

Document the endpoints an ERP uses to send documents into Complifly, and every response it can receive. This is the main integration surface and the one most integration effort is spent on.

Audience

Integration developers.

Prerequisites

  • API Authentication, with a working machine credential
  • A mapping template, if using the mapped endpoints — see Mapping Templates API
  • The credential's GSTIN scope covering the registrations you will send for

Reference

Base path: /api/v1/ingest

The endpoints

Method Path Purpose
POST /invoice One document, already in the platform envelope
POST /invoice/batch Up to 50 documents in the platform envelope
POST /invoice/mapped/{templateId} One document in your ERP's own format
POST /invoice/mapped/{templateId}/batch A batch in your ERP's own format
GET /status/{docNo} Current status of a document
POST /tds/deduction One TDS deduction
POST /tds/deduction/batch A batch of deductions
POST /tds/mapped/{templateId} A deduction in your own format

Raw or mapped — choosing

Raw (/invoice) Mapped (/invoice/mapped/{templateId})
Payload The platform envelope Your ERP's native shape
Translation Your ERP does it A mapping template does it
Changing a field mapping An ERP code change A configuration change
Suits An ERP that can produce the envelope natively Nearly everything else

Prefer mapped. It moves field translation out of ERP code and into configuration that an implementation consultant can adjust without a development cycle. That difference is worth more over an integration's life than the small extra setup.

Sending one document

POST /api/v1/ingest/invoice/mapped/7
X-API-Key: cfly_...
Content-Type: application/json

{
  "GSTNO": "29AAACW3775F000",
  "DOCTYPE": 0,
  "DOCNO": "INV-2026-0001",
  "DOCDATE": "5/26/2026 12:00:00 AM",
  "BUYER_GSTIN": "27AABCU9603R1ZM",
  "LINES": [ { "SLNO": 1, "HSN": "8471", "QTY": 2, "RATE": 25000 } ]
}

The template translates field names, converts the date, and maps coded values — DOCTYPE: 0 to a tax invoice, for example.

Accepted:

{
  "status": "ACCEPTED",
  "invHdrId": 12345,
  "docNo": "INV-2026-0001",
  "gstin": "29AAACW3775F000",
  "fiscalYear": "2026-27",
  "itemsIngested": 1,
  "message": "Invoice accepted."
}

Store invHdrId and docNo. They are how you correlate everything that follows.

Every rejection, and what to do

HTTP status Meaning Retry? Action
401 Credential not accepted Once, after refresh See API Authentication
403 REJECTED Supplier registration outside the credential's scope No Fix the scope, or send from the right credential
409 DUPLICATE The document number already exists Never Terminal and permanent
422 REJECTED Validation failed No The errors array names the fields
429 Rate limited Yes, after backoff See Rate Limits and Errors
500 ERROR Server error Yes, with backoff Escalate if it persists

A duplicate response includes the existing invHdrId and where it was found, so your ERP can reconcile rather than treating it as an error to resolve manually.

Every refusal is recorded on the platform side before the response is sent, so a rejected document can always be accounted for — it is never silently discarded.

Validation failures

{
  "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": "..."
}

Two validation passes run before acceptance: the document envelope is checked, and — on the IRN route — the full set of generation-time rules is brought forward to the door. That second pass is why a document accepted here will not usually be rejected later for a rule that could have been checked at ingest.

Surface Field to your users, not only Message. It is the stable identifier and the key to look up in Validation Error Codes.

Batch

Send an array; receive per-item results. The batch limit is 50. A batch is not a transaction — some items may be accepted while others are rejected, so read every result. Reading only the overall status code loses partial failures silently.

Checking status

GET /api/v1/ingest/status/INV-2026-0001

Returns the document's identity, its current status, and — once generated — the reference number and acknowledgement.

Use this to reconcile after a timeout. A timed-out request does not mean the document was not accepted; it means you do not know. Query status before resending, or the resend becomes a duplicate.

What happens after acceptance

Acceptance means the document is stored and valid — not that it has been registered with the government. Generation is a separate step, performed by a user or, where enabled, automatically.

Status after ingest Meaning
PENDING On the IRN route, awaiting generation
EWB_PENDING On the e-Way-Bill-only route

Do not treat an ingest acceptance as compliance evidence. Wait for the generated status, either by polling status or — better — by consuming the Write-back API.

Validation

Test each of these in the sandbox before going live:

Scenario Expected Your handling
Valid document 201 ACCEPTED Store invHdrId
Missing required field 422 with the field named Surface to a human; do not retry
Malformed registration number 422 Same
Out-of-scope registration 403 Report as a scope problem
Same document twice 409 DUPLICATE Recognise as terminal
Batch with one bad item Per-item results Only the bad item is corrected; good ones are not resent
Batch of 51 422 Chunk to 50
Invalid credential 401 Refresh and retry once
Rate limit exceeded 429 Back off
Timeout Unknown Query status before resending
Status query Current state returned Reconciliation works
Ambiguous date Stored as intended Verify with a date such as the fifth of March

Troubleshooting

Symptom Cause Action
403 on every document Credential's GSTIN scope does not include the supplier Check the scope, not the user permissions
403 on some documents only Those belong to a registration outside scope Identify which registration each document is for
409 on a document never sent The ERP series restarted, or another system uses the same series Numbers are permanently consumed. Fix the series
422 naming a field the ERP does send Mapping rule targets the wrong path, or the value is empty Inspect the mapped payload before blaming validation
Dates a month out Date format token does not match the ERP's real format Correct the template; re-test with an unambiguous date
All documents rejected after a template change The template was changed without re-testing Test template changes against real documents
Documents accepted but never registered Acceptance is not generation Generate, or enable automatic generation deliberately
Duplicates after a network problem Blind resend on timeout Query status first
Batch appears successful, documents missing Only the overall code was read Read every per-item result
Rejections not visible to the business The ERP swallows them Surface Field and Message to a person who can act