DEVELOPERS
Aurient API · v1

Connect to Aurient

Put web orders into Aurient, keep your online shop's stock and prices right, pull sales into Power BI or Excel, send invoices and payments to your accounting software, or build your own apps. Everything you can do in Aurient's screens, a program can do through the API.

In short: make a key under Admin → API, send it as Authorization: Bearer ak_live_… to https://api.aurient-gls.com/v1, and get JSON back. Use a test key to try things on your practice company first.

Start here

  1. Your company needs API access in its plan (Admin → Plan and Modules).
  2. Go to Admin → API → New API key. Give it a name you'll recognise (“Shop website”) and pick live or test. Copy the key: it's shown once.
  3. Try it:
# the first 5 items
curl "https://api.aurient-gls.com/v1/items?limit=5" \
  -H "Authorization: Bearer ak_test_…"

The addresses are the same ones Aurient's own screens use, and each one does exactly what the screen does: the same checks, the same documents and numbers, the same postings to the books and the same audit trail.

Keys and test mode

Key starts withWorks onUse it for
ak_live_Your companyThe real thing: orders, stock and books
ak_test_Your practice companyBuilding and testing. Nothing touches the real company

A key can do all your business work: items, prices and stock, customers and suppliers, quotes, orders, shipping and invoices, payments and the books, purchasing, deliveries, production and the till. A key can never change people, permission groups, sign-in security or devices, your Aurient plan or bill, close the company, or make more keys.

Make one key for each system that connects, so you can turn one off without stopping the others. Everything a key changes is on the audit trail as “API key: its name”, and every call is listed under Admin → API → Activity for 30 days.

Keep keys on your server. Never put one in a web page or a phone app that customers download: anyone who has it can do what it can do. If one gets out, delete it under Admin → API and make a new one.

Making a call

  • Reading (GET): what you're asking for goes in the address, e.g. /v1/customers?q=sunbay.
  • Doing something (POST, PUT): send JSON with Content-Type: application/json.
  • Records are named the way people name them: a customer by its code or name, an item by its SKU, a document by its number (INV-8901), a warehouse by its code.
  • Money is in the company's own currency unless a field says otherwise. Dates are YYYY-MM-DD in the company's time zone.
  • Lists take limit and offset where shown in the reference, and answer with the rows and a total.
# a web order into Aurient
curl https://api.aurient-gls.com/v1/sales-orders \
  -H "Authorization: Bearer ak_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: web-10442" \
  -d '{ "customer": "C1042", "customerRef": "WEB-10442", "lines": [{ "item": "SB-0318", "qty": 2 }] }'

The reference below lists each address with what it takes. The full description is also an OpenAPI file you can load into Postman, Insomnia or a code generator.

Errors

Anything that isn't a success comes back with an HTTP status and the same shape, with a message you can show to a person:

{ "error": { "code": "INSUFFICIENT_STOCK", "message": "Not enough Sunbay Water 24 × 500ml in Main (2 on hand).", "requestId": "…" } }
StatusCodeWhat it means
401UNAUTHENTICATED · KEY_OFFNo key, a wrong or deleted key, or a key that's turned off
402BILLING_READ_ONLY · BILLING_LOCKEDThe company's Aurient bill is overdue: reading still works while it's read-only
403FORBIDDEN · NOT_IN_PLANNot something a key can do, or the module isn't in the company's plan
404NOT_FOUNDNo such record, or no such address
409INVALID_STATE · CONFLICT · PRACTICE_NOT_READYNot possible right now (already shipped, period closed, no practice company yet)
422VALIDATION · INSUFFICIENT_STOCK · …Something in the request needs fixing; details.issues says which field
423COMPANY_CLOSINGThe company is being closed: nothing new can be entered
429RATE_LIMITEDToo many calls this minute (see Limits)
500INTERNALOur fault. Try again; if it keeps happening, send us the requestId

Limits

Each company can make a number of calls a minute (usually 120), all its keys together. Every answer says where you are:

RateLimit-Limit: 120
RateLimit-Remaining: 87
RateLimit-Reset: 24          # seconds until the minute starts again

Over the limit you get 429 with Retry-After in seconds. Wait that long, then carry on. For a big job (an import of thousands of items) spread the calls out, or use Aurient's own import (Admin → Import and export). If you need more, ask us.

Doing it once

Networks drop. If a request to make something times out you can't tell whether it was made. Send an Idempotency-Key header with any value that's unique to that thing (your own order number works well). Sending the same request with the same key again within 24 hours gives you the first answer back, with Idempotent-Replayed: true, and nothing is done twice.

  • The same key with a different request is refused (422 IDEMPOTENCY_MISMATCH).
  • While the first request is still running, a repeat gets 409 IDEMPOTENCY_IN_PROGRESS: wait a moment and try again.
  • A 5xx answer isn't kept, so trying again can work.

Webhooks

Instead of asking Aurient again and again whether anything changed, let Aurient tell you. Under Admin → API → Webhooks, add your address (https), pick the events, and copy the signing secret. Within a few seconds of something happening, Aurient sends a POST like this:

POST /aurient/hook
Content-Type: application/json
Aurient-Event: invoice.posted
Aurient-Delivery: evt_4c1e…
Aurient-Signature: t=1793440800,v1=5f0a…

{ "id": "evt_4c1e…", "type": "invoice.posted", "created": "2026-11-02T14:00:00.000Z", "mode": "live", "company": "Best Sweet Spot",
  "data": { "object": "invoice", "id": 8901, "docNumber": "INV-8901", "action": "invoice", "by": "user",
            "summary": "INV-8901 issued to Sunbay Mart (BBD 468.00)", "link": "/v1/documents/INV-8901" } }
  • Answer with any 2xx within 10 seconds. Do the slow work afterwards.
  • Anything else (or no answer) is tried again: after 1 minute, 5 minutes, 15 minutes, 1 hour, 3, 6 and 10 hours — 8 tries in about a day. After that it shows as failed under Admin → API → Webhooks, where it can be sent again.
  • Messages can arrive out of order, and now and then twice. Use id to ignore one you've already had, and fetch the record from data.link when you need the latest.
  • stock.changed carries the item, the warehouse, what's on hand now and what moved it, so a shop can update its stock without calling back.
  • Webhooks are for the company itself, or (for testing) its practice company. A message says which in mode.

Checking signatures

Check every message really came from Aurient. Aurient-Signature holds a time t and v1, the HMAC-SHA256 of t + "." + the raw body made with your webhook's secret. Work it out yourself, compare, and refuse messages more than 5 minutes old.

import crypto from 'node:crypto';

// rawBody: the body exactly as it arrived (before JSON.parse)
export function fromAurient(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const want = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1 ?? ''));
}

Events

Pick any of these for a webhook. Each comes from what Aurient records, so it's sent whoever made the change: a person, the till, the assistant, a scheduled job or another API key.

Loading…

Reference

Loading the endpoints…