← Back to product·Simulmedia Switchboard Docs · v1 draft·Get started / Quickstart: place your first order
Get started

Quickstart: place your first order

Ten minutes, sandbox credentials, one order on air (in the sandbox sense). The sandbox seller is Acme Broadcasting; it behaves like a real seller, including the approval pause. This walks the catalog path because it is the fastest way to see an order move; it is not the only front door. When a seller withholds avails or prices per campaign, or when you would rather send a brief than pick line items, start with a proposal instead. Both are first-class.

1. Get a token

curl -s https://auth.sandbox.switchboard.simulmedia.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$SWB_CLIENT_ID -d client_secret=$SWB_CLIENT_SECRET

2. Browse the catalog

curl -s https://api.sandbox.switchboard.simulmedia.com/v1/sellers/acme/catalog \
  -H "Authorization: Bearer $TOKEN"

{ "packages": [ {
    "package_id": "acme-prime",
    "type": "daypart",
    "definition": { "selling_title": "Prime", "days": ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
                    "start": "20:00", "end": "23:00" },
    "taxonomy_version": "acme-2026q4-v1",
    "network": "ACME", "currencies": ["spots","hh","p2plus","a25_54"],
    "buy_types": ["preemptible","non_preemptible","fixed_position","audience_guaranteed"] } ] }

3. Check avails and rates

curl -s "https://api.sandbox.switchboard.simulmedia.com/v1/sellers/acme/avails?package_id=acme-prime&weeks=2026-W41&spot_length=30" \
  -H "Authorization: Bearer $TOKEN"

{ "avails": [ { "week": "2026-W41", "grain": "week", "state": "available", "basis": "actual",
    "spots": { "available": 42, "total": 80 }, "sellout_level": 0.62,
    "rates": [
      { "class": "scatter", "advertiser_class": "general", "spot_length": 30,
        "buy_type": "preemptible", "clearance_tier": "P2", "rate_basis": "per_unit",
        "gross_rate_unit": 405.00, "locked_rate_id": "lr_88c1" },
      { "class": "scatter", "advertiser_class": "general", "spot_length": 30,
        "buy_type": "non_preemptible", "rate_basis": "per_unit",
        "gross_rate_unit": 520.00, "locked_rate_id": "lr_88c2" },
      { "class": "scatter", "advertiser_class": "general", "spot_length": 30,
        "buy_type": "fixed_position", "rate_basis": "per_unit",
        "gross_rate_unit": 640.00, "locked_rate_id": "lr_88c4" },
      { "class": "scatter", "advertiser_class": "general", "spot_length": 30,
        "buy_type": "audience_guaranteed", "rate_basis": "cpm",
        "gross_rate_cpm": 3.40, "audience_code": "hh", "locked_rate_id": "lr_88c3" }
    ] } ] }

4. Place the order

Catalog orders reference package IDs and locked rates; because you ordered against published availability and a current rate version, there is no confirmation round-trip.

curl -s https://api.sandbox.switchboard.simulmedia.com/v1/orders \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "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-W41", "units": 10,
        "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "preemptible", "clearance_tier": "P2",
                  "rate_basis": "per_unit", "unit_cost": 405.00,
                  "locked_rate_id": "lr_88c1", "currency": "USD" } }],
    "buyer_metadata": { "advertiser": "YourClient", "campaign": "Q4-2026" },
    "notify_url": "https://yourapp.example.com/webhooks/switchboard"
  }'

{ "order_id": "ord_9f3a12c4b7e1", "status": "submitted" }

5. Watch it move

Your notify_url receives events as the order validates and enters seller_review (a person at the seller approves; in the sandbox this takes about a minute). At any time the snapshot is authoritative:

curl -s https://api.sandbox.switchboard.simulmedia.com/v1/orders/ord_9f3a12c4b7e1 \
  -H "Authorization: Bearer $TOKEN"

On approval you receive a placement_receipt (the booking receipt: the seller's order ID, daylocks applied, timestamp) as the order lands in placed: the buy is booked. When the flight has run, a final completed event closes the order; the sandbox compresses that wait to about a minute. Discovered, ordered, approved, placed. No email involved.

Things to try

The sandbox seller publishes four packages across the whole quarter, 2026-W40 through 2026-W52, so there is a second question worth asking. Below is one runnable request per outcome, in the order of the table. The sandbox tutorial walks them with the responses and the reasoning.

OutcomeWhat you get
Full acceptance to placedunits_accepted: 10, an allocation, and a placement_receipt
Partial acceptanceThe order is placed; the unit is partial with num_accepted: 6
INVENTORY_UNAVAILABLErejected from a well-formed request, because 2026-W43 is dark
STALE_RATE_CARDfailed, with rate_drift per unit: what you locked against what the card says now
A countered unitnegotiating, and the unit reads countered with num_accepted: 0
Two buy types on one orderplaced at 11049.60. No buy type on the order; each line carries its own
Two posting policies on one orderplaced at 5964.00, each line reconciling under its own package's policy

Three variables first. The seller secret is a second credential: the approval decision is a different actor, and the sandbox will not let the buyer token make it. The session id keeps your run separate from everyone else's, and both sides must send the same one.

export SWB=https://api.sandbox.switchboard.simulmedia.com/v1
export TOKEN=<your buyer secret>
export SELLER_TOKEN=<your seller secret>
export SWB_SESSION=$(uuidgen)

1. Full acceptance. Approve the order you placed in step 4. The order id is derived from your external_order_id, so the same key always resolves to the same order.

curl -s -X POST $SWB/me/orders/ord_9f3a12c4b7e1/decide \
  -H "Authorization: Bearer $SELLER_TOKEN" -H "X-Sandbox-Session: $SWB_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve", "actor": "sales@acmebroadcasting.example",
        "units": [ { "unit_id": "u-0001", "action": "accept", "num_accepted": 10 } ] }'

