View as Markdown

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:

  1. Pulls orders awaiting fulfillment from OmniCart.
  2. Processes them (routing, rate shopping, batching, label prep — vendor-side).
  3. 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/health

1.2 Base URL#

https://your-store.omnicart.cc        # replace with the store domain from onboarding

1.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.99 means $19.99 everywhere in this API.
  • Order references: the human-facing order number is display_id (numeric). Pushback also accepts the internal order_id (order_…) — preferred when you have it.
  • Idempotency: pushback is idempotent per tracking_number within 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 reported updated, 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 an items array 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": true in 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_review SKU-alias entry for operations to map; orders whose shipping name is "Test Test" are auto-held and reported skipped.

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/delivered ride 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/fulfillment

Query 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)

Response200 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 500 with { "message", "request_id", "stage" } — log the request_id and include it when reporting issues. Responses also carry X-Request-Id and X-Cache headers 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.

Response200 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/json

Request 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

Response200 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.