API Documentation

The EGFULFILL API lets you programmatically create and manage print-on-demand orders, retrieve product catalogs, and receive real-time fulfillment events via webhooks. All requests and responses use JSON.

Base URL
api.egfulfill.com
Version
v1
Format
JSON
Auth
Bearer token

Authentication

All API requests must include your secret key in the Authorization header using Bearer token format. You can generate and manage API keys from Settings → API Keys.

Request header
Authorization: Bearer egf_live_sk_••••••••••••••••••••
⚠️ Never expose your secret key in client-side code. Always make API calls from your backend server.

Base URL & versioning

Prefix all endpoints with the versioned base URL. We use path-based versioning to ensure backward compatibility — breaking changes are always released as new versions.

Base URL
https://api.egfulfill.com/v1
Example endpoints
GET https://api.egfulfill.com/v1/products POST https://api.egfulfill.com/v1/orders GET https://api.egfulfill.com/v1/orders/:id DELETE https://api.egfulfill.com/v1/orders/:id

Errors

EGFULFILL uses standard HTTP status codes. All error responses return a JSON body with a code and a human-readable message.

StatusMeaning
200OK — request succeeded
201Created — resource was created
400Bad request — invalid or missing parameters
401Unauthorized — missing or invalid API key
404Not found — resource doesn't exist
422Unprocessable — validation failed
429Rate limited — too many requests
500Server error — try again later
Error response
{ "error": { "code": "order_not_found", "message": "No order found with ID ord_abc123", "status": 404 } }

Rate limits

Requests are limited to 120 per minute per API key. Your current usage is returned in every response via the headers below. When limited, wait until the X-RateLimit-Reset Unix timestamp before retrying.

Response headers
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 114 X-RateLimit-Reset: 1714000860

Products

Retrieve products from the EGFULFILL catalog, including variants, pricing, and available print areas.

GET /products List all products

Returns a paginated list of all available print-on-demand products.

Query parameters
NameTypeDescription
limitintegeroptionalResults per page (default: 20, max: 100)
pageintegeroptionalPage number (default: 1)
categorystringoptionalFilter by category slug, e.g. apparel
Response 200
{ "data": [ { "id": "prod_Uct9xR", "name": "Unisex Crew-Neck Tee", "category": "apparel", "base_price": 8.50, "currency": "USD", "variants_count": 17 } ], "meta": { "total": 491, "page": 1, "limit": 20 } }
GET /products/:id Get a single product

Returns a single product with all variants, print areas, and available colors/sizes.

Response 200
{ "id": "prod_Uct9xR", "name": "Unisex Crew-Neck Tee", "description": "100% combed cotton, 180gsm", "category": "apparel", "base_price": 8.50, "print_areas": ["front", "back", "sleeve"], "variants": [ { "id": "var_001", "size": "S", "color": "Black", "in_stock": true }, { "id": "var_002", "size": "M", "color": "Black", "in_stock": true } ] }

Orders

Create and manage fulfillment orders. Once submitted, orders are automatically routed to production.

POST /orders Create a new order

Submits a new order for fulfillment. The order is immediately queued for production upon creation.

Body parameters
NameTypeDescription
external_idstringrequiredYour store's order ID for reference
shippingobjectrequiredRecipient address object
itemsarrayrequiredArray of line item objects
items[].variant_idstringrequiredProduct variant ID
items[].design_urlstringrequiredPublic URL to print file (PNG or SVG)
items[].quantityintegerrequiredQuantity (min: 1)
items[].print_areastringoptionalfront, back, or sleeve (default: front)
Request body
{ "external_id": "shopify-order-4821", "shipping": { "name": "Alex Kim", "address1": "42 Sunset Blvd", "city": "Los Angeles", "state": "CA", "zip": "90028", "country": "US" }, "items": [{ "variant_id": "var_002", "design_url": "https://yourcdn.com/designs/logo-front.png", "quantity": 1, "print_area": "front" }] }
Response 201
{ "id": "ord_7fGkTp", "status": "pending", "external_id": "shopify-order-4821", "created_at": "2024-04-22T09:14:00Z" }
GET /orders/:id Get order & tracking

Returns current status and tracking info for a specific order.

Response 200
{ "id": "ord_7fGkTp", "status": "shipped", "tracking_number": "1Z999AA10123456784", "carrier": "UPS", "shipped_at": "2024-04-24T13:20:00Z", "estimated_delivery": "2024-04-27" }
GET /orders List all orders
NameTypeDescription
statusstringoptionalFilter: pending in_production shipped delivered
fromstringoptionalISO 8601 date — returns orders after this date
limitintegeroptionalDefault: 20, max: 100
DELETE /orders/:id Cancel an order

Cancels an order. Only possible while the status is pending.

🚫 Once an order moves to in_production, it can no longer be cancelled via the API.
Response 200
{ "id": "ord_7fGkTp", "status": "cancelled", "cancelled_at": "2024-04-22T10:01:00Z" }

Webhooks

EGFULFILL pushes real-time events to your server so you don't have to poll. Configure your webhook endpoint URL in Settings → Webhooks.

EventDescription
order.createdA new order was received
order.in_productionOrder moved into production
order.shippedOrder dispatched — includes tracking number
order.deliveredCarrier confirmed delivery
order.cancelledOrder was cancelled
Webhook payload — order.shipped
{ "event": "order.shipped", "created_at": "2024-04-24T13:20:00Z", "data": { "id": "ord_7fGkTp", "external_id": "shopify-order-4821", "tracking_number": "1Z999AA10123456784", "carrier": "UPS", "tracking_url": "https://www.ups.com/track?tracknum=1Z999AA10123456784" } }

Signature verification

Verify every incoming webhook by checking the X-EGFULFILL-Signature header — an HMAC-SHA256 of the raw request body signed with your webhook secret. Your secret is in Settings → Webhooks.

Node.js — verify signature
const crypto = require('crypto'); function verifyWebhook(rawBody, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) ); }