ProfitGuard is here. AI repricing for Amazon, fully protected and on autopilot. Try it free
Guideapi_version 2026-11-01https://api.profitpath.app

ProfitDesk Prep API v1

Connect your own warehouse software to ProfitDesk as a prep center.

Connect your own warehouse or prep software to ProfitDesk. Once connected, ProfitDesk treats your software like a prep center:

  • ProfitDesk → you: every announcement, cancellation and update reaches your URL as a signed webhook event.
  • You → ProfitDesk: you report goods received and outbound shipments through this REST API. ProfitDesk updates the seller's purchases from your reports.

Every object carries stable identifiers, so both sides can follow each unit from purchase to inbound, inbound line, outbound and finally the Amazon FBA shipment.

This guide covers a seller's own software: a private connection that the seller creates in ProfitDesk, with its own signing secret and API key.

Building software for many sellers? Warehouse software that serves many ProfitDesk sellers is registered by ProfitDesk as a prep provider, with one endpoint and one key for all of them. See prep-providers-v1.md.

Machine-readable spec: prep-v1.openapi.yaml (OpenAPI 3.1). Interactive docs: prep-v1-swagger.html.


1. How it works

 Seller buys goods ──► ProfitDesk lists them on Amazon
                              │
                              ▼
                 inbound.announced  ───────────────►  your software
                 (signed webhook)                     creates an expected inbound
                                                          │
                                   goods arrive at your warehouse
                                                          │
 ProfitDesk updates received qty ◄── POST /v1/prep/inbounds/{id}/receipts
                                                          │
                                   you ship to Amazon (FBA) or elsewhere
                                                          │
 ProfitDesk stores the outbound  ◄── POST /v1/prep/outbounds
 trace (incl. FBA shipment id)                            │
                                                          ▼
 Amazon receives the FBA shipment ──► ProfitDesk books "forwarded" from Amazon's data
  • An inbound is one announcement: the goods of one order, or of several orders that arrive together. It has one line per purchase.
  • A purchase counts as announced in ProfitDesk only once your endpoint has answered 2xx to its inbound.announced. Until then the seller sees "Delivering". If the delivery fails, the purchase stays unannounced and the seller can retry the delivery or announce it again.
  • Outbounds are a trace. They link purchases to the shipments you send out, including the Amazon FBA shipment id, but they do not change stock or forwarded quantities. ProfitDesk books forwarded quantities from Amazon's FBA data.

2. Setup

The seller creates the connection in ProfitDesk under Settings → Integrations → Prep centers → Add → "Own software (Webhook)" and enters:

FieldMeaning
NameDisplay name of your software or warehouse, max 100 characters
Endpoint URLYour HTTPS URL that receives the webhook events, max 2048 characters, without user name or password. It must resolve to public addresses only; private and internal addresses are refused.
EventsWhich event types to receive (§5.4). Without a choice, the events marked "on" there.
Link template (optional)A deep link into your software for one inbound, e.g. https://wms.example.com/inbound/{external_id}, shown to the seller next to the announced purchase. Must be an http(s) URL. Exactly two placeholders: {inbound_id} becomes the URL-encoded inb_… id, {external_id} your URL-encoded external_id. A template with {external_id} shows no link until the inbound has an external id.
Auto-announce FBA/FBM, transfer product name/notesSame options as for other prep centers

After saving, ProfitDesk shows two credentials once:

CredentialFormatUsed for
Signing secretwhsec_…Verifying webhook signatures (§5.2)
API keyppk_…Calling the REST API (§4)

On the connection's detail page the seller can change the URL, the events and the other settings, and rotate both credentials. After a secret rotation the old secret stays valid for 24 hours. A key rotation revokes the old key immediately. The detail page also has "Send test", which sends a ping event, and a delivery log where deliveries can be sent again.

The seller then assigns the connection to a warehouse. From then on, purchases in that warehouse are announced to you.

A warehouse is linked to exactly one prep center. Purchases in a warehouse linked to your connection are announced to you and to no one else.

A seller can use their own software (webhook) and PrepBusiness/PrepTool prep centers at the same time, each for different warehouses. They can also hold several webhook connections, for example one per warehouse setup. Each has its own pcn_… id, signing secret and API key, and a key only sees its own connection.


3. Identifiers

All ids are strings with a type prefix. Store them as opaque strings.

PrefixObjectStable?Notes
pur_PurchaseForeverThe seller's purchase. The key to trace a unit end-to-end. Stays the same if the goods are re-announced or move warehouse.
itm_ItemForever per SKUOne article per seller SKU on your connection. Carries sku, fnsku, asin, ean.
inb_InboundPer announcementA re-announcement creates a new inbound; the old one gets inbound.cancelled with replaced_by_inbound_id.
inl_Inbound linePer announcementOne line per purchase inside an inbound. Use it in receipts and outbounds for exact matching.
out_OutboundForeverOur id for an outbound. You address outbounds by your own external_id.
pcn_ConnectionForeverThe seller's connection to your software. Every response and event names it as connection_id.
wh_WarehouseForeverThe seller's warehouse the goods are sent to
evt_EventForeverWebhook event id, for deduplication

Your own references. Inbounds and outbounds can carry your external_id. Every path that takes an inbound or outbound {id} also accepts ext:<external_id>.

Traces. Every inbound response and inbound event contains traces: one entry per line, with all ids known for that unit.

JSON
{
  "purchase_id": "pur_4720", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_120", "item_id": "itm_17",
  "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
  "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": ["out_55"], "amazon_shipment_ids": ["FBA15XYZ"]
}

4. Authentication, rate limits, conventions

Base URL: https://api.profitpath.app

Authentication. Send the API key as a bearer token on every request:

HTTP
Authorization: Bearer ppk_3f9a…

A key only sees the data of its own connection. A missing, unknown or revoked key returns 401 unauthorized.

Rate limit. 2 requests per second per key, with bursts up to 10. Beyond that the API returns 429 rate_limited with Retry-After: 1.

Format.

  • Requests and responses are JSON (UTF-8).
  • Timestamps are RFC 3339 (2026-11-05T08:01:00Z). Calendar dates are YYYY-MM-DD.
  • Request bodies are limited to 1 MB. Unknown fields are ignored.

Pagination. List endpoints take limit (1–100, default 50) and cursor. They return next_cursor; pass it as cursor to get the next page. It is null on the last page.

