Fulfillment Pre-Processor Integration API
Status: Pull endpoints live; pushback endpoint rolling out — confirm availability with your OmniCart contact before go-live Audience: Fulfillment pre-processor vendor / integration engineers Last Updated: 2026-07-23
The fulfillment pre-processor is an external service that, on a cron schedule:
- Pulls orders awaiting fulfillment from OmniCart.
- Processes them (routing, rate shopping, batching, label prep — vendor-side).
- Pushes back fulfillment results: current fulfillment state and tracking.
OmniCart records the pushback as OmniCart fulfillments (tracking labels, shipped/delivered timestamps), which drives the fulfillment dashboard, shipping-confirmation emails, and downstream Konnektive sync.
1. Instructions#
1.1 Authentication#
Use a secret API key (server-to-server; recommended over JWT logins — keys don't
expire on a timer and can be revoked individually). Create one in the admin dashboard
under Settings → API Key Management (secret keys start with sk_).
Send it as HTTP Basic auth — the key is the username, the password is empty:
Authorization: Basic base64("sk_YOUR_SECRET_KEY:")curl -u "sk_YOUR_SECRET_KEY:" https://your-store.omnicart.cc/admin/fulfillment/health1.2 Base URL#
https://your-store.omnicart.cc # replace with the store domain from onboarding1.3 Cron flow#
| Step | Endpoint | Purpose |
|---|---|---|
| 1. Pull queue | GET /admin/fulfillment?fulfillment_filter=awaiting |
Page through orders with unfulfilled items |
| 2. Pull detail (optional) | GET /admin/fulfillment/:id |
Full order: line items + SKUs, quantities, shipping address |
| 3. Pushback | POST /admin/fulfillment/preprocessor/update |
Report fulfillment state + tracking (batch) |
Recommended cadence: pull every 15–30 minutes; pushback as soon as state changes
(or batched on the same cron). Always send no_cache=true on the pull — the list
endpoint has a 3-minute server-side cache intended for the dashboard UI.
1.4 Ground rules#
- Prices are decimals, not cents.
19.99means $19.99 everywhere in this API. - Order references: the human-facing order number is
display_id(numeric). Pushback also accepts the internalorder_id(order_…) — preferred when you have it. - Idempotency: pushback is idempotent per
tracking_numberwithin an order — re-sending a tracking number the order already has never creates a duplicate fulfillment. A re-send that adds new information (e.g.delivered_at) updates the existing fulfillment; a re-send with nothing new is also reportedupdated, and the order is additionally flagged for manual review (needs_review) so repeated re-sends are visible to operations. Concurrent submissions for the same order are serialized by a per-order lock, so retrying a whole batch after a network failure is safe. - Split shipments: every pushback line must list the
items(SKU + quantity) it covers unless the order has exactly one line item. Multi-item orders without anitemsarray are rejected — OmniCart will not guess which items shipped. - Partial fulfillment is supported: send one pushback entry per physical shipment, each with its own tracking number and item subset.
- Batch limits: max 10,000 update entries per pushback request (matches the
platform's existing bulk-import cap). Pull pagination is
limit/offset— there is no fixed page-size ceiling, but keep pages ≤ 500 for response-time reasons. - Quantity capping: a shipped quantity above the item's unfulfilled quantity is silently capped to what remains unfulfilled — it is not rejected. Don't rely on OmniCart to catch over-shipment claims; validate quantities on your side.
- Dry run: send
"dry_run": truein the pushback body for a no-write preview — entries are matched and per-entry results predicted, but nothing is persisted. Use this during vendor onboarding. (It is a preview, not a full validator.) - Side effects to know about: a pushback SKU that doesn't match any order item
creates a
needs_reviewSKU-alias entry for operations to map; orders whose shipping name is "Test Test" are auto-held and reportedskipped.
1.5 Fulfillment status model#
The pre-processor reports one of these status values per order (lifecycle order):
| Status | Meaning | Effect in OmniCart |
|---|---|---|
received |
Pre-processor has ingested the order | Stamps metadata only (visible in dashboard) |
processing |
Picked / packed / label pending | Stamps metadata only |
shipped |
Handed to carrier — tracking required | Creates/updates fulfillment, sets shipped_at, records tracking label, triggers shipping-confirmation email (only when shipped_at is within the last 48 hours — late/backfill pushes do not email) |
delivered |
Carrier confirmed delivery | Sets delivered_at on the matching fulfillment |
exception |
Problem (address, stock, damage) | Flags order for review; include message |
cancelled |
Pre-processor will not fulfill | Flags order; no fulfillment created |
shipped and delivered require a tracking_number. exception and cancelled
should include a human-readable message.
Implementation scope:
shipped/deliveredride on OmniCart's existing, battle-tested fulfillment engine. The four non-shipping statuses (received,processing,exception,cancelled) are metadata-only stamps implemented in the pushback endpoint itself — treat them as phase 2 if the vendor can go live with shipped/delivered only.
2. API Reference#
2.1 Pull — list orders awaiting fulfillment#
GET /admin/fulfillmentQuery parameters
| Parameter | Type | Description |
|---|---|---|
fulfillment_filter |
string | Use awaiting — orders not yet shipped (no shipped fulfillment, not WMS-shipped) that still have line items; excludes on-hold, cancelled, refunded/declined/partial, sync-error, and needs-review orders. Other values: shipped, on_hold, cancelled, all |
limit |
number | Page size (default 20) |
offset |
number | Page start |
created_at_gte / created_at_lte |
ISO 8601 | Window on order-placed date |
sales_channel_id |
string | Restrict to a sales channel |
no_cache |
string | true — bypass the 3-minute dashboard cache (always set this from the cron) |
Response — 200 OK
{
"orders": [
{
"id": "order_01JGXA8Y2K3M4N5P6Q7R8S9T0V",
"display_id": 4521,
"email": "jane.doe@example.com",
"created_at": "2026-07-22T18:41:03.000Z",
"currency_code": "usd",
"total": 89.94,
"status": "pending",
"payment_status": "captured",
"fulfillment_status": "not_fulfilled",
"is_shipped": false,
"shipped_at": null,
"last_fulfillment_update_at": null,
"customer_name": "Jane Doe",
"sales_channel": { "id": "sc_01H...", "name": "VNSH Storefront" },
"metadata": { "konnektive_order_id": "25C1D4E9F0" }
}
],
"count": 137,
"limit": 100,
"offset": 0
}Page until offset + limit >= count.
Notes:
- Responses include additional dashboard-oriented fields not shown above
(
shipping_email_sent,confirmation_email_at,quickbox_*, …) — ignore unknown fields; don't use strict schema validation. - The list view does not expose per-item fulfillment state. To know which items and quantities remain unfulfilled, call the detail endpoint (§2.2).
- On failure these endpoints return
500with{ "message", "request_id", "stage" }— log therequest_idand include it when reporting issues. Responses also carryX-Request-IdandX-Cacheheaders useful for debugging.
2.2 Pull — order detail (items, SKUs, address)#
GET /admin/fulfillment/:id # :id = internal order id (order_…):id must be the internal order_… id from the list response — a bare numeric
display_id returns 404.
Response — 200 OK (abridged to the fields the pre-processor needs)
{
"order": {
"id": "order_01JGXA8Y2K3M4N5P6Q7R8S9T0V",
"display_id": 4521,
"email": "jane.doe@example.com",
"currency_code": "usd",
"total": 89.94,
"subtotal": 79.99,
"shipping_total": 9.95,
"items": [
{
"id": "ordli_01JGXA8Y2K...",
"title": "VNSH Holster - 20rd Extended",
"variant_sku": "4011-VNSH-20EXT",
"quantity": 2,
"fulfilled_quantity": 0,
"unfulfilled_quantity": 2,
"unit_price": 39.99,
"requires_shipping": true
}
],
"shipping_address": {
"first_name": "Jane",
"last_name": "Doe",
"address_1": "123 Main St",
"address_2": "Apt 4B",
"city": "Austin",
"province": "TX",
"postal_code": "78701",
"country_code": "us",
"phone": "+1 512 555 0134"
},
"fulfillments": []
}
}Only fulfill items where requires_shipping is true and unfulfilled_quantity > 0.
2.3 Pushback — report fulfillment state + tracking#
POST /admin/fulfillment/preprocessor/update
Content-Type: application/jsonRequest body
{
"source": "acme-preprocessor",
"dry_run": false,
"updates": [
{
"order_id": "order_01JGXA8Y2K3M4N5P6Q7R8S9T0V",
"reference": "4521",
"status": "shipped",
"tracking_number": "9400111899223334445551",
"carrier": "USPS",
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400111899223334445551",
"shipped_at": "2026-07-23T14:05:00Z",
"delivered_at": null,
"items": [
{ "sku": "4011-VNSH-20EXT", "quantity": 2 }
],
"warehouse_order_id": "WH-88123",
"message": null,
"metadata": {}
}
]
}Field reference (per update entry)
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
string | one of order_id/reference |
Internal OmniCart order id (order_…) — preferred |
reference |
string | one of order_id/reference |
Order display_id (numeric string, e.g. "4521") |
status |
enum | yes | received | processing | shipped | delivered | exception | cancelled |
tracking_number |
string | for shipped/delivered |
Carrier tracking number — also the idempotency key |
carrier |
string | recommended | e.g. USPS, UPS, FedEx, DHL. Always send it when known. When omitted, USPS/UPS/FedEx are inferred from the tracking-number format; unrecognized formats (including DHL) default to USPS |
tracking_url |
string | no | Reserved. OmniCart generates the tracking URL from carrier + tracking number; a supplied value is currently ignored |
shipped_at |
ISO 8601 | no | Handoff-to-carrier time (defaults to now for shipped) |
delivered_at |
ISO 8601 | for delivered |
Carrier delivery confirmation time |
items |
array | for multi-item orders | Shipped lines: { "sku": string, "quantity": number }. Omit only on single-item orders |
warehouse_order_id |
string | no | Pre-processor's internal order reference, stored in order metadata |
message |
string | for exception/cancelled |
Human-readable reason, shown in the dashboard |
metadata |
object | no | Free-form key/values merged into fulfillment metadata |
Response — 200 OK (per-entry results; the batch never fails atomically)
{
"success": true,
"dry_run": false,
"summary": { "created": 1, "updated": 0, "skipped": 0, "errors": 0 },
"results": [
{
"reference": "4521",
"order_id": "order_01JGXA8Y2K3M4N5P6Q7R8S9T0V",
"status": "created",
"message": "Fulfillment created with tracking 9400111899223334445551",
"tracking_number": "9400111899223334445551"
}
]
}Per-entry status values: created (new fulfillment), updated (existing fulfillment
enriched — e.g. delivery date added — or a re-send of a tracking number the order
already has; a pure re-send with nothing new also flags the order for manual review),
skipped (no-op — e.g. a test order auto-held, or a race where the items were already
fulfilled), error (see message; other entries still process).
Error responses
| Code | Meaning |
|---|---|
400 |
Malformed body / missing required fields / >10,000 entries |
401 |
Missing or invalid API key |
404 |
(Per-entry, reported in results) order not found |
422 |
(Per-entry) validation failure — e.g. multi-item order without items, or SKU not on the order. Note: a quantity above the unfulfilled amount is not rejected — it is silently capped (§1.4) |
429 |
Rate limited — retry with backoff |
Retry guidance: retry 5xx and 429 with exponential backoff. Do not blind-retry
4xx. Because pushback is idempotent per tracking number, retrying a whole batch after
a network failure is safe.
3. Examples#
3.1 Cron pull — first page of the awaiting queue#
curl -u "sk_YOUR_SECRET_KEY:" \
"https://your-store.omnicart.cc/admin/fulfillment?fulfillment_filter=awaiting&limit=100&offset=0&no_cache=true"Then fetch details for each order id returned:
curl -u "sk_YOUR_SECRET_KEY:" \
"https://your-store.omnicart.cc/admin/fulfillment/order_01JGXA8Y2K3M4N5P6Q7R8S9T0V"3.2 Pushback — single shipment, whole order shipped#
curl -u "sk_YOUR_SECRET_KEY:" \
-X POST "https://your-store.omnicart.cc/admin/fulfillment/preprocessor/update" \
-H "Content-Type: application/json" \
-d '{
"source": "acme-preprocessor",
"updates": [
{
"reference": "4521",
"status": "shipped",
"tracking_number": "9400111899223334445551",
"carrier": "USPS",
"shipped_at": "2026-07-23T14:05:00Z",
"items": [ { "sku": "4011-VNSH-20EXT", "quantity": 2 } ]
}
]
}'3.3 Pushback — split shipment (two boxes, two tracking numbers)#
Order #4522 has three line items; item C ships separately:
{
"source": "acme-preprocessor",
"updates": [
{
"reference": "4522",
"status": "shipped",
"tracking_number": "1Z999AA10123456784",
"carrier": "UPS",
"shipped_at": "2026-07-23T15:10:00Z",
"items": [
{ "sku": "4011-VNSH-20EXT", "quantity": 1 },
{ "sku": "4012-VNSH-BELT", "quantity": 1 }
]
},
{
"reference": "4522",
"status": "shipped",
"tracking_number": "1Z999AA10123456791",
"carrier": "UPS",
"shipped_at": "2026-07-23T15:10:00Z",
"items": [
{ "sku": "4020-VNSH-MAGPOUCH", "quantity": 2 }
]
}
]
}3.4 Pushback — mixed batch: delivery confirmation + exception#
{
"source": "acme-preprocessor",
"updates": [
{
"reference": "4521",
"status": "delivered",
"tracking_number": "9400111899223334445551",
"delivered_at": "2026-07-25T19:42:00Z"
},
{
"reference": "4530",
"status": "exception",
"message": "Address undeliverable — apartment number missing. Holding at warehouse."
}
]
}Response:
{
"success": true,
"dry_run": false,
"summary": { "created": 0, "updated": 2, "skipped": 0, "errors": 0 },
"results": [
{
"reference": "4521",
"status": "updated",
"message": "Delivery date recorded on existing fulfillment",
"tracking_number": "9400111899223334445551"
},
{
"reference": "4530",
"status": "updated",
"message": "Order flagged for review: Address undeliverable — apartment number missing. Holding at warehouse."
}
]
}Note on delivered: send the shipped pushback (same tracking number) first. A
delivered entry whose tracking number is unknown on the order creates the
fulfillment and marks it delivered in one step — valid, but you lose the distinct
ship event.