Skip to main content

API for integrations

An API key lets an external system read from and write to INSSACS without anyone's session. This section documents the contract: which endpoints exist, what body each one expects and what it returns.

Keys are created from the panel: API Keys.

Authentication

Two equivalent forms, on every request:

curl https://api.inssacs.com/products \
-H 'x-api-key: ics_a1b2c3d4_e5f6...'

curl https://api.inssacs.com/products \
-H 'Authorization: Bearer ics_a1b2c3d4_e5f6...'

With Authorization: Bearer, the secret must start with ics_; otherwise the server treats it as a user JWT and answers 401.

SituationResponse
No authentication header401
Invalid, revoked or expired secret401 Invalid API key
Valid key without permission or out of scope403

Scope: client or account

A key is issued at one of two levels, and everything it does is bound to it:

LevelCoversWhen to pick it
ClientEvery account of the client, including ones created laterA corporate integration consolidating several accounts
AccountThat account onlyA system for one site or subsidiary

In both cases:

  • in lists, client, account and operation filters are ignored when they point outside the scope: the server overwrites them with the key's;
  • when reading or writing a specific resource, anything outside answers 403;
  • when creating, you need not send clientId: it comes from the key. A client-level key must state which account the new resource belongs to (accountId); an account-level one may omit it.

The key's level shows in the Scope column of the table and in the scope field of responses.

What a key can do

On creation you choose which permissions it holds: by default it gets every permission its creator can grant, and you trim from there (see API Keys). A key has no role: its permission list is everything it can do, and it can be changed later without reissuing it.

With every permission, it covers:

ResourceEndpoints
Account and user tagsAccount and operations
OperationsAccount and operations
Products and profilesCatalogue
Consumer users, codes and fingerprintsUsers and codes
Devices and inventory (planogram)Devices
Deliveries, top-ups, returns, available credit, dashboard, exports, auditReports

What it cannot do

BlockedReason
Managing API keys (/api-keys)Excluded on purpose: a key cannot mint another key.
Creating, editing or deleting clients (POST/PATCH/DELETE /clients)Only a global operator manages clients (422 clients.actor_not_global). Reading GET /clients does work and returns its own client.
Controllers and licences (/controllers)Resource reserved to INSSA.
Creating or deleting devices (POST/DELETE /devices)Reserved to INSSA (device_provisioning). The rest of the resource is available.
Operators and roles (/users, /roles)Those use cases resolve a real acting user; a key is not one, so they answer 403.
Server and lambda errorsReserved to INSSA.
/auth/*That is a human operator's session flow.

Rate limit

Fixed one-minute window per key: 120 requests by default, or whatever the key has under Rate limit. Exceeding it returns 429. There is also a global per-IP limit of 300 per minute.

Space out batch jobs and retry with exponential backoff on 429.

Response format

  • Success: 200 with the resource or list, 201 on create, 202 when an export is accepted, and 204 with no body on activate/deactivate/delete.
  • Page-based lists (accounts, operations, products, profiles, users): { items, total, page, pageSize }, with page starting at 1 and pageSize capped at 1000.
  • Cursor-based lists (movements): { items, nextCursor }. Ask for the next page with cursor=<nextCursor> and limit capped at 1000; a null cursor means there is no more.
  • Amounts: integers in minor units (see Amounts and currency).
  • Dates: ISO 8601 in UTC (2026-08-11T14:03:00.000Z). from and to are mandatory on movements.

Error format

application/problem+json, carrying the domain code and — on validations — the field-by-field detail:

{
"type": "https://api.inssacs.com/problems/profiles.device_not_in_account",
"title": "Bad Request",
"status": 400,
"code": "profiles.device_not_in_account",
"detail": "device 665f… not in account",
"traceId": "01J9…"
}

Body validation:

{
"status": 400,
"code": "validation.failed",
"errors": [
{ "field": "cost", "rule": "isInt" },
{ "field": "businessLine", "rule": "isIn" }
]
}
HTTP codeWhen
400Invalid body or query (including undeclared fields: the server rejects them, it does not ignore them).
401Key missing, invalid, revoked or expired.
403No permission, out of scope, or reserved resource.
404The resource does not exist or is not visible to the key.
409State conflict: duplicate, export already running, profile with codes.
422Domain rule broken (e.g. invalid fingerprint).
429Rate limit.
500Server error. The traceId is what support needs.

Per-resource codes are listed in Error messages.

Swagger only outside production

The app publishes Swagger at /docs, but only when not running in production, and it only describes the JWT authentication scheme. To integrate against production, this manual section is the reference.