Skip to content

Integration Policies

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

Purpose

For each limit, the most specific level that switches it on wins: connection override, then company override, then the global policy. This page explains the limits, how to give one customer different limits in one place, and what an ERP receives when it hits one.

Every limit is off by default. With nothing configured, ingestion is not limited by this layer at all.

Audience

Platform Super Admins who set limits; integration developers who must handle the resulting responses; implementation partners agreeing limits with a customer.

Prerequisites

  • Platform Super Admin. The screen and its API are refused to every other role, on the server. A customer administrator cannot change a limit; they ask their Complifly contact.
  • The company's identifier, and its connections under ERP Connections.

Reference

The three levels

  Connection override   one API key, or one OAuth (Entra) connection
          |  not set
          v
  Company override      every connection of one company, present and future
          |  not set
          v
  Global policy         every company
Level Set it when Applies to
Global Always — the normal limits Everyone
Company A customer's contract differs All of that company's connections, including ones created later
Connection One ERP needs different treatment That API key or OAuth connection only

Three consequences worth stating outright:

  • Decided per limit, not per level. A company with its own rate limit still inherits the global spike arrest.
  • Limits count per connection. A company rate limit of 600 per minute lets each of its connections make 600 per minute. It is not one allowance shared across them.
  • An override can raise or lower a limit; it cannot switch one off. A level "sets" a limit only when that limit is switched on at that level. To exempt a customer from a globally enabled limit, give them a high value.

The limits

Limit Unit Response when exceeded
Rate limit Requests per minute 429 RATE_LIMIT
Monthly quota Accepted invoices per calendar month, counted per company or per GSTIN None. The invoice is accepted. Going over is recorded (see below)
Spike arrest Requests per second 429 SPIKE_ARREST
Concurrency Requests in progress at once 429 CONCURRENCY_LIMIT
IP allowlist Source addresses permitted 403 IP_NOT_ALLOWED
Maximum GSTINs (from the plan) Distinct GSTINs ingested in the month 403 GSTIN_LIMIT

A blank monthly quota means "use the connection's plan quota"; a plan with no quota is unlimited.

The monthly quota never refuses an invoice. A transaction limit is a capacity-management control, not a gate: an ERP that sends its 5,001st invoice against a 5,000 quota has it accepted exactly like the first. The first time in a month that a company (or a GSTIN, when the quota is counted per GSTIN) is found at or over its quota, the platform records one event, INGEST_QUOTA_EXCEEDED, with the limit and the count at that moment. How far over is on the company's usage statement. Staff are told through the capacity alerts for customers whose contract states the volume (Commercial API), and the quota is raised through a conversation about the contract, not an outage.

The limits that do refuse are the ones that protect the platform (rate limit, spike arrest, concurrency, IP allowlist) and the plan's GSTIN cap, which stops invoices from a GSTIN that is new this month.

Give one customer different limits

  1. Admin & Logs → Integration Policies.
  2. In Editing, choose the company.
  3. Switch on only the limits that differ and enter the value. Leave the others on Inherit; the screen shows the value each inherits.
  4. Save the company override. It applies from the next request — no restart.

The table below the form lists the company's connections with the value each one actually gets and the level it comes from. It is computed by the same logic that enforces the limits.

To treat one connection differently, use Override on its row. To undo either, Remove override: that level inherits again.

The API

Base path /api/admin/policies. User token; Platform Super Admin only (403 otherwise).

Method Path Purpose
GET / Global policy, all company overrides, all connection overrides, plans
PUT /global Set the global policy
PUT /company/{companyId} Create or update a company override
DELETE /company/{companyId} Remove it — the company inherits global
PUT /client/{scopeId} Create or update a connection override
DELETE /client/{scopeId} Remove it — the connection inherits company / global
GET /company/{companyId}/connections The company's connections with each one's effective policy and source level
GET / PUT /plans Subscription plans

scopeId is the API key's id for an API-key connection and the Entra application id for an OAuth connection — the connections endpoint returns the right one for each.

Body for the three PUT policy calls (send only what you are changing):

{
  "rate_limit_enabled": true,  "rate_limit_per_min": 600,
  "quota_enabled": false,      "quota_per_month": null, "quota_scope": "company",
  "spike_enabled": false,      "spike_per_sec": null,
  "concurrency_enabled": false,"concurrency_max": null,
  "ip_allowlist_enabled": false, "ip_allowlist": "203.0.113.10\n203.0.113.11"
}

Subscription plans

A plan carries a monthly quota, a maximum number of GSTINs and a rate. It applies to a connection only when the connection carries that plan's code. No screen assigns a plan to a connection today — Complifly sets it. The connections list shows which plan each connection carries.

These plans are not the commercial plans. This table (default, starter, growth, enterprise) sets only what an ERP connection's API key may send. The commercial plans (Starter, Growth, Enterprise, Custom), which say what a customer has bought, are a separate record, and their limits are different numbers. Until the two are brought together, a customer on the commercial Growth plan is limited at the ingest gate by this table's growth row, if their connection carries that code, and by nothing if it does not.

The code must match exactly. starter is a plan; Starter and starter (with a trailing space) are not, and a connection carrying an unrecognised code has no quota and no GSTIN cap. The gate lets it through rather than stop an invoice. Complifly now reports every such credential: it appears as PLAN_ASSIGNMENTS in the Support Readiness check and in the start-up log. Correcting the code is a data change made by whoever owns the connection.

What is not here yet

Payload-size limits, retention periods and processing limits are not part of Integration Policies today. They are fixed platform settings. The three-level model is built so a further limit can be added as one more field that resolves the same way; until one is, do not promise a customer a per-company value for them.

Validation

Check Pass condition
The override is in force The connections table shows the new value with a company badge on every connection that has no override of its own
A later connection inherits Create a connection for the company; it appears in the table with the company value, with nothing set on it
The change is on record Audit Logs shows API_POLICY_UPDATE (or API_POLICY_REMOVE) with the values before and after
Enforcement A request beyond the limit returns the code in the table above

Troubleshooting

Symptom Cause Action
An ERP receives 429 SPIKE_ARREST during a nightly batch A burst inside a generous per-minute limit Raise spike arrest for the company, or have the ERP pace its sends
A customer's limit did not change after a company override That connection has its own override, which wins Remove or edit the connection override
A customer is still limited after "switching off" at company level An override cannot switch a globally enabled limit off Set a high value at company level
403 IP_NOT_ALLOWED after an ERP moved hosting The allowlist holds the old address Update the allowlist at the level that set it — the badge names it
A limit seems higher than configured on a multi-server deployment Rate, spike and concurrency counters are kept per application server Size the value with the server count in mind
A limit is not applied at all The policy layer fails open: an internal error, or a connection carrying a plan code that does not exist, lets the request through rather than block ingestion Check the Support Readiness check (POLICY_GATE, PLAN_ASSIGNMENTS), the application log for lines tagged [POLICY-GATE] (throttled to one per gate and reason per fifteen minutes by default, with a count of how many were let through), and POLICY_GATE_DEGRADED in the platform event log