← Back to product·Simulmedia Switchboard Docs · v1 draft·API reference / Orders
API reference

Orders

Create, track, and decide orders. Buyers write through /orders; sellers decide through /me/orders. The order shape is defined once, at order; the lifecycle at the state machine.

At a glance
  • Scopes: orders:write (buyers), orders:approve (sellers)
  • Idempotency: external_order_id; byte-identical resubmission replays the stored response, 409 only on a changed payload
  • Price locks at order time via locked_rate_id; see the price-clearance invariant
  • Track by webhook or event replay; the snapshot is always authoritative

Create an order

POST/v1/ordersorders:write

Creates an order from line items, a proposal request, or a document reference. Returns immediately; everything after intake arrives as events.

Request body

FieldTypeDescription
seller requiredstringThe counterparty, from GET /sellers.
deal_type requiredenumOne of the deal types. proposal carries a request instead of line items.
external_order_id requiredstringYour idempotency key, echoed on every event. Identical duplicates replay the original response with X-Idempotent-Replay: true; that replay is how you learn the outcome of a request that timed out on you.
rate_version requiredstringThe card commit you priced against. Version-current cards pass rate match by construction.
workflowenumOne of the workflows. Default: catalog.
line_itemsarray of objectCatalog orders. One line books one week or one slot; a two-week buy is two lines. Fields below. Omit when deal_type is proposal or workflow is document.
requestobjectProposal orders: a proposal_request. No package IDs required.
documentobjectDocument orders: {upload_id, format}. The parse resumes at needs_confirmation.
settlement_modeenumOne of the settlement modes. Default: direct.
notify_urlstringWebhook target for this order; must be registered and allowlisted.
buyer_metadataobjectNamed keys: advertiser, advertiser_class, campaign, campaign_id, brand, agency, estimate_id, notes. estimate_id is the agency estimate number, echoed on EDI delivery so your billing system can match the record.

Send exactly one of line_items, request, or document, matched to deal_type and workflow.

line_items[]

FieldTypeDescription
package_id requiredstringFrom the seller's catalog.
provider, network requiredstringBookable entity and channel, as the catalog lists them.
weekISO weekBooks the package week; the seller distributes units across the week's slots and reports the dates in allocation. Set week or slot_id, not both.
slot_idstringPins the air date: acme-prime:2026-10-07, daylocked by construction. See slot.
unitsintegerFor spot currency. Set units or impressions_goal, not both.
impressions_goalintegerFor impression currency.
spot_length requiredintegerSeconds; a length the cited rate entry prices.
rate requiredobjectThe priced cell you are locking: {class, advertiser_class, guarantee_tier, clearance_tier, unit_cost, locked_rate_id, currency}. Take locked_rate_id from avails; it locks the price at order time.
include_mirrorbooleanBooks the package's paired mirror airing. Default: false.
rc_impressionsobjectImpression estimates at order time, per audience code, for buysheet parity.
copy_idstringCreative reference; Ad-ID and ISCI ride here.

Example request

{
  "seller": "acme",
  "deal_type": "preemptible",
  "workflow": "catalog",
  "external_order_id": "q4-campaign-042",
  "rate_version": "acme-2026q4-v1",
  "line_items": [
    {
      "package_id": "acme-prime",
      "provider": "National ACME",
      "network": "ACME",
      "week": "2026-W40",
      "units": 10,
      "spot_length": 30,
      "rate": {
        "class": "scatter",
        "advertiser_class": "general",
        "guarantee_tier": "ng_preemptible",
        "clearance_tier": "P2",
        "unit_cost": 405.00,
        "locked_rate_id": "lr_88c1",
        "currency": "USD"
      },
      "rc_impressions": {
        "hh": 128000,
        "p2plus": 195000,
        "a25_54": 82000
      },
      "copy_id": "creative-q4-30s"
    }
  ],
  "buyer_metadata": {
    "advertiser": "YourClient",
    "estimate_id": "EST-4402"
  }
}

Response

201 Response
{
  "order_id": "ord_9f3a12c4b7e1",
  "external_order_id": "q4-campaign-042",
  "status": "submitted",
  "created_at": "2026-10-01T14:02:11Z"
}

Errors

StatusCodeWhen
409CONFLICTSame external_order_id, different payload. Identical duplicates replay instead.
422INVALID_INPUTMalformed input, unknown package_id, or a non-allowlisted notify_url.
422CREDIT_LIMITA cleared order that would breach your credit cap fails fast at create.
asyncSTALE_RATE_CARDSurfaces as a failed event with a rate_drift diff per line. Refresh the card and resubmit; see the price-clearance invariant.
asyncINVENTORY_UNAVAILABLEAvailability rejection, the one legitimate validation failure. Carries details.pool_id when a shared pool is exhausted.

Error shape and recovery: Errors.

Read an order

GET/v1/orders/{id}orders:write

The authoritative snapshot, with a real last_seq. Webhooks are hints; this is truth. Full field reference: order.

Response

