Developer documentation

Order transfers from your own system.

Print a design once and it becomes an asset tag. From then on, one call prices it at any quantity and one call orders it again — same separation, same colours, no artwork upload and no setup charge. That loop is what a print-on-demand storefront runs all day.

It is the same API our dashboard runs on, not a reduced side door.

  1. 1POST /Jobs

    Print a design once

    Send the artwork on the line as externalArtworkUrl. The response gives you a job number and, per line, the asset tag your print was filed under. That tag is the design from now on.

  2. 2GET /Assets/{assetTag}

    Get a live quote for any quantity

    One call returns priceBands for that design at this account’s tier, the reset fee, and a preview image. It is everything a storefront needs to price a repeat at checkout without asking anyone.

  3. 3POST /Jobs

    Order it again — no artwork, no setup

    A line of itemType "Asset" carrying the tag reproduces the previous run: same separation, same colours, no upload and no re-approval. Reorders pay the reset fee, never setup.

Always in step

Generated from the live API.

This page, the specification and the agent guide are all built from the running API rather than written alongside it, so they change when it changes. Every operation documented here works with your credentials, and every example is a real response shape.

openapi.json
OpenAPI 3.0 for codegen and tooling.
agents.md
The sequence and the rules, for coding agents.
llms-full.txt
Every endpoint, field and example in one fetch.

Getting started

How do I get credentials?

Create them yourself in the dashboard, under Integrations → API access. You get a key ID and a secret; the secret is shown once, so copy it then. Keys are issued per wholesale account and see only that account’s data.

Which host do I call?

Two, and they differ. Credentials are exchanged for a token at your region’s auth host; every call after that goes to its API host. Region is fixed when your credentials are issued and comes with them — you cannot discover it by calling the API, because you need the right host to get a token at all. This copy of the documentation targets https://api.supacolour.co.uk.

  • United States
    https://api.supacolor.comhttps://auth.supacolor.com/realms/sc/protocol/openid-connect/token
  • New Zealand
    https://api.supacolour.co.nzhttps://auth.supacolour.co.nz/realms/sc/protocol/openid-connect/token
  • Australia
    https://api.supacolour.co.nzhttps://auth.supacolour.com.au/realms/sc-au/protocol/openid-connect/token
  • United Kingdom
    https://api.supacolour.co.ukhttps://auth.supacolour.co.uk/realms/sc/protocol/openid-connect/token
  • Europe
    https://api.supacolour.euhttps://auth.supacolour.eu/realms/sc-eu/protocol/openid-connect/token
Get a token
curl -X POST https://auth.supacolour.co.uk/realms/sc/protocol/openid-connect/token \
  -d grant_type=client_credentials \
  -d client_id=$CLIENT_ID \
  -d client_secret=$CLIENT_SECRET
Use it
curl https://api.supacolour.co.uk/account \
  -H "Authorization: Bearer $TOKEN"

Build against validateOnly. Sending "validateOnly": true to POST /Jobs runs the whole validation path and creates nothing, so you can iterate on a real payload without putting anything into production.

How ordering works

Each call produces something the next one needs. Read them in order once and the rest of the API explains itself.

01

Find out what this account can order

Availability is per-account. A hardcoded product list breaks for some customers and silently hides products from others.

GET /PriceCodes/processes
02

Get codes and prices at their tier

The code you get back is what a job line carries. Prices are the account’s own, not a list price.

GET /PriceCodes/price-codes
03

Read the delivery methods they can use

Carriers differ by region and account. The method code goes straight into the delivery address.

GET /Jobs/delivery-options
04

Ask when it could leave

The answer accounts for factory workload and cut-off times, so it is not a fixed lead time you can assume.

GET /Jobs/earliest-ship-date
05

Place the order

Send validateOnly first and nothing is created. When you drop the flag you get a job number and, per line, the asset tag the print was filed under.

POST /Jobs
06

Track everything open

One request regardless of how many jobs are running. It is what our own dashboard polls.

GET /Jobs/active

Attributes: the form the API hands you

Every price code carries its own input fields. Read them, render them, send them back — never hardcode them.

A transfer is not just a size and a quantity. The factory needs to know what garment colour it is going on, which colours are in the design, what to call it on the job sheet. Those questions differ per product, and they change. So the API tells you what to ask.

Every price code returned by GET /PriceCodes/price-codes carries an attributes array. Each entry is a field definition: what to call it, what type of input, whether it is required, what the allowed values are. You build your form from that array, collect the answers as a flat object, and send it back on the job line.

Render type: "select" as a dropdown of enumerableValues, type: "text" as a text input bounded by maxLength. Use label for the human, name for the key. Then send what the user chose, keyed by name:

⛔ Never hardcode the key names. They are locale-specific: a US account is asked for Colors, an NZ or UK account for Colours. An integration that hardcodes either one silently drops the answer in the other region, and the job reaches the factory missing the colours. Always key off the name the API gave you.

⚠️ attributes is a hierarchy, not one flat shape. The base carries name, label, type, required, description, enumerableValues, isMetaDataAttribute and assetAttributeName; text attributes add maxLength and minLength. The specification does not discriminate the subtypes, so branch on type rather than assuming every field is present.

Two things to do before submitting. Truncate each value to its own maxLength — an over-long value is rejected at the far end, not helpfully. And keep the keys exactly as given: they are case-sensitive.

If the attributes include DG-X and DG-Y with isMetaDataAttribute: true, this is a custom-dimension product such as SupaDTF. Those two are not questions for the customer — they are the width and height, and they drive the price. Pass them to GET /PriceCodes/{priceCode}?dgX=…&dgY=… to get the price for that size.

