Skip to content

Mapping Templates API

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

Purpose

Define the translation between your ERP's native payload and the document envelope Complifly submits, so the ERP never has to produce the envelope itself.

Audience

Integration developers and implementation consultants.

Prerequisites

  • API Authentication — these endpoints use a user token, not a machine credential
  • A real sample payload from the ERP, unmodified
  • Knowledge of the ERP's date format and coded values

Building a template by hand?

This page is the REST interface. If you are building the mapping in the product instead, Build a Mapping Template is the screen-by-screen procedure for the same capability.

Reference

Base path: /api/admin/mapping-templates

The endpoints

Method Path Purpose
GET / List the company's templates. ?includeDeleted=1 adds deleted ones
GET /{id} One template with its rules (a deleted template is still readable)
POST / Create a template
PUT /{id}/rules Replace the rule set — atomic
GET /{id}/versions Version history
POST /{id}/versions/{versionId}/rollback Restore a prior version's rules as the active rule set — atomic
PATCH /{id} Rename, re-describe, enable or disable
DELETE /{id} Delete — restorable; rules and history are kept
POST /{id}/restore Undo a delete. The template comes back disabled
POST /extract-fields Discover the field paths in a sample payload

Every write requires mapping.edit (Org Admin, Platform Super Admin); reads need only a signed-in user.

Which company's templates

A template belongs to one company, and every endpoint acts on the caller's own company. A list that looks short is usually the other company's templates not being shown, not templates missing.

A Platform Super Admin may add ?companyId= to any endpoint to act on another company. The parameter is ignored for every other role.

Which template is carrying traffic

is_active: true does not mean a template is in use — an ERP reaches a template only by naming its id in the ingest URL. Each item returned by GET / therefore carries two usage fields, so a caller can tell the template an ERP is actually posting through from the ones that are merely enabled:

Field Meaning
is_active The template accepts ingest calls. false → an ingest call naming it returns 404 "not found or inactive"
ingest_calls_30d Mapped-ingest calls (accepted and rejected, invoice and TDS) that named this template in the last 30 days. 0 = enabled but unused
last_ingest_at Time of the most recent such call in the last 90 days; null if none
ingest_sources One entry per ERP connection that used the template in the last 90 days: caller, label, calls_30d, accepted_30d, last_at. Two ERPs sharing one template appear as two entries
version_count Number of versions held. 0 means the template has never been saved through this API; its first save will record a baseline
deleted_at, deleted_by Set on a deleted template (only returned with includeDeleted=1)

The Admin screen shows Live only when ingest_calls_30d > 0. The usage fields are omitted when the usage lookup fails; treat a missing field as "unknown", not as zero.

calls_30d − accepted_30d is the number of sends that were refused. A large difference means an ERP is repeatedly sending documents that fail validation — investigate with Support Investigation.

Versioning and rollback

A mapping template is production configuration — every document an ERP sends through it takes the shape this rule set defines. PUT /{id}/rules and the rollback endpoint both require mapping.edit and an explicit { "confirmed": true } in the body; this is enforced server-side, not only by a frontend confirmation dialog, so it cannot be bypassed by calling the API directly.

Response Condition
200 Saved (or rolled back). The rule set just saved is recorded as a new version. The first time a template is saved, the rules it held beforehand are recorded first as version 1 ("Baseline"), so the pre-edit state is always recoverable
422 confirmed: true was not sent
403 The caller does not hold mapping.edit

| 500 | The save or rollback failed and was undone. The previous rule set is still live and no version was written. Re-send it |

A save or a rollback is one transaction. The baseline (first save only), the rule replacement and the new version either all happen or none do; a failure part-way cannot leave a partial rule set live.

GET /{id}/versions returns every version, newest first:

