Help centre › API & webhooks

API & webhooks

When the ready-made integrations don’t cover what you need, build your own. Your systems can read and write Syncendio data with an API key, and Syncendio can tell them the moment something happens.

What it’s for

UseExample
API: your system asks SyncendioA website shows live stock; a dashboard pulls this week’s orders; another system creates products in bulk
Webhooks: Syncendio tells your systemA new order starts your fulfilment process; a shipped order emails your customer; low stock alerts a buyer

An add-on. API & webhooks is $49 a month on Starter, Standard and Growth, and included in Pro. Workspaces that already had API keys or webhooks before it became an add-on keep them at no charge.

Turn it on

  1. Open Integrations → API. If the add-on is off, the page says so.
  2. An Owner clicks Add for $49/month, or turns on API & webhooks under Settings → Add-ons. On Pro it’s already on.

Only an Owner can create API keys and webhooks, because a key can read your whole workspace.

Create an API key

  1. In Integrations → API, under API keys, name what will use the key, such as “Website stock feed”. Name the system, not the person.
  2. Tick only the permissions it needs.
  3. Click Create key, then copy the key straight away. It’s shown once and can’t be retrieved later.
PermissionLets the key
read:productsRead products and their stock
write:productsCreate products
read:inventoryRead stock by location, locations and stock movements
write:inventoryAdjust stock and transfer it between locations
read:ordersRead sales orders, customers and sales invoices
write:ordersCreate sales orders and customers
read:purchasingRead purchase orders, suppliers and supplier bills
write:purchasingCreate purchase orders and suppliers
read:financialsRead the chart of accounts and journal entries
manage:webhooksAdd and remove webhooks through the API

Writes follow the same rules as the app. An order through the API is refused for an inactive customer, over a credit limit, from a location whose stock is out on consignment, or without an exchange rate — exactly as on screen. Orders created with a key are marked as coming from the API.

A key that leaks can be stopped at once with Revoke. Last used shows whether anything still uses a key before you revoke it.

Call the API

The base URL is shown at the top of Integrations → API and ends in /api/v1. Send the key with every request:

curl https://YOUR-API-ADDRESS/api/v1/products?limit=20 \
  -H "Authorization: Bearer syn_your_key_here"
RuleDetail
AuthenticationAuthorization: Bearer syn_… or X-API-Key: syn_…
Finding your way inThe base URL itself — /api/v1 with nothing after it — answers without a key and describes the permissions, paging and errors below. Every other address needs one.
ListsTake limit (up to 200) and offset; return data and pagination with the total
ErrorsReturn error (a code for your program) and message (for a person)
MoneyIn the document’s own currency, always with currency beside it
Unknown parametersRefused with 400, so a typo in a filter never returns everything
Rate limit600 requests a minute per key; after that, 429 with how long to wait

Endpoints

RequestPermissionWhat it does
GET /productsread:productsProducts with stock totals and their attributes. Filter by sku, status, or by attribute
GET /products/{id or sku}read:productsOne product, with stock per location
POST /productswrite:productsCreate a product: sku and name required; optional attributes
GET /inventoryread:inventoryStock per product and location. Filter by locationId, lowOnly=true
GET /inventory/movementsread:inventoryAdjustments, transfers, receipts and counts with their lines. Filter by type, since, productId
GET /locationsread:inventoryYour locations, with consignment direction and owner
POST /inventory/adjustmentswrite:inventoryAdjust stock: productId or sku, locationId, quantityChange, reason. An increase completes at once; a decrease is a draft
POST /inventory/movements/{id}/authorise, /complete, /cancelwrite:inventoryMove a draft adjustment through its stages
POST /inventory/transferswrite:inventoryMove stock: fromLocationId, toLocationId, lines of productId or sku and quantity
GET /ordersread:ordersSales orders. Filter by status, since
GET /orders/{id or number}read:ordersOne order with its lines and total
POST /orderswrite:ordersCreate a sales order: customerId, locationId, lines of productId or sku, quantity and optional unitPrice (left out, the customer’s own price is used). quote: true creates a quote
GET /customers, GET /customers/{id}read:ordersCustomers with their attributes. Filter by q, status, or by attribute
POST /customerswrite:ordersCreate a customer: name; optional email, paymentTermsDays, currency, attributes
GET /invoices, GET /invoices/{id or number}read:ordersSales invoices with total, paid and outstanding. Filter by status, customerId, since
GET /purchase-ordersread:purchasingPurchase orders. Filter by status
POST /purchase-orderswrite:purchasingCreate a draft purchase order: supplierId, locationId, lines of productId or sku, quantity, unitCost
GET /suppliersread:purchasingSuppliers with their attributes. Filter by q, or by attribute
POST /supplierswrite:purchasingCreate a supplier: name and leadTimeDays; optional contact, email, currency
GET /purchase-invoicesread:purchasingSupplier bills with total, paid and outstanding. Filter by status, supplierId, since
GET /accountsread:financialsThe chart of accounts
GET /journal-entriesread:financialsJournal entries with their lines. Filter by from, to, source
GET/POST /webhooks, DELETE /webhooks/{id}manage:webhooksManage webhook endpoints from your own code

Webhooks

  1. Under Webhooks, choose an event in Tell me when.
  2. Enter the address in Send it to. It must start with https://.
  3. Click Add endpoint and copy the signing secret. Like a key, it’s shown once.
  4. Click Send test, then Attempts to see whether it arrived.
EventSent when
order.createdA sales order is created
order.shippedAn order ships, in part or in full
invoice.createdA sales invoice is raised
invoice.paidA sales invoice is paid in full
purchase_order.createdA purchase order is created
purchase_order.receivedStock is received against a purchase order
product.created, product.updatedA product is created or changed
stock.lowA product falls to or below its reorder point

If your endpoint doesn’t answer with a 2xx status, the delivery is retried 6 times over about a day (30 seconds, 2 minutes, 8 minutes, 32 minutes, 2 hours, 8 hours). After 20 failures in a row, the endpoint is switched off and the reason is shown; switch it back on once it’s fixed. Retries reuse the same Syncendio-Delivery id, so you can ignore a delivery you’ve already processed.

Check the signature

Every delivery carries Syncendio-Signature: t=<unix time>,v1=<hex>. v1 is an HMAC-SHA256 of <t>.<raw request body>, keyed with the endpoint’s signing secret. Recompute it, compare, and reject anything older than a few minutes. This proves the request came from Syncendio.

// Node.js
const crypto = require('crypto');

function isFromSyncendio(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

If the add-on is switched off

  • API calls return 403 with addon_not_enabled; the keys aren’t deleted.
  • No webhooks are sent. Events that happen while it’s off show as not sent in Attempts, and aren’t delivered later.
  • Turning the add-on back on makes your existing keys and endpoints work again.