Public REST API · v1

Build your own
storefront on top.

Every order, product, and tracking event VATFlow holds is reachable over a plain JSON REST API. Point a custom UI at it and keep the classification, invoicing, and fulfilment running underneath — you write the front, we settle the VAT.

Keys are scoped and revocable. Every webhook is signature-verified.

POST /v1/orders → 201
$ curl -s api.vatflow.com/v1/orders -d @order.json

201 Created
  number             1042
  vat.placeOfSupply  "DE"
  vat.scheme         "oss"
  vat.rate           "0.19"
  vat.total          "24.10"   # EUR, decimal-exact
  invoice.url        "/v1/invoices/inv_8f2a.pdf"
01Core resources

One endpoint per thing you already manage.

The same data the dashboard reads, addressable over HTTP. List, fetch, and write with predictable JSON, cursor pagination, and idempotency keys on every create.

/v1/orders

Orders

Create an order and the VAT engine classifies it inline — place of supply, scheme, rate, totals. List, fetch, fulfil, and refund.

/v1/products

Products

Products with the full size/colour variant matrix, prices in minor units, and slugs for your storefront routes.

/v1/inventory

Inventory

Per-variant stock across every warehouse. Read levels, post adjustments — each write lands in the audit log.

/v1/vat

VAT

Classify a hypothetical order without persisting it. Pure read of the engine: scheme, rate, and the resolved place of supply.

/v1/shipping

Shipping

Rate-shop your configured carriers, create a label, and pull tracking events — the same events that render on your tracking page.

/v1/invoices

Invoices

Compliant invoice and credit-note PDFs, addressable by id. Generated the moment an order is classified or returned.

02Request → classification

Post an order, get the VAT back in the response.

You send the ship-to country, the buyer type, and the lines. The engine resolves place of supply, commits to one of the four schemes, applies the rate, and returns it — before you ever take payment.

Request
$ curl https://api.vatflow.com/v1/orders \
  -H "Authorization: Bearer sk_live_3Qf9...c7a1" \
  -H "Content-Type: application/json" \
  -d '{
    "shipTo":  { "country": "DE", "postcode": "10115" },
    "buyer":   { "type": "consumer" },
    "currency": "EUR",
    "lines": [
      { "sku": "TEE-BLK-M", "qty": 1, "unitNet": "20.25" }
    ]
  }'
Response
201 Created

{
  "id": "ord_8f2a41",
  "number": "1042",
  "status": "classified",
  "currency": "EUR",
  "vat": {
    "placeOfSupply": "DE",
    "scheme":        "oss",
    "rate":          "0.19",
    "net":           "20.25",
    "amount":        "3.85",
    "total":         "24.10"
  },
  "invoice": { "url": "/v1/invoices/inv_8f2a.pdf" }
}
This order resolved asOSSat19%for ship-to DE. Money is returned as decimal-exact strings — never a float.
03Write surface

Create and update from your own systems.

The write endpoints reuse the exact same services the dashboard does — same VAT classification, same inventory ledger, same audit log. Each is gated on a scoped key; a wrong or missing scope is a 403, a missing key a 401.

EndpointScope
POST/api/public/v1/orders

Create an order from { customer, lines, shippingAddress }. The VAT engine classifies it inline and returns the order with scheme, rate, and totals.

orders:write
POST/api/public/v1/products

Create a product with its full size/colour variant matrix and net prices.

products:write
PATCH/api/public/v1/products/:id

Update an existing product — title, status, pricing, or variants.

products:write
POST/api/public/v1/inventory/adjust

Post a stock adjustment for a variant at a warehouse. Each write lands in the audit log.

inventory:write

Mint a key with the matching write scope in Settings → API keys. Keep write keys server-side.

Build your own UI

Read the catalogue, render it however you like.

List endpoints return a consistent envelope — an object: "list" with cursor pagination via has_more and a starting_after cursor. Every variant carries its size, colour, and net price, so your storefront grid maps straight from the payload.

See the hosted platform

GET /v1/products
$ curl "https://api.vatflow.com/v1/products?limit=2" \
  -H "Authorization: Bearer sk_live_3Qf9...c7a1"

