← Back to product·Simulmedia VAMOS Docs·API reference / Avails
API reference

Avails

Push availability

PUT/v1/me/availscatalog:write

Push availability with impression estimates per audience. Updates are batched and partial: only the products and weeks you include are updated, others are unchanged.

Request body

FieldTypeDescription
as_of requiredISO datetimeWhen this batch was true in your system. Applies to every entry; echoed to buyers on every read.
entries[] requiredarray of objectThe avail entries to write. Fields below.

entries[]

FieldTypeDescription
product_id requiredstringThe product the entry speaks for.
period requiredobjectThe period this entry speaks for: {grain, start}, e.g. {"grain": "week", "start": "2026-W41"}. Its grain has to be one the product's grains offers. There are no week, grain, or date fields, and no slot_id: the period says all of it in one place.
state requiredenumOne of the avail states.
basis requiredenummeasured or modelled: whether the figures come from live inventory data or a forward projection. The values never share a token with state: "projected", which is clearability, a different axis.
spots.available required on a product that sells spotsintegerSpots available for this period: what an order's units validates against. Required on every product except one that sells nothing but audience_guaranteed, which states units.available instead and is refused if it states this. There is no spots.total: a total nothing validates against can only mislead.
units.available required on an audience-guaranteed productintegerBookable units of this product open in this period, on a product whose buy_types is exactly ["audience_guaranteed"]. A unit is what an order books one of: priced at gross_rate_unit and delivering the guaranteed_impressions its rate line states, so what a unit delivers is read there and never from this block. Required on such a product and refused on any other, which states spots.available instead. Zero is valid and means the period is open for nothing. There is no units.total, for the reason there is no spots.total, and no impressions figure of any kind: a week-level total is the reader's own arithmetic, units.available times the guarantee on the rate line, where the week prices exactly one guaranteed line.
impressionsobjectsource (the measurement basis, e.g. nielsen, comscore, seller_model) and stream (the measurement stream the figures are read on: c3, c7, live_sd) declared once, alongside one key per audience code carrying per_spot, the gross estimated delivery per spot. A per-spot estimate says nothing about a product that does not sell spots, so an audience-guaranteed product publishes no impressions block: one sent on such an entry is not read back, and what a unit delivers is the guaranteed_impressions on its rate line.
sellout_levelnumber0.0 (empty) to 1.0 (sold out): your declared sell-through of your own sales plan for the product and period. Buyers are told it is not the complement of whichever count the entry states and never validates an order; see the avail object.

One count per entry, and the product decides which. A product that sells spots states spots.available; a product whose buy_types is exactly ["audience_guaranteed"] states units.available. Never both, on any entry: an entry carrying the wrong one is an INVALID_ENTRY error naming the field, and the read publishes only the count the product sells in. There is no unit_type field to declare the denomination, because which key is present already says it, and a second copy of that fact could disagree with the first.

{
  "product_id": "simul-a25-54-ron",
  "period": { "grain": "week", "start": "2026-W40" },
  "state": "available",
  "basis": "measured",
  "units": { "available": 40 }
}

What one of those units delivers is on the entry's rate line, as guaranteed_impressions. The platform publishes no week-level impressions figure and accepts none: with one guaranteed line priced, 40 units against a guarantee of 3,980,100 is 159,204,000 in total, which is the reader's multiplication and not a field. Where a week prices more than one guaranteed line the product has no single answer, so there is nothing to compute: each line states its own guarantee.

The write payload is a batch envelope: as_of is declared once per batch and source/stream once per entry. Reads return the same shape: as_of echoed on every entry, and source and stream once per entry's impressions block, exactly as written. Per-audience measurement sources are not a thing the model can express, so the read does not imply them by repeating the pair inside every audience key.

Example request

PUT /v1/me/avails

Timestamps in these examples are illustrative. The sandbox stamps every event, receipt, and as_of at the moment of the call, so a response you get back carries today's instant rather than the one printed here. What the examples fix is the shape and the ordering: stamps on one order are strictly increasing, and two calls a minute apart are a minute apart in the log.

{ "as_of": "2026-10-01T08:00:00Z", "entries": [ { "product_id": "acme-prime", "period": { "grain": "week", "start": "2026-W41" }, "state": "available", "basis": "measured", "sellout_level": 0.62, "spots": { "available": 42 }, "impressions": { "source": "nielsen", "stream": "c3", "hh": { "per_spot": 128000 }, "p2plus": { "per_spot": 195000 }, "a25_54": { "per_spot": 82000 } } } ] }

Response

