# Supacolour API — guide for coding agents

> Machine-readable spec: https://0.0.0.0:8080/developers/openapi.json
> Everything in one fetch: https://0.0.0.0:8080/developers/llms-full.txt

Order heat transfers programmatically: quote, submit, attach artwork, then track to despatch.

This is the same API the Supacolour dashboard runs on — not a reduced side-door version. Anything the
dashboard does, your system can do.

## This document is region-specific

This copy of the spec targets `https://api.supacolour.co.nz`. Your account exists in exactly one region, and
your credentials only work against that region's host — **which host that is comes with your
credentials when they are issued** (it is not discoverable from the API itself). If your account is
in a different region, use that region's own copy of this document:

- **United States** — `https://api.supacolor.com` · [spec](https://supacolor.com/developers/openapi.json)
- **United Kingdom** — `https://api.supacolour.co.uk` · [spec](https://supacolour.co.uk/developers/openapi.json)
- **Europe** — `https://api.supacolour.eu` · [spec](https://integrate.supacolour.eu/developers/openapi.json)

## Authenticate

OAuth2 client credentials. `POST https://api.supacolour.co.nz/api/auth/token` with `client_id` and
`client_secret` (form-encoded) returns a bearer token; send it as
`Authorization: Bearer <token>`. Credentials are issued per wholesale account and see only that
account's data.

## The ordering flow

Each step produces something the next one needs. Call them in this order:

1. **`GET /PriceCodes/processes`** — the processes your account can order (Wearable, SupaDTF, Sub
   Block …). Start here: availability is per-account, so never hardcode it.
2. **`GET /PriceCodes/price-codes`** — price codes for those processes, with sizes and prices at
   *your* tier. The `code` returned is what goes in a job line.
3. **`GET /Jobs/delivery-options`** and **`GET /Jobs/earliest-ship-date`** — the delivery methods
   available to you, and the soonest despatch for the processes you're ordering. The method code goes
   in `deliveryAddress.deliveryMethod`.
4. **`POST /Jobs`** — create the job, with the artwork on each line as
   `externalArtworkUrl`. ⚠️ Send `"validateOnly": true` first: it runs the full validation path
   and returns the errors without creating anything. Build against that.
5. **`GET /Jobs/{jobNumber}`** — status, money, tracking. **`GET /Jobs/active`** returns
   everything open in one call; prefer it over polling jobs individually.

## Artwork goes on the job line

Put a URL to the artwork in `externalArtworkUrl` on each `PriceCode` line. That is how our own
ordering works, and it means one request places the order and delivers the art together.

The URL must stay reachable until the job is in production — we fetch it, we do not hold a copy of
your link. A signed URL is fine as long as it outlives the job reaching the factory.

## After you have ordered

A job is not frozen the moment you submit it. `GET /Jobs/{jobNumber}` tells you what you may still
do, and you should read that rather than assume:

- **`permissions.canEdit`** — whether **`PATCH /Jobs/{jobNumber}`** will be accepted. You can
  amend the description, your PO number, comments, the requested ship date and the must-ship flag.
  When it is `false`, `permissions.lockedReason` says why.
- **`isCancelable`** — whether **`POST /Jobs/{jobNumber}/cancel`** will be accepted. Once the job
  is in production it will not be, and you get a `409`.

⚠️ They are independent, and they do not change together. A job already in production is commonly
`canEdit: true` and `isCancelable: false` — you can still fix the PO number, but the order is
past the point of being called back. Read both, rather than inferring one from the other or caching
an earlier answer.

## Reordering costs you nothing

An asset is a print already made. `GET /Assets` lists yours. To run it again, send a job line with
`"itemType": "Asset"` and the asset tag as `code` — no artwork upload, no colour re-approval, and
the result matches the previous run.

## Conventions that will bite you otherwise

- **`items[].itemType` selects the line type.** `"PriceCode"` for a new print, `"Asset"` for a
  reorder, `"Stock"` for stocked goods — and it decides what `code` means (full price code, asset
  tag, or stock code respectively). The schema does not enumerate these values; this list is the
  documentation.
- **A 500 from `POST /Jobs` usually means an invalid payload, not an outage.** Empty `items`, an
  unroutable delivery address, a nonexistent asset code or a bad `sizeQuantities` key all surface
  as an opaque 500. Fix the payload before retrying; `validateOnly` with a well-formed payload
  returns real validation messages.
- **Read enums, don't hardcode them.** Job statuses come from `GET /Lookups/job-statuses`,
  processes from `GET /Lookups/process-codes`, countries and states from `GET /Lookups/countries`.
- **Size strings are labels, not geometry.** A price code's size is the code's own string. Don't
  parse it to compute artwork dimensions.
- **Money is in the account's currency.** No conversion is applied anywhere.
- **`countryCodeISO2` is two characters** — `"US"`, not `"USA"`. A three-letter code is rejected.
- **4xx bodies carry the reason.** Every operation documents its failure codes; read the body rather
  than retrying blind.

## Working rules

These are the things that are not visible from the schema and that cost time when discovered the
hard way.

1. **Build against `validateOnly`.** `POST /Jobs` with `"validateOnly": true` runs the entire
   authentication and validation path and creates nothing, answering
   `"Validated with NO Errors. Job NOT created."` when the payload is good. Use it for every
   iteration until the payload is right.
2. **A 500 from `POST /Jobs` usually means your payload, not an outage.** Measured triggers: an
   empty `items` array, a delivery address that cannot be routed, an unknown asset code, a missing
   `dateDue`, or a `sizeQuantities` key that is not in the variant's size run. Retrying unchanged
   will not help.
3. **`items[].itemType` decides what `code` means.** `"PriceCode"` takes a full price-code
   string, `"Asset"` takes an asset tag, `"Stock"` takes a stock code. The schema does not
   enumerate these values.
4. **Never hardcode an enum.** Job statuses come from `GET /Lookups/job-statuses`, processes from
   `GET /Lookups/process-codes`, countries and states from `GET /Lookups/countries`. New values
   are added over time and must not break your integration.
5. **A price code's embedded size is a label, not a measurement.** Do not parse `WE_LC:Wearable-4"
   x 4"` for geometry; read `sizeWidth`, `sizeHeight` and `sizeUnit`.
6. **`countryCodeISO2` is two characters.** `"US"`, never `"USA"`.
7. **Prices are per-account.** Everything you read is at that account's tier. Never cache pricing
   across accounts or show it to another customer.
8. **Reordering is the cheap path.** An existing asset reordered by tag reproduces the previous run
   with no artwork to supply and no colour re-approval.
9. **Artwork goes on the job line as `externalArtworkUrl`.** There is no separate upload step. The
   URL must stay reachable until the job is in production — we fetch it rather than holding your
   link.
10. **Check the job before amending or cancelling it.** `GET /Jobs/{jobNumber}` returns
    `permissions.canEdit` (whether `PATCH /Jobs/{jobNumber}` will work, with
    `permissions.lockedReason` when it will not) and `isCancelable` (whether
    `POST /Jobs/{jobNumber}/cancel` will work). ⚠️ They are independent: a job in production is
    commonly still editable but no longer cancellable. Read both rather than inferring one from the
    other.
11. **Region is a property of the credentials.** You cannot discover it by calling the API — you need
    the right regional host to get a token at all. It is stated when the credentials are issued.

## What you can call

32 operations, every one of them callable with a customer credential. If it is
documented here, your token can use it.

- **PriceCodes** — What your account can order and what it costs. The first call in any integration — process availability and pricing are per-account, so never hardcode them.
  - `GET /PriceCodes/{priceCode}` — getPriceCodesByPriceCode
  - `GET /PriceCodes/price-codes` — getPriceCodesPricecodes
  - `GET /PriceCodes/processes` — getPriceCodesProcesses
- **Jobs** — Orders. Create a job, attach artwork, then track to despatch. `validateOnly: true` dry-runs a payload so you can build without creating real jobs.
  - `POST /Jobs` — createJobs
  - `GET /Jobs/{jobNumber}` — getJobsByJobNumber
  - `PATCH /Jobs/{jobNumber}` — updateJobsByJobNumber
  - `POST /Jobs/{jobNumber}/cancel` — createJobsByJobNumberCancel
  - `GET /Jobs/active` — getJobsActive
  - `GET /Jobs/delivery-options` — getJobsDeliveryoptions
  - `GET /Jobs/earliest-ship-date` — getJobsEarliestshipdate
- **Assets** — Prints you have made before. Reorder by asset tag — no artwork upload, no colour re-approval.
  - `GET /Assets` — getAssets
  - `GET /Assets/{assetCode}/jobs` — getAssetsByAssetCodeJobs
  - `GET /Assets/{assetTag}` — getAssetsByAssetTag
  - `GET /Assets/{assetTag}/files` — getAssetsByAssetTagFiles
  - `GET /Assets/global` — getAssetsGlobal
  - `GET /Assets/types` — getAssetsTypes
- **Account** — Your account: address, users, tax certificates, transactions. `GET /account` confirms which account a set of credentials belongs to.
  - `GET /account` — getAccount
  - `GET /account/shipment-settings` — getAccountShipmentsettings
  - `GET /account/transactions` — getAccountTransactions
  - `GET /account/user` — getAccountUser
  - `GET /account/users` — getAccountUsers
  - `GET /account/users/{userId}` — getAccountUsersByUserId
- **Lookups** — Reference data — countries, states, job statuses, process codes, size sets. Read these instead of hardcoding values that change.
  - `GET /Lookups/countries` — getLookupsCountries
  - `GET /Lookups/countries/{countryCode}/states` — getLookupsCountriesByCountryCodeStates
  - `GET /Lookups/job-statuses` — getLookupsJobstatuses
  - `GET /Lookups/process-codes` — getLookupsProcesscodes
  - `GET /Lookups/size-sets` — getLookupsSizesets
- **Stock** — Stocked items — heat presses and supplies — and their shipping options.
  - `GET /Stock` — getStock
  - `GET /Stock/{code}` — getStockByCode
- **PromoCodes** — Validate a promotional code before applying it to a job.
  - `GET /promocode/validate/{code}` — getPromocodeValidateByCode
- **StockItemShipping** — Shipping options for stocked items, by stock item and market.
  - `GET /stock/{stockItemId}/shipping` — getStockByStockItemIdShipping
  - `GET /stock/shipping` — getStockShipping

## Errors

Every failure carries an RFC 7807 problem body. `detail` explains this request; on a 400 the
validation failures are in `errors` (or `validationMessages` for jobs). `401` means the token
is missing, expired, or from another region. `403` means the operation is not available to your
account's role — it will not start working on retry. `404` means no such record *or* it belongs to
another account; the two are indistinguishable by design.
