inkwell
v1 · stable

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.

Check your key
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/me \
  -H "Authorization: Bearer $INKWELL_API_KEY"
200 OK
{
  "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 body
{
  "error": {
    "code": "invalid_transition",
    "message": "Cannot change fulfillment IW-48213K2P-1 from New to Packaged"
  }
}
FieldTypeDescription
400 invalid_jsonstatusThe body is not valid JSON.
401 unauthorizedstatusMissing, malformed, unknown or revoked API key.
403 forbiddenstatusYour supplier account is not approved (or is suspended).
404 not_found / not_linkedstatusThe order or blueprint does not exist for your account, or the product is not linked yet.
409 invalid_transitionstatusThe status change is not allowed from the current status (see the lifecycle below).
422 validation_errorstatusA field is missing or invalid. Bulk endpoints are all-or-nothing and list every failing row.
500 internal_errorstatusSomething 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.

NEWACCEPTEDPRINTEDPACKAGEDSHIPPEDDELIVEREDON_HOLDCANCELEDhold / cancel from any status before SHIPPEDforward moves (skips allowed) · re-send /ship to update tracking

Orders

The order object every endpoint and webhook returns:

Order (fulfillment) object
{
  "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": []
}
FieldTypeDescription
numberstringHuman-readable fulfillment number. Print it on your work tickets.
sla_due_attimestampShip on or before this time to meet your SLA.
cancel_requestedbooleanThe merchant asked to cancel. Cancel if production has not started.
ship_toobjectCustomer address. Use it only to ship this order.
items[].skustringThe merchant's SKU (shown on the packing slip).
items[].supplier_skustringYour SKU for the blank, from your catalog settings.
items[].print_files[]arrayOne file per print area, already sized to your placeholder (width_px × height_px at dpi). Download before printing; URLs are stable.
packing_slip_urlstringWhite-label PDF packing slip. Always include it in the package.

List orders

GET/orders

Newest first. Use it to pick up new work if a webhook was missed, or to reconcile.

FieldTypeDescription
statusstringComma-separated: new, accepted, printed, packaged, shipped, delivered, on_hold, canceled.
sincetimestampOnly orders updated at or after this time.
limitinteger1–100, default 20.
pageintegerDefault 1.
bash
curl "https://inkwell.meta-mind-lab.com/api/supplier/v1/orders?status=new,accepted&limit=50" \
  -H "Authorization: Bearer $INKWELL_API_KEY"
200 OK
{
  "data": [
    {
      "id": "cmv5lhg360003yji8t0fojnr1",
      "number": "IW-48213K2P-1",
      "status": "NEW",
      "…": "…"
    }
  ],
  "page": 1,
  "limit": 50,
  "total": 1,
  "has_more": false
}

Get an order

GET/orders/{id}

Returns the full order object.

bash
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/orders/IW-48213K2P-1 \
  -H "Authorization: Bearer $INKWELL_API_KEY"

Accept an order

POST/orders/{id}/accept

Confirms you received the order and scheduled it. Accept new orders promptly — merchants see when production starts.

FieldTypeDescription
notestring?Optional note for the timeline.
bash
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

POST/orders/{id}/status

Report accepted, printed, packaged or delivered. Shipping, holds and cancellations use their own endpoints because they carry extra data.

FieldTypeDescription
statusstringaccepted | printed | packaged | delivered
notestring?Optional note.
bash
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

POST/orders/{id}/ship

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.

FieldTypeDescription
carrierstringCarrier name, e.g. USPS.
tracking_numberstringRequired.
tracking_urlstring?Public tracking page.
notestring?Optional note.
bash
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"}'
200 OK
{
  "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

POST/orders/{id}/hold

Pauses production when something blocks you (e.g. unreadable print file, missing blank). The merchant sees the reason. Release by accepting it again.

FieldTypeDescription
reasonstringShown to the merchant. Required.
bash
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

POST/orders/{id}/cancel

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.

FieldTypeDescription
reason_codestringOne of the reason codes below.
notestring?Optional explanation.
bash
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"}'

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

GET/catalog
bash
curl https://inkwell.meta-mind-lab.com/api/supplier/v1/catalog -H "Authorization: Bearer $INKWELL_API_KEY"
200 OK
{
  "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

PUT/catalog/{blueprintId}/variants

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.

FieldTypeDescription
variant_skustringPlatform variant SKU (see GET /catalog or the CSV export).
supplier_skustring?Your SKU, max 64 chars.
blank_centsinteger?Wholesale price in cents, ≥ 1.
stock_statusstring?IN_STOCK | OUT_OF_STOCK | DISCONTINUED
bash
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"}]'
200 OK
{
  "data": {
    "updated": 2,
    "created": 0,
    "unchanged": 0
  }
}
422 Unprocessable Entity
{
  "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

PUT/stock

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.

bash
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"}]'
200 OK
{
  "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.

stock.csv
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_STOCK

Webhooks

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.

Envelope
{
  "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": []
    }
  }
}
FieldTypeDescription
X-Inkwell-EventheaderEvent type, same as type in the body.
X-Inkwell-DeliveryheaderDelivery id, same as id in the body.
X-Inkwell-Signatureheadersha256=<hex HMAC-SHA256 of the raw body with your secret>
User-AgentheaderInkwell-Webhooks/1.0

Events

FieldTypeDescription
fulfillment.createdeventA new order routed to you; print files and packing slip are ready. Start production.
fulfillment.updatedeventOur team or the system changed the order (e.g. address fix, admin status change). Re-read resource.data.
fulfillment.cancel_requestedeventThe merchant asked to cancel; resource.data also carries cancel_reason and cancel_reason_code. Cancel via the API if you have not started printing.
pingeventSent 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.

Node.js (Express)
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

  1. A delivery fails on a non-2xx response, a network error or a timeout (10 s).
  2. We retry 3 times with backoff: after 1 minute, 5 minutes and 15 minutes.
  3. 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.
  4. 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.

CodeMeaningGroupSupplier fault
100Print is faded or blurryPrint qualityYes
110Colors differ noticeably from designPrint qualityYes
120Print is cracking or peelingPrint qualityYes
130Stains, lint or ink spotsPrint qualityYes
405Print placed incorrectly (beyond 0.5 in tolerance)PlacementYes
406Print is crookedPlacementYes
900Wrong item, size or color sentItemYes
910Item missing from packageItemYes
2400Wrong packing slipItemYes
300Item damaged on arrivalDamageYes
3404Damaged in transitShippingNo
801Lost in transitShippingNo
2601Production late (7–9 business days)ProductionYes
2602Production late (10+ business days)ProductionYes
4000Canceled by supplier: out of stockCancellationYes
410Canceled: copyright / IP issueCancellationNo
2812Canceled: technical issue with print fileCancellationNo
3304Courtesy reprintItemNo

Not a print provider yet?

Join the network and start receiving orders from thousands of stores.

Become a print provider