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": [ ... ] }'

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": [ ... ] }'
{
"_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.

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:
status | Meaning |
|---|---|
created | The response exists; packing has not started yet. |
awaiting_bins | An intermediate in-progress state. |
busy | Packing is in progress. |
finished | Done. The full result (packed orders and bins, costs, fill rate, execution_time) is populated. |
error | Packing failed. Inspect errors and messages for the cause. |
deleted | The 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>'
{
"_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).
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_idand 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. Theerrorsarray andmessages(each with acategoryandseverity) explain why.404 Request not found. The_iddoesn't exist (wrong id, or the response wasdeleted).422. The request failed validation. This is returned on thePOSTitself, 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.