Send us orders from your own system
REST over JSON. Your software posts an order, we digitize it, and the finished machine files come back to you automatically. One order can carry many logos, and each logo is charged at the rate already agreed on your account.
The API is for trade accounts on an agreed fixed price. If your account is quoted per job there is no rate for it to charge, and it will refuse the key. Read about trade accounts or ask us to set one up.
Start here
Four things to know first
Read these and the rest of the page will make sense.
- A line is one logo, and the logo is what is charged. An order groups lines for your own convenience: a purchase order, a batch, one of your customers. The order total is the sum of its lines.
- Your account manager issues the key. One key per integration, so one can be revoked without stopping the others. It is shown once and never again: we keep only its hash.
- A trade order starts straight away. No quote to approve and no payment to clear. It goes on your monthly invoice.
- Send a
referenceand retrying is safe. Resending the same reference returns the order that already exists rather than opening a second one, so a timeout cannot charge you twice.
https://api.plixalabs.com/api/trade/v1 Every request Authorization: Bearer plx_live_... Endpoints
What you can call
Six of them. That is the whole surface.
- GET
/pingCheck the key Answers with the account name, its currency and its payment terms. Use it once when you wire things up. - GET
/servicesPrices and allowed values Every service, complexity and rush value we accept, with your own agreed price for each one, so you can price on your side and reconcile against the invoice. - POST
/ordersPlace an order One order holding one or more lines. A line is one logo, and the logo is what is charged. - GET
/ordersList your orders Newest first, paged. Pass per_page up to 100. - GET
/orders/{reference}One order By our order number or by the reference you sent with it, whichever you kept. - GET
/orders/{reference}/files/{id}Download a finished file The files we produced. Your own artwork is not handed back, because that would look like a delivery and is not one.
Placing an order
Post the order, get the lines back priced
Sizes are millimetres. There is no unit switch: a form has one because a person thinks in inches, and offering it to a machine only creates a way for two systems to disagree about what 60 meant.
curl -X POST https://api.plixalabs.com/api/trade/v1/orders \
-H "Authorization: Bearer plx_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"reference": "PO-4471",
"rush": "standard",
"lines": [
{
"reference": "PO-4471-1",
"design_name": "Lion crest",
"service": "logo",
"complexity": "medium",
"placement": "Left chest",
"width_mm": 60,
"height_mm": 45,
"machine_format": "dst",
"artwork_urls": ["https://files.your-system.example/lion.ai"]
},
{
"reference": "PO-4471-2",
"design_name": "Sleeve badge",
"service": "logo",
"complexity": "simple",
"artwork_urls": ["https://files.your-system.example/badge.png"]
}
]
}' {
"data": {
"id": "PLX-26-00412",
"reference": "PO-4471",
"status": "order_received",
"currency": "USD",
"total": 16.0,
"lines": [
{ "id": "PLX-26-00412-1", "reference": "PO-4471-1", "price": 8.0, "files": [] },
{ "id": "PLX-26-00412-2", "reference": "PO-4471-2", "price": 8.0, "files": [] }
]
}
} What a line may say
| Field | Needed | What it is |
|---|---|---|
design_name | required | What to call the design. It is what your team and ours will both see. |
service | required | One of the service keys from GET /services. |
complexity | required | simple, medium or complex. |
rush | optional | standard, rush or super_rush. Defaults to the order, then to standard. |
reference | optional | Your own identifier for this line. |
placement | optional | Left chest, cap front, jacket back, and so on. |
garment | optional | What it is going on. |
width_mm | optional | Finished width in millimetres. |
height_mm | optional | Finished height in millimetres. |
colors | optional | How many thread colours. |
fabric | optional | Piqué, fleece, softshell, and so on. |
backing | optional | Cut-away, tear-away, none. |
machine_format | optional | The format you want back: dst, pes, jef and the rest. |
instructions | optional | Anything a digitizer needs to know. |
artwork | one of two | File parts, when you post the request as multipart/form-data. |
artwork_urls | one of two | https URLs we fetch instead. A line needs artwork one way or the other. |
Artwork, two ways
Either per line, and you can mix them across lines in one order.
artwork_urls are https URLs we fetch: we do not follow redirects,
so send the final URL, and the host has to resolve to a public address. Or post the
whole request as multipart/form-data and send file parts as
lines[0][artwork][]. Up to ten per line, 25 MB each. A line with
neither is refused.
Getting work back
We call you when a logo is finished
Give us a callback URL and we post to it as each logo is done, and again when the whole order is. No callback is a supported choice: poll GET /orders instead.
POST https://your-system.example/hooks/plixalabs
X-PlixaLabs-Event: line.completed
X-PlixaLabs-Delivery: 1234
X-PlixaLabs-Signature: t=1761345600,v1=9f8a...
{
"event": "line.completed",
"sent_at": "2026-10-24T09:15:00+00:00",
"order": { "id": "PLX-26-00412", "...": "..." },
"line": {
"id": "PLX-26-00412-1",
"design_name": "Lion crest",
"stitch_count": 7412,
"files": [
{ "id": 88, "name": "lion-crest.dst", "kind": "dst", "url": "https://api.plixalabs.com/api/trade/v1/orders/PLX-26-00412/files/88" }
]
}
} // Node. Verify before you trust the body.
const [t, v1] = req.headers['x-plixalabs-signature']
.split(',')
.map((part) => part.split('=')[1]);
const expected = crypto
.createHmac('sha256', PLIXALABS_SIGNING_SECRET)
.update(t + '.' + rawBody) // the raw bytes, not JSON.stringify(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(400); - SignatureHMAC-SHA256 of
t + "." + raw body, hex, using your signing secret. Compute it over the bytes you received, never over a re-encoded copy of the parsed JSON. - ReplayThe timestamp is inside the signed string. Reject anything whose
tis more than a few minutes from now. - RetriesAnswer 2xx. Anything else is retried four times over about ten minutes, then we stop and the order is still readable by polling.
- Exactly onceEach event is sent once per order. A retry on our side never produces a second call, so you do not need to de-duplicate, though checking the delivery id costs nothing.
When it says no
Errors
Every refusal is JSON with a message. These are the ones worth handling by code.
| Status | What it means |
|---|---|
401 | The key is wrong, or it has been revoked. |
403 | The key is good, but the account is not on an agreed contract price. This API places orders at a rate already agreed, so there is nothing for it to charge. Talk to your account manager. |
422 | Something in the order is not acceptable. The body names the field and says why. |
404 | No such order on this account. Orders are scoped to the key, so another customer's number reads as missing rather than forbidden. |
429 | Too many requests. 120 a minute, of which 60 may be new orders. |
Ready to connect?
Ask your account manager for a key, or tell us what you are building and we will set the account up with it.