What a price code tells you to ask
{
  "priceCode": "WE_SM:Wearable-2.5\" x 2.5\"",
  "attributes": [
    {
      "name": "garment",
      "label": "What garment color will this be applied to?",
      "type": "select",
      "required": false,
      "isMetaDataAttribute": false,
      "enumerableValues": [
        { "value": "Light color fabric", "text": "Light color fabric", "selected": true },
        { "value": "Dark color fabric",  "text": "Dark color fabric",  "selected": false },
        { "value": "Mixed",              "text": "Mixed",              "selected": false }
      ]
    },
    {
      "name": "Colours",
      "label": "Colours in design",
      "type": "text",
      "maxLength": 100,
      "enumerableValues": null
    }
  ]
}
What you send back on the job line
{
  "itemType": "PriceCode",
  "code": "WE_SM:Wearable-2.5\" x 2.5\"",
  "quantity": 100,
  "externalArtworkUrl": "https://your-cdn.example/designs/riverside-crest.pdf",
  "attributes": {
    "garment": "Dark color fabric",
    "Colours": "Red, white, blue",
    "description": "Riverside Rugby crest"
  }
}

Pull the catalogue once, not per order

Availability and prices are per-account. Fetch them on a schedule, store them, and read your own copy at checkout.

GET /PriceCodes/processes tells you what this account may order; GET /PriceCodes/price-codes returns the codes and their prices at that account’s tier. Neither is a public list — two customers calling the same endpoint get different answers.

Fetch both on a schedule and store them. Read your stored copy when someone is building an order. That is faster at checkout, it survives a blip on our side, and it is where the attribute definitions come from — you do not need a live call per order to know what to ask.

⛔ The first band’s from is the minimum orderable quantity, and it is never assumed to be 1 — a Wearable transfer starts at 10. Read it off the bands rather than hardcoding a floor, and reject a smaller quantity in your own basket instead of letting the customer reach checkout and be refused.

⚠️ Refresh it. Prices and availability change, and a stale catalogue quotes a number your customer will not be billed. Treat your copy as a cache with an expiry, not as a fixture you ship once.

The one thing not to cache is the ship date. GET /Jobs/earliest-ship-date accounts for factory workload and cut-off times, so it is a live answer by design.

Setup, then per-order
// Setup — on a schedule, e.g. nightly
const processes  = await api('/PriceCodes/processes');
const priceCodes = await api('/PriceCodes/price-codes');
await store.replaceCatalogue({ processes, priceCodes });

// Per order — from your own store, no network call
const code = await store.findPriceCode(chosenCode);
renderAttributeForm(code.attributes);

Paging, sorting and filtering

The list endpoints share one set of query parameters. Learn them once.

Most list endpoints take the same shape, so you can write the plumbing once and reuse it for assets, jobs, stock and price codes.

Paged responses carry totalCount, totalPages, hasNextPage and hasPreviousPage alongside the rows, so you can drive a pager without counting.

⚠️ Not every parameter applies to every endpoint, and a few use searchText or sortBy instead. The specification lists the exact query parameters per operation — treat this as the pattern and the reference as the authority.

The shared parameters
?page=1&pageSize=25
?sortColumn=DateDue&sortDirection=Descending
?filter=riverside          # free-text search
?includeProcesses=WE,BL    # only these product families
?excludeProcesses=NA,NU    # everything but these

Delivery addresses

The delivery method code and the country code both come from the API. Neither is free text.

A delivery address carries the usual lines plus two values you must not invent: the delivery method, which comes from GET /Jobs/delivery-options, and the country, which comes from GET /Lookups/countries.

⛔ Carriers are per-region and per-account. A method code that works for one customer may not exist for another, so read the options for the account you are ordering for rather than shipping a hardcoded list.

For regions with states or provinces, GET /Lookups/countries/{countryCode}/states gives the accepted values. Send the value the lookup gives you — a full state name where the lookup returns a full state name, not an abbreviation you shortened yourself.

A delivery address
{
  "deliveryMethod": "COURIER",
  "companyName": "Riverside Print Co",
  "addressLine1": "12 Mill Road",
  "suburb": "Riverside",
  "city": "Christchurch",
  "postCode": "8011",
  "countryCode": "NZ",
  "contactName": "Sam Reed",
  "contactPhone": "+64 3 555 0142"
}

Tokens: get one, keep it, retry once

Tokens are short-lived. Cache until just before expiry rather than per request.

Exchange your client id and secret for an access token at your region’s token endpoint, then reuse it. Requesting a fresh token per API call is the most common thing a first integration gets wrong — it is slower and it is unnecessary.

⚠️ On a 401, drop the cached token and retry once. Retrying repeatedly with the same rejected token will not start working, and a loop against the token endpoint is how an integration gets itself rate limited.

⛔ Credentials belong to exactly one region and only work against that region’s host. A token minted in one region presented to another is a 401 that looks like a broken secret.

Cache with a margin
let cached = null;

async function token() {
  // A minute of margin: a token that expires mid-flight reads as a 401 you did not cause.
  if (cached && Date.now() < cached.expiresAt - 60_000) return cached.value;

  const res = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    }),
  });

  const body = await res.json();
  cached = { value: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 };
  return cached.value;
}

What happens after you submit

A job moves through production. What you may still change depends on where it has got to — and the job tells you.

Poll GET /Jobs/active for everything open on the account — one request regardless of how many jobs are running, which is what our own dashboard does. Use GET /Jobs/{jobNumber} when you need the full detail of one.

Do not infer from the status what you are allowed to do. The job carries that directly: read permissions.canEdit before offering an edit, and isCancelable before offering a cancel. ⚠️ They sit at different levels — canEdit is nested under permissions (with permissions.lockedReason explaining a refusal), isCancelable is on the job itself.

⚠️ Those two are independent, and it is measured, not assumed: a job already in production is commonly still editable but no longer cancellable. Treating either flag as a proxy for the other produces a button that fails when the customer presses it.

Statuses are reference data, not constants. Read them from GET /Lookups/job-statuses rather than hardcoding the strings — the set changes.

Poll on a sensible interval. Production is measured in days, so minutes between polls tells you everything a tighter loop would.

When something fails

