Supplier API
Connect your production software to Inkwell. We push every new order to your webhook with print-ready files, SKUs and the shipping address. You report progress — accepted, printed, packaged, shipped with tracking — and keep wholesale prices and stock in sync. Everything is white-label: you only ever see the merchant's store name.
Push
fulfillment.created arrives seconds after print files are ready.
Pull
Poll GET /orders?status=new as a fallback or for reconciliation.
Signed
Every webhook is HMAC-SHA256 signed with your secret.
Base URL: https://inkwell.meta-mind-lab.com/api/supplier/v1
All requests and responses are JSON (UTF-8). Timestamps are ISO 8601 in UTC. Money is in integer cents (USD). Wherever an order {id} is accepted you may pass either the fulfillment id or its number (e.g. IW-48213K2P-1).
Authentication
Create keys in the supplier portal under API & webhooks. A key looks like ik_3f9a1c7b2e_Vh2… and is shown once; we only store a SHA-256 hash. Send it as a bearer token on every request. Revoked or unknown keys get 401; suppliers that are not approved get 403.
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/me \
-H "Authorization: Bearer $INKWELL_API_KEY"{
"data": {
"id": "cmv5kg4gb00y4yjgfmup9rqqf",
"name": "Monarch Print Co.",
"slug": "monarch-print-co",
"status": "APPROVED",
"tier": "GREEN",
"score": 9.4,
"country": "US",
"capabilities": [
"DTG",
"EMBROIDERY"
],
"sla_days": {
"DTG": 3,
"EMBROIDERY": 4
},
"webhook_url": "https://erp.example.com/inkwell/webhooks",
"webhook_paused_until": null
}
}Errors & limits
Errors use conventional HTTP status codes and a consistent body. Validation errors include a details array.
{
"error": {
"code": "invalid_transition",
"message": "Cannot change fulfillment IW-48213K2P-1 from New to Packaged"
}
}| Field | Type | Description |
|---|---|---|
| 400 invalid_json | status | The body is not valid JSON. |
| 401 unauthorized | status | Missing, malformed, unknown or revoked API key. |
| 403 forbidden | status | Your supplier account is not approved (or is suspended). |
| 404 not_found / not_linked | status | The order or blueprint does not exist for your account, or the product is not linked yet. |
| 409 invalid_transition | status | The status change is not allowed from the current status (see the lifecycle below). |
| 422 validation_error | status | A field is missing or invalid. Bulk endpoints are all-or-nothing and list every failing row. |
| 500 internal_error | status | Something failed on our side. Retry with exponential backoff. |
Rate limit: 600 requests per minute per supplier. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). List endpoints are paginated with page and limit (max 100) and return { data, page, limit, total, has_more }.
Order lifecycle
Each order you receive is a fulfillment: the items of one customer order that route to you. Move it forward as production progresses — skipping steps forward is allowed (e.g. accepted → shipped). The SLA deadline (sla_due_at) is counted in business days from receipt and is met when the order is shipped by then.
Orders
The order object every endpoint and webhook returns:
{
"id": "cmv5lhg360003yji8t0fojnr1",
"number": "IW-48213K2P-1",
"order_number": "IW-48213K2P",
"status": "NEW",
"created_at": "2026-10-12T14:03:11.000Z",
"updated_at": "2026-10-12T14:03:11.000Z",
"accepted_at": null,
"sla_due_at": "2026-10-15T14:03:11.000Z",
"hold_reason": null,
"cancel_reason_code": null,
"cancel_requested": false,
"is_reprint": false,
"facility": {
"id": "cmv5kg4gb00y6yjgf2z1x0abc",
"name": "Dallas, TX",
"country": "US"
},
"shop": {
"name": "Sunset Supply Co."
},
"ship_to": {
"name": "Jamie Rivera",
"email": "jamie@example.com",
"phone": "+1 555 0142",
"address1": "742 Evergreen Terrace",
"address2": "",
"city": "Springfield",
"region": "OR",
"zip": "97477",
"country": "US"
},
"shipping_method": "STANDARD",
"cost": {
"supplier_cost_cents": 2500,
"shipping_cost_cents": 475,
"currency": "USD"
},
"items": [
{
"id": "cmv5lhg3c0007yji8m1a2b3c4",
"sku": "SUNSET-TEE-WHT-M",
"supplier_sku": "MON-1002",
"variant_sku": "3001-WHITE-M",
"blueprint": {
"id": "cmv5kg4dp00swyjgf2fuq9n5f",
"slug": "unisex-jersey-tee",
"title": "Unisex Jersey Short Sleeve Tee"
},
"title": "Sunset Mountains Tee",
"variant": {
"color": "White",
"color_hex": "#FFFFFF",
"size": "M",
"label": "White / M"
},
"quantity": 2,
"decoration_method": "DTG",
"print_files": [
{
"placement": "front",
"url": "https://inkwell.meta-mind-lab.com/files/print-files/2026-10/Qm9yZWQ.png",
"width_px": 3600,
"height_px": 4800,
"dpi": 300,
"format": "PNG",
"status": "READY"
}
],
"mockup_url": "https://inkwell.meta-mind-lab.com/files/mockups/2026-10/aGVsbG8.png"
}
],
"packing_slip_url": "https://inkwell.meta-mind-lab.com/files/packing-slips/2026-10/c2xpcA.pdf",
"tracking": {
"carrier": null,
"number": null,
"url": null,
"shipped_at": null,
"delivered_at": null
},
"events": []
}| Field | Type | Description |
|---|---|---|
| number | string | Human-readable fulfillment number. Print it on your work tickets. |
| sla_due_at | timestamp | Ship on or before this time to meet your SLA. |
| cancel_requested | boolean | The merchant asked to cancel. Cancel if production has not started. |
| ship_to | object | Customer address. Use it only to ship this order. |
| items[].sku | string | The merchant's SKU (shown on the packing slip). |
| items[].supplier_sku | string | Your SKU for the blank, from your catalog settings. |
| items[].print_files[] | array | One file per print area, already sized to your placeholder (width_px × height_px at dpi). Download before printing; URLs are stable. |
| packing_slip_url | string | White-label PDF packing slip. Always include it in the package. |
List orders
Newest first. Use it to pick up new work if a webhook was missed, or to reconcile.
| Field | Type | Description |
|---|---|---|
| status | string | Comma-separated: new, accepted, printed, packaged, shipped, delivered, on_hold, canceled. |
| since | timestamp | Only orders updated at or after this time. |
| limit | integer | 1–100, default 20. |
| page | integer | Default 1. |
curl "https://inkwell.meta-mind-lab.com/api/supplier/v1/orders?status=new,accepted&limit=50" \
-H "Authorization: Bearer $INKWELL_API_KEY"{
"data": [
{
"id": "cmv5lhg360003yji8t0fojnr1",
"number": "IW-48213K2P-1",
"status": "NEW",
"…": "…"
}
],
"page": 1,
"limit": 50,
"total": 1,
"has_more": false
}Get an order
Returns the full order object.
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1 \
-H "Authorization: Bearer $INKWELL_API_KEY"Accept an order
Confirms you received the order and scheduled it. Accept new orders promptly — merchants see when production starts.
| Field | Type | Description |
|---|---|---|
| note | string? | Optional note for the timeline. |
curl -X POST https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1/accept \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note":"Scheduled on line 3"}'Responds with the updated order object. Calling it on an order that is already past NEW returns 409.
Update production status
Report accepted, printed, packaged or delivered. Shipping, holds and cancellations use their own endpoints because they carry extra data.
| Field | Type | Description |
|---|---|---|
| status | string | accepted | printed | packaged | delivered |
| note | string? | Optional note. |
curl -X POST https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1/status \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"printed"}'Ship an order
Marks the order shipped and notifies the merchant and customer. If tracking_url is omitted we build one for well-known carriers (USPS, UPS, FedEx, DHL, Royal Mail, Deutsche Post, Australia Post, Canada Post, DPD, GLS). Calling it again on a shipped order updates the tracking.
| Field | Type | Description |
|---|---|---|
| carrier | string | Carrier name, e.g. USPS. |
| tracking_number | string | Required. |
| tracking_url | string? | Public tracking page. |
| note | string? | Optional note. |
curl -X POST https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1/ship \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"carrier":"USPS","tracking_number":"9400111202555842761234"}'{
"data": {
"id": "cmv5lhg360003yji8t0fojnr1",
"number": "IW-48213K2P-1",
"order_number": "IW-48213K2P",
"status": "SHIPPED",
"created_at": "2026-10-12T14:03:11.000Z",
"updated_at": "2026-10-12T14:03:11.000Z",
"accepted_at": "2026-10-12T15:10:00.000Z",
"sla_due_at": "2026-10-15T14:03:11.000Z",
"hold_reason": null,
"cancel_reason_code": null,
"cancel_requested": false,
"is_reprint": false,
"facility": {
"id": "cmv5kg4gb00y6yjgf2z1x0abc",
"name": "Dallas, TX",
"country": "US"
},
"shop": {
"name": "Sunset Supply Co."
},
"ship_to": {
"name": "Jamie Rivera",
"email": "jamie@example.com",
"phone": "+1 555 0142",
"address1": "742 Evergreen Terrace",
"address2": "",
"city": "Springfield",
"region": "OR",
"zip": "97477",
"country": "US"
},
"shipping_method": "STANDARD",
"cost": {
"supplier_cost_cents": 2500,
"shipping_cost_cents": 475,
"currency": "USD"
},
"items": [
"…"
],
"packing_slip_url": "https://inkwell.meta-mind-lab.com/files/packing-slips/2026-10/c2xpcA.pdf",
"tracking": {
"carrier": "USPS",
"number": "9400111202555842761234",
"url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400111202555842761234",
"shipped_at": "2026-10-14T09:12:44.000Z",
"delivered_at": null
},
"events": [
{
"status": "ACCEPTED",
"note": "Accepted for production",
"source": "API",
"created_at": "2026-10-12T15:10:00.000Z"
},
{
"status": "PRINTED",
"note": "",
"source": "API",
"created_at": "2026-10-13T11:40:02.000Z"
},
{
"status": "SHIPPED",
"note": "USPS 9400111202555842761234",
"source": "API",
"created_at": "2026-10-14T09:12:44.000Z"
}
]
}
}Put an order on hold
Pauses production when something blocks you (e.g. unreadable print file, missing blank). The merchant sees the reason. Release by accepting it again.
| Field | Type | Description |
|---|---|---|
| reason | string | Shown to the merchant. Required. |
curl -X POST https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1/hold \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"Blank out of stock, restock expected Friday"}'Cancel an order
Cancels the fulfillment before it ships; the customer is refunded for these items. A reason code is required. Supplier-fault codes (e.g. 4000 out of stock) count against your scorecard.
| Field | Type | Description |
|---|---|---|
| reason_code | string | One of the reason codes below. |
| note | string? | Optional explanation. |
curl -X POST https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1/cancel \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason_code":"4000","note":"Discontinued by the mill"}'Download the print pack
A JSON manifest with every print file (suggested file name, URL, size, DPI), the packing slip URL and the full order. Works with an API key or a portal session. Add ?download=1 to save it as a file.
curl https://inkwell.meta-mind-lab.com/api/supplier/print-pack/IW-48213K2P-1 \
-H "Authorization: Bearer $INKWELL_API_KEY"{
"manifest_version": 1,
"generated_at": "2026-10-12T14:05:00.000Z",
"fulfillment_number": "IW-48213K2P-1",
"ready": true,
"packing_slip_url": "https://inkwell.meta-mind-lab.com/files/packing-slips/2026-10/c2xpcA.pdf",
"files": [
{
"file_name": "IW-48213K2P-1_01_3001-WHITE-M_front.png",
"item_id": "cmv5lhg3c0007yji8m1a2b3c4",
"sku": "SUNSET-TEE-WHT-M",
"supplier_sku": "MON-1002",
"quantity": 2,
"placement": "front",
"url": "https://inkwell.meta-mind-lab.com/files/print-files/2026-10/Qm9yZWQ.png",
"width_px": 3600,
"height_px": 4800,
"dpi": 300,
"format": "PNG",
"status": "READY"
}
],
"order": "{ …order object… }"
}Catalog & stock
Your catalog is the set of platform blueprints you have linked, with your wholesale price per variant. The blank price includes printing one print area; each additional print area adds its printing price. Link new products in the portal; keep prices and stock current through the API, a CSV upload or feeds.
List your catalog
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/catalog -H "Authorization: Bearer $INKWELL_API_KEY"{
"data": [
{
"id": "cmv5kg4j901k0yjgf8h2s0a7d",
"blueprint": {
"id": "cmv5kg4dp00swyjgf2fuq9n5f",
"slug": "unisex-jersey-tee",
"title": "Unisex Jersey Short Sleeve Tee",
"brand": "Bella+Canvas",
"model": "3001"
},
"decoration_method": "DTG",
"production_days": 3,
"active": true,
"placeholders": [
{
"placement": "front",
"width_px": 3600,
"height_px": 4800,
"dpi": 300
}
],
"printing_cents": {
"front": 0,
"back": 450
},
"variants": [
{
"variant_sku": "3001-WHITE-M",
"color": "White",
"size": "M",
"supplier_sku": "MON-1002",
"blank_cents": 1095,
"stock_status": "IN_STOCK",
"updated_at": "2026-10-01T08:00:00.000Z"
}
],
"shipping_profiles": [
{
"method": "STANDARD",
"countries": [
"US"
],
"first_item_cents": 475,
"additional_item_cents": 240,
"handling_days": {
"from": 2,
"to": 5
}
}
],
"updated_at": "2026-10-01T08:00:00.000Z"
}
]
}Update variants of one product
Bulk update supplier SKU, blank price and stock for one linked blueprint (id or slug). Variants of the blueprint that you don't offer yet are added when you send a blank_cents. All-or-nothing: if any row is invalid, nothing is saved and every error is listed.
| Field | Type | Description |
|---|---|---|
| variant_sku | string | Platform variant SKU (see GET /catalog or the CSV export). |
| supplier_sku | string? | Your SKU, max 64 chars. |
| blank_cents | integer? | Wholesale price in cents, ≥ 1. |
| stock_status | string? | IN_STOCK | OUT_OF_STOCK | DISCONTINUED |
curl -X PUT https://inkwell.meta-mind-lab.com/api/supplier/v1/catalog/unisex-jersey-tee/variants \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"variant_sku":"3001-WHITE-M","blank_cents":1125},
{"variant_sku":"3001-BLACK-3XL","stock_status":"OUT_OF_STOCK"}]'{
"data": {
"updated": 2,
"created": 0,
"unchanged": 0
}
}{
"error": {
"code": "validation_error",
"message": "1 row(s) failed validation; nothing was saved.",
"details": [
{
"index": 1,
"field": "stock_status",
"message": "stock_status must be one of IN_STOCK, OUT_OF_STOCK, DISCONTINUED (got \"SOLD_OUT\")"
}
]
}
}Update stock across your catalog
Availability only, matched by supplier_sku or variant_sku. Out-of-stock variants are hidden from new products and held or rerouted in new orders. All-or-nothing.
curl -X PUT https://inkwell.meta-mind-lab.com/api/supplier/v1/stock \
-H "Authorization: Bearer $INKWELL_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"supplier_sku":"MON-1002","stock_status":"OUT_OF_STOCK"}]'{
"data": {
"updated": 1,
"unchanged": 0
}
}Price & stock feeds
Prefer pull? Add a price feed and/or stock feed URL in the portal. We fetch them every 6 hours (and on demand with "Sync now"). Feeds use the same columns as the CSV import — a JSON array (or { "data": [...] }) or CSV. Rows with unknown SKUs are skipped and reported; valid rows still apply.
variant_sku,color,size,supplier_sku,blank_cents,stock_status
3001-WHITE-M,White,M,MON-1002,1095,IN_STOCK
3001-BLACK-3XL,Black,3XL,MON-1047,1495,OUT_OF_STOCKWebhooks
Set your endpoint in the portal. We POST JSON with this envelope; resource.data is the full order object for fulfillment events. Respond with any 2xx within 10 seconds. Deliveries are at-least-once — deduplicate on id.
{
"id": "cmv5m0x1e000hyj8s3c9d2k1q",
"type": "fulfillment.created",
"created_at": "2026-10-12T14:03:12.418Z",
"resource": {
"id": "cmv5lhg360003yji8t0fojnr1",
"type": "fulfillment",
"data": {
"id": "cmv5lhg360003yji8t0fojnr1",
"number": "IW-48213K2P-1",
"order_number": "IW-48213K2P",
"status": "NEW",
"created_at": "2026-10-12T14:03:11.000Z",
"updated_at": "2026-10-12T14:03:11.000Z",
"accepted_at": null,
"sla_due_at": "2026-10-15T14:03:11.000Z",
"hold_reason": null,
"cancel_reason_code": null,
"cancel_requested": false,
"is_reprint": false,
"facility": {
"id": "cmv5kg4gb00y6yjgf2z1x0abc",
"name": "Dallas, TX",
"country": "US"
},
"shop": {
"name": "Sunset Supply Co."
},
"ship_to": {
"name": "Jamie Rivera",
"email": "jamie@example.com",
"phone": "+1 555 0142",
"address1": "742 Evergreen Terrace",
"address2": "",
"city": "Springfield",
"region": "OR",
"zip": "97477",
"country": "US"
},
"shipping_method": "STANDARD",
"cost": {
"supplier_cost_cents": 2500,
"shipping_cost_cents": 475,
"currency": "USD"
},
"items": [
"…same shape as GET /orders/{id}…"
],
"packing_slip_url": "https://inkwell.meta-mind-lab.com/files/packing-slips/2026-10/c2xpcA.pdf",
"tracking": {
"carrier": null,
"number": null,
"url": null,
"shipped_at": null,
"delivered_at": null
},
"events": []
}
}
}| Field | Type | Description |
|---|---|---|
| X-Inkwell-Event | header | Event type, same as type in the body. |
| X-Inkwell-Delivery | header | Delivery id, same as id in the body. |
| X-Inkwell-Signature | header | sha256=<hex HMAC-SHA256 of the raw body with your secret> |
| User-Agent | header | Inkwell-Webhooks/1.0 |
Events
| Field | Type | Description |
|---|---|---|
| fulfillment.created | event | A new order routed to you; print files and packing slip are ready. Start production. |
| fulfillment.updated | event | Our team or the system changed the order (e.g. address fix, admin status change). Re-read resource.data. |
| fulfillment.cancel_requested | event | The merchant asked to cancel; resource.data also carries cancel_reason and cancel_reason_code. Cancel via the API if you have not started printing. |
| ping | event | Sent by "Send test event" in the portal. resource.type is supplier. |
Verifying signatures
Compute an HMAC-SHA256 of the raw request body with your signing secret and compare it to X-Inkwell-Signature using a constant-time comparison. Reject anything that doesn't match. Regenerating the secret in the portal takes effect immediately.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.INKWELL_WEBHOOK_SECRET; // from Supplier portal → API & webhooks
// Use the RAW body: re-serialized JSON will not match the signature.
app.post('/inkwell/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
const got = req.get('X-Inkwell-Signature') ?? '';
const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
// Deduplicate on event.id — deliveries are at-least-once.
switch (event.type) {
case 'fulfillment.created':
queueForProduction(event.resource.data); // full order: items, print files, ship_to
break;
case 'fulfillment.cancel_requested':
stopIfNotPrinted(event.resource.id);
break;
}
res.sendStatus(200); // answer within 10 s; do heavy work asynchronously
});Retries & pausing
- A delivery fails on a non-2xx response, a network error or a timeout (10 s).
- We retry 3 times with backoff: after 1 minute, 5 minutes and 15 minutes.
- If the last retry fails, your endpoint is paused for 1 hour; queued deliveries wait and resume automatically. You can resume early in the portal.
- Every attempt is visible in the delivery log, where you can inspect payloads and "Retry now".
Orders are never lost when webhooks fail — they stay in your production queue and in GET /orders?status=new.
Reason codes
Used for cancellations and claims. Supplier-fault codes count toward your Defects & Issues rate when a claim is accepted.
| Code | Meaning | Group | Supplier fault |
|---|---|---|---|
| 100 | Print is faded or blurry | Print quality | Yes |
| 110 | Colors differ noticeably from design | Print quality | Yes |
| 120 | Print is cracking or peeling | Print quality | Yes |
| 130 | Stains, lint or ink spots | Print quality | Yes |
| 405 | Print placed incorrectly (beyond 0.5 in tolerance) | Placement | Yes |
| 406 | Print is crooked | Placement | Yes |
| 900 | Wrong item, size or color sent | Item | Yes |
| 910 | Item missing from package | Item | Yes |
| 2400 | Wrong packing slip | Item | Yes |
| 300 | Item damaged on arrival | Damage | Yes |
| 3404 | Damaged in transit | Shipping | No |
| 801 | Lost in transit | Shipping | No |
| 2601 | Production late (7–9 business days) | Production | Yes |
| 2602 | Production late (10+ business days) | Production | Yes |
| 4000 | Canceled by supplier: out of stock | Cancellation | Yes |
| 410 | Canceled: copyright / IP issue | Cancellation | No |
| 2812 | Canceled: technical issue with print file | Cancellation | No |
| 3304 | Courtesy reprint | Item | No |
Not a print provider yet?
Join the network and start receiving orders from thousands of stores.
Become a print provider