API: users and codes
General contract in How the API works. This is the most integrated part of the API: syncing the client's headcount with INSSACS consumers.
Consumer users
POST /consumers
GET /consumers
GET /consumers/summary
GET /consumers/:id
PATCH /consumers/:id
PATCH /consumers/:id/active
DELETE /consumers/:id
GET /consumers/:id/entitlements
Creating — 201
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | — |
identification | string | yes | unique within the account |
tags | { key, value }[] | no | key must exist in the account's tags |
accountId | string | no | ignored: the key's account wins |
initialCode | object | no | creates the first code in the same call |
curl -X POST https://api.inssacs.com/consumers \
-H 'x-api-key: ics_…' -H 'content-type: application/json' \
-d '{
"name": "Juan Pérez",
"identification": "1023456789",
"tags": [{ "key": "departamento", "value": "Producción" }],
"initialCode": { "codeId": "1023456789", "codeType": "QR", "operationId": "665f…c7" }
}'
Response: the consumer with their codes.
{
"id": "665f…e1",
"clientId": "665f…c5",
"accountId": "665f…c6",
"name": "Juan Pérez",
"identification": "1023456789",
"isActive": true,
"tags": [{ "key": "departamento", "value": "Producción" }],
"codes": [
{
"codeId": "1023456789",
"codeType": "QR",
"operationId": "665f…c7",
"isActive": true,
"vendingProfileId": null,
"easProfileId": null,
"vendingBalance": 0,
"vendingRecharge": 0
}
],
"createdAt": "2026-08-11T14:03:00.000Z"
}
Editing and status
PATCH /consumers/:id→name,identification,tags(optional).200.PATCH /consumers/:id/activewith{ "active": false }→ deactivates the user and with them all their codes.200.DELETE /consumers/:id→204; removes codes, tags and assigned EAS products. Historical deliveries are kept.
Querying
GET /consumers— query:operationId,isActive,search(name, ID number or code),page,pageSize(max 1000).GET /consumers/summary— counts of users and codes, active and inactive.GET /consumers/:id/entitlements— what that user may take today: handy to show quotas in your own portal.
Errors: 409 consumers.identification_exists, 404 consumers.not_found,
400 consumers.unknown_tag_key, 400 consumers.too_many_tags.
Codes
POST /consumers/:id/codes
PATCH /consumers/:id/codes/:codeId
DELETE /consumers/:id/codes/:codeId
PATCH /consumers/:id/codes/:codeId/active
PUT /consumers/:id/codes/:codeId/profile
DELETE /consumers/:id/codes/:codeId/profile/:businessLine
Adding a code — 201
| Field | Type | Required | Notes |
|---|---|---|---|
codeId | string | yes | the value the machine reads |
codeType | string | yes | QR, NFC, FINGERPRINT, CODE128, CODE39, PDF417, EAN13, UPCA, MAXICODE, MRZ |
operationId | string | no | omit = account-level code: same value, balance and quota across every operation |
vending.balance | integer | no | initial balance in minor units |
A code is born with no profiles: its lines are defined by assigning them.
Assigning and removing a profile
PUT /consumers/:id/codes/:codeId/profile { "profileId": "665f…d1" }
DELETE /consumers/:id/codes/:codeId/profile/vending
DELETE /consumers/:id/codes/:codeId/profile/eas
The line comes from the profile: a vending profile fills the vending slot. The profile must belong to
the user's account (400 consumers.profile_not_for_this_account) and the code must keep at least
one profile (409 consumers.code_requires_profile).
Editing a code
PATCH /consumers/:id/codes/:codeId:
| Field | Type | Notes |
|---|---|---|
newCodeId | string | changes the value; uniqueness is re-validated |
codeType | string | changing away from FINGERPRINT deletes the fingerprints |
vendingBalance | integer | sets the subsidy balance |
operationId | string | null | moves level; null = becomes account-level. Keeps balance and profiles |
lineProfiles | object | sets both profiles at once |
Value conflicts when moving level: 409 consumers.code_value_taken_in_operation and
409 consumers.code_value_taken_at_account.
Status and removal
PATCH …/activewith{ "active": false }→ disables just that code.DELETE …→204. History is kept.
Fingerprints
GET /consumers/:consumerId/codes/:codeId/fingerprints
POST /consumers/:consumerId/codes/:codeId/fingerprints
DELETE /consumers/:consumerId/codes/:codeId/fingerprints/:fingerId
FINGERPRINT codes only. The POST expects the finger and its samples, already captured by the
reader:
| Field | Type | Required | Notes |
|---|---|---|---|
fingerId | integer 0–9 | yes | which finger |
fingerLabel | string | no | Right index |
fingerprints | object[] | yes | the samples (base64 template) |
Errors: 422 consumers.no_fingerprint_template, 409 consumers.fingerprint_already_enrolled,
409 consumers.too_many_fingerprints, 400 consumers.code_not_fingerprint.
The API does not capture fingerprints: it receives the templates produced by the reader's local agent. Without that agent there is no enrolment.
Bulk loads
POST /consumers/bulk { "items": [ … ] }
POST /consumers/codes/bulk { "items": [ … ] }
Each item carries its op field plus that operation's data:
| Endpoint | op values |
|---|---|
/consumers/bulk | create, update, delete, upsert |
/consumers/codes/bulk | create, update, delete |
{
"items": [
{ "op": "create", "name": "Juan Pérez", "identification": "1023456789" },
{ "op": "update", "identification": "1010087922", "name": "Ana Ruiz" }
]
}
The response is 201 with the per-row outcome: one invalid row does not abort the batch.
{
"summary": { "created": 1, "updated": 1, "deleted": 0, "failed": 0 },
"results": [{ "index": 0, "op": "create", "status": "created", "id": "665f…e9" }]
}
This is the recommended path for syncing headcount: one call instead of N.
Because the batch is multi-op, it does not require one specific action but at least one of
create, update or delete on the resource (consumers for the first, codes for the second). With
none, it answers 403. Then each row is authorised by its own operation: with only create, the
update or delete rows fail individually and the rest of the batch goes through.