Idempotency. Every POST/PUT may carry an idempotency key: either event_id in the body or the Idempotency-Key header. If both are present, the body wins. The key is required for receipts and outbounds.

You sendResult
Same key, identical method, path and bodyThe stored response is replayed with "duplicate": true. Nothing changes.
Same key, different request409 idempotency_conflict
A request that failed (4xx/5xx)Not stored. Fix the request and retry with the same key.

Keys are scoped to the connection (also across key rotations), max 200 characters, and a stored response is kept for 30 days. A good key is your own event or document id plus a version, e.g. rcpt-8812-v2.

Ordering. Receipts and outbounds carry occurred_at, the time the event happened in your warehouse (default: now). A line or outbound only changes if the event is newer than the last one applied to it. Older events are reported as stale and change nothing, so retries and out-of-order delivery are safe.


5. Webhooks (ProfitDesk → you)

5.1 Delivery

ProfitDesk sends each event as POST <your endpoint URL> with a JSON body (the envelope) and these headers:

HeaderValue
Content-Typeapplication/json
X-Webhook-EventThe event id (evt_…), the same as the envelope's id. Use it to deduplicate.
X-Webhook-Event-Typee.g. inbound.announced
X-Webhook-TimestampUnix seconds of this delivery attempt
X-Webhook-Signaturesha256=<hex>. During a secret rotation it holds two values, separated by a comma.
User-AgentProfitDesk-Webhooks/1

Envelope (here of a ping):

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a08",
  "type": "ping",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-03T09:00:00Z",
  "created_at": "2026-11-03T09:00:00Z",
  "connection_id": "pcn_812",
  "data": {"message": "Test event from ProfitDesk"}
}

Every retry of an event sends the same envelope with the same id; only X-Webhook-Timestamp and the signature change. Your endpoint receives one request at a time.

Responding.

  • Answer with any 2xx within 10 seconds. Do slow work asynchronously after answering.
  • For inbound.announced you may answer with {"external_id": "<your id>"} (a string or a number, max 200 characters). ProfitDesk stores it as the inbound's external_id, like PUT …/external-id, but sends no inbound.updated for it: you already know the value. Later events carry it. It follows the same rule as PUT: unique within your connection. If another inbound already uses the value, the delivery still counts as delivered, but the value is not stored; set it with PUT …/external-id to get the 409 external_id_conflict error explicitly.
  • Any other body, and the body of any other event, is ignored.

Retries.

Your responseWhat ProfitDesk does
2xxDelivered
Timeout, connection error, 408, 429, 5xxRetried after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, then every 12 h, until 72 h after the event was queued; the last attempt is made at the 72 h mark
3xx and any other 4xx (including 410)Failed immediately, no retry. A failed announcement shows as failed to the seller. The endpoint stays enabled.
  • Redirects are not followed, so a 3xx counts as a failed delivery.
  • An endpoint is disabled only after a failed attempt once it has gone 72 hours without a single successful delivery; no status code disables it on its own (a 410 is a failed delivery like any other 4xx). While it is disabled, waiting deliveries are skipped, new events are stored but not queued, and new announcements fail. The seller can re-enable it and send failed or skipped deliveries again from the delivery log; events created while it was off are not in the log, so catch up with GET /v1/prep/events.
  • A failed inbound.announced is not re-sent once its inbound has been cancelled; the seller re-announces the purchase instead, which creates a new inbound.

Ordering, skips and duplicates.

  • Events of the same inbound are delivered in order, one at a time. An inbound.cancelled never overtakes the inbound.announced of the same inbound. An event whose delivery failed for good does not hold back later events.
  • Cancelled before you saw it. If an inbound is cancelled before you received any of its events, ProfitDesk skips all its open deliveries, the inbound.cancelled included. You never hear of that inbound by webhook. A delivery that failed counts as not received.
  • Reactivated without you having seen it. If such an inbound becomes active again, the reactivation arrives as inbound.announced, not inbound.restored. Treat it like any announcement.
  • Never sent after a cancel. An event of an inbound is never delivered after an inbound.cancelled of that inbound that you already received (unless an inbound.restored reached you after it), also not when it is sent again from the delivery log. It is skipped instead, so you never receive an older state on top of a cancellation.
  • Skipped events stay in the feed. GET /v1/prep/events holds every event, delivered or not. A feed reader therefore also sees inbounds that were cancelled before delivery, and can see inbound.announced more than once for the same inbound: treat it as an upsert.
  • Delivery is at least once. Deduplicate by X-Webhook-Event / id.
  • Missed something? GET /v1/prep/events replays every event (§6.7).

5.2 Verifying the signature

Verify every request before trusting its body:

  1. Read the raw request body. Do not parse and re-serialize it first.
  2. Compute hex(HMAC-SHA256(secret, timestamp + "." + rawBody)). The key is your signing secret exactly as shown (including whsec_), and the timestamp comes from the X-Webhook-Timestamp header.
  3. The header X-Webhook-Signature holds one or more sha256=<hex> values separated by commas. Accept if any of them equals yours. Compare in constant time.
  4. Reject the request if the timestamp is more than 5 minutes away from your clock.
import crypto from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return headers["x-webhook-signature"].split(",").some((sig) => {
    const a = Buffer.from(sig.trim()), b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });
}

5.3 The inbound object

Every inbound event carries data.inbound, data.traces, data.changes, data.reason and data.replaced_by_inbound_id (the last three are null unless the event type sets them):

JSON
{
  "inbound": {
    "id": "inb_2000000123",
    "external_id": null,
    "batch_id": null,
    "status": "open",
    "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
    "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
    "fulfillment": "FBA",
    "warehouse": {"id": "wh_12", "name": "Warehouse South"},
    "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
    "tracking_numbers": ["1Z999AA10123456784"],
    "lines": [
      {
        "id": "inl_99",
        "purchase_id": "pur_4711",
        "item": {
          "id": "itm_17", "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH", "ean": "4006381333931",
          "title": "Bosch GSR 18V-55 Drill Driver", "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
        },
        "quantity_expected": 6,
        "quantity_received": 0,
        "quantity_damaged": 0,
        "status": "open"
      }
    ],
    "created_at": "2026-11-03T10:15:00Z"
  },
  "traces": [
    {
      "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
      "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567", "batch_id": null,
      "outbound_ids": [], "amazon_shipment_ids": []
    }
  ],
  "changes": null,
  "reason": null,
  "replaced_by_inbound_id": null
}
FieldNotes
statusopen, received (every line closed) or cancelled (every line cancelled). Each line has its own status as well.
batch_idSet when several purchases were announced together and will physically arrive together
name, notesBuilt like the names other prep centers receive. The product name and the seller's notes are included only if the seller enabled "transfer product name/notes". lines[].item.title and image_url are sent either way.
fulfillmentFBA or FBM: where the goods go after your prep
item.fnskuCan be null at announcement time. It follows in an inbound.updated event once Amazon has assigned it.
tracking_numbersInbound parcel tracking codes known so far. New codes follow in inbound.updated.

