iVendNextDevelopers Request a sandbox

Building a connected app — the recipe

A sale on the till, and the warranty service's comment on it

A cashier sells a covered phone; a moment later the warranty service's comment is on the sale in Desk.

A connected app is your own service, on its own server, in any language, that reads and writes a shop's iVendNext over the record API. This recipe is for a partner, or our own Custom Development team, building one.

Reference implementation: samples/s2_bins/service/ (bins: reads stock, books stock movements, keeps its own records in the shop) and samples/s1_warranty/service/ (warranties: reads sales and serial numbers, writes a comment). Every call they make is in api/collection/ and described in api/openapi.yaml.

When to use it. It is step 2 of the build ladder, after configuration. Use it when the work belongs on your side: an outside system, your own store, a process that runs after a sale. If the cashier must see an answer during the sale, add a till key (recipes/point_handler.md). If you need your own record types in the shop, ship them in a manifest (recipes/manifest.md).

Steps

  1. Get a key. The shop makes one integration user for your app, with one narrow role (recipes/integration_user.md). Every request carries Authorization: token <api key>:<api secret>.

  2. Start from the request collection, not from a library. api/collection/NN_*.http holds every call the samples make, as plain HTTP files. Any tool can send them, and run.sh sends them all with curl and jq alone:

    BASE=http://<shop>:<port> KEY=<api key> SECRET=<api secret> bash api/collection/run.sh
    

    Generate a client from api/openapi.yaml only if you want one; nothing needs it.

  3. Read records. List with fields and filters; read one record with its child rows:

    GET /api/resource/POS Profile?fields=["name","warehouse"]&limit_page_length=5
    GET /api/resource/POS Profile/<name>                               → .data.warehouse: where a till sells from
    GET /api/resource/Bin?fields=["actual_qty"]&filters=[["item_code","=","<item>"],["warehouse","=","<warehouse>"]]
    

    A list returns only name unless you ask for fields. A stock balance is the shop's own figure (Bin.actual_qty): read it, never keep your own copy of it.

  4. Write records. POST /api/resource/<type> creates; PUT /api/resource/<type>/<name> changes only the fields you send.

    ⚠ A child table sent in a PUT replaces the record's rows. To add one row, send the existing rows plus the new one. The bin service does this on every move: one save carries the new quantity and the row that explains it, so a quantity never changes without its reason (samples/s2_bins/service/bins.py).

  5. Book a stock movement: you ask, the shop books it. Create a Stock Entry with docstatus: 1. One call creates and books it, and the shop keeps valuation and the ledger. Send purpose as well as stock_entry_type: over the API the purpose is not filled in for you.

    POST /api/resource/Stock Entry
    {"stock_entry_type": "Material Transfer", "purpose": "Material Transfer", "company": "<company>", "docstatus": 1,
     "items": [{"item_code": "<item>", "qty": 4, "s_warehouse": "<back room>", "t_warehouse": "<till's warehouse>"}]}
    

    For serialised items, your role also needs create on Serial No and Serial and Batch Bundle, and the line carries use_serial_batch_fields: 1 with serial_no (collection file 05). The two-step form (a draft, then POST /api/v2/document/Stock Entry/<name>/method/submit/) also works (file 07).

  6. Do the shop's work first, then your own. Book the movement first, then update your own records with its name as the reason. If your second step fails, the movement's name in your log tells you what to finish. Never invent stock in your own records first.

    ⚠ A booking call that times out may still have booked. Before you send it again, look for it, for example by a reference you put in its remarks. The sample's /pick does not do this yet: a caller must not blindly retry a pick that timed out.

  7. React to events with webhooks, and make up for lost ones. A booked sale reaches you as a signed webhook. Follow recipes/webhook_receiver.md: verify the raw body, reject replays, handle each sale idempotently, and run a reconciliation pull.

    Here is the warranty sample end to end. A cashier sells a covered phone:

    the sale

    The till asks for the phone's serial number before it charges:

    the serial

    The sale is booked:

    booked

    The warranty service hears about the sale and writes its comment on it, in Desk:

    the comment

  8. Handle errors by their status:

    Status Meaning What to do
    401 wrong or rotated key alert; do not retry in a loop
    403 outside your role, or no key fix the role or the call
    404 no such record treat as absent
    417 the record was refused (a rule in the shop) read exception / _server_messages; do not retry unchanged
    timeout / 5xx the shop is busy or down retry later, idempotently — check whether a create already happened before you create again

What is refused, and why

When your service is down, and when the till is offline

Tests

What we would like to improve

Technical notes

What was proven where

What has not been proven

This page in the kit