200 Response
{
  "order_id": "ord_9f3a12c4b7e1",
  "external_order_id": "q4-campaign-042",
  "seller": "acme",
  "deal_type": "preemptible",
  "status": "placed",
  "substatus": null,
  "last_seq": 5,
  "rate_version": "acme-2026q4-v1",
  "settlement_mode": "direct",
  "created_at": "2026-10-01T14:02:11Z",
  "updated_at": "2026-10-02T14:41:09Z",
  "line_items": [
    {
      "unit_id": "u-0001",
      "package_id": "acme-prime",
      "provider": "National ACME",
      "network": "ACME",
      "week": "2026-W40",
      "allocation": [
        {
          "date": "2026-10-05",
          "units": 4
        },
        {
          "date": "2026-10-07",
          "units": 3
        },
        {
          "date": "2026-10-09",
          "units": 3
        }
      ],
      "spot_length": 30,
      "num_requested": 10,
      "num_accepted": 10,
      "status": "accepted",
      "reason": null,
      "rate": {
        "class": "scatter",
        "unit_cost": 405.00,
        "locked_rate_id": "lr_88c1",
        "currency": "USD"
      },
      "rc_impressions": {
        "hh": 128000,
        "p2plus": 195000,
        "a25_54": 82000
      },
      "availability": {
        "state": "available",
        "basis": "actual",
        "sellout_level": 0.62
      }
    }
  ],
  "totals": {
    "gross": 4050.00,
    "currency": "USD",
    "spots": 10,
    "impressions_hh": 1280000,
    "impressions_p2plus": 1950000,
    "impressions_a25_54": 820000
  },
  "artifacts": [
    {
      "name": "placement_receipt",
      "seq": 5,
      "seller_order_id": "ACME-77120",
      "daylocks": [
        {
          "unit_id": "u-0001",
          "date": "2026-10-05",
          "units": 4
        },
        {
          "unit_id": "u-0001",
          "date": "2026-10-07",
          "units": 3
        },
        {
          "unit_id": "u-0001",
          "date": "2026-10-09",
          "units": 3
        }
      ],
      "placed_at": "2026-10-02T14:41:09Z"
    }
  ],
  "events_url": "/v1/events?order=ord_9f3a12c4b7e1"
}

The allocation on each line is authoritative: a week line's units spread across the package's weekday pattern, one entry per air date, summing to num_accepted. A slot line has a single entry. See day resolution.

Confirm an order at needs_confirmation

POST/v1/orders/{id}/confirmorders:write