5.4 Event types

EventWhendataDefault
inbound.announcedGoods are on their way to youinbound, traceson
inbound.updatedTracking numbers were added, an FNSKU became known, your external_id changed, or some lines of a shared inbound were cancelled or restoredinbound, traces, changes (see below); reason when a line was cancelledon
inbound.cancelledThe whole inbound is cancelled: the purchase was cancelled or deactivated, moved to another warehouse, or re-announcedinbound, traces, reason, optionally replaced_by_inbound_idon
inbound.restoredA cancelled inbound is active again. If you never received the inbound, the reactivation arrives as inbound.announced instead.inbound, traceson
inbound.receivedEcho of your receipt via the APIinbound, tracesoff
outbound.recordedEcho of your outbound via the APIoutbound, tracesoff
ping"Send test" in ProfitDesk. Not stored and not replayable.messagealways

The seller picks the events in the connection settings. A new connection receives the four events marked on. The two echo events are off by default: they only repeat what your software itself just reported through the API, and an integration that reacts to them naively loops. GET /v1/prep/events returns every event type, whether or not the connection is subscribed to it.

changes values on inbound.updated:

ValueMeaning
tracking_numbersNew inbound tracking codes, in inbound.tracking_numbers
fnskuAn item got its FNSKU, in lines[].item.fnsku
external_idYour external_id was set or changed with PUT …/external-id. Not sent for an external_id returned in your webhook response, nor when the value is unchanged.
linesSome lines of a shared inbound were cancelled or restored while the inbound stays open. Check each line's status.

reason values: purchase_cancelled, purchase_deactivated, warehouse_changed, reannounced (offer_deleted is reserved). On inbound.updated it is set when a line was cancelled and null when one was restored.

When the reason is reannounced, replaced_by_inbound_id names the new inbound that replaces this one. Move your expectation over to it.

5.5 Webhook schemas

Every key is always present. A field without a value is null, never left out; fields typed \| null below can be null.

Envelope (every event)

FieldTypeDescription
idstringEvent id evt_<uuid>, equal to the X-Webhook-Event header. Unique; use it to deduplicate.
typestringinbound.announced, inbound.updated, inbound.cancelled, inbound.restored, inbound.received, outbound.recorded or ping
api_versionstringPayload version, currently 2026-11-01
occurred_atstring (date-time)When the change happened in ProfitDesk; for the echo events, the occurred_at you reported
created_atstring (date-time)When the event was created. Delivery may be later because of retries.
connection_idstringpcn_… of the seller's connection
dataobjectDepends on type; see below

data of inbound.* events (InboundEventData)

FieldTypeDescription
inboundInboundThe inbound after the change
tracesarray of TraceOne entry per line, in the same order as inbound.lines
changesarray of string | nullnull except on inbound.updated: tracking_numbers, fnsku, external_id, lines
reasonstring | nullnull except on inbound.cancelled, and on inbound.updated when a line was cancelled: purchase_cancelled, purchase_deactivated, warehouse_changed, reannounced
replaced_by_inbound_idstring | nullnull except on inbound.cancelled/inbound.updated with reason reannounced: the new inb_… that takes over the goods

Inbound

FieldTypeDescription
idstringinb_…
external_idstring | nullYour reference, if set
batch_idstring | nullSame value for purchases announced together, which arrive together
statusstringopen; received when every line is received or cancelled and at least one is received; cancelled when every line is cancelled
namestring<supplier> | <product> | <order numbers>. The product (first 30 characters, plus +N when the inbound has more lines) only if the seller transfers product names. Parts that are unknown are left out; if none is known, the name is the inbound id. Max 255 characters.
notesstringThe seller's notes (if transferred, each distinct note once), plus a final line Fulfillment: FBA or Fulfillment: FBM
fulfillmentstringFBA or FBM
warehouseobject | null{ "id": "wh_…", "name": "…" }: the seller's warehouse the goods are sent to. Moving a purchase between two warehouses of the same connection sends no event; the next payload carries the new warehouse. A move to another connection or to a warehouse without a prep center cancels the inbound (warehouse_changed).
order.numbersarray of stringSupplier order numbers of all purchases in the inbound
order.supplier_hoststring | nullShop domain, e.g. amazon.de; null if unknown
order.purchased_atstring (date) | nullPurchase date YYYY-MM-DD; null if unknown
tracking_numbersarray of stringInbound parcel tracking codes known so far, sorted ascending. Codes are normalised (see below).
linesarray of InboundLineOne line per purchase
created_atstring (date-time)When the inbound was created

Tracking-code normalisation. Tracking codes come from free-text fields and carrier data, and are normalised before they are sent:

  1. The text is split at whitespace (including non-breaking and other Unicode spaces) and at , ; : | / \.
  2. Inside each part, every character other than A–Z, a–z and 0–9 is removed. Hyphens and dots are dropped, not split on: DE-LPLV-0GLI becomes DELPLV0GLI.
  3. Parts without a digit are dropped. This removes carrier labels such as DHL.
  4. Duplicates are compared case-insensitively. The first spelling seen is kept; letter case is not changed.

Example: "DHL: 00340434161094015902, 1z999-aa1" gives ["00340434161094015902", "1z999aa1"].

InboundLine

FieldTypeDescription
idstringinl_…. Use it as line_id in receipts and outbounds.
purchase_idstringpur_…, the stable trace key
itemItemThe article
quantity_expectedintegerUnits announced
quantity_receivedintegerAbsolute count reported via receipts, 0 before the first one
quantity_damagedintegerDamaged units reported via receipts
statusstringopen, received or cancelled

