Premium Apparel API — Integration FAQ

Answers to the questions integrators ask before they build. Everything below describes the API as it behaves today. Where something is not available, it says so plainly rather than implying it is coming.

API reference · Error reference · Changelog · MCP guide


Rate limits

There is no enforced rate limit. No endpoint returns 429, and no Retry-After or RateLimit-* headers are sent.

If a limit is ever introduced it will be announced on the changelog before it takes effect, and it will behave conventionally: HTTP 429 with a Retry-After header giving the number of seconds to wait. Honour Retry-After on a 429 in your client now and you will not need to change anything later.

Poll sensibly rather than hard

The catalogue is not live data. FileMaker rewrites each customer's catalogue file on a rolling cycle that works through every provisioned token and takes roughly an hour, so a given token's data changes about once an hour. Polling faster returns the same as_of and — with since — an empty result set.

The limits that do exist

Limit Value What happens when you exceed it
Filter values on /v1/stock and /v1/prices 200 across product_id + style + sku combined 422 validation_error naming the count you sent
Response size over MCP (POST /mcp) ~256 KB The tool result is truncated with a notice. Filter, or use since. REST has no such cap.
Upstream call timeout 5 seconds 503 upstream_unavailable. Retry with backoff — see the error reference.
Upstream call timeout, POST /v1/orders 30 seconds A large order is given longer. If it still runs out, the API looks for the order by order_no before answering: you get the real 201 if it was created, otherwise 503 with details.reason: "outcome_unknown" — see the error reference.

Are prices exclusive of GST?

Yes. Every price the API accepts or returns for a token holder is exclusive of GST/VAT, in both regions.

That covers:

Prices are decimal strings in the catalogue responses and are never rounded or reformatted on the way out.

Sending tax-inclusive prices on an order

If your system holds tax-inclusive prices and you would rather not convert them, say so explicitly and the conversion is done for you:

{
  "order_no": "OMS-12345",
  "taxes_included": true,
  "tax_lines": [{ "rate": 0.15 }],
  "line_items": [{ "sku": "A1022AR-LS", "quantity": 2, "price": 10.81 }]
}

rate is a fraction, not a percentage — 0.15 for 15% NZ GST, 0.10 for 10% AU GST. The order stores the tax-exclusive figure either way. Both fields are ignored when no line carries a price.

The one exception: the no-token RRP catalogue

Calling the catalogue endpoints without a bearer token returns the default catalogue, whose price is the recommended retail price, and RRP is GST-inclusive by Premium Apparel's own convention. It is a display price, not a price you can transact at.

Authenticate. A token returns your contracted pricing, exclusive of GST, which is the only pricing an order will ever be accepted at.


Can a response carry the account code it was priced for?

Not today. Catalogue responses identify no account. Your bearer token selects a pre-generated catalogue file, and the account behind that token is resolved inside FileMaker at generation time — the file itself carries no account identifier for the API to echo back.

In practice this is rarely a problem, because the mapping is one-to-one and stable: one token, one account, one price list. If your system genuinely needs the account code in the payload — for example you hold several Premium Apparel accounts and route responses by code rather than by which token you used — tell your account contact. It is a small change to the file generator and it would surface as a top-level field on /v1/products, /v1/stock and /v1/prices. It is not currently scheduled.

Orders are not affected: an order is always attributed server-side to the account the token resolves to, whether or not you name one.


Incremental sync: what changed_at actually tracks

changed_at already moves only on a real change. Nothing stamps the whole catalogue on a schedule.

The catalogue files carry no timestamps of their own, so the API derives change detection by diffing each regeneration against the previous one's recorded values and stamping only the entries whose value actually differs. An unchanged entry carries its old timestamp forward untouched, across regenerations, deploys and restarts.

What differs between the endpoints is what counts as a change:

Endpoint since returns Because
/v1/stock SKUs whose stock number changed tracked per SKU, stock only
/v1/prices SKUs whose price changed tracked per SKU, price only
/v1/products styles where anything at all changed — title, images, description, size chart, and stock, and price you are syncing whole product records, so any difference counts

That last row is why /v1/products?since= feels like it returns everything. Stock moves constantly, and one SKU moving makes its whole style "changed". Measured on a live account, one regeneration of a 441-style / 12,376-SKU catalogue moved:

The fix is to stop asking /v1/products for the volatile dimensions:

There is currently no way to ask /v1/products?since= for descriptive changes only. If that would materially help your integration, raise it with your account contact.

Cursor mechanics

Pass back exactly what you were given. Every response carries as_of (ISO 8601), as_of_epoch_ms, and the same instant as the Last-Modified header; since accepts an ISO 8601 timestamp or a Unix epoch in seconds or milliseconds. The comparison is strictly greater-than, so replaying the previous as_of returns nothing rather than the batch you already have. A value we cannot parse returns 422 rather than being silently ignored.


Recovering removals after losing a since watermark

You do not need a special reconcile path. Removals are never forgotten.

