Skip to main content
This guide covers what the API reference doesn’t: how to structure your provider offerings for Nexio and interpret the results. For request/response schemas, see Submit Run and Get Run Status. For retry-safe submission, send an optional Idempotency-Key header. The same key and request return the original run without another enqueue. A changed request returns 409 idempotency_key_reused. The key is scoped to your organization, environment, and API key, and remains reserved as long as the run row exists.

Flow

Persist run_id. Polling remains the canonical reconciliation path even when using webhooks.

What you send

input is the subject’s profile and run context. offerings is one entry per provider per requirement category.
input.current_coverage describes existing state (e.g., incumbent products or services). It provides context for scoring but does not replace the offerings array.

Building offerings from your data

Each provider entry becomes one offering per requirement category. If you have 3 providers each covering 3 categories, that’s 9 offerings (3 providers x 3 categories). Keep offerings[*].category aligned with input.coverage_types. Extra offerings for categories not in coverage_types are ignored.

Multi-provider input

When comparing packages from multiple providers, submit offerings from each. The engine evaluates all valid combinations: both single-provider bundles and mixed-provider packages:
Example: Insurance: Personal Lines. The input and offerings schema depends on your engine’s configuration. This example shows a personal-lines insurance placement engine.
Results may include an all-Carrier A bundle, an all-Carrier B bundle, and mixed-provider combinations (e.g. Carrier A home + Carrier C auto): all ranked by composite scorecard.

Polling

Poll GET /api/v1/runs/{run_id} until terminal. Start at 2s delay, backoff 1.5x, cap at 30s. The terminal set is exactly completed, degraded, and failed. A degraded run is terminal. Never keep polling it.

Showing a result before the run finishes

A placement run finishes all of its deterministic scoring before it calls the model, and it persists that answer at the boundary. From that moment the response carries completed_deterministic_at plus output and solutions, marked output_phase: deterministic_draft, while status is still processing. Terminal responses carry output_phase: final. A draft is a real answer over part of the decision, not a preview. Its ranks, cluster labels, and overall_level values are real, but they are computed over the deterministic scoring dimensions only, with the weights renormalized over them. The model-scored dimensions are absent from a draft scorecard rather than empty. They carry real weight, so the final answer can reorder the shortlist. The polling loop above does not change: it still exits only on completed, degraded, or failed. Render the draft from inside the loop instead.
Never treat a draft as the final answer, and never stop polling on one. Label anything rendered from a draft as provisional, and replace it when the response arrives with output_phase: final. A failed run carries no output and no solutions, and therefore no output_phase, even when it reached the boundary before it failed. Webhook payloads carry neither new field: every webhook event type is terminal, so polling is the only surface that serves a draft. Curl and TypeScript versions of this loop are on the Get Run Status page.

Reading results

Key fields on a completed response: There is no solutions[*].carrier_name: use offerings[*].provider_name. There is no solutions[*].score: use scorecard.overall_level. Treat solutions[*].rank as the ordering contract.

Failure cases

completed with 0 solutions: output.diagnostic explains why. Common causes: coverage_types / offerings[*].category mismatch, missing required categories, or all offerings filtered out. This is a valid terminal state, not an error. failed: read error and error_details. Terminal. degraded: read the output, warnings when present, and any documented engine-specific diagnostics. Terminal. Stuck in processing: the EVALUATE stage can take time. Use backoff, don’t tight-loop.

Integration checklist

  1. offerings generated per category per provider, not per package
  2. offerings[*].category aligns with input.coverage_types
  3. Polling treats completed, degraded, and failed as terminal
  4. A rendered deterministic_draft is labeled provisional and never ends the poll
  5. Package score read from scorecard.overall_level
  6. Provider identity read from offerings[*].provider_name
  7. Both request and terminal response persisted

Reconciliation doctrine

Persist the run_id returned by submission. It is stable for the life of the run and is the identifier used by polling, webhook payloads, delivery history, and support. Webhooks are notifications, not the only source of truth. After a missed, duplicate, delayed, or out-of-order callback, reconcile by polling GET /api/v1/runs/{run_id} with that same ID.

Example artifacts

  • Canonical request: complete POST /api/v1/engines/default/runs body with input and offerings (insurance: personal lines)
  • Canonical response: complete GET /api/v1/runs/{run_id} response for a completed run (insurance: personal lines)