Item

FieldTypeDescription
idstring | nullitm_…; one item per SKU and connection
skustringThe seller's Amazon SKU
fnskustring | nullAmazon label barcode. Can be null at announcement; then it follows with inbound.updated and changes: ["fnsku"].
asinstring | nullAmazon ASIN
eanstring | nullEAN/GTIN
titlestring | nullProduct title
image_urlstring | nullProduct image

Trace

FieldTypeDescription
purchase_idstringpur_…
inbound_idstring | nullinb_…
inbound_line_idstring | nullinl_…
item_idstring | nullitm_…
sku, fnsku, asinstring | nullAs on the item
order_numberstring | nullSupplier order number of this purchase
batch_idstring | nullAs on the inbound
outbound_idsarray of stringout_… of non-cancelled outbounds that carry units of this purchase
amazon_shipment_idsarray of stringFBA shipment ids (FBA…) of those outbounds

data of outbound.recorded

FieldTypeDescription
outboundOutboundThe outbound as stored after your request
tracesarray of TraceOne per purchase on the outbound

Outbound

FieldTypeDescription
idstringout_…
external_idstringYour outbound id
statusstringcreated, shipped or cancelled
destination.typestringfba, fbm or other
destination.amazon_shipment_idstring | nullFBA shipment id
destination.marketplacestring | nulle.g. DE
tracking_numbersarray of stringOutbound tracking codes
shipped_atstring (date-time) | nullSet the first time the status is shipped; kept afterwards
lines[].purchase_idstringpur_… the units were matched to
lines[].inbound_line_idstring | nullinl_…, when known
lines[].quantityintegerUnits of this purchase in the outbound
lines[].resolved_bystringHow the line was matched: line_id, purchase_id, item_id or sku_fifo

data of ping

FieldTypeDescription
messagestringA fixed test message

Your response

FieldTypeDescription
external_idstring or number, optionalOnly read on inbound.announced with a 2xx, max 200 characters. Stored as the inbound's external_id. Any other body is ignored.

5.6 Example per event

inbound.announced: see §5.3 for data; the full envelope:

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a01",
  "type": "inbound.announced",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-03T10:15:00Z",
  "created_at": "2026-11-03T10:15:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000123",
      "external_id": null,
      "batch_id": null,
      "status": "open",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
      "tracking_numbers": ["1Z999AA10123456784"],
      "lines": [
        {
          "id": "inl_99",
          "purchase_id": "pur_4711",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "open"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567", "batch_id": null,
        "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": null,
    "reason": null,
    "replaced_by_inbound_id": null
  }
}

Respond:

HTTP
HTTP/1.1 200 OK
Content-Type: application/json

{"external_id": "WMS-IN-5521"}

inbound.updated: new tracking code and FNSKU

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a02",
  "type": "inbound.updated",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-04T07:30:00Z",
  "created_at": "2026-11-04T07:30:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000123",
      "external_id": "WMS-IN-5521",
      "batch_id": null,
      "status": "open",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
      "tracking_numbers": ["1Z999AA10123456784", "JJD000390007754321"],
      "lines": [
        {
          "id": "inl_99",
          "purchase_id": "pur_4711",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "open"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": ["tracking_numbers", "fnsku"],
    "reason": null,
    "replaced_by_inbound_id": null
  }
}

inbound.updated: one line of a shared inbound was cancelled

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a03",
  "type": "inbound.updated",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-04T12:00:00Z",
  "created_at": "2026-11-04T12:00:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000131",
      "external_id": "WMS-IN-5530",
      "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10",
      "status": "open",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver +1 | 302-1234567-1234567, 302-7654321-7654321",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {
        "numbers": ["302-1234567-1234567", "302-7654321-7654321"], "supplier_host": "amazon.de",
        "purchased_at": "2026-11-02"
      },
      "tracking_numbers": ["1Z999AA10123456784"],
      "lines": [
        {
          "id": "inl_120",
          "purchase_id": "pur_4720",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "open"
        },
        {
          "id": "inl_121",
          "purchase_id": "pur_4721",
          "item": {
            "id": "itm_18", "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "ean": "4059952513164",
            "title": "Bosch GBA 18V 4.0Ah Battery", "image_url": "https://m.media-amazon.com/images/I/51Zk2mN8pRL.jpg"
          },
          "quantity_expected": 2,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "cancelled"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4720", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_120", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": [], "amazon_shipment_ids": []
      },
      {
        "purchase_id": "pur_4721", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_121", "item_id": "itm_18",
        "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "order_number": "302-7654321-7654321",
        "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": ["lines"],
    "reason": "purchase_cancelled",
    "replaced_by_inbound_id": null
  }
}

inbound.cancelled: re-announced as a new inbound

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a04",
  "type": "inbound.cancelled",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-04T15:00:00Z",
  "created_at": "2026-11-04T15:00:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000123",
      "external_id": "WMS-IN-5521",
      "batch_id": null,
      "status": "cancelled",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
      "tracking_numbers": ["1Z999AA10123456784"],
      "lines": [
        {
          "id": "inl_99",
          "purchase_id": "pur_4711",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "cancelled"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": null,
    "reason": "reannounced",
    "replaced_by_inbound_id": "inb_2000000140"
  }
}

inbound.restored

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a05",
  "type": "inbound.restored",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-04T16:00:00Z",
  "created_at": "2026-11-04T16:00:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000123",
      "external_id": "WMS-IN-5521",
      "batch_id": null,
      "status": "open",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
      "tracking_numbers": ["1Z999AA10123456784"],
      "lines": [
        {
          "id": "inl_99",
          "purchase_id": "pur_4711",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 0,
          "quantity_damaged": 0,
          "status": "open"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": null,
    "reason": null,
    "replaced_by_inbound_id": null
  }
}

