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
| Field | Type | Description |
|---|---|---|
as_of required | ISO datetime | When this batch was true in your system. Applies to every entry; echoed to buyers on every read. |
entries[] required | array of object | The avail entries to write. Fields below. |
entries[]
| Field | Type | Description |
|---|---|---|
product_id required | string | The product the entry speaks for. |
period required | object | The 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 required | enum | One of the avail states. |
basis required | enum | measured 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 spots | integer | Spots 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 product | integer | Bookable 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. |
impressions | object | source (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_level | number | 0.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.
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{ "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 } } } ] }Timestamps in these examples are illustrative. The sandbox stamps every event, receipt, and
as_ofat 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.
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.
| Status | Code | When |
|---|---|---|
| 422 | INVALID_INPUT | A malformed batch envelope: a missing or non-datetime as_of, or no entries[] at all. |
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.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.
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
| Field | Type | Description |
|---|---|---|
product_id required | string | Repeatable. One or more product ids: ?product_id=acme-prime&product_id=acme-news. |
weeks required | ISO week | Repeatable. The air weeks to check: ?weeks=2026-W41&weeks=2026-W42. |
audiences | enum | Repeatable. Request audience codes beyond the defaults. Default: hh, p2plus, and a25_54 are always returned. |
market_class | enum | Filter 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_length | integer | Filter the returned rate lines to a creative length in seconds, e.g. ?spot_length=30. |
market | string | Filter 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.
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.