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
- Your company needs API access in its plan (Admin → Plan and Modules).
- 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.
- 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 with | Works on | Use it for |
|---|---|---|
ak_live_ | Your company | The real thing: orders, stock and books |
ak_test_ | Your practice company | Building 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 withContent-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-DDin the company's time zone. - Lists take
limitandoffsetwhere shown in the reference, and answer with the rows and atotal.
# 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": "…" } }
| Status | Code | What it means |
|---|---|---|
| 401 | UNAUTHENTICATED · KEY_OFF | No key, a wrong or deleted key, or a key that's turned off |
| 402 | BILLING_READ_ONLY · BILLING_LOCKED | The company's Aurient bill is overdue: reading still works while it's read-only |
| 403 | FORBIDDEN · NOT_IN_PLAN | Not something a key can do, or the module isn't in the company's plan |
| 404 | NOT_FOUND | No such record, or no such address |
| 409 | INVALID_STATE · CONFLICT · PRACTICE_NOT_READY | Not possible right now (already shipped, period closed, no practice company yet) |
| 422 | VALIDATION · INSUFFICIENT_STOCK · … | Something in the request needs fixing; details.issues says which field |
| 423 | COMPANY_CLOSING | The company is being closed: nothing new can be entered |
| 429 | RATE_LIMITED | Too many calls this minute (see Limits) |
| 500 | INTERNAL | Our 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
5xxanswer 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
2xxwithin 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
idto ignore one you've already had, and fetch the record fromdata.linkwhen you need the latest. stock.changedcarries 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 ?? ''));
}
<?php
// e.g. in a WordPress / WooCommerce endpoint
$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_AURIENT_SIGNATURE'] ?? ''), $sig);
$want = hash_hmac('sha256', $sig['t'] . '.' . $raw, AURIENT_WEBHOOK_SECRET);
if (abs(time() - (int) $sig['t']) > 300 || !hash_equals($want, $sig['v1'] ?? '')) { http_response_code(400); exit; }
$event = json_decode($raw, true);
import hashlib, hmac, time
def from_aurient(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
if abs(time.time() - int(parts['t'])) > 300:
return False
want = hmac.new(secret.encode(), parts['t'].encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, parts.get('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…