Field Meaning
version_id, version_no Identifier, and the per-template sequence number shown in the product
change_kind RULES (a save), BASELINE (the rules held before the first save), ROLLBACK, METADATA (rename, re-describe, enable, disable), DELETE, RESTORE
change_note The note given on save, or a generated description for the other kinds
changed_by, created_at Who and when
rule_count Number of rules in that version's snapshot
template_name, erp_type, description, is_active The template's attributes as they stood at that version

The snapshot itself is not returned in the list; roll back to a version to make its rules current. A rollback never rewrites history — it is recorded as a new ROLLBACK version.

Deleting and restoring

DELETE /{id} does not remove anything. It disables the template, marks it deleted and records a DELETE version; its rules and every version are kept. An ERP still posting to it receives 404 "not found or inactive", exactly as for a disabled template.

POST /{id}/restore clears the deletion and records a RESTORE version. The template returns disabled, so restoring never resumes ERP traffic by itself — enable it with PATCH once checked.

Response Condition
200 Deleted, or restored
404 No such template for this company (delete), or it is not deleted (restore)
409 Restore: a live template already uses the deleted one's name. Rename that one first

A deleted template's name can be reused by a new template.

Updating template attributes

PATCH /{id} changes templateName, erpType, description and/or isActive — omitted fields are left unchanged. Unlike PUT /{id}/rules, this does not require confirmed: true, since it never touches the rule set itself.

Disabling a template that carries traffic stops that ERP's documents arriving from the next request. Each change that alters something is recorded as a METADATA version (the response carries its versionNo; null when nothing changed) and in the audit log with the values before and after.

Response Condition
200 Updated. The response body is the template's current state
404 No template with this id under your company
409 CONCURRENT_MODIFICATION Sent an If-Match-style rowVersion that no longer matches — someone else changed this template since you last read it. Re-fetch with GET /{id} and re-apply your change

The rowVersion check is optional: a request body without a rowVersion field always succeeds (last write wins), matching this endpoint's behavior before this check existed. Send the rowVersion from your last GET/PATCH response back on the next PATCH to opt into the conflict check — recommended for any integration that reads a template, lets a human edit it, then writes it back, since that is exactly the window a second editor's change would otherwise be silently lost in.

The model

A template represents one ERP integration — typically one per ERP and company combination. It holds rules, each translating one source field to one target field.

Field Purpose
templateName Unique within the company
erpType A label: SAP, D365, Tally, Oracle, Custom
description Free text; use it to say which system and which company
sample_payload A stored sample, for reference while editing
is_active An inactive template cannot be used at ingest

The four transform types

direct — copy the value unchanged. Supports dot notation into nested structures.

{ "erpFieldPath": "seller.address.pincode",
  "nicFieldPath": "SellerDtls.Pin",
  "transformType": "direct" }

date — convert from the ERP's format to the envelope's DD/MM/YYYY. Declare the source format:

{ "erpFieldPath": "DOCDATE",
  "nicFieldPath": "DocDtls.Dt",
  "transformType": "date",
  "erpDateFormat": "M/D/YYYY h:mm:ss AM" }

Get this exactly right. A wrong format token does not usually fail — it silently produces the wrong date, which passes validation and produces a legally wrong document. Test with an unambiguous date such as the fifth of March, where a transposition is visible.

static — supply a fixed value regardless of the payload. Useful for a constant the ERP does not carry.

lookup — translate a coded value through a map, with a default:

{ "erpFieldPath": "DOCTYPE",
  "nicFieldPath": "DocDtls.Typ",
  "transformType": "lookup",
  "lookupMap": { "0": "INV", "1": "CRN", "2": "DBN" },
  "lookupDefault": "INV" }

Choose lookupDefault deliberately. A default that quietly turns an unrecognised code into a tax invoice will route documents to the wrong pipeline and register things that should not be registered. Where any unrecognised value is a genuine error, prefer a default that fails validation over one that guesses.