{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "id": "prod_7c10",
      "slug": "organic-cotton-tee",
      "title": "Organic Cotton Tee",
      "variants": [
        { "sku": "TEE-BLK-M", "size": "M", "colour": "Black", "priceNet": "20.25" },
        { "sku": "TEE-BLK-L", "size": "L", "colour": "Black", "priceNet": "20.25" }
      ]
    }
  ]
}
04Authentication

Scoped API keys, revocable in a click.

Every request is a bearer token in the Authorization header. Mint as many keys as you need, scope each to the resources it touches, and revoke any one without rotating the rest.

Two prefixes

Live keys start sk_live_, test keys sk_test_. The prefix tells you which environment you are about to hit.

Least privilege

Scope a key to read:products or write:orders. A storefront key never needs to touch inventory writes.

Rotate cleanly

Issue the replacement, deploy, then revoke the old key. Both work during the overlap.

Server-side only

Secret keys never ship to the browser. Your custom UI talks to your backend; your backend holds the key.

Authorization header
Authorization: Bearer sk_live_3Qf9...c7a1
05Rate limits

Fixed ceilings, honest headers.

Limits are per key, per minute, with a short burst allowance. Every response carries your remaining budget so you can back off before you hit 429.

PlanSustainedBurst
Free60 req / min120 req
Growth600 req / min1,200 req
Scale3,000 req / min6,000 req

Read X-RateLimit-Remaining and X-RateLimit-Reset on every response. When you exhaust the budget you get a 429 with a Retry-After header.

429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718900042

{ "error": "rate_limited",
  "message": "Slow down — retry in 12s." }
06Webhooks

Signature-verified, or rejected with 401.

VATFlow pushes order and shipment events to your endpoint, signed with your endpoint secret. The X-VATFlow-Signature header is an HMAC-SHA256 of the raw body; the X-VATFlow-Event header names the event. Verify the signature before you do any other work — an unverified webhook is a 401, full stop.

Incoming webhook
POST /your/webhook/endpoint
X-VATFlow-Event: shipment.status.updated
X-VATFlow-Signature: 4f0c9b1e7a2d...e9b2   # HMAC-SHA256 of the raw body

{
  "event": "shipment.status.updated",
  "data": {
    "orderId":   "ord_8f2a41",
    "awb":       "JD0002182734",
    "carrier":   "dhl_express",
    "status":    "in_transit",
    "occurredAt": "2026-06-20T11:32:04Z"
  }
}

Compute the HMAC over the exact bytes you received — do not re-serialize the JSON first. A non-2xx response makes us retry with exponential backoff, so make your handler idempotent. Anything you cannot verify is a 401 — never act on the payload.

Verify, then act
import crypto from "node:crypto";

// Compute HMAC-SHA256 of the EXACT raw body string with your endpoint secret.
const expected = crypto
  .createHmac("sha256", process.env.VATFLOW_WEBHOOK_SECRET)
  .update(req.rawBody) // the bytes as received — do not re-serialize
  .digest("hex");

const signature = req.headers["x-vatflow-signature"];
const ok =
  signature &&
  crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

// Reject anything we can't prove came from VATFlow — return 401.
if (!ok) return res.status(401).end();

// Safe to act on now.
if (req.headers["x-vatflow-event"] === "shipment.status.updated") {
  await recordTrackingEvent(JSON.parse(req.rawBody).data);
}
order.created

An order was placed — through the storefront, the dashboard, or a POST to the API. The VAT is already classified on the payload.

order.paid

An order was paid: a storefront checkout confirmed, or an order marked paid in the back office. It now moves to fulfilment.

shipment.status.updated

A carrier reported a new tracking event — in transit, out for delivery, delivered — the same event that renders on your tracking page.

OpenAPI 3.1

The whole surface, machine-readable.

Every read and write endpoint — with its security scheme, request bodies, and response schemas — is described in a single OpenAPI 3.1 document. Point your generator at it for a typed client, or load it into Postman and start poking.

GET /api/public/v1/openapi.json

GET /api/public/v1/openapi.json
$ curl -s api.vatflow.com/api/public/v1/openapi.json | jq .openapi
"3.1.0"

{
  "openapi": "3.1.0",
  "info":    { "title": "VATFlow Public API", "version": "1.0.0" },
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer" }
    }
  },
  "paths": { "/orders": { ... }, "/products": { ... } }
}
Ship against v1 today

Your front end, our tax engine underneath.

Create an account, mint a test key, and post your first order. The VAT comes back in the response — no carrier credentials, no card required.