inbound.received (echo, opt-in)

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a06",
  "type": "inbound.received",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-05T09:30:00Z",
  "created_at": "2026-11-05T09:30:01Z",
  "connection_id": "pcn_812",
  "data": {
    "inbound": {
      "id": "inb_2000000123",
      "external_id": "WMS-IN-5521",
      "batch_id": null,
      "status": "received",
      "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
      "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
      "fulfillment": "FBA",
      "warehouse": {"id": "wh_12", "name": "Warehouse South"},
      "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
      "tracking_numbers": ["1Z999AA10123456784"],
      "lines": [
        {
          "id": "inl_99",
          "purchase_id": "pur_4711",
          "item": {
            "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
            "title": "Bosch GSR 18V-55 Drill Driver",
            "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
          },
          "quantity_expected": 6,
          "quantity_received": 6,
          "quantity_damaged": 1,
          "status": "received"
        }
      ],
      "created_at": "2026-11-03T10:15:00Z"
    },
    "traces": [
      {
        "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
      }
    ],
    "changes": null,
    "reason": null,
    "replaced_by_inbound_id": null
  }
}

outbound.recorded (echo, opt-in)

JSON
{
  "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a07",
  "type": "outbound.recorded",
  "api_version": "2026-11-01",
  "occurred_at": "2026-11-06T14:00:00Z",
  "created_at": "2026-11-06T14:00:01Z",
  "connection_id": "pcn_812",
  "data": {
    "outbound": {
      "id": "out_55",
      "external_id": "OUT-311",
      "status": "shipped",
      "destination": {"type": "fba", "amazon_shipment_id": "FBA15XYZ", "marketplace": "DE"},
      "tracking_numbers": ["1Z999AA10123456784"],
      "shipped_at": "2026-11-06T14:00:00Z",
      "lines": [
        {"purchase_id": "pur_4720", "inbound_line_id": "inl_120", "quantity": 4, "resolved_by": "line_id"},
        {"purchase_id": "pur_4721", "inbound_line_id": "inl_121", "quantity": 2, "resolved_by": "sku_fifo"}
      ]
    },
    "traces": [
      {
        "purchase_id": "pur_4720", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_120", "item_id": "itm_17",
        "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
        "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": ["out_55"],
        "amazon_shipment_ids": ["FBA15XYZ"]
      },
      {
        "purchase_id": "pur_4721", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_121", "item_id": "itm_18",
        "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "order_number": "302-7654321-7654321",
        "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": ["out_55"],
        "amazon_shipment_ids": ["FBA15XYZ"]
      }
    ]
  }
}

ping: see §5.1.

What your response means

Your responseResult
2xx (any body)Delivered. On inbound.announced, an optional {"external_id": "…"} is stored.
3xx, other 4xx (except 408, 429; 410 included)Failed at once, no retry; the endpoint stays enabled
5xx, 408, 429, timeout (10 s), connection errorRetried for up to 72 h (§5.1)

6. REST API (you → ProfitDesk)

MethodPathPurpose
GET/v1/prep/inboundsList inbounds
GET/v1/prep/inbounds/{id}Get one inbound
PUT/v1/prep/inbounds/{id}/external-idSet your reference
POST/v1/prep/inbounds/{id}/receiptsReport goods received
POST/v1/prep/outboundsReport an outbound (create, update, ship, cancel)
GET/v1/prep/outbounds/{id}Get one outbound
GET/v1/prep/itemsList items (one per SKU)
GET/v1/prep/eventsReplay webhook events
GET/v1/prep/connectionsThe key's connection

Every inbound, receipt, outbound and item response carries connection_id.

6.1 List inbounds

GET /v1/prep/inbounds?status=open&updated_since=2026-11-01T00:00:00Z&limit=50&cursor=

QueryMeaning
statusopen, received or cancelled
updated_sinceOnly inbounds changed after this instant, including tracking and FNSKU updates
limit, cursorPagination
JSON
{
  "data": [
    {
      "connection_id": "pcn_812",
      "inbound": {
        "id": "inb_2000000123",
        "external_id": "WMS-IN-5521",
        "batch_id": null,
        "status": "open",
        "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
        "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
        "fulfillment": "FBA",
        "warehouse": {"id": "wh_12", "name": "Warehouse South"},
        "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
        "tracking_numbers": ["1Z999AA10123456784"],
        "lines": [
          {
            "id": "inl_99",
            "purchase_id": "pur_4711",
            "item": {
              "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
              "title": "Bosch GSR 18V-55 Drill Driver",
              "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
            },
            "quantity_expected": 6,
            "quantity_received": 0,
            "quantity_damaged": 0,
            "status": "open"
          }
        ],
        "created_at": "2026-11-03T10:15:00Z"
      },
      "traces": [
        {
          "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
          "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
          "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
        }
      ]
    }
  ],
  "next_cursor": "inb_2000000123"
}

Inbounds are ordered by id. Use the list as a safety net: for example, a nightly sync of all open inbounds.

The list contains inbounds that still have lines in ProfitDesk. An inbound whose lines were all removed is not listed: this happens when the purchase moved to another warehouse (warehouse_changed) or was re-announced as a new inbound (reannounced). Find these through GET /v1/prep/events (their inbound.cancelled event). GET /v1/prep/inbounds/{id} still returns them.

6.2 Get one inbound

GET /v1/prep/inbounds/inb_2000000123 or GET /v1/prep/inbounds/ext:WMS-IN-5521

JSON
{
  "connection_id": "pcn_812",
  "inbound": {
    "id": "inb_2000000123",
    "external_id": "WMS-IN-5521",
    "batch_id": null,
    "status": "open",
    "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
    "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
    "fulfillment": "FBA",
    "warehouse": {"id": "wh_12", "name": "Warehouse South"},
    "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
    "tracking_numbers": ["1Z999AA10123456784"],
    "lines": [
      {
        "id": "inl_99",
        "purchase_id": "pur_4711",
        "item": {
          "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
          "title": "Bosch GSR 18V-55 Drill Driver", "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
        },
        "quantity_expected": 6,
        "quantity_received": 0,
        "quantity_damaged": 0,
        "status": "open"
      }
    ],
    "created_at": "2026-11-03T10:15:00Z"
  },
  "traces": [
    {
      "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
      "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
      "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
    }
  ]
}

For an inbound whose lines were removed (moved or re-announced, see §6.1), the response is the inbound as of its latest event, with status: "cancelled" and every line cancelled. 404 inbound_not_found means the id was never announced to this connection.

6.3 Set your reference

PUT /v1/prep/inbounds/inb_2000000123/external-id

