Orders
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
Orders
The order is the core resource in Shippified. Each order is one purchase. It can be built from several messages: a Discord checkout embed, an order-confirmation email, a shipping email, and carrier tracking lookups can all contribute to the same record.
Endpoints
Method | Path | Purpose |
|---|---|---|
|
| List and filter orders (paginated). |
|
| Get one order. |
|
| Create an order by hand, or merge into an existing one by order number. |
|
| Fill in missing fields and set sale price. |
|
| Permanently delete an order. |
|
| Rebuild all orders from their stored source messages. |
|
| Carrier tracking for an order (cached). |
|
| Carrier tracking, bypassing the cache. |
|
| Refresh up to 50 in-flight orders at once. |
|
| Which carriers have live tracking on this server. |
All order endpoints require authentication and only see orders that belong to the authenticated account. Request bodies larger than 2 MiB are rejected with 413.
The order object
{
"id": "ord_mpvu9uim_aeh5t",
"userId": "usr_4f1c2a9b7e0d3c11",
"source": "email",
"sources": ["discord", "email"],
"botId": "bot_m1x2y3z4",
"botName": "Checkout monitor",
"emailSourceId": "email_m1x2y3z5",
"templateId": "hayha",
"templateName": "Hayha",
"eventType": "order_shipped",
"parser": "shape:walmart-order_shipped",
"store": "walmart",
"storeLabel": "Walmart",
"orderNumber": "2000123-45678",
"itemSummary": "Example Console Bundle",
"imageUrl": "https://cdn.example.com/p/12345.png",
"total": "$499.99",
"price": "$499.99",
"quantity": 1,
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"estimatedDelivery": "2026-09-27",
"costCents": 49999,
"salePriceCents": 62000,
"status": "shipped",
"receivedAt": "2026-09-22T18:04:51.000Z",
"updatedAt": "2026-09-24T09:12:03.114Z",
"rawText": "…",
"intakeIds": ["webhook:m1x2y3z4ab12cd", "email:[email protected]"]
}Fields with no value are left out of the response entirely. They are never sent as null.
Identity and provenance
Field | Type | Description |
|---|---|---|
| string | Order ID ( |
| string | Owning account. |
|
| Channel of the most recent message that created or updated this order. |
| array of the same values | Every channel that has contributed to this order. More than one entry means the order was confirmed by more than one channel. |
| string | The Discord intake bot that first reported the order, if any. |
| string | The email source that first reported the order, or the source linked when the order was created by hand. |
| string | The built-in shape or custom template that matched the first message. |
| string | Which parser handled the latest message. Values: |
| string[] | IDs of the stored messages that make up this order. Email IDs are |
| string[] | Names of fields entered by hand through |
| string | Text of the first message (header plus body, up to 4,000 characters). For hand-created orders, this is the |
Lifecycle
Field | Type | Description |
|---|---|---|
| enum | Where the order is in its lifecycle. See Status. |
| enum | The kind of message that moved the order furthest along. See Event type. |
| timestamp | When the order was first seen. For email this is the message's own |
| timestamp | Last change of any kind. Missing on older records. Fall back to |
| string | Event time from the source: the email |
Item and store
Field | Type | Description |
|---|---|---|
|
| Retailer key. |
| string | Display name, e.g. |
| string | The retailer's order number. This is the key used to merge messages into one order. |
| string | Product name. |
| string | Product link and image. |
| string | As extracted. |
| integer | Number of units. Absent means one unit. |
| string | Profile, buyer, or customer name from the message. |
| object (string → string) | Values from |
Money
Field | Type | Description |
|---|---|---|
| string | Display strings copied from the message, e.g. |
| integer | What you paid, in cents. Parsed from |
| integer | What you sold it for, in cents. Only ever set by you. Profitability reports use it. Spend totals never do. |
Shipping and carrier
Field | Type | Description |
|---|---|---|
| string | Tracking number. |
| string | Carrier name as provided or detected, e.g. |
|
| Estimated arrival date, from a message, a |
|
| Set when a carrier lookup confirms delivery: the date of the carrier's delivered scan, or the day Shippified learned of it if the scan is undated. |
| object |
|
| number | Package weight from the carrier. |
| object |
|
| string | Name on the delivery signature. |
| string | Carrier service, copied verbatim, e.g. |
| object | Raw response from the most recent carrier lookup. Only stored when it is 16 KB or less. |
Status
status is always derived from real signals. It cannot be set through the API.
Value | Meaning |
|---|---|
| Order placed, no tracking yet. |
| A tracking number is known, or a shipping message arrived. |
| A delivery message arrived, or a carrier lookup confirmed delivery. |
| The retailer or bot reported a cancellation. |
| The parser couldn't identify the store or the item. Check the order by hand. |
Status only moves forward. When messages merge, the order keeps the highest-ranked status:
issue (0) < ordered (1) < canceled (1.5) < shipped (2) < delivered (3)A partial-cancellation email therefore can't move a shipped order back to canceled, and a late order-confirmation email can't move a delivered order back.
How the status is derived from a message, first match wins:
eventTypeisorder_delivered→deliveredeventTypeisorder_shipped, or a tracking number is present →shippedeventTypeisorder_canceled→canceledStore is
unknownand item is"Unparsed order"→issueOtherwise →
ordered
Event type
Value | Meaning |
|---|---|
| Checkout or order confirmation. |
| Shipping confirmation. |
| Out for delivery, delayed, ready for pickup, or another delivery update. |
| Delivered. |
| Cancellation. |
| Couldn't be classified. |
List orders
GET /api/orders
Returns orders newest first by receivedAt, in the standard pagination envelope.
Query parameter | Description |
|---|---|
| One of |
| Exact store key, e.g. |
|
|
| Case-insensitive exact match on |
|
|
|
|
|
|
| Case-insensitive substring search across |
| Pagination. Default 50, max 200. |
curl -s "https://shippified.net/api/orders?status=shipped&hasTracking=true&from=2026-09-01&limit=100" \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY"{
"items": [ { "id": "ord_mpvu9uim_aeh5t", "status": "shipped", "…": "…" } ],
"total": 37,
"limit": 100,
"offset": 0,
"hasMore": false
}Get an order
GET /api/orders/:id
curl -s https://shippified.net/api/orders/ord_mpvu9uim_aeh5t \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY"Returns the order object itself, with no wrapper. Returns 404 {"error": "Order not found"} if the order doesn't exist or belongs to another account.
Create an order
POST /api/orders
Creates an order by hand, for purchases that no connected bot or inbox will report. The request goes through the same ingest path as parsed messages. If orderNumber matches an existing order, the request merges into that order instead of creating a new one.
Body field | Type | Required | Notes |
|---|---|---|---|
| string | Yes | Must be non-empty after trimming. |
| string | No | One of |
| string | No | Defaults to the label for |
| string | No | The merge key. Strongly recommended. |
| string | No | When set, the order starts as |
| string | No | Stored as sent. When omitted, it is detected from |
| string | No | Display string. |
| integer | No | Rounded. Values of 0 or less are dropped. |
| integer | No | Rounded. |
| integer | No | Must be positive. Otherwise it is dropped. |
| string | No | |
| string | No | ISO 8601: |
| string | No | Links the order to one of your email sources. Ignored if the source doesn't exist. |
| string | No | Defaults to |
curl -s -X POST https://shippified.net/api/orders \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"itemSummary": "Example Console Bundle",
"store": "bestbuy",
"orderNumber": "BBY01-000000000001",
"trackingNumber": "1Z999AA10123456784",
"costCents": 49999,
"quantity": 1
}'Response 201 Created:
{
"order": {
"id": "ord_mq1a2b3c_x9y8z",
"source": "manual",
"sources": ["manual"],
"parser": "manual",
"store": "bestbuy",
"storeLabel": "Best Buy",
"orderNumber": "BBY01-000000000001",
"itemSummary": "Example Console Bundle",
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"eventType": "order_shipped",
"status": "shipped",
"costCents": 49999,
"quantity": 1,
"receivedAt": "2026-09-24T15:20:00.000Z",
"…": "…"
},
"merged": false
}merged: true means an order with the same orderNumber already existed and your request was folded into it. The response status is still 201, and order is the merged record, which keeps the existing order's id. In a merge, existing non-empty values win. The new request only fills fields that were empty, and status never moves backward.
Error | Status |
|---|---|
|
|
|
|
|
|
Body larger than 2 MiB |
|
Free-plan monthly limit reached (new orders only) |
|
Creating an order fires order.created, or order.merged for a merge. It also fires order.shipped / order.delivered when the eventType implies that transition. See Webhooks.
Update an order
PATCH /api/orders/:id
Updates follow a fill-missing-only model. You can fill fields that are empty, but you can't overwrite a value the parser or a carrier already set. This keeps order data traceable to real messages. Fields not listed below are ignored.
Body field | Rule |
|---|---|
| Always writable. Rounded to an integer. Send |
| Written only if the order has no |
| Written only if empty. If the order is |
| Written only if empty. |
| Written only if empty. |
| Written only if empty. Must start with |
| Written only if empty. Must be a positive integer. |
| Written only if the current value is |
| Ignored. Status is derived, and delivery is carrier-driven. |
A value counts as empty if it is missing, null, or an empty string. Values that don't meet a rule are skipped silently. The request still returns 200.
Every field the request actually writes (except the derived status) is added to the order's userEdited list, so re-parsing and mailbox rebuilds keep your values.
curl -s -X PATCH https://shippified.net/api/orders/ord_mpvu9uim_aeh5t \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{"salePriceCents": 62000, "trackingNumber": "1Z999AA10123456784"}'The response is the updated order object with no wrapper. To see whether a field was written, compare the returned values with what you sent.
To clear a sale price:
curl -s -X PATCH https://shippified.net/api/orders/ord_mpvu9uim_aeh5t \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{"salePriceCents": null}'Sending "" for salePriceCents has no effect.
Events:
A
PATCHthat writes at least one field firesorder.updatedwith{ order, patch }, wherepatchlists the fields that were applied (a cleared sale price appears as"salePriceCents": null).A
PATCHthat adds a tracking number to anorderedorder also firesorder.shipped.A
PATCHthat changes nothing writes nothing, fires no event, and returns the order unchanged with200.
Delete an order
DELETE /api/orders/:id
curl -s -X DELETE https://shippified.net/api/orders/ord_mpvu9uim_aeh5t \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY"Returns 200 {"ok": true} or 404. Deletion is permanent and fires no webhook event. The order disappears from analytics. Share cards already created keep their frozen snapshot. The dashboard offers the same action as Delete order in every order's drawer.
Important: If a stored source message is ingested again later, for example during a mailbox rebuild, the order can reappear.
Re-parse orders
POST /api/orders/reparse
Rebuilds every order that has stored source messages, using the current built-in shapes and your current custom templates. Run it after you create or fix a custom template to apply the change to past orders.
curl -s -X POST https://shippified.net/api/orders/reparse \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY"{ "reparsed": 412, "changed": 37, "merged": 3, "removed": 5, "skipped": 21 }Field | Meaning |
|---|---|
| Orders rebuilt from their stored messages. |
| Rebuilt orders whose content actually changed. |
| Orders folded into another order because they now share an order number. |
| Orders deleted because none of their messages are now recognized as order mail. |
| Orders with no stored messages, such as hand-created or older records, plus orders that would have been removed but have hand-entered data. They are left unchanged. |
What re-parsing keeps from the old record:
id,userId, the earliestreceivedAt, the union ofsources,intakeIds, anduserEdited.Carrier and user fields:
salePriceCents,estimatedDelivery,actualDelivery,shippingAddress,billingAddress,weightGrams,dimensionsCm,signedBy,serviceLevel,rawCarrierResponse.Every field listed in
userEdited.A carrier-confirmed delivery: an order with
actualDeliverystaysdelivered.
Everything else is rebuilt from the messages. An order is only removed if it has no salePriceCents and no userEdited entries. Emails you imported by hand with POST /api/email/import are never dropped as non-order mail. Re-parsing fires no webhook events.
Note: The request runs synchronously and can take a while on large workspaces. Use a generous client timeout.
Tracking
For how carriers are detected and which ones are supported, see the tracking guide.
Get tracking
GET /api/orders/:id/tracking
Returns cached results when they are fresh: 5 minutes for packages in transit, 24 hours once delivered. Otherwise it runs a live carrier lookup.
Live lookups are only made for carriers that have live tracking enabled on the server. On shippified.net that is currently DHL and DHL Express. UPS and FedEx numbers are detected and labelled, but return the "live unavailable" response described below, as do USPS and unrecognised numbers. Call GET /api/carrier-status to see what's live instead of hard-coding it.
curl -s https://shippified.net/api/orders/ord_mpvu9uim_aeh5t/tracking \
-H "Authorization: Bearer $SHIPPIFIED_API_KEY"{
"track": {
"carrier": "dhl-express",
"trackingNumber": "1234567890",
"delivered": false,
"events": [
{ "date": "2026-09-23T14:10:00Z", "location": "Louisville, KY", "description": "Departed facility", "status": "in_transit" }
]
},
"fetchedAt": "2026-09-24T15:21:09.002Z",
"stale": false,
"carrierKey": "dhl-express",
"liveUnavailable": false,
"credsRequired": false,
"trackingNumber": "1234567890"
}Field | Meaning |
|---|---|
| Normalized result: |
| Carrier or lookup error message, if any. |
| When the data was fetched from the carrier. |
| Always |
|
|
|
|
| Legacy field kept for older clients. |
| After a live lookup: the order with the carrier result applied, so you can pick up status and delivery-date changes without another request. |
| On |
Situation | Response |
|---|---|
Order has no tracking number |
|
Carrier can't be detected |
|
Carrier has no live tracking on this server |
|
{
"error": "Live UPS tracking isn't enabled on this Shippified server — showing updates from your order emails.",
"carrierKey": "ups",
"liveUnavailable": true,
"credsRequired": true,
"trackingNumber": "1Z999AA10123456784"
}A live lookup that returns data also updates the order:
It fills
shippingAddress,weightGrams,dimensionsCm,signedBy,serviceLevelandrawCarrierResponsewhen the carrier returns them. Fields the carrier didn't return are never wiped.It sets
estimatedDeliveryfrom the carrier's estimate while the package is in transit.It moves
orderedtoshippedonce the carrier has scan events.When the carrier reports delivery, it sets
actualDeliveryand movesorderedorshippedtodelivered.canceledandissueorders keep their status.
Every carrier response fires tracking.refreshed. order.shipped and order.delivered fire only on a real status transition.
Force refresh
POST /api/orders/:id/tracking/refresh has the same response, but always does a live lookup.
Bulk sync
POST /api/orders/sync-tracking refreshes up to 50 orders with status ordered or shipped that have a tracking number on a carrier with live tracking, bypassing the cache. Orders on USPS, unrecognised carriers, or carriers without live tracking are skipped rather than counted as failures.
{ "total": 12, "refreshed": 11, "failed": 1, "delivered": 4 }total: 0 means there was nothing to look up.
Background refresh
You don't need to poll to keep orders current. Every 5 minutes the server plans a bounded batch of carrier lookups across all workspaces and applies each result to the orders exactly as the endpoints above do, so status, estimatedDelivery, actualDelivery and the tracking.refreshed / order.shipped / order.delivered events all move without any client involvement.
Rule | Behaviour |
|---|---|
Candidates | Orders with status |
Cadence | By order age ( |
Batch size | Up to 40 lookups per 5-minute tick, never overlapping the previous tick. |
Priority | Never-checked packages first, then the most overdue. |
Fairness | Workspaces take turns (round-robin) within each batch. |
Deduplication | One lookup per carrier and tracking number, applied to every order carrying it. |
Carrier budgets | A rolling 24-hour cap per carrier API account. DHL and DHL Express share one account with a cap of 240 lookups a day. Lookups from the endpoints above count against the same cap. Calls to one account are spaced out, and a rate-limit response pauses that account for 30 minutes. |
Because the DHL budget is shared across all workspaces, packages may be checked less often than the cadence above when many are in flight. If you need a fresh answer, call POST /api/orders/:id/tracking/refresh. Subscribe to webhooks rather than polling for changes.
Carrier status
GET /api/carrier-status lists every carrier Shippified recognises and whether live tracking is enabled on this server.
{
"carriers": [
{ "key": "ups", "label": "UPS", "configured": false, "envVars": ["…"] },
{ "key": "fedex", "label": "FedEx", "configured": false, "envVars": ["…"] },
{ "key": "dhl", "label": "DHL", "configured": true, "envVars": ["…"] },
{ "key": "dhl-express", "label": "DHL Express", "configured": true, "envVars": ["…"] },
{ "key": "usps", "label": "USPS", "configured": false, "envVars": [], "note": "Free USPS tracking API is unavailable. Paid USPS Web Tools key required." }
]
}configured: true means live lookups, background refresh and sync-tracking apply to that carrier. envVars names the server settings a self-hosted instance needs to enable it; there is nothing to configure on your account.
Related
Profitability guide: how
costCentsandsalePriceCentsare used.Webhooks: get notified instead of polling.