Entries succeed per entry, under the one write model every publish surface shares. An entry that fails is reported in errors[] and skipped, the rest are written: UNKNOWN_PRODUCT_ID for a product not in your published catalog, INVALID_ENTRY for a bad state value, a malformed period, or a period whose grain the product does not offer. An unknown product_id is an error, never a warning: a typoed id that only warned would return 200 and silently not publish the inventory, which is the one failure a seller must not be able to miss. warnings[] stays for what is genuinely non-fatal and still written, e.g. a period already in the past.

200 Response
{
  "entries_written": 1,
  "as_of": "2026-10-01T08:00:00Z",
  "errors": [
    {
      "code": "UNKNOWN_PRODUCT_ID",
      "product_id": "acme-primetime",
      "period": { "grain": "week", "start": "2026-W41" }
    }
  ],
  "warnings": []
}

Errors

Error bodies carry the structured shape described in Errors. Statuses here refuse the request; a single bad entry is instead an errors[] element on a 200, per the write model above.

StatusCodeWhen
422INVALID_INPUTA malformed batch envelope: a missing or non-datetime as_of, or no entries[] at all.
An audience-guaranteed product carries neither of these blocks: no spots, because nothing commits to a spot count on inventory sold in units, and no impressions, because a per-spot estimate needs a spots-per-unit conversion the platform does not have. It carries units.available, and the guarantee its rate line states. Both omissions hold on the read whatever a write sent.
Impression figures are seller-declared estimates, not guarantees. The source field records the measurement basis so buyers can weight them appropriately. Audience-guaranteed orders settle on the agreed currency, not on these avail estimates.

If you intend to carry day-locked orders, publish at day grain. Direct response buys by the date, not the week, and a buyer pinning an air date sets period: {"grain": "day", "start": "2026-09-16"} on the line. Day-grain avails is the stated precondition for that, and it is softly enforced: a dated line whose date you have not covered still books, and the buyer's create response carries a DATE_GRAIN_AVAILS_MISSING warning naming your product and the dates. Nothing is rejected and no batch is lost, so a seller partway through onboarding is not punished for partial progress. See the day-locked buys walkthrough.

Read availability

GET/v1/sellers/{seller}/availscatalog:read

Returns availability and impression estimates for requested products and weeks. Entries come back at whatever grain the seller published them at: a week-grain entry states availability for the whole week at the seller's own precision, a day-grain seller returns one entry per date, and a month-grain entry comes back when a requested week falls inside its month. An avail entry is not a guarantee of clearance; confirmed availability is established at order validation.

This endpoint answers inline only for products whose avails_policy is published (as resolved for you, after any agreement override). A product set to on_request returns 202 with a request resource to poll; one set to withheld returns 409 AVAILS_WITHHELD. The async contract is below.

Query parameters

FieldTypeDescription
product_id requiredstringRepeatable. One or more product ids: ?product_id=acme-prime&product_id=acme-news.
weeks requiredISO weekRepeatable. The air weeks to check: ?weeks=2026-W41&weeks=2026-W42.
audiencesenumRepeatable. Request audience codes beyond the defaults. Default: hh, p2plus, and a25_54 are always returned.
market_classenumFilter the returned rate lines to one market class, e.g. ?market_class=discounted. The retired ?rate_class= name is accepted as a deprecated alias for the remainder of the preview period, its old values mapped onto the collapsed enum: remnant and opportunistic read as discounted, and upfront reads as scatter, the value its cells migrated to (at-card on an agreement-scoped card).
spot_lengthintegerFilter the returned rate lines to a creative length in seconds, e.g. ?spot_length=30.
marketstringFilter to products in a market as scheme:code, e.g. ?market=nielsen_dma:501.

Example request

GET /v1/sellers/acme/avails?product_id=acme-prime&weeks=2026-W41&spot_length=30

Response

Each entry is an avail: a state, its basis and sellout_level, one count in the denomination the product sells in (spots.available, or units.available on a product that sells nothing but audience_guaranteed), per-audience impression estimates where the product sells spots, and the rate lines that apply, each carrying the locked_rate_id your order references under the price-clearance invariant. The request above filters to one creative length; without spot_length you get every length the seller prices, ordered by length and then by buy type (see below). A state: "projected" entry is quotable but not firmly clearable: orders against it validate at submission, and INVENTORY_UNAVAILABLE is possible if actuals differ when they land. discounted rate lines add a window (a standing remnant cadence commonly closes Monday 9:00A ET of the air week) and an optional signal payload agents can trigger on.