JSON
{ "external_id": "WMS-IN-5521" }
  • The value is unique per connection (409 external_id_conflict otherwise), 1–200 printable characters.
  • Afterwards the inbound is addressable as ext:WMS-IN-5521.
  • A changed value sends inbound.updated with changes: ["external_id"].
  • An idempotency key is optional here; event_id is null in the response without one.
  • A cancelled inbound answers 409 inbound_cancelled.
  • Alternatively, return {"external_id": "…"} in your response to inbound.announced.

Response 200:

JSON
{
  "event_id": null,
  "duplicate": false,
  "connection_id": "pcn_812",
  "inbound": {
    "id": "inb_2000000123",
    "external_id": "WMS-IN-5521",
    "batch_id": null,
    "status": "open",
    "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
    "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
    "fulfillment": "FBA",
    "warehouse": {"id": "wh_12", "name": "Warehouse South"},
    "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
    "tracking_numbers": ["1Z999AA10123456784"],
    "lines": [
      {
        "id": "inl_99",
        "purchase_id": "pur_4711",
        "item": {
          "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
          "title": "Bosch GSR 18V-55 Drill Driver", "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
        },
        "quantity_expected": 6,
        "quantity_received": 0,
        "quantity_damaged": 0,
        "status": "open"
      }
    ],
    "created_at": "2026-11-03T10:15:00Z"
  },
  "traces": [
    {
      "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99", "item_id": "itm_17",
      "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
      "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
    }
  ]
}

6.4 Report goods received

POST /v1/prep/inbounds/{id}/receipts

JSON
{
  "event_id": "rcpt-8812",
  "occurred_at": "2026-11-05T08:01:00Z",
  "status": "receiving",
  "lines": [
    {"line_id": "inl_120", "quantity_received": 5, "quantity_damaged": 1},
    {"sku": "CD-456", "quantity_received": 2}
  ]
}
FieldMeaning
event_idIdempotency key (required, or send the Idempotency-Key header)
occurred_atWhen you counted (default: now)
statusRequired. receiving: count so far, more may follow. A line whose count reaches its expected quantity closes; a later receiving count below it opens the line again. received: the inbound is complete; every line is closed, including lines counted short or not counted.
lines[].line_idinl_…, exact match (preferred)
lines[].skuAlternative to line_id, matched within this inbound. If several lines share the SKU, they are filled in order up to their expected quantity, and any surplus goes to the last line.
lines[].quantity_receivedRequired. The absolute count for the line, not a delta. To correct a count downwards, send the smaller number.
lines[].quantity_damagedOptional, stored and returned. If omitted, the previous value is kept.

lines may be empty only together with status: "received", to close an inbound. At most 1000 lines; each line may be addressed only once per request.

Response 200:

JSON
{
  "event_id": "rcpt-8812",
  "duplicate": false,
  "connection_id": "pcn_812",
  "inbound": {
    "id": "inb_2000000131",
    "external_id": "WMS-IN-5530",
    "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10",
    "status": "open",
    "name": "amazon.de | Bosch GSR 18V-55 Drill Driver +1 | 302-1234567-1234567, 302-7654321-7654321",
    "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
    "fulfillment": "FBA",
    "warehouse": {"id": "wh_12", "name": "Warehouse South"},
    "order": {
      "numbers": ["302-1234567-1234567", "302-7654321-7654321"], "supplier_host": "amazon.de",
      "purchased_at": "2026-11-02"
    },
    "tracking_numbers": ["1Z999AA10123456784"],
    "lines": [
      {
        "id": "inl_120",
        "purchase_id": "pur_4720",
        "item": {
          "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
          "title": "Bosch GSR 18V-55 Drill Driver", "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
        },
        "quantity_expected": 6,
        "quantity_received": 5,
        "quantity_damaged": 1,
        "status": "open"
      },
      {
        "id": "inl_121",
        "purchase_id": "pur_4721",
        "item": {
          "id": "itm_18", "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "ean": "4059952513164",
          "title": "Bosch GBA 18V 4.0Ah Battery", "image_url": "https://m.media-amazon.com/images/I/51Zk2mN8pRL.jpg"
        },
        "quantity_expected": 2,
        "quantity_received": 2,
        "quantity_damaged": 0,
        "status": "received"
      }
    ],
    "created_at": "2026-11-03T10:15:00Z"
  },
  "traces": [
    {
      "purchase_id": "pur_4720", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_120", "item_id": "itm_17",
      "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "order_number": "302-1234567-1234567",
      "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": [], "amazon_shipment_ids": []
    },
    {
      "purchase_id": "pur_4721", "inbound_id": "inb_2000000131", "inbound_line_id": "inl_121", "item_id": "itm_18",
      "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "order_number": "302-7654321-7654321",
      "batch_id": "0d1f6a2e-4b7c-4c11-9a3e-5f2b8c7d9e10", "outbound_ids": [], "amazon_shipment_ids": []
    }
  ],
  "lines": [{"line_id": "inl_120", "result": "applied"}, {"line_id": "inl_121", "result": "applied"}]
}

lines[].result is one of:

  • applied: changed
  • unchanged: same values as before
  • stale: a receipt with the same or a later occurred_at had already been applied to the line

A receipt that changed anything sends inbound.received.

Only announced goods are accepted:

SituationResponse
Inbound never announced to this connection404 inbound_not_found
line_id or SKU not part of this inbound422 line_not_found, details lists index, line_id and sku of each unknown line
Inbound (or the addressed line) is cancelled, including an inbound whose lines were removed because the purchase moved or was re-announced409 inbound_cancelled

If goods arrive that were never announced, ask the seller to announce them in ProfitDesk. Do not book them on a different inbound.

6.5 Report an outbound

POST /v1/prep/outbounds

JSON
{
  "event_id": "out-311-v3",
  "external_id": "OUT-311",
  "occurred_at": "2026-11-06T14:00:00Z",
  "status": "shipped",
  "destination": {"type": "fba", "amazon_shipment_id": "FBA15XYZ", "marketplace": "DE"},
  "tracking_numbers": ["1Z999AA10123456784"],
  "lines": [{"line_id": "inl_120", "quantity": 4}, {"sku": "CD-456", "quantity": 2}]
}
FieldMeaning
event_idIdempotency key (required, or send the Idempotency-Key header)
external_idYour outbound id, 1–200 printable characters. The request is an upsert on this value: send the same outbound again whenever it changes.
occurred_atWhen it happened (default: now)
statuscreated, shipped or cancelled (cancelled clears the lines)
destination.typeRequired: fba, fbm or other. Missing or empty is refused with 400 invalid_request.
destination.amazon_shipment_idThe Amazon FBA shipment id (FBA…), max 64 characters. Please always send it for FBA: it links your outbound to Amazon's shipment.
destination.marketplacee.g. DE, max 8 characters
tracking_numbersUp to 50 codes, 1–200 characters each
linesThe complete list of lines, at most 1000. It replaces what was stored before.