{ "order_id": "ord_9f3a12c4b7e1", "status": "placed",
  "units_accepted": 10, "units_countered": 0, "units_rejected": 0 }

2. Partial acceptance. Ask for 10 and have the seller take 6. Plan against num_accepted, never against what you asked for.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-partial",
    "rate_version": "acme-2026q4-v1",
    "line_items": [
      { "package_id": "acme-prime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W41", "units": 10, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "preemptible", "clearance_tier": "P2",
                  "rate_basis": "per_unit", "unit_cost": 405.00,
                  "locked_rate_id": "lr_88c1", "currency": "USD" } }]
  }'

{ "order_id": "ord_dd4117e6dfa2", "status": "submitted" }

curl -s -X POST $SWB/me/orders/ord_dd4117e6dfa2/decide \
  -H "Authorization: Bearer $SELLER_TOKEN" -H "X-Sandbox-Session: $SWB_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve", "actor": "sales@acmebroadcasting.example",
        "units": [ { "unit_id": "u-0001", "action": "accept", "num_accepted": 6 } ] }'

{ "order_id": "ord_dd4117e6dfa2", "status": "placed",
  "units_accepted": 6, "units_countered": 0, "units_rejected": 4 }

3. Inventory unavailable. 2026-W43 is dark. Availability is the only business reason an order is rejected outright, and the check runs at create.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-unavailable",
    "rate_version": "acme-2026q4-v1",
    "line_items": [
      { "package_id": "acme-prime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W43", "units": 4, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "preemptible", "clearance_tier": "P2",
                  "rate_basis": "per_unit", "unit_cost": 405.00,
                  "locked_rate_id": "lr_pr43p", "currency": "USD" } }]
  }'

curl -s "$SWB/events?order=ord_b7afeb158ee0&after_seq=2" \
  -H "Authorization: Bearer $TOKEN" -H "X-Sandbox-Session: $SWB_SESSION"

{ "order_id": "ord_b7afeb158ee0", "events": [
    { "event_type": "order.status_changed", "seq": 3, "status": "rejected",
      "substatus": null, "artifact": null,
      "error": { "error_code": "INVENTORY_UNAVAILABLE",
                 "error_message": "Requested week or slot lacks availability",
                 "stage": "availability", "recoverable": true, "details": {} },
      "occurred_at": "2026-10-01T14:02:13Z" } ], "last_seq": 3 }

4. Stale rate card. Send a rate_version behind the current one and the platform refuses to guess which price you meant. rate_drift is the useful half: per unit, what you locked against what the card says now.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-stale",
    "rate_version": "acme-2026q4-v0",
    "line_items": [
      { "package_id": "acme-prime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W41", "units": 4, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "preemptible", "clearance_tier": "P2",
                  "rate_basis": "per_unit", "unit_cost": 405.00,
                  "locked_rate_id": "lr_88c1", "currency": "USD" } }]
  }'

curl -s "$SWB/events?order=ord_3ec0e342f256&after_seq=2" \
  -H "Authorization: Bearer $TOKEN" -H "X-Sandbox-Session: $SWB_SESSION"

{ "order_id": "ord_3ec0e342f256", "events": [
    { "event_type": "order.status_changed", "seq": 3, "status": "failed",
      "substatus": null, "artifact": null,
      "error": { "error_code": "STALE_RATE_CARD",
                 "error_message": "rate_version acme-2026q4-v0 is behind acme-2026q4-v1",
                 "stage": "validation", "recoverable": true,
                 "details": { "rate_drift": [ { "unit_id": "u-0001",
                                                "locked_rate_id": "lr_88c1",
                                                "your_cost": 405, "current_cost": 405 } ] } },
      "occurred_at": "2026-10-01T14:02:13Z" } ], "last_seq": 3 }

5. A countered unit. On the cheaper daypart, have the seller answer with a price instead of a yes.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-counter",
    "rate_version": "acme-2026q4-v1",
    "line_items": [
      { "package_id": "acme-daytime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W41", "units": 4, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "preemptible", "clearance_tier": "P2",
                  "rate_basis": "per_unit", "unit_cost": 120.00,
                  "locked_rate_id": "lr_dt41p", "currency": "USD" } }]
  }'

{ "order_id": "ord_3699c15718f3", "status": "submitted" }

