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 with 200 OK. This is the simplest thing to integrate and is the right choice for interactive, single-order requests. (The asynchronous path instead returns 202 Accepted, since its result is not ready yet.)

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.