400
The request was rejected and nothing happened. The body names the fields that failed. Retrying it unchanged will fail identically.
401
No valid token — or a token from another region. Request a new one from the host your credentials belong to.
403
Your account’s role cannot use this operation. It will not start working on retry.
404
No such record, or it belongs to another account. The two are indistinguishable by design.
500
Usually an outage — except on POST /Jobs, where it more often means the payload is invalid. Send it again with validateOnly to see what is wrong.

To tell an outage from a bug in your own code, check /status. It reports what our monitoring sees for this region, including failures that start with the systems we depend on.

Reference

Every operation you can call, generated from the same document /developers/openapi.json serves — so this page cannot drift from the API it describes.

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}

One price code in full, including its attributes and quantity breaks. Use it to price a single product without pulling the whole catalogue. ⚠️ The code contains a double quote and spaces, so percent-encode it for the path: WE_LC:Wearable-4" x 4" is sent as WE_LC%3AWearable-4%22%20x%204%22. (The \" you see in a JSON response is JSON string escaping — a different thing, and not what goes in the URL.)

priceCoderequired
The Price Code to find
dgX
(optional)Needed if the PriceCode has additional MetaData requirements
dgY
(optional)Needed if the PriceCode has additional MetaData requirements
stitches
(optional)Needed if the PriceCode has additional MetaData requirements

getPriceCodesByPriceCode

Response
{
  "priceCode": "WE_LC:Wearable-4\" x 4\"",
  "description": "Wearable 4\" x 4\"",
  "categoryCode": "WEAR",
  "categoryName": "Wearables",
  "name": "Wearable — Left chest 4\" x 4\"",
  "processCode": "WE",
  "masterProcessCode": "WE",
  "minimumQuantity": 10,
  "externalSupplier": null,
  "externalPriceCode": null,
  "hasMetaDataAttributes": true,
  "standardSetup": null,
  "standardReset": 18.5,
  "sampleAvailable": true,
  "samplePrice": 9.5,
  "sampleLabel": "Sample",
  "sizeUnit": "in",
  "sizeWidth": 4,
  "sizeHeight": 4,
  "attributes": [],
  "priceBands": [
    {
      "from": 10,
      "to": 19,
      "unitPrice": 4.95
    },
    {
      "from": 20,
      "to": 49,
      "unitPrice": 3.6
    },
    {
      "from": 50,
      "to": 99,
      "unitPrice": 3.05
    },
    {
      "from": 100,
      "to": null,
      "unitPrice": 2.85
    }
  ]
}
get/PriceCodes/price-codes

Price codes for your account, with sizes and prices at your tier. The code value is what you pass as a job line code when itemType is PriceCode. Prices are yours, not list — do not cache them across accounts.

page
The page number
pageSize
The size of the Page in rows
filter
(optional)If supplied will filter the search results

getPriceCodesPricecodes

Response
[
  {
    "priceCode": "WE_LC:Wearable-4\" x 4\"",
    "description": "Wearable 4\" x 4\"",
    "categoryCode": "WEAR",
    "categoryName": "Wearables",
    "name": "Wearable — Left chest 4\" x 4\"",
    "processCode": "WE",
    "masterProcessCode": "WE",
    "minimumQuantity": 10,
    "externalSupplier": null,
    "externalPriceCode": null,
    "hasMetaDataAttributes": true,
    "standardSetup": null,
    "standardReset": 18.5,
    "sampleAvailable": true,
    "sizeUnit": "in",
    "sizeWidth": 4,
    "sizeHeight": 4,
    "attributes": [
      {
        "minLength": 0,
        "maxLength": 60,
        "name": "description",
        "type": "text",
        "label": "Description",
        "description": "Shown on the job line",
        "required": true,
        "isMetaDataAttribute": false,
        "assetAttributeName": null
      }
    ],
    "priceBands": [
      {
        "from": 10,
        "to": 19,
        "unitPrice": 4.95
      },
      {
        "from": 20,
        "to": 49,
        "unitPrice": 3.6
      },
      {
        "from": 50,
        "to": 99,
        "unitPrice": 3.05
      },
      {
        "from": 100,
        "to": null,
        "unitPrice": 2.85
      }
    ]
  }
]
get/PriceCodes/processes

The processes available to your account. Start every integration here: availability is per-account and changes, so a hardcoded process list will break for some customers and silently exclude products for others.

getPriceCodesProcesses

Response
{
  "processes": [
    {
      "processCode": "WE",
      "description": "Wearable transfers",
      "active": true,
      "sheetRank": 10,
      "sheetColor": "E4572E",
      "tooltip": "Full-colour wearable",
      "mapsToMaster": "WE",
      "maximumSheetUnit": null,
      "maximumSheetWidth": null,
      "maximumSheetHeight": null
    }
  ]
}

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

Create a job. ⚠️ Send "validateOnly": true while building: the payload runs the entire validation path and returns errors without creating anything ("Validated with NO Errors. Job NOT created."). Set items[].itemType"PriceCode" (new print), "Asset" (reorder) or "Stock" — it selects the line type AND what code means. A 500 from this endpoint usually means an invalid payload (empty items, unroutable address, unknown asset code, bad sizeQuantities key), not an outage. Put the artwork on each PriceCode line as externalArtworkUrl — one request places the order and delivers the art. The URL must stay reachable until the job reaches production. The response returns a jobNumber and, per line, the new asset tag your print was filed under.

X-Application-Name
An Valid OriginCode value (this will be validated)

createJobs

Request
{
  "validateOnly": true,
  "orderNumber": "PO-10482",
  "description": "Riverside Rugby — club tees",
  "dateDue": "2026-08-14",
  "mustDate": false,
  "items": [
    {
      "itemType": "PriceCode",
      "code": "WE_LC:Wearable-4\" x 4\"",
      "quantity": 120,
      "customerReference": "PO-10482-1",
      "attributes": {
        "description": "Left chest crest",
        "Size": "4\" x 4\"",
        "position": "Left chest",
        "Colors": "3"
      }
    },
    {
      "itemType": "Asset",
      "code": "WE123456",
      "quantity": 60,
      "customerReference": "PO-10482-2"
    }
  ],
  "deliveryAddress": {
    "contactName": "Sam Patel",
    "organisation": "Riverside Print Co",
    "streetAddress": "12 Tannery Road",
    "city": "Auckland",
    "postalCode": "1010",
    "country": "New Zealand",
    "countryCodeISO2": "NZ",
    "deliveryMethod": "Ground",
    "phone": "+64 9 555 0100",
    "emailAddress": "[email protected]"
  }
}
Response
{
  "jobNumber": 30291,
  "location": "AKL",
  "dateDue": "2026-08-14T00:00:00Z",
  "totalJobCost": 513,
  "expectingArtworkToBeUploaded": true,
  "jobLineDetails": [
    {
      "needsArtworkToBeUploaded": true,
      "customerReference": "PO-10482-1",
      "quantity": 120,
      "newAssetSku": "WE123999",
      "jobLineLabelUrl": null
    },
    {
      "needsArtworkToBeUploaded": false,
      "customerReference": "PO-10482-2",
      "quantity": 60,
      "newAssetSku": null,
      "jobLineLabelUrl": null
    }
  ]
}
get/Jobs/{jobNumber}

Full job detail: status, money, artwork state and tracking once despatched. Status values come from GET /Lookups/job-statuses — read that list rather than matching strings you have seen before.

jobNumberrequired
A Job Number

getJobsByJobNumber

Response
{
  "customerId": 4820,
  "dateIn": "2026-08-01T02:10:00Z",
  "dateOut": null,
  "shippedDateStatus": null,
  "shippedDaysToProcess": null,
  "processingDays": 3,
  "taxTotal": 24.53,
  "isCancelable": false,
  "location": null,
  "permissions": {
    "canEdit": true,
    "lockedReason": null
  },
  "invoiceFile": null,
  "lines": [
    {
      "jobLineId": 990412,
      "assetSku": "WE123456",
      "processCode": "WE",
      "garment": "Tee",
      "description": "Riverside Rugby crest",
      "comments": null,
      "quantity": 120,
      "unitPrice": 2.85,
      "customerReference": "PO-10482-1",
      "imageUrl": "https://example.com/assets/WE123456/preview",
      "gang": null,
      "jobLineStatus": "In production"
    }
  ],
  "shippingAddresses": [
    {
      "trackingNumber": null,
      "trackingLink": null
    }
  ]
}
patch/Jobs/{jobNumber}

Amend a job after submitting it — the description, your PO number, the comments, the requested ship date and the must-ship flag. ⚠️ Check permissions.canEdit on GET /Jobs/{jobNumber} first: a job locks once it is dispatched, closed, returned or cancelled, and permissions.lockedReason says which. Send only the fields you are changing; anything you omit is left alone. Returns the updated job.

jobNumberrequired
The job number to update

updateJobsByJobNumber

Request
{
  "orderNumber": "PO-10482-REV2",
  "dateDue": "2026-08-21",
  "comments": "Customer asked to hold for the revised crest."
}
Response
{
  "customerId": 4820,
  "dateIn": "2026-08-01T02:10:00Z",
  "dateOut": null,
  "shippedDateStatus": null,
  "shippedDaysToProcess": null,
  "processingDays": 3,
  "taxTotal": 24.53,
  "isCancelable": true,
  "location": null,
  "permissions": {
    "canEdit": true,
    "lockedReason": null
  },
  "invoiceFile": null,
  "lines": [
    {
      "jobLineId": 990412,
      "assetSku": "WE123456",
      "processCode": "WE",
      "garment": "Tee",
      "description": "Riverside Rugby crest",
      "comments": null,
      "quantity": 120,
      "unitPrice": 2.85,
      "customerReference": "PO-10482-1",
      "imageUrl": "https://example.com/assets/WE123456/preview",
      "gang": null,
      "jobLineStatus": "In production"
    }
  ],
  "shippingAddresses": [
    {
      "trackingNumber": null,
      "trackingLink": null
    }
  ]
}
post/Jobs/{jobNumber}/cancel

⚠️ Check first, do not guess. GET /Jobs/{jobNumber} returns isCancelable; only call this when it is true. Once the job has entered production the answer is **409** and it cannot be undone from the API — talk to your account manager instead. Success is **204 with no body**. Cancelling an already-cancelled job succeeds rather than erroring, so a retry after a dropped connection is safe.

jobNumberrequired
The job number to cancel

createJobsByJobNumberCancel

get/Jobs/active

Every open job for the account in one call. Prefer this over looping GET /Jobs/{jobNumber}: it is one request regardless of how many jobs are open, and it is what our own dashboard polls.

excludeJobNumber
Optional job number to exclude from results
page
Page number (1-based)
pageSize
Page size (default 20, max 100)
searchText
Search by job number, order number, or description
includeClosedJobs
Include closed jobs in results. Cancelled jobs are never included
sortColumn
sortDirection
orderGroup
No description.

getJobsActive

Response
{
  "items": [
    {
      "permissions": {
        "canEdit": true,
        "lockedReason": null
      },
      "jobNumber": 30291,
      "masterJobStatus": "In production",
      "originCode": "WEB",
      "description": "Riverside Rugby — club tees",
      "mustDate": false,
      "dateDue": "2026-08-14T00:00:00Z",
      "dateOut": null,
      "shippedDaysToProcess": null,
      "orderNumber": "PO-10482",
      "orderGroup": null,
      "orderGroupSequence": null,
      "tracking": []
    }
  ],
  "totalCount": 1,
  "pageNumber": 1,
  "pageSize": 25,
  "totalPages": 1,
  "hasPreviousPage": false,
  "hasNextPage": false
}
get/Jobs/delivery-options

Delivery methods your account can use. The returned method code goes in deliveryAddress.deliveryMethod on POST /Jobs. Availability varies by region and account, so read it rather than assuming a carrier.

getJobsDeliveryoptions

Response
[
  {
    "code": "Ground",
    "label": "GND",
    "isCollection": false,
    "rank": 10,
    "gangRank": 0,
    "value": "Ground",
    "deliveryLabelEnabled": false
  },
  {
    "code": "Will Collect",
    "label": "COL",
    "isCollection": true,
    "rank": 90,
    "gangRank": 0,
    "value": "Will Collect",
    "deliveryLabelEnabled": false
  }
]
get/Jobs/earliest-ship-date

The soonest despatch date for the processes you intend to order. Pass the process codes you are quoting; the answer accounts for factory workload and cut-off times, so it is not a fixed lead time.

processCodes
Optional comma-separated process codes (e.g., "PR,WE")

getJobsEarliestshipdate

Response
{
  "shippingDateUtc": "2026-08-05T18:00:00+00:00",
  "shippingDateLocal": "2026-08-06T06:00:00+12:00",
  "timezone": "Pacific/Auckland",
  "dateExclusions": []
}

Assets

Prints you have made before. Reorder by asset tag — no artwork upload, no colour re-approval.

get/Assets

Prints already made for your account. Each carries an asset tag; ordering that tag again reproduces the previous run exactly, with no artwork upload and no colour re-approval.

page
The page number
pageSize
The size of the Page in rows
sortColumn
Column to sort by
sortDirection
Sort direction (ascending or descending)
includeProcesses
(optional)If passed, can contain a list of Process codes to include (comma separated)
excludeProcesses
(optional)If passed, can contain a list of Process codes to exclude (comma separated)
filter
(optional)If passed will filter the search using the filter text
isArchived
(optional)If true, returns only archived assets. If false, only non-archived. If omitted, returns both.
priceCodeContains
(optional)Comma-separated case-insensitive substrings. Asset is included if its PriceCode contains ANY of them. NULL PriceCodes are excluded.
priceCodeNotContains
(optional)Comma-separated case-insensitive substrings. Asset is included if its PriceCode contains NONE of them, including assets with NULL PriceCode.

getAssets

Response
{
  "pageSize": 25,
  "returnedResults": 1,
  "totalResults": 42,
  "totalPages": 1,
  "currentPage": 1,
  "hasNext": false,
  "hasPrevious": false,
  "filter": null,
  "nextUrl": null,
  "previousUrl": null,
  "entities": [
    {
      "assetId": 774301,
      "assetTag": "WE123456",
      "processCode": "WE",
      "description": "Riverside Rugby crest",
      "garment": "Tee",
      "priceTierCode": "B",
      "setup": 0,
      "reset": 18.5,
      "priceCode": "WE_LC:Wearable-4\" x 4\"",
      "assetUrl": "https://example.com/assets/WE123456/preview",
      "createDate": "2026-06-18T22:41:05Z",
      "isGlobal": false,
      "isArchived": false,
      "priceBands": [
        {
          "from": 10,
          "to": 19,
          "unitPrice": 4.95
        },
        {
          "from": 20,
          "to": 49,
          "unitPrice": 3.6
        },
        {
          "from": 50,
          "to": 99,
          "unitPrice": 3.05
        },
        {
          "from": 100,
          "to": null,
          "unitPrice": 2.85
        }
      ]
    }
  ]
}
get/Assets/{assetCode}/jobs

Returns both active and closed jobs for the authenticated customer only. Customer ID is extracted from the authentication context. Filters by Job's customer (CustID), not Asset's ClientID. Sorting: Active jobs (dateOut=null) appear FIRST at the top, followed by completed/shipped jobs sorted by dateOut descending (most recent first). Used by SC Integrate to display Order History on asset detail pages.

assetCoderequired
The asset code to search for

getAssetsByAssetCodeJobs

Response
{
  "assetCode": "WE123456",
  "jobs": [],
  "totalCount": 0
}
get/Assets/{assetTag}

One asset by its tag. Use it to confirm an asset is still orderable — check isArchived — and to read the reset charge and price bands a reorder will be billed at.

assetTagrequired
A valid AssetTag

getAssetsByAssetTag

Response
{
  "assetId": 774301,
  "assetTag": "WE123456",
  "processCode": "WE",
  "description": "Riverside Rugby crest",
  "garment": "Tee",
  "priceTierCode": "B",
  "setup": 0,
  "reset": 18.5,
  "priceCode": "WE_LC:Wearable-4\" x 4\"",
  "assetUrl": "https://example.com/assets/WE123456/preview",
  "createDate": "2026-06-18T22:41:05Z",
  "isGlobal": false,
  "isArchived": false,
  "priceBands": [
    {
      "from": 10,
      "to": 19,
      "unitPrice": 4.95
    },
    {
      "from": 20,
      "to": 49,
      "unitPrice": 3.6
    },
    {
      "from": 50,
      "to": 99,
      "unitPrice": 3.05
    },
    {
      "from": 100,
      "to": null,
      "unitPrice": 2.85
    }
  ]
}
get/Assets/{assetTag}/files

The artwork files held against an asset, current and superseded. historicFiles records what was replaced and when.

assetTagrequired
The asset tag also known as asset code (e.g., WE12345)

getAssetsByAssetTagFiles

Response
{
  "assetTag": "WE123456",
  "currentFiles": [],
  "historicFiles": []
}
get/Assets/global

Catalogue assets available to every account, rather than ones your account created. Order them exactly like your own: itemType: "Asset" with the tag as code.

page
The page number
pageSize
The size of the Page in rows
sortColumn
Column to sort by
sortDirection
Sort direction (ascending or descending)
includeProcesses
(optional)If passed, can contain a list of Process codes to include (comma separated)
excludeProcesses
(optional)If passed, can contain a list of Process codes to exclude (comma separated)
filter
(optional)If passed will filter the search using the filter text
isArchived
(optional)If true, returns only archived assets. If false, only non-archived. If omitted, returns both.
priceCodeContains
(optional)Comma-separated case-insensitive substrings. Asset is included if its PriceCode contains ANY of them. NULL PriceCodes are excluded.
priceCodeNotContains
(optional)Comma-separated case-insensitive substrings. Asset is included if its PriceCode contains NONE of them, including assets with NULL PriceCode.

getAssetsGlobal

Response
{
  "pageSize": 25,
  "returnedResults": 1,
  "totalResults": 1,
  "totalPages": 1,
  "currentPage": 1,
  "hasNext": false,
  "hasPrevious": false,
  "filter": null,
  "nextUrl": null,
  "previousUrl": null,
  "entities": [
    {
      "assetId": 5012,
      "assetTag": "GL00042",
      "processCode": "STOCK",
      "description": "Care label",
      "garment": null,
      "priceTierCode": "",
      "setup": 0,
      "reset": 0,
      "priceCode": null,
      "assetUrl": null,
      "createDate": "2025-11-02T03:15:00Z",
      "isGlobal": true,
      "isArchived": false,
      "priceBands": [
        {
          "from": 1,
          "to": null,
          "unitPrice": 0.35
        }
      ]
    }
  ]
}
get/Assets/types

Which asset type each process produces. Useful for labelling assets in your own UI without hardcoding a mapping that changes.

getAssetsTypes

Response
[
  {
    "process": "WE",
    "assetType": "Wearable"
  },
  {
    "process": "BL",
    "assetType": "Blocker"
  }
]

Account

Your account: address, users, tax certificates, transactions. GET /account confirms which account a set of credentials belongs to.

get/account

The account these credentials belong to. Call it once a token works to confirm you are pointed at the right customer. (It cannot tell you which region to use — you need the right regional host to get a token at all, and that host comes with your credentials.)

getAccount

Response
{
  "id": 4820,
  "name": "Riverside Print Co",
  "priceTierCode": "B",
  "currency": "NZD",
  "taxRateType": "Inclusive",
  "taxRate": 15,
  "taxSystemTaxExempt": false,
  "taxSystemTaxExemptReason": "",
  "showPayNow": true,
  "creditCardRequired": false,
  "shipmentCarriersCSV": "",
  "excludeShipmentsCsv": null,
  "courierAccountCode": "",
  "courierSiteID": null,
  "crmId": "",
  "accountManagerName": "Alex Rivera",
  "accountManagerEmail": "[email protected]",
  "tierLast12Months": "B",
  "paymentTermDays": 20,
  "paymentTermType": "FollowingMonthEnd",
  "entityAddress": {
    "id": 90211,
    "streetAddress": "12 Tannery Road",
    "addressLine2": "",
    "city": "Auckland",
    "state": "",
    "stateFull": "",
    "postalCode": "1010",
    "countryCode": "NZ",
    "countryName": "New Zealand",
    "contactName": "Sam Patel",
    "organisation": "Riverside Print Co",
    "phone": "+64 9 555 0100",
    "emailAddress": "[email protected]",
    "addressSummaryOneLine": "12 Tannery Road, Auckland 1010, New Zealand"
  },
  "taxCertificates": [],
  "paymentMode": null,
  "paymentMethods": []
}
get/account/shipment-settings

Retrieves shipment configuration for your organization.

getAccountShipmentsettings

Response
{
  "availableCarriers": [
    "NZC01"
  ],
  "carrierOptions": [
    "NZC01"
  ],
  "receiverPays": {
    "billingAccountNumber": "",
    "billingPostalCode": "",
    "billingCountryCode": ""
  },
  "showCustomerAddressOnLabel": true,
  "handlingFee": 0,
  "applyHandlingFeeInsteadOfFreight": false,
  "jobComment": "",
  "availableShipmentTypes": [
    "Ground"
  ],
  "shipmentTypeOptions": [
    {
      "code": "Ground",
      "name": "GND"
    }
  ],
  "freightRates": [
    {
      "carrier": "NZC01",
      "carrierName": "Courier",
      "service": null,
      "serviceName": null,
      "package": null,
      "packageName": null,
      "chargeType": "FlatRatePerJob",
      "value": 12.5,
      "minimum": null,
      "isDefault": true
    }
  ]
}
get/account/transactions

Retrieves a paginated list of payment transactions for the authenticated customer. Includes card details (masked), amounts, and transaction status.

page
Page number, 1-based (default: 1)
pageSize
Number of records per page (default: 20)
sortBy
Column to sort by (default: Id). Valid values: Id, CreatedAt, CompletedAt, AmountSettlement
sortDirection
Sort direction: ASC or DESC (default: DESC)
completedOnly
Filter to show only completed transactions (default: false)
textFilter
Optional text filter to search transaction details

getAccountTransactions

Response
{
  "pageSize": 25,
  "returnedResults": 0,
  "totalResults": 0,
  "totalPages": 1,
  "currentPage": 1,
  "hasNext": false,
  "hasPrevious": false,
  "filter": null,
  "nextUrl": null,
  "previousUrl": null,
  "entities": []
}
get/account/user

Retrieves your user profile information including contact details, preferences, and notification settings.

getAccountUser

Response
{
  "id": 51188,
  "guid": "3f7c1e28-0000-4a1b-9c44-2b6d5e900001",
  "customerId": 4820,
  "customerName": "Riverside Print Co",
  "userName": "spatel",
  "firstName": "Sam",
  "lastName": "Patel",
  "emailAddress": "[email protected]",
  "active": true,
  "phone": "+64 9 555 0100",
  "mobile": "",
  "streetAddress": "12 Tannery Road",
  "addressLine2": "",
  "suburb": "",
  "city": "Auckland",
  "state": "",
  "postalCode": "1010",
  "country": "New Zealand",
  "locationID": null,
  "locationName": null,
  "defaultPage": "",
  "orderNumberPrefix": "",
  "receiveEmails": true,
  "promotionRecipient": false,
  "receiveInvoice": true,
  "receiveInwards": false,
  "textNotifyProof": false,
  "textNotifyDispatch": false,
  "usernameLoginOnly": false,
  "lastLogin": "2026-08-01T09:14:22Z",
  "loginCount": 214
}
get/account/users

Retrieves a paginated list of all users in your organization. Only available to customer administrators. Supports filtering by active status, text search, sorting, and pagination.

includeInactive
Include inactive users (default: false)
page
Page number, 1-based (default: 1)
pageSize
Number of records per page (default: 50)
sortBy
Column to sort by (default: LastName). Valid values: LastName, FirstName, UserName, Email, LastLoginUtc, LoginCount
sortDirection
Sort direction: ASC or DESC (default: ASC)
textFilter
Optional text filter to search username, first name, last name, or email

getAccountUsers

Response
{
  "pageSize": 25,
  "returnedResults": 1,
  "totalResults": 1,
  "totalPages": 1,
  "currentPage": 1,
  "hasNext": false,
  "hasPrevious": false,
  "filter": null,
  "nextUrl": null,
  "previousUrl": null,
  "entities": [
    {
      "id": 51188,
      "guid": "3f7c1e28-0000-4a1b-9c44-2b6d5e900001",
      "userName": "spatel",
      "firstName": "Sam",
      "lastName": "Patel",
      "emailAddress": "[email protected]",
      "active": true,
      "locationName": null,
      "usernameLoginOnly": false,
      "lastLogin": "2026-08-01T09:14:22Z",
      "loginCount": 214
    }
  ]
}
get/account/users/{userId}

One user on your account. Requires the account administrator role; an ordinary user credential receives a 403.

userIdrequired
User ID to retrieve

getAccountUsersByUserId

Response
{
  "id": 51188,
  "guid": "3f7c1e28-0000-4a1b-9c44-2b6d5e900001",
  "userName": "spatel",
  "firstName": "Sam",
  "lastName": "Patel",
  "emailAddress": "[email protected]",
  "active": true,
  "locationName": null,
  "usernameLoginOnly": false,
  "lastLogin": "2026-08-01T09:14:22Z",
  "loginCount": 214
}

Lookups

Reference data — countries, states, job statuses, process codes, size sets. Read these instead of hardcoding values that change.

get/Lookups/countries

Returns a list of countries available for address selection, along with the tenant's default country.

getLookupsCountries

Response
{
  "countries": [
    {
      "name": "New Zealand",
      "iso2": "NZ",
      "iso3": "NZL",
      "hasSuburb": true,
      "hasState": false,
      "hasPostalCode": true
    },
    {
      "name": "United States",
      "iso2": "US",
      "iso3": "USA",
      "hasSuburb": false,
      "hasState": true,
      "hasPostalCode": true
    }
  ],
  "defaultCountry": "NZ"
}
get/Lookups/countries/{countryCode}/states

Returns a list of states/provinces/regions for the specified country code. Some countries may return an empty list if they don't have administrative divisions.

countryCoderequired
ISO 3166-1 alpha-2 country code (e.g., US, NZ, GB)

getLookupsCountriesByCountryCodeStates

Response
{
  "countryCode": "US",
  "states": []
}
get/Lookups/job-statuses

The full set of job statuses. Read this rather than hardcoding: statuses are added over time, and an unrecognised status should never break your integration.

getLookupsJobstatuses

Response
[
  {
    "id": 1,
    "name": "Awaiting artwork"
  },
  {
    "id": 2,
    "name": "In production"
  },
  {
    "id": 3,
    "name": "Shipped"
  }
]
get/Lookups/process-codes

Returns a list of manufacturing process codes available to you, ordered by rank (ascending).

getLookupsProcesscodes

Response
[
  {
    "processCode": "WE",
    "description": "Wearable",
    "rank": 10,
    "metadata": {
      "setupApplies": false,
      "resetApplies": true,
      "scheduleable": true,
      "physicalStockRequired": false,
      "printable": true
    }
  }
]
get/Lookups/size-sets

The named size runs (e.g. Adult, Youth) used by stock lines and inwards records. Read the sizes from here rather than assuming a set.

getLookupsSizesets

Response
[
  {
    "id": 3,
    "name": "Adult",
    "sizes": [
      "S",
      "M",
      "L",
      "XL",
      "2XL"
    ]
  }
]

Stock

Stocked items — heat presses and supplies — and their shipping options.

get/Stock

The stocked-goods catalogue: heat presses, supplies, sample packs. A stock line on POST /Jobs uses itemType: "Stock", the stockCode as code, a variantCode from stockVariants, and sizeQuantities keyed by the sizes in stockVariants[].sizesCsv (equipment uses the single size key Qty).

page
The page number
pageSize
The size of the Page in rows
filter
(optional)If supplied will filter the search results
includePricing
stockSortColumn
stockSortDirection
variantSortColumn
variantSortDirection
markets
CW-4317: optional comma-separated list of market codes (e.g. "US,NZ"). When supplied, only stock items with at least one ACTIVE shipping configuration covering ANY of the listed markets are returned (OR semantics). Input is trimmed, uppercased, and de-duplicated. Omitted or empty => no market filter (existing behaviour). The filter is applied at the SQL level so pagination stays correct.

getStock

Response
[
  {
    "supplier": "Example Supply Co",
    "categoryName": "Tees",
    "stockCode": "5000",
    "processCode": "STOCK",
    "stockName": "Heavy Cotton Adult T-Shirt",
    "stockDescription": "Tees",
    "stockSpecifications": "100% cotton, 180gsm.",
    "priceCompatibleLookupCode": "STOCKLOOKUP",
    "rank": 10,
    "stockVariants": [
      {
        "variantCode": "50002",
        "colour": "Black",
        "variantImageUrl": "https://example.com/stock/50002/image",
        "numberOfSizes": 5,
        "sizesCsv": "S,M,L,XL,2XL"
      }
    ],
    "stockImageUrl": "https://example.com/stock/5000/image",
    "images": [
      {
        "url": "https://example.com/stock/5000/image",
        "name": null,
        "altText": null
      }
    ],
    "showAvailableStock": true,
    "markets": [
      "NZ"
    ],
    "badges": [
      "Best seller"
    ],
    "slug": "heavy-cotton-tee",
    "seoTitle": "Heavy Cotton Adult T-Shirt",
    "seoDescription": "A 180gsm cotton tee, stocked in five sizes.",
    "priceBands": [
      {
        "from": 10,
        "to": 19,
        "unitPrice": 4.95
      },
      {
        "from": 20,
        "to": 49,
        "unitPrice": 3.6
      },
      {
        "from": 50,
        "to": 99,
        "unitPrice": 3.05
      },
      {
        "from": 100,
        "to": null,
        "unitPrice": 2.85
      }
    ]
  }
]
get/Stock/{code}

One stocked item with its variants, sizes and shipping configuration. The stockVariants[].sizesCsv values are the only valid keys for a stock line's sizeQuantities.

coderequired
A stock Code or stock variant code
includePricing
variantSortColumn
variantSortDirection

getStockByCode

Response
{
  "supplier": "Example Supply Co",
  "categoryName": "Tees",
  "stockCode": "5000",
  "processCode": "STOCK",
  "stockName": "Heavy Cotton Adult T-Shirt",
  "stockDescription": "Tees",
  "stockSpecifications": "100% cotton, 180gsm.",
  "priceCompatibleLookupCode": "STOCKLOOKUP",
  "rank": 10,
  "stockVariants": [
    {
      "variantCode": "50002",
      "colour": "Black",
      "variantImageUrl": "https://example.com/stock/50002/image",
      "numberOfSizes": 5,
      "sizesCsv": "S,M,L,XL,2XL"
    }
  ],
  "stockImageUrl": "https://example.com/stock/5000/image",
  "images": [
    {
      "url": "https://example.com/stock/5000/image",
      "name": null,
      "altText": null
    }
  ],
  "showAvailableStock": true,
  "shippingConfigs": [
    {
      "id": 71,
      "markets": [
        "NZ"
      ],
      "shipMode": "Standard",
      "weight": null,
      "weightUnit": "kg",
      "length": null,
      "width": null,
      "height": null,
      "dimensionUnit": "cm",
      "fixedShipping": null,
      "groundOnly": false,
      "estimatedLeadDays": null,
      "fulfilmentLocations": [],
      "shipmentTypeOptions": [
        {
          "code": "Ground",
          "name": "GND",
          "price": 12.5
        }
      ]
    }
  ],
  "markets": [
    "NZ"
  ],
  "badges": [
    "Best seller"
  ],
  "slug": "heavy-cotton-tee",
  "seoTitle": "Heavy Cotton Adult T-Shirt",
  "seoDescription": "A 180gsm cotton tee, stocked in five sizes.",
  "priceBands": [
    {
      "from": 10,
      "to": 19,
      "unitPrice": 4.95
    },
    {
      "from": 20,
      "to": 49,
      "unitPrice": 3.6
    },
    {
      "from": 50,
      "to": 99,
      "unitPrice": 3.05
    },
    {
      "from": 100,
      "to": null,
      "unitPrice": 2.85
    }
  ]
}

PromoCodes

Validate a promotional code before applying it to a job.

get/promocode/validate/{code}

Validates a promo code for the authenticated customer. Checks if the code exists, is active, not expired, and hasn't exceeded usage limits (overall or per-customer). Optionally validates that the promo code is applicable to specific process codes. Returns: - isValid: Whether the promo code can be used - promoCode: The promo code that was validated - discount: Discount amount (cents if isFixed=true, percentage if isFixed=false) - isFixed: true for fixed amount discount, false for percentage discount - message: Error message if not valid (expired, max uses reached, not applicable to process, etc.)

coderequired
Promo code to validate
processCodes
Optional comma-separated list of process codes (e.g., "Embroidery,Printing") from ProcessRank.Process

getPromocodeValidateByCode

Response
{
  "isValid": true,
  "promoCode": "WINTER10",
  "description": null,
  "discount": 10,
  "isFixed": false,
  "message": "Promotion applied successfully.",
  "applicableProcessCodes": null
}

StockItemShipping

Shipping options for stocked items, by stock item and market.

get/stock/{stockItemId}/shipping

How one stocked item ships — weight, dimensions, lead time and which warehouses it can ship from.

stockItemIdrequired
The stock item ID

getStockByStockItemIdShipping

Response
[
  {
    "id": 71,
    "stockItemId": 545,
    "markets": [
      "NZ"
    ],
    "shipMode": "ShipsSeparately",
    "fulfilmentLocations": [
      {
        "locationCode": "AKL",
        "locationName": "Auckland",
        "locale": "NZ",
        "shipFromCity": "Auckland",
        "shipFromCountryCodeIso2": "NZ"
      }
    ],
    "weight": 0.25,
    "weightUnit": "kg",
    "length": 30,
    "width": 22,
    "height": 4,
    "dimensionUnit": "cm",
    "fixedShipping": null,
    "groundOnly": false,
    "estimatedLeadDays": 2,
    "active": true,
    "createdAt": "2026-03-11T20:00:00Z",
    "updatedAt": "2026-07-02T04:30:00Z",
    "shipmentTypeOptions": []
  }
]
get/stock/shipping

Shipping options for stocked items. ⚠️ The market query parameter is required even though the schema does not mark it required — omitting it returns a 400.

market
The market code (e.g., US, AU, NZ, UK)

getStockShipping

Response
[
  {
    "id": 71,
    "stockItemId": 545,
    "markets": [
      "NZ"
    ],
    "shipMode": "ShipsSeparately",
    "fulfilmentLocations": [
      {
        "locationCode": "AKL",
        "locationName": "Auckland",
        "locale": "NZ",
        "shipFromCity": "Auckland",
        "shipFromCountryCodeIso2": "NZ"
      }
    ],
    "weight": 0.25,
    "weightUnit": "kg",
    "length": 30,
    "width": 22,
    "height": 4,
    "dimensionUnit": "cm",
    "fixedShipping": null,
    "groundOnly": false,
    "estimatedLeadDays": 2,
    "active": true,
    "createdAt": "2026-03-11T20:00:00Z",
    "updatedAt": "2026-07-02T04:30:00Z",
    "shipmentTypeOptions": []
  }
]