Resumes an order parked at needs_confirmation, the gate where you confirm an artifact before it books. Two workflows park here: a document order (confirm the parsed interpretation) and a proposal (confirm the seller's returned plan_proposal). Catalog orders never park here. On confirm, a document order proceeds to seller_review and a proposal books its plan through the normal order path (one order per pay-basis type). On reject, the order goes to canceled. To counter a plan instead of confirming it, use respond. A satisfied buyer acceptance band issues this confirm automatically.

Request body

FieldTypeDescription
action requiredenumconfirm or reject.
actor requiredstringWho decided; persisted in the audit trail. For an auto-accepted plan, the recorded actor is the acceptance band.
plan_idstringOn a proposal confirm: the plan being accepted, so a plan superseded by a fresh one cannot be confirmed by mistake.
reasonstringRequired on reject; persisted and surfaced to the seller.

Response

202 Response : document order
{
  "order_id": "ord_9f3a12c4b7e1",
  "status": "confirmed"
}

202 Response : proposal, plan booked
{
  "order_id": "ord_c19a44e2f0b1",
  "status": "seller_review",
  "booked_orders": ["ord_7a10b3c2d4e5"]
}

Respond in a negotiation

POST/v1/orders/{id}/respondorders:write

Answers a seller counter on an order parked in negotiating. Rounds live on the negotiation object. See Negotiation.

Request body

FieldTypeDescription
action requiredenumaccept, counter, or withdraw.
actor requiredstringWho decided; persisted.
counterobjectRequired on counter: revised line_items (unit_id, week, units, rate) and an expires_at.

Example request

{
  "action": "counter",
  "actor": "buyer@yourco.com",
  "counter": {
    "line_items": [
      {
        "unit_id": "u-0001",
        "week": "2026-W41",
        "units": 8,
        "rate": {
          "unit_cost": 392.00
        }
      }
    ],
    "expires_at": "2026-09-30T20:00:00Z"
  }
}

Response

202 Response
{
  "order_id": "ord_9f3a12c4b7e1",
  "status": "negotiating",
  "negotiation_id": "neg_33a7",
  "round": 2
}

Cancel an order

POST/v1/orders/{id}/cancelorders:write

Idempotent. Canceling a terminal order returns the terminal snapshot unchanged, so a cancel is always safe to retry.

Request body

FieldTypeDescription
reason requiredstringPersisted; canceled snapshots keep their structured reason.
actor requiredstringWho decided.

Response

202 Response : order was non-terminal
{
  "order_id": "ord_9f3a12c4b7e1",
  "status": "canceled"
}

202 Response : order was already terminal
{
  "order_id": "ord_9f3a12c4b7e1",
  "status": "completed",
  "note": "order already in terminal state; no change made"
}

List orders

GET/v1/ordersorders:write

Your order book: the reconciliation sweep. Page with the cursor until exhausted.

Query parameters

FieldTypeDescription
statusenumOne or more order statuses; repeatable or comma-separated.
sellerstringFilter to one counterparty.
deal_typeenumOne of the deal types.
created_afterISO datetimeLower bound on creation time.
afterstringOpaque cursor from the prior response.

Response

200 Response
{
  "orders": [
    {
      "order_id": "ord_9f3a12c4b7e1",
      "status": "placed",
      "seller": "acme",
      "external_order_id": "q4-campaign-042"
    }
  ],
  "cursor": "eyJpZCI6Im...",
  "total": 142
}

cursor is null when exhausted; pass it as ?after= on the next call.

Approval queue

GET/v1/me/ordersorders:approve

The seller's order book. With the default filter it is your approval queue: everything waiting at seller_review, your gate on every order. Also available in the console. See Approve orders.

Query parameters

FieldTypeDescription
statusenumAny order status. Default: seller_review.
afterstringOpaque cursor from the prior response.

Response

200 Response
{
  "orders": [
    {
      "order_id": "ord_9f3a12c4b7e1",
      "status": "seller_review",
      "buyer": "yourco",
      "deal_type": "preemptible",
      "totals": {
        "gross": 4050.00,
        "currency": "USD"
      },
      "received_at": "2026-10-01T14:02:14Z"
    }
  ],
  "cursor": null,
  "total": 1
}

Read an order (seller)

GET/v1/me/orders/{id}orders:approve

The seller's view of one order: the same authoritative snapshot the buyer sees, plus the buyer identity and buyer_metadata you decide against. Shape: order.

Return a plan for a proposal

POST/v1/me/orders/{id}/planorders:approve

Answer a proposal order: attach the assembled plan_proposal and park the order at needs_confirmation for the buyer. Precondition: the order is a proposal (deal_type: "proposal") in submitted, validating, or seller_review; a non-proposal order or a terminal one is rejected. Mint per-campaign prices as quote commits first, then reference their locked_rate_ids here; where you cannot meet the whole ask, list it in unmet rather than dropping it silently. Posting a new plan supersedes any prior one on the same order (the buyer's stale plan_id then fails to confirm).

Request body

FieldTypeDescription
plan requiredobjectThe plan_proposal: line_items (each citing a locked_rate_id), minted_packages, quote_refs, plan_totals (including projected_delivery), unmet, and an expires_at.
actor requiredstringWho assembled the plan; persisted.

Response

202 Response
{
  "order_id": "ord_c19a44e2f0b1",
  "status": "needs_confirmation",
  "plan_id": "plan_5b2e"
}

Errors

StatusCodeWhen
409INVALID_STATEThe order is not a proposal, or not in a state that accepts a plan.
422INVALID_INPUTA line cites an unknown or expired locked_rate_id, or the plan omits a required field.

Decide an order

POST/v1/me/orders/{id}/decideorders:approve

Your decision on an order in seller_review: approve, reject, or counter, per unit. Nothing airs without it.

Request body

FieldTypeDescription
decision requiredenumapprove, reject, or counter: the headline. Per-unit actions below may mix.
actor requiredstringWho decided; persisted in the audit trail.
units requiredarray of objectOne entry per unit. Fields below.

units[]

FieldTypeDescription
unit_id requiredstringThe line being decided.
action requiredenumaccept, counter, or reject.
num_acceptedintegerOn accept: may be fewer than requested (partial acceptance).
counterobjectRequired on counter: {week, unit_cost, num_offered}. Requires the counter_proposals capability.
reasonstringRequired on reject and counter; persisted and surfaced to the buyer.

Example request

{
  "decision": "approve",
  "actor": "planner@seller.com",
  "units": [
    {
      "unit_id": "u-0001",
      "action": "accept",
      "num_accepted": 10
    },
    {
      "unit_id": "u-0002",
      "action": "counter",
      "counter": {
        "week": "2026-W42",
        "unit_cost": 392.00,
        "num_offered": 8
      },
      "reason": "W41 sold out; offering W42 at adjusted rate"
    },
    {
      "unit_id": "u-0003",
      "action": "reject",
      "reason": "daypart unavailable this week"
    }
  ]
}

Response

202 Response
{
  "order_id": "ord_9f3a12c4b7e1",
  "status": "placed",
  "units_accepted": 10,
  "units_countered": 8,
  "units_rejected": 6
}

status is negotiating when counters were issued. Counters surface on the order as additional line items with status: "countered" and an in_response_to naming the unit they answer; the original line keeps its own result. Consumers treat countered lines as additive: a seller that only approves and rejects today can add counters later without breaking anyone. See Negotiation.