Withdrawn SKUs and styles are kept as tombstones and are never expired, so a since value from arbitrarily far back returns the complete removal history along with a full snapshot:

GET /v1/stock?since=1
GET /v1/products?since=1

Two things to know:

  1. The tombstone list begins when the API started change-indexing your catalogue, not at the beginning of time. Anything withdrawn before that was already absent from the first snapshot, so it never produced a tombstone.
  2. A plain full read is always authoritative. GET /v1/products with no since is the complete current catalogue — anything of yours that is not in it no longer exists, tombstone or no tombstone. Use that as the belt-and-braces reconcile if you prefer not to depend on tombstones at all.

removed and removed_styles are only present on a since request. Without since the response is the current state, so absence already says it.

Read them. A SKU missing from a since response means "did not change", not "withdrawn". If you ignore removed, discontinued items stay in your system forever.


Telling discontinued apart from out of stock

There is no active or status flag on a style, colour or SKU — and none is needed, because the two states are already unambiguous:

State How it appears
Out of stock The SKU is present in the catalogue with stock: 0
Withdrawn / discontinued The SKU is absent from the catalogue, and appears in removed (or its style in removed_styles) on a since request

stock is the sellable quantity and is floored at zero — it is never negative, so 0 means exactly "none available", not "oversold".

The grains work the way you would expect. Discontinue one colour and that colour's SKUs are tombstoned in removed while the style stays live. Discontinue the whole style and it appears in removed_styles.

A worked example from live data — one colour of style 1301 was withdrawn, giving six SKU tombstones and no style tombstone:

{
  "sku": "UCP220L URBAN COLLAB LADIES POLO - NAVY-S",
  "product_id": "UCP220L URBAN COLLAB LADIES POLO - NAVY",
  "style": "1301",
  "removed_at": "2026-09-14T21:50:24.000Z"
}

One caveat worth building for: "removed" means "no longer offered through this catalogue", which is usually a discontinuation but can also mean a product was deliberately excluded from the API feed. Either way you should stop offering it. If a product is reinstated its tombstone is dropped and it reappears as a normal change, so a removal is not permanent.


Barcodes, carton quantity, packing units and RRP

Each size in /v1/products carries:

Each style carries packing_units: the packing-unit figure courier freight bands are worked out from (a tee is about 5, a hoodie about 25), or null where the style has none. It is not a weight, and has no unit.

Coverage is not universal: a value that is not held is null, never guessed. Weight, unit dimensions and volume are held but not published.


Dispatch information on order reads

GET /v1/orders/{our_ref} and GET /v1/orders?order_no= return dispatch details with the order:


Can I get a read-only token?

Yes — this already works. Permissions are set per token, so a token can be issued that reads the catalogue and cannot create anything.

Ask your account contact for a read-only token. What you get:

Operation Read-only token
GET /v1/products, /v1/stock, /v1/prices Allowed
GET /v1/orders, GET /v1/orders/{id} Allowed, limited to your own orders
POST /v1/orders 403 forbidden
PUT /v1/orders/{our_ref}/line-items 403 forbidden
Purchase orders, product create/update, image upload 403 forbidden

The refusal happens in Premium Apparel's own system, not in a middleware check that could be bypassed, and it is a clean 403 with the standard error envelope — see the error reference.

A practical note: run your catalogue sync on the read-only token and keep the order-writing token separate and narrowly deployed. A catalogue sync that cannot place an order is one fewer way for a bug to become an order.


Is there a sandbox or test account?

No. There is no sandbox or test environment — api.premiumapparel.co.nz and api.premiumapparel.com.au are production, and there is no staging equivalent.

That is less limiting than it sounds, because the two halves of the API carry very different risk.

Reads are completely safe. GET /v1/products, /v1/stock, /v1/prices and the order reads are side-effect free. Develop against them freely, at whatever volume you like — you cannot break anything and there is nothing to clean up. Start without a token at all if you only want to see the catalogue shape: you get RRP pricing (GST-inclusive, see above) and the identical structure.

POST /v1/orders creates a real order. There is no dry-run flag. To test order creation:

  1. Ask your account contact to nominate a test account for you — a real account, set up knowing that orders placed against it are tests, so they can be cancelled rather than picked and shipped.
  2. Prefix order_no distinctively, for example TEST-OMS-0001, so test orders are obvious on both sides.
  3. Tell your contact when you are testing, and confirm afterwards that the orders were cancelled.

Two things make this safer than it sounds. order_no is idempotent: re-sending the same one returns 409 duplicate_reference rather than creating a second order, so a retry loop in a half-built client cannot flood the system. And a read-only token cannot create an order at all — do most of your development on one and switch to a writing token only for the order step.


Changelog and breaking changes

The changelog is at /docs/changelog. It records every change to the API surface, newest first, with the date it reached production.

The versioning commitment lives in full on that page. In short:


Something here unanswered, or answered wrongly? Tell your account contact — this page is maintained, not archived.