Partner API · v1 · read-only
Partner API reference
Three endpoints: what we make, what it costs you, and your whole price grid as a CSV. Prices come from the same engine as pandapatches.com — there is no second price list to drift out of date.
Read this before you quote
Fixed-size, fixed-quantity packs on pandapatches.com/offers are a separate product and are not available through this API. They may be cheaper than a custom-spec quote at the same size and quantity, and partners are welcome to buy packs at the public price. API pricing is the custom calculator price less your partner discount.
You will not have to go looking for this: when a pack matches your exact spec, the price response carries a public_pack object with its price and a link. If the pack is the better buy, we would rather tell you than have a client show you.
Authentication
Send your key in the X-API-Key header. Authorization: Bearer … works too.
curl https://www.pandapatches.com/api/v1/catalogue \ -H "X-API-Key: $PANDA_API_KEY"
We store only a SHA-256 hash of your key, so we cannot read it back to you. Your key is shown once when it is issued. If you lose it or it leaks, tell us and we will revoke it and issue another — a revoked key stops working immediately. Keys carry scopes; a key without the scope an endpoint needs gets 403, not 401.
Every response carries an X-Request-Id header. Quote it if you need to ask us about a response and we can find the exact request.
How your price is worked out
retail = the live calculator price for type / size / qty discount_pct = your partner discount partner_price = round(retail x (1 - discount_pct / 100), 2)
Every pricing response carries all three figures so you can see your margin without keeping a copy of our retail prices. retail is the order total, not the per-piece price; the CSV price sheet carries per-piece columns as well.
Size is a band, not an area. We price on ceil((width_in + height_in) / 2), so a 2"Ă—6" and a 4"Ă—4" cost the same, and a 3.5"Ă—3.5" bills at the 4-inch band. This is the most common surprise in our pricing, so it is worth checking against your own assumptions before you build against it.
What every price carries
| Field | What it means |
|---|---|
| valid_until | Prices hold for 30 days. An order placed against this quote_id inside that window is honoured at the quoted price even if our rates move. |
| quote_id | Quote this when you place the order. We look the figures up on our side rather than taking the order's word for what we said. |
| rush_fee | Rush is 25% of YOUR discounted subtotal with a $50 minimum — the discount applies first. partner_price_with_rush is the two added together. |
| price_basis | "quoted" from a factory-costed row. "estimate" where the figure is derived from the sizes either side of it rather than costed directly — see below. |
| public_pack | The equivalent public pack, when one matches your spec exactly. Null when none does. |
| size_band_in | The band we actually priced: ceil((width_in + height_in) / 2). |
| currency | Always USD. An account configured in any other currency is refused rather than quoted in USD figures wearing the wrong label. |
Rows marked “estimate”
A few sizes carry "price_basis": "estimate" — woven at the 8-inch band, leather at 7 and 8 inches, printed at 13 and 14 inches. Those figures are derived from the sizes either side of them rather than costed directly by the factory. They are good enough to quote against at small quantities and we are not going to pretend otherwise at large ones.
An order above 250 pieces at one of those sizes is confirmed by email before we accept it. We are getting real factory numbers for them; when those land the label comes off and the confirmation step goes with it.
GET /api/v1/catalogue
Types you can order, the minimum, and the size above which we quote by hand instead of pricing automatically. Scope: catalogue.
Type ids: embroidered, woven, pvc, chenille, leather, printed, silicone, sequin, chenille-tpu, chenille-glitter, 3d-embroidered-transfer. Minimum 5 pieces on every type.
GET /api/v1/price
One price. Scope: price. Pass size=3x3, or width_in and height_in separately.
curl "https://www.pandapatches.com/api/v1/price?type=embroidered&size=3x3&qty=250" \
-H "X-API-Key: $PANDA_API_KEY"
{
"type": "embroidered",
"width_in": 3, "height_in": 3, "qty": 250,
"size_band_in": 3,
"currency": "USD",
"retail": 489.50,
"discount_pct": 10.00,
"partner_price": 440.55,
"price_basis": "quoted",
"rush_fee": 110.14,
"partner_price_with_rush": 550.69,
"public_pack": null,
"discount_source": "tier",
"quote_id": "b7f2…",
"valid_until": "2026-10-15T00:00:00.000Z"
}POST /api/v1/price
Up to 100 lines at once. A bad line does not fail the batch — each result carries its own ok, so 40 SKUs with one mistake gives you 39 prices and one clear failure.
curl -X POST https://www.pandapatches.com/api/v1/price \
-H "X-API-Key: $PANDA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[
{"type":"embroidered","size":"3x3","qty":250},
{"type":"pvc","size":"2x2","qty":500}
]}'GET /api/v1/price-sheet
Your whole grid as CSV — every type, eight sizes, nine quantities, with per-piece columns. Add ?type=pvc for one type, or ?format=json to get the CSV inside a JSON envelope. Scope: price.
Combinations we will not auto-price — below the 5-piece minimum, or above a type’s auto-pricing size ceiling — are omitted, not shown as zero. Generate a fresh sheet rather than keeping one: it is priced live, so a downloaded copy goes stale the moment a rate moves.
Rate limits
Per key, per minute. If you hold both a sandbox and a live key, they have separate budgets — a test loop cannot exhaust your production allowance.
| Endpoint | Limit | Why |
|---|---|---|
| /v1/price | 60 / min | Batch up to 100 items in one POST rather than looping — it costs one request, not a hundred. |
| /v1/catalogue | 60 / min | It changes rarely. Cache it. |
| /v1/price-sheet | 10 / min | Each request prices every cell in your grid live — around 800 calculations. It is the expensive one, deliberately. |
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. A 429 carries Retry-After in seconds — wait that long rather than retrying immediately. Repeated failed authentication is limited separately and more tightly, by IP.
Errors
| Code | Meaning |
|---|---|
| 401 | Key missing, unrecognised, or revoked. |
| 403 | Key is valid but does not carry the scope this endpoint needs. |
| 422 | Unknown type, a size or quantity we will not auto-price, or a malformed request. The message says which. |
| 429 | Rate limited. Back off and retry. |
| 500 | Our fault. Quote the X-Request-Id. |
A 422 on a size is not a refusal to make it — patches above a type’s auto-pricing ceiling are still produced, they just need a hand quote, because above that size cost follows design complexity rather than area. Ask us and we will price it.
Not in this version
Week one is read-only. Order submission, mockup approve / request-changes, and order status are next, and are not available yet. Build against pricing now; we will publish the write endpoints here when they are live rather than before.
Questions, a new key, or a revocation: lance@pandapatches.com.
