Skip to main content

Communicating with the Pulse Pack API

Every packing request is sent the same way: a POST to /pack or /pack_pallet with your orders and bins in the body and your key in the x-api-key header. What differs is how you get the result back. Pulse Pack supports two modes:

  • Synchronous (the default): the packed result comes back in the same HTTP response.
  • Asynchronous: the request is queued, computed in the background, and you collect the result later, by polling or via a callback.

If you have used our Flux or Pulse Pick APIs, note that Pulse Pack's asynchronous mode is deliberately more barebones: there is no svix, no webhook subscriptions, and no signing secrets to configure. You flip one query parameter and then either poll an endpoint or hand us a URL to post the answer to.

Synchronous communication (the default)

synchronous defaults to true, so a plain request blocks until packing is done and returns the full response body. This is the simplest thing to integrate and is the right choice for interactive, single-order requests.

curl -X POST 'https://optiapp.api.optioryx.com/pack' \
-H 'x-api-key: <API_KEY>' \
-d '{ "orders": [ ... ], "bins": [ ... ] }'

Synchronous

The client posts a request and blocks until Optioryx returns the packed response. Read /optimize in the diagram as /pack (or /pack_pallet) for Pulse Pack.

The catch is time. A synchronous request stays open for at most two minutes. If packing runs longer than that, the request fails with a timeout error and returns no result. Cartonising large multi-order batches, box-on-demand right-sizing, or carrier-cost optimisation over a big preset catalogue can all push past that limit, so for heavy requests, switch to asynchronous.

Asynchronous communication

Set ?synchronous=false on the POST. Instead of blocking, the API accepts the request, starts packing in the background, and immediately returns 202 Accepted with a small JSON body. The only field you need from it is the top-level _id, the handle for this response. A background job may run for up to fifteen minutes, so this mode suits heavy requests that would exceed the two-minute synchronous limit.

curl -X POST 'https://optiapp.api.optioryx.com/pack?synchronous=false' \
-H 'x-api-key: <API_KEY>' \
-d '{ "orders": [ ... ], "bins": [ ... ] }'
202 Accepted
{
"_id": "665f1e2a9c8c3e4dc7625abc",
"status": "created"
}

From here you have two ways to collect the finished result: poll for it, or let us call you back. The sequence diagram below shows both: polling on the left, a callback on the right.

Asynchronous

Illustrative. The diagram uses OptiRoute's /optimize and /route/{REQUEST_ID} endpoints. For Pulse Pack, read POST /optimize as POST /pack?synchronous=false, the returned "Request ID" as the response _id, GET /route/{REQUEST_ID} as GET /responses/{id}, and status = OPTIMISING / FINISHED as Pulse Pack's busy / finished. On the callback (right) side, Pulse Pack posts the full response body to your URL, so no follow-up GET is required.

The response lifecycle

An asynchronous response moves through a set of status values. Only two of them are outcomes you wait for:

statusMeaning
createdThe response exists; packing has not started yet.
awaiting_binsAn intermediate in-progress state.
busyPacking is in progress.
finishedDone. The full result (packed orders and bins, costs, fill rate, execution_time) is populated.
errorPacking failed. Inspect errors and messages for the cause.
deletedThe response has been deleted and is no longer retrievable.

created, awaiting_bins, and busy all mean "not ready yet". finished and error are the two terminal states.

Option A: Polling

Take the _id from the 202 response and GET /responses/{id} until status is finished (or error). While the request is still running you get the same document back with an in-progress status and no packed result yet.

curl -X GET 'https://optiapp.api.optioryx.com/responses/665f1e2a9c8c3e4dc7625abc' \
-H 'x-api-key: <API_KEY>'
GET /responses/{id} (finished)
{
"_id": "665f1e2a9c8c3e4dc7625abc",
"status": "finished",
"orders": [ ... ],
"execution_time": 4.12,
"errors": [],
"messages": []
}

Poll on a sensible interval rather than in a tight loop. Start around one request every second or two and back off for longer jobs. A GET for an unknown _id returns 404. See the Read Response reference for the full response schema.

Option B: Callback URL

If you would rather not poll, pass a callback_url on the POST. When packing finishes, we send a POST to that URL with the complete response body, exactly what GET /responses/{id} would have returned. You still get the 202 with the _id up front, so you can correlate the callback with the request that triggered it.

curl -X POST 'https://optiapp.api.optioryx.com/pack?synchronous=false&callback_url=https%3A%2F%2Fyour-app.example.com%2Foptipack%2Fcallback' \
-H 'x-api-key: <API_KEY>' \
-d '{ "orders": [ ... ], "bins": [ ... ] }'

Your endpoint must be publicly reachable over HTTPS and should acknowledge quickly with a 2xx. Because the URL is itself a query parameter, remember to URL-encode it when embedding it in the request URL (as above).

warning

Unlike the svix-backed webhooks in Flux and Pulse Pick, this callback is a single direct POST. It does not come with svix's signed payloads, delivery dashboard, or managed retries. Two practical consequences:

  • Authenticate it yourself. Put an unguessable secret token in the callback_url (e.g. .../callback?token=...) and reject any call that doesn't carry it.
  • Keep polling as a safety net. If a callback is ever missed, the result is still durably available at GET /responses/{id}. Persist the _id and treat the callback as an optimisation, not the only path to the answer.

Polling or callback?

  • Poll when your integration can't accept inbound HTTP (behind a firewall, a batch job, a desktop client) or when you want the simplest possible control flow.
  • Use a callback when you have a reachable HTTPS endpoint and want to avoid the latency and request overhead of polling. You are told the moment packing finishes.

You can also combine them: register a callback_url for the fast path, and fall back to polling GET /responses/{id} if the callback doesn't arrive within your expected window.

Reusing the response _id

The _id is durable and is the same handle used elsewhere in the API. Once a response is finished you can fetch it again at any time with GET /responses/{id}, and you can feed the same _id into the visualisation endpoints to render a packed bin (see the getting-started guide). Each packed bin in the response also carries a ready-made interactive_viz_url.

When something goes wrong

  • status: "error". Packing ran but failed. The errors array and messages (each with a category and severity) explain why.
  • 404 Request not found. The _id doesn't exist (wrong id, or the response was deleted).
  • 422. The request failed validation. This is returned on the POST itself, synchronous or not, so a malformed request never reaches the asynchronous path.
  • A request that needs more than fifteen minutes of calculation will not return a finished result. Split it into smaller requests, for example fewer orders per call.