lookupDefault applies only to a value the ERP actually sent that is not a key in lookupMap. When the source field is absent or empty, the rule produces nothing and the target field is omitted from the envelope entirely — the default is not substituted. This matters for optional fields: a lookup on EwbDtls.TransMode with lookupDefault: "1" does not stamp every document as road transport; it fills that in only when the ERP sends a transport mode its map does not recognise.

When the e-Way Bill rides with the IRN

If the mapped envelope carries an EwbDtls block, NIC will issue an e-Way Bill together with the IRN — a second document, with its own cancellation rules. That block is present whenever a transport mode is mapped, but NIC only generates the e-Way Bill number when the block also carries enough to start Part-A or Part-B: a vehicle number, a transporter ID, or a transport document number. A transport mode on its own produces the IRN with no e-Way Bill and no error.

So map EwbDtls.* fields only for documents that genuinely move goods under that invoice. If some documents through a template should get an e-Way Bill and others should not, the ERP must send the transport fields on the first kind and leave them empty on the second — a static rule or a lookupDefault that fills a transport mode in unconditionally will not force an e-Way Bill by itself, but it does make every IRN response carry an EwbDtls block the reader then has to reason about.

Array handling

Line items map with index notation, for example ItemList[0].SlNo. Multi-line documents are expanded from the source array.

Always test with a multi-line document. A single-line sample hides every array-handling problem, and those problems appear on the first real invoice.

Rule order

Rules are applied in a defined order. Where one rule's output feeds another, order matters; where they are independent, it does not. Keep the order meaningful for a human reading the template later.

Building a template

Step Action
1 Obtain a raw, unmodified sample from the ERP, including its quirks
2 Use extract-fields to discover the addressable paths
3 Create the template
4 Add rules, starting with the required fields
5 Test with the sample and inspect the mapped payload
6 Test with edge cases: export, reverse charge, zero-rated, discounts, multi-line
7 Activate

Step 1 is the one most often compromised. A hand-cleaned sample produces a template that works in testing and fails on real documents.

Validation

Check Method Pass condition
Every required field is mapped Ingest a real document Accepted, with no missing-field errors
Dates convert correctly Use a date such as 05/03 Stored as the intended date, not transposed
Coded values translate Send one document of each type Each routes correctly
The lookup default is safe Send an unrecognised code Produces the intended outcome, not a silent misclassification
Multi-line documents work Send a document with several lines Every line present, in order, with correct values
Nested paths resolve Check a deeply nested field Correct value
Edge cases work Send export, reverse charge and discounted documents All accepted, with correct values
Amounts are numeric Inspect the mapped payload No formatted strings
Inactive templates are refused Deactivate and attempt ingest Refused
The mapped payload is inspected Review it directly Every field carries the intended value

Inspecting the mapped payload is the highest-value check on this page. Acceptance proves the document was valid; only the payload proves it was correct.

Troubleshooting

Symptom Cause Action
A field arrives empty Path wrong, or the ERP omits it for this document type Compare the rule's path against the raw payload
Dates a month out Format token does not match the ERP's real format Correct and re-test with an unambiguous date
All documents become tax invoices Lookup default too permissive Choose a default that fails rather than guesses
Documents route to the wrong pipeline The mapped type value is not what you expect Routing reads the mapped value, not the ERP's original
Only the first line appears Array notation wrong, or the source array path is wrong Test with a multi-line document
Works in testing, fails on real documents The sample was hand-cleaned Use raw ERP output
A template change broke everything Changed without re-testing Test every template change against real documents before activating
Ingest refused with a template error Template inactive, deleted, or the wrong identifier in the URL Confirm the identifier and its active state
Amounts rejected Mapped as formatted strings Map to numeric values
Two ERPs interfere One template shared between them One template per ERP and company combination
PATCH /{id} returns 409 CONCURRENT_MODIFICATION Someone else changed this template between your GET and your PATCH GET /{id} again for the current state and rowVersion, re-apply your change, and send that rowVersion back