Making the integration user for a connected app — the recipe
A connected app signs in to a shop's iVendNext from outside, with an API key: a warranty service, a bin-location service, a marketplace bridge. This recipe shows how the shop gives that app its own user, holding only the rights it needs. It is for partners, and for our own Custom Development team. Reference implementation: the integration user the two sample services use (samples/s1_warranty/service/, samples/s2_bins/service/).
When to use it. Rung 2 of the build ladder: a connected app. Try configuration first (pricing rules, promotions, print formats, sale details, permissions). If the job really needs your own server, every connected app gets its own user, made this way. A till key hosted inside the shop, an embedded app and a manifest do not need one: they run as the cashier, the person booking the sale, or nothing at all.
The rule in one line. One connected app = one desk user = one user licence, holding one narrow role. The shop decides which apps get a user, what each may do, and when to switch one off.
Steps
The shop's administrator does these in iVendNext Desk.
-
Make a role for this app alone (Desk → Role → New, then Role Permissions Manager). Give it only what the app reads and writes. The bin-location sample's role, for example:
Record type Rights Item, Customer, Warehouse, Sales Invoice, POS Invoice, Bin (stock balances), POS Profile read Comment read, create Stock Entry read, write, create, submit Serial No; Serial and Batch Bundle read, write, create (+ submit on the bundle) — only if the app books serialised stock the app's own record types (from its manifest) read, write, create — granted by the manifest itself Do not start from an existing role such as Stock User: it carries far more than your app needs.
-
Make one user for the app (Desk → User → New):
- an e-mail that names the app, e.g.
bins-service@yourshop.example; - tick Is Desk User. iVendNext refuses a user that is neither a desk user nor a POS user, so there is no API-only kind;
- give it only the role from step 1;
- set no password. The user never signs in to Desk: only its API key is used.
- an e-mail that names the app, e.g.
-
Generate its API key and secret (User → Settings → API Access → Generate Keys). The secret is shown once. Put it straight into your service's secret store. Every request then carries:
Authorization: token <api key>:<api secret> -
Know what the key can do. On a product before 2026-09-30, every desk user, the app's user included, also held iVendNext's Workspace Manager role. That role lets a user change the shared Desk workspaces. With it, the key could put content on a shared workspace that runs in the browser of whoever opens it, an administrator included. So on such a product a connected app's key can do what an administrator can. Since 2026-09-30 only a System Manager gets that role, so an app's user holding only the role from step 1 does not have it; the manifest installer refuses an integration user given it (
guide/07_manifests.md). What the key can do is the shop's choice when it connects the app. Keep the key in a secret store, never in code or chat, and rotate it at any doubt (step 6). -
Prove the key, and prove its limits:
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: token $KEY:$SECRET" \ "$BASE/api/resource/Item?limit_page_length=1" # 200 curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: token $KEY:$SECRET" \ "$BASE/api/resource/Journal%20Entry?limit_page_length=1" # 403: outside the role curl -s -o /dev/null -w '%{http_code}\n' "$BASE/api/resource/Item?limit_page_length=1" # 403: no keyThe request collection checks the same two refusals (files
17_refused_outside_roleand18_refused_without_key). Run it with curl and jq only:BASE=… KEY=… SECRET=… bash api/collection/run.sh. -
Rotate or revoke the key. To rotate it (a person who saw it leaves, or on a schedule), press Generate Keys again. The old secret is refused (
401) at once, and the new one works (200). Update your service's secret store in the same change. To switch the app off, the shop deletes the user, or generates new keys and keeps the secret to itself. Disabling the user is not enough: its key still signs in.
What is refused, and why
- An API-only user. A user must be a desk user or a POS user, so a connected app takes a desk user and its licence. That is deliberate: the licence is how a connected app is counted.
- A server-to-server sign-in with enforced scopes. Not available today. The API key plus a narrow role is the boundary, so keep the role narrow.
When your service is down, and when the till is offline
The user does not change either. If the key is revoked or rotated without your service knowing, every call answers 401. Alert on it, and do not retry it in a loop. Till sales made offline reach you when the till syncs, through your webhooks (recipes/webhook_receiver.md).
What we would like to improve
- A server-to-server sign-in whose scopes are enforced against record types.
- Stop every desk user, integration users included, from listing every user on the site.
Technical notes
What was proven where
- The reference integration user is our test bench's, which the two sample services use (
samples/s1_warranty/service/,samples/s2_bins/service/), proven on the bench. - On our test bench the steps were made by a setup script that writes the same records, and each result was then measured against the live record API.
- Workspace Manager since 2026-09-30: engine commit
d52056cgives it to a desk user only as a System Manager (ivendnext_pos/doc_events/user.py, read at the bench's engine pinf7afcfb61); the manifest installer refuses an integration user given it,guide/07_manifests.md(row KINDS, runpp-20261007T033429Z-67655, the older engine simulated). Measured on the bench at its earlier pin (ivendnext_pos457ba607d, without that commit; 2026-10-03): with the role, the key could put content on a shared workspace that runs in the browser of whoever opens it, an administrator included. Not measured: whether a key without that role can reach an administrator's browser another way. - Key rotation, measured on the bench: the old secret is refused (
401) at once, and the new one works (200). - Disabling the user is not enough: its key still signs in (measured on our test bench, 2026-10-07).
Tests
api/collection/run.sh(curl and jq only) signs in with the key and checks both refusals:BASE=… KEY=… SECRET=… bash api/collection/run.sh.- The measurements above — what the Workspace Manager role allows, and key rotation — were taken on our test bench.