curl -s -X POST $SWB/me/orders/ord_3699c15718f3/decide \
  -H "Authorization: Bearer $SELLER_TOKEN" -H "X-Sandbox-Session: $SWB_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "counter", "actor": "sales@acmebroadcasting.example",
        "units": [ { "unit_id": "u-0001", "action": "counter",
                     "reason": "Rate is below card for this week",
                     "counter": { "week": "2026-W41", "unit_cost": 135.00,
                                  "num_offered": 4 } } ] }'

{ "order_id": "ord_3699c15718f3", "status": "negotiating",
  "units_accepted": 0, "units_countered": 4, "units_rejected": 0 }

6. Two buy types on one order. A fixed position in the tentpole plus a CPM guarantee on Prime. One order, two ways of buying, and no buy type on the order itself.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-mixed-buy-types",
    "rate_version": "acme-2026q4-v1",
    "line_items": [
      { "package_id": "acme-gameday", "provider": "National ACME", "network": "ACME",
        "week": "2026-W47", "units": 2, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "fixed_position", "rate_basis": "per_unit",
                  "unit_cost": 2600.00, "locked_rate_id": "lr_gd47f",
                  "currency": "USD" } },
      { "package_id": "acme-prime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W47", "impressions_goal": 1280000, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "audience_guaranteed", "rate_basis": "cpm",
                  "cpm": 4.57, "locked_rate_id": "lr_pr47g",
                  "currency": "USD" } }]
  }'

{ "order_id": "ord_f944243d5f02", "status": "submitted" }

curl -s -X POST $SWB/me/orders/ord_f944243d5f02/decide \
  -H "Authorization: Bearer $SELLER_TOKEN" -H "X-Sandbox-Session: $SWB_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve", "actor": "sales@acmebroadcasting.example",
        "units": [ { "unit_id": "u-0001", "action": "accept", "num_accepted": 2 },
                   { "unit_id": "u-0002", "action": "accept", "num_accepted": 8 } ] }'

{ "order_id": "ord_f944243d5f02", "status": "placed",
  "units_accepted": 10, "units_countered": 0, "units_rejected": 0 }

7. Two posting policies on one order. Prime posts over four weeks at 90 percent; the syndicated sponsorship over two at 95. Mixed-policy orders are legal, and each line reconciles under its own package's terms.

curl -s -X POST $SWB/orders -H "Authorization: Bearer $TOKEN" \
  -H "X-Sandbox-Session: $SWB_SESSION" -H "Content-Type: application/json" \
  -d '{
    "seller": "acme", "workflow": "catalog",
    "external_order_id": "try-mixed-policies",
    "rate_version": "acme-2026q4-v1",
    "line_items": [
      { "package_id": "acme-prime", "provider": "National ACME", "network": "ACME",
        "week": "2026-W41", "impressions_goal": 1280000, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "audience_guaranteed", "rate_basis": "cpm",
                  "cpm": 3.40, "locked_rate_id": "lr_88c3",
                  "currency": "USD" } },
      { "package_id": "acme-morning-syndicated", "provider": "Acme Syndication",
        "network": "ACME", "week": "2026-W41",
        "impressions_goal": 620000, "spot_length": 30,
        "rate": { "class": "scatter", "advertiser_class": "general",
                  "buy_type": "audience_guaranteed", "rate_basis": "cpm",
                  "cpm": 2.60, "locked_rate_id": "lr_88d3",
                  "currency": "USD" } }]
  }'

{ "order_id": "ord_4415819986b9", "status": "submitted" }

curl -s -X POST $SWB/me/orders/ord_4415819986b9/decide \
  -H "Authorization: Bearer $SELLER_TOKEN" -H "X-Sandbox-Session: $SWB_SESSION" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approve", "actor": "sales@acmebroadcasting.example",
        "units": [ { "unit_id": "u-0001", "action": "accept", "num_accepted": 10 },
                   { "unit_id": "u-0002", "action": "accept", "num_accepted": 6 } ] }'

{ "order_id": "ord_4415819986b9", "status": "placed",
  "units_accepted": 16, "units_countered": 0, "units_rejected": 0 }

Reading the quarter is cheaper still, and needs no order at all:

curl -s "$SWB/sellers/acme/avails?package_id=acme-prime&weeks=2026-W40&weeks=2026-W47" \
  -H "Authorization: Bearer $TOKEN"      # 320 a spot against 545
curl -s "$SWB/sellers/acme/catalog?buy_type=fixed_position" \
  -H "Authorization: Bearer $TOKEN"      # acme-daytime drops out
curl -s "$SWB/sellers/acme/catalog?distribution=syndication" \
  -H "Authorization: Bearer $TOKEN"      # the syndication layer alone
curl -s "$SWB/sellers/acme/avails?package_id=acme-gameday&weeks=2026-W47&weeks=2026-W51" \
  -H "Authorization: Bearer $TOKEN"      # two dates, and nowhere else
One-time setup: your webhook host must be allowlisted before the platform will call it. Register it under Settings → Webhooks or POST /v1/webhooks/endpoints; unregistered hosts fail order creation fast with a 422 so you never wonder why events did not arrive.