200 Response
{
  "seller": "acme",
  "avails": [
    {
      "product_id": "acme-prime",
      "period": { "grain": "week", "start": "2026-W41" },
      "state": "available",
      "basis": "measured",
      "as_of": "2026-10-01T08:00:00Z",
      "sellout_level": 0.62,
      "spots": {
        "available": 42
      },
      "impressions": {
        "source": "nielsen",
        "stream": "c3",
        "hh":     { "per_spot": 128000 },
        "p2plus": { "per_spot": 195000 },
        "a25_54": { "per_spot":  82000 }
      },
      "rates": [
        {
          "market_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",
          "currency": "USD"
        },
        {
          "market_class": "discounted",
          "advertiser_class": "general",
          "spot_length": 30,
          "buy_type": "preemptible",
          "clearance_tier": "P3",
          "rate_basis": "per_unit",
          "gross_rate_unit": 260.00,
          "locked_rate_id": "lr_f2a9",
          "currency": "USD",
          "window": {
            "opens": "2026-09-22T00:00:00Z",
            "closes": "2026-09-28T13:00:00Z"
          },
          "signal": {
            "sellout_pressure": "low",
            "expected_volume_pct": -0.30
          }
        }
      ]
    }
  ]
}

Rate lines are per creative length

Creative length is a key of the rate card, so a week returns one rate line per (length, buy type) the seller prices, ordered by length and then by buy type. Each line carries its own gross_rate_unit and its own locked_rate_id: no length's price is derived from another's, so a :15 is whatever the seller charges for a :15. Filter with ?spot_length= when you want one length, and expect several lines per buy type when you do not.

GET /v1/sellers/acme/avails?product_id=acme-prime&weeks=2026-W41

200 Response (rate lines only, abbreviated)
[
  { "spot_length": 15, "buy_type": "preemptible",        "gross_rate_unit": 315.00, "locked_rate_id": "lr_88c1l15" },
  { "spot_length": 15, "buy_type": "non_preemptible",    "gross_rate_unit": 410.00, "locked_rate_id": "lr_88c2l15" },
  { "spot_length": 30, "buy_type": "preemptible",        "gross_rate_unit": 405.00, "locked_rate_id": "lr_88c1" },
  { "spot_length": 30, "buy_type": "non_preemptible",    "gross_rate_unit": 520.00, "locked_rate_id": "lr_88c2" },
  { "spot_length": 60, "buy_type": "preemptible",        "gross_rate_unit": 750.00, "locked_rate_id": "lr_88c1l60" }
]

A length a seller does not price has no line and no locked_rate_id, so it cannot be ordered from the catalog even when the product declares it in spot_lengths. That is a disclosure rather than an error: ask for it through the proposal path.

Check availability twice. Plan against cached avails, then re-request exactly the products and weeks you are about to order immediately before POST /orders: a narrow re-check catches inventory that moved since planning and cuts rejections at the source. Validation's INVENTORY_UNAVAILABLE remains the backstop for the residual race between the re-check and the order.

On-request and withheld avails

When a product's resolved avails_policy is not published, the read does not answer inline:

on_request. The call returns 202 Accepted with a Location header and a correlation id; the seller answers asynchronously, possibly coarsened. Discover the answer two ways (hints, then the authoritative resource): an avails.answered event carrying the same avails_request_id, or poll the request resource. Auth is catalog:read, same as the feed.

202 Accepted
Location: /v1/sellers/acme/avails/requests/avr_6c1d90
{
  "avails_request_id": "avr_6c1d90",
  "status": "pending",
  "expires_at": "2026-10-01T20:00:00Z"
}

GET/v1/sellers/{seller}/avails/requests/{id}catalog:read

Poll the request. status is pending, answered, declined (the seller will not answer; next points to the proposal path), or expired (no answer by expires_at). When answered, avails[] is the same shape the published feed returns, at the seller's chosen precision.

200 Response : answered
{
  "avails_request_id": "avr_6c1d90",
  "status": "answered",
  "as_of": "2026-10-01T18:30:00Z",
  "avails": [
    { "product_id": "acme-prime", "period": { "grain": "week", "start": "2026-W41" },
      "state": "available", "basis": "measured", "sellout_level": 0.7,
      "spots": { "available": 20 } }
  ]
}

200 Response : declined
{
  "avails_request_id": "avr_6c1d90",
  "status": "declined",
  "next": { "path": "/v1/orders", "workflow": "proposal" }
}

withheld. The read returns 409 AVAILS_WITHHELD; the body's next points to the proposal path. Availability resolves inside the plan or at seller review, never here.