Each line names its units with one of these references, tried in this order:

ReferenceMatching
line_id (inl_…)Exact. Recommended.
purchase_id (pur_…)Exact. The purchase must belong to an inbound of this connection.
item_id (itm_…) or skuFirst received, first out, across the connection's purchases of that item. Capped at what was received minus what other open or shipped outbounds already hold.

Response 200:

JSON
{
  "event_id": "out-311-v3",
  "duplicate": false,
  "stale": false,
  "connection_id": "pcn_812",
  "outbound": {
    "id": "out_55",
    "external_id": "OUT-311",
    "status": "shipped",
    "destination": {"type": "fba", "amazon_shipment_id": "FBA15XYZ", "marketplace": "DE"},
    "tracking_numbers": ["1Z999AA10123456784"],
    "shipped_at": "2026-11-06T14:00:00Z",
    "lines": [
      {"purchase_id": "pur_4720", "inbound_line_id": "inl_120", "quantity": 4, "resolved_by": "line_id"},
      {"purchase_id": "pur_4721", "inbound_line_id": "inl_121", "quantity": 2, "resolved_by": "sku_fifo"}
    ]
  },
  "unresolved": []
}
  • Quantities that cannot be matched are listed in unresolved, each with index (its position in lines), the reference you sent (the others null), the part of quantity that could not be matched, and a reason: line_not_found, purchase_not_found, item_not_found, sku_not_found, insufficient_received_quantity or invalid_reference.
  • The request is refused with 422 line_not_found only if nothing matches; details then holds the unresolved entries.
  • stale: true means a newer version of this outbound had already been applied, and nothing changed.
  • Every request that is not stale sends outbound.recorded.

A partly matched outbound (4 units of AB-123 requested, 2 still available):

JSON
{
  "event_id": "out-312-v1",
  "duplicate": false,
  "stale": false,
  "connection_id": "pcn_812",
  "outbound": {
    "id": "out_56",
    "external_id": "OUT-312",
    "status": "created",
    "destination": {"type": "fba", "amazon_shipment_id": "FBA15XZA", "marketplace": "DE"},
    "tracking_numbers": [],
    "shipped_at": null,
    "lines": [{"purchase_id": "pur_4720", "inbound_line_id": "inl_120", "quantity": 2, "resolved_by": "sku_fifo"}]
  },
  "unresolved": [
    {
      "index": 0, "line_id": null, "purchase_id": null, "item_id": null, "sku": "AB-123", "quantity": 2,
      "reason": "insufficient_received_quantity"
    }
  ]
}

6.6 Get one outbound

GET /v1/prep/outbounds/out_55 or GET /v1/prep/outbounds/ext:OUT-311

JSON
{
  "connection_id": "pcn_812",
  "outbound": {
    "id": "out_55",
    "external_id": "OUT-311",
    "status": "shipped",
    "destination": {"type": "fba", "amazon_shipment_id": "FBA15XYZ", "marketplace": "DE"},
    "tracking_numbers": ["1Z999AA10123456784"],
    "shipped_at": "2026-11-06T14:00:00Z",
    "lines": [
      {"purchase_id": "pur_4720", "inbound_line_id": "inl_120", "quantity": 4, "resolved_by": "line_id"},
      {"purchase_id": "pur_4721", "inbound_line_id": "inl_121", "quantity": 2, "resolved_by": "sku_fifo"}
    ]
  }
}

6.7 Replay events

GET /v1/prep/events?since=evt_0193a1b2-…&types=inbound.announced,inbound.cancelled&limit=100

Returns the same envelopes that are sent to your URL, in feed order (see below). This includes every event type, whether or not the connection is subscribed to it, and events whose delivery failed or was skipped. Events are kept for 90 days.

QueryMeaning
sinceAn event id (exclusive, recommended) or an RFC 3339 instant. An unknown event id answers 400 invalid_request.
typesComma-separated event types (not ping, which is never stored)
limit1–100, default 50
JSON
{
  "data": [
    {
      "id": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a01",
      "type": "inbound.announced",
      "api_version": "2026-11-01",
      "occurred_at": "2026-11-03T10:15:00Z",
      "created_at": "2026-11-03T10:15:01Z",
      "connection_id": "pcn_812",
      "data": {
        "inbound": {
          "id": "inb_2000000123",
          "external_id": null,
          "batch_id": null,
          "status": "open",
          "name": "amazon.de | Bosch GSR 18V-55 Drill Driver | 302-1234567-1234567",
          "notes": "Fragile, please bubble-wrap\nFulfillment: FBA",
          "fulfillment": "FBA",
          "warehouse": {"id": "wh_12", "name": "Warehouse South"},
          "order": {"numbers": ["302-1234567-1234567"], "supplier_host": "amazon.de", "purchased_at": "2026-11-02"},
          "tracking_numbers": ["1Z999AA10123456784"],
          "lines": [
            {
              "id": "inl_99",
              "purchase_id": "pur_4711",
              "item": {
                "id": "itm_17", "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH", "ean": "4006381333931",
                "title": "Bosch GSR 18V-55 Drill Driver",
                "image_url": "https://m.media-amazon.com/images/I/61Xf3kqcQhL.jpg"
              },
              "quantity_expected": 6,
              "quantity_received": 0,
              "quantity_damaged": 0,
              "status": "open"
            }
          ],
          "created_at": "2026-11-03T10:15:00Z"
        },
        "traces": [
          {
            "purchase_id": "pur_4711", "inbound_id": "inb_2000000123", "inbound_line_id": "inl_99",
            "item_id": "itm_17", "sku": "AB-123", "fnsku": null, "asin": "B0ABCDEFGH",
            "order_number": "302-1234567-1234567", "batch_id": null, "outbound_ids": [], "amazon_shipment_ids": []
          }
        ],
        "changes": null,
        "reason": null,
        "replaced_by_inbound_id": null
      }
    }
  ],
  "next_cursor": "evt_0193a1b2-7c4d-7e8f-9a0b-1c2d3e4f5a01",
  "has_more": true
}

Feed order. Events are ordered by the database transaction that created them, not strictly by created_at, and an event only shows up once every transaction that started before it has finished (usually within a second; at most ten minutes). A cursor therefore never skips an event: nothing is ever inserted behind a next_cursor you already have.

Resume with the evt_… id: pass the last next_cursor (or the id of the last event you processed) as since. That position is exact. has_more tells you whether to fetch again right away.

An instant is a rough starting point. It is compared with the time the event was stored, which can differ from the envelope's created_at by a few milliseconds. To make sure the event at that instant is included, the window starts one second early. Results for an instant can therefore repeat events you already have. Always deduplicate by id.

You can use this instead of webhooks, as a pull-only integration: poll every minute with since set to the last next_cursor you processed, and deduplicate by id.

6.8 List items

GET /v1/prep/items?sku=AB-123&limit=50&cursor=

JSON
{
  "data": [
    {
      "id": "itm_17", "sku": "AB-123", "fnsku": "X00ABCDEFG", "asin": "B0ABCDEFGH", "ean": "4006381333931",
      "title": "Bosch GSR 18V-55 Drill Driver", "image_url": null, "connection_id": "pcn_812",
      "created_at": "2026-11-03T10:15:00Z", "updated_at": "2026-11-03T10:15:00Z"
    },
    {
      "id": "itm_18", "sku": "CD-456", "fnsku": "X00BCDEFGH", "asin": "B0BCDEFGHI", "ean": "4059952513164",
      "title": "Bosch GBA 18V 4.0Ah Battery", "image_url": null, "connection_id": "pcn_812",
      "created_at": "2026-11-03T10:15:00Z", "updated_at": "2026-11-04T07:30:00Z"
    }
  ],
  "next_cursor": null
}

sku filters by exact SKU. image_url is not filled in this list; the inbound payloads carry it.

6.9 The key's connection

GET /v1/prep/connections returns the key's own connection, in the list shape the endpoint shares with prep providers (prep-providers-v1.md):

JSON
{
  "data": [
    {
      "id": "pcn_812",
      "status": "active",
      "seller": {"name": "Sample Trading", "customer_reference": null, "seller_id": "A1B2C3D4E5F6G7"},
      "created_at": "2026-11-01T09:00:00Z"
    }
  ],
  "next_cursor": null
}

seller.name is the seller's workspace name (null if none is set), seller.seller_id the Amazon selling partner id of the account owner (null without an Amazon connection), and seller.customer_reference is always null here.


7. Errors

Every non-2xx response has this body:

JSON
{
  "error": {
    "code": "line_not_found",
    "message": "lines not announced in this inbound",
    "details": [{"index": 1, "line_id": "", "sku": "EF-789"}]
  }
}
HTTPcodeMeaning
400invalid_requestInvalid input; the message names the field
400invalid_jsonEmpty body or malformed JSON
400idempotency_key_requiredReceipt or outbound sent without event_id / Idempotency-Key
401unauthorizedMissing, unknown or revoked API key
404inbound_not_found, outbound_not_foundNo such object on this connection (for inbounds: never announced to it)
409inbound_cancelledThe inbound or line is cancelled, or its lines were removed because the purchase moved or was re-announced
409idempotency_conflictThe key was already used for a different request
409external_id_conflictAnother inbound already uses this external_id
413payload_too_largeBody over 1 MB
422line_not_foundThe line, SKU or reference is not part of the inbound, or nothing matched
429rate_limitedSlow down. Retry after Retry-After seconds.
500internal_errorRetry with the same idempotency key

details is an array, empty unless the code fills it: line_not_found (receipts: index, line_id, sku of each unknown line; outbounds: the unresolved entries) and inbound_cancelled for one line (line_id, sku).


8. Integration checklist

  1. Receive webhooks:
    • an HTTPS endpoint that verifies the signature (§5.2)
    • answers 2xx fast
    • deduplicates by X-Webhook-Event
  2. On inbound.announced, create an expected inbound in your software, keyed by inbound.id, with one expected line per lines[].id. Return your id as {"external_id": "…"} if you have one. Treat an announcement of an inbound you already know as an update.
  3. On inbound.updated, refresh tracking numbers and FNSKUs (label printing needs the FNSKU). With changes: ["lines"], refresh each line's status.
  4. On inbound.cancelled, stop expecting the goods. If replaced_by_inbound_id is set, the goods now belong to that inbound.
  5. When goods arrive, POST …/receipts with absolute counts per line_id. Send status: "received" when the inbound is complete.
  6. When you ship, POST /v1/prep/outbounds with your external_id, the lines by line_id, and for FBA the amazon_shipment_id. Send it again when it changes (created → shipped, or cancelled).
  7. Safety net: poll GET /v1/prep/events or GET /v1/prep/inbounds?status=open once in a while.
  8. Test with "Send test" in ProfitDesk, then with a real purchase.

9. FAQ

Why is the FNSKU sometimes empty? Goods are announced as soon as the seller's Amazon listing is created. Amazon assigns the FNSKU a little later, and ProfitDesk then sends inbound.updated with changes: ["fnsku"].

What happens if my endpoint is down? ProfitDesk retries for up to 72 hours (§5.1), and the seller sees the announcement as "Delivering". After that the announcement is marked failed, and the seller can send it again from the delivery log. You can also always catch up with GET /v1/prep/events.

Can I correct a receipt? Yes. Send the corrected absolute count with a newer occurred_at and a new idempotency key.

Several purchases in one parcel? They arrive as one inbound with one line per purchase and share a batch_id. Count per line. If you only know the SKU, send sku: lines with the same SKU are filled in order.

Do outbounds change the seller's stock? No. Outbounds are a trace. "Forwarded" quantities for FBA come from Amazon's own shipment data. Send amazon_shipment_id so the two can be linked.

Is there a sandbox? Use a test warehouse and a test purchase in the seller's own account, and "Send test" for connectivity. Events can always be replayed via GET /v1/prep/events.

Versioning. The envelope carries api_version (currently 2026-11-01). New optional fields and new event types can appear at any time, so ignore what you do not know. Breaking changes come with a new api_version.