← Back to product·Simulmedia VAMOS Docs·Get started / Versioning
Get started

Versioning

The API is versioned in the path (/v1). Additive changes (new fields, new endpoints, new enum values on fields documented as open) ship without a version bump; treat unknown fields as ignorable and unknown enum values as pass-through. Breaking changes ship as a new version with at least 12 months of overlap, announced in the changelog and flagged with Sunset headers on the old version. A machine-readable changelog publishes with the first post-GA change; until then, this page is the contract.

Before GA. VAMOS has not reached GA. Until it does, /v1 is a preview contract: a correction to it ships in place, dated and listed below, rather than as a new version. The 12-month overlap and Sunset guarantee above attaches at GA, when there is a production contract worth guaranteeing. Every in-place correction is listed here, so a partner reading this page learns what moved and when instead of discovering a field is gone.

DateChange
2026-08-21A relationship can narrow what a buyer may buy, and the rule is intersection. agreement.permitted_grains joins permitted_buy_types and catalog_scope on the relationship record. All three narrow and none can widen: the effective set is what the product offers and the relationship permits, so naming a grain or a buy type the product does not sell is a no-op rather than a grant. Omitted or empty is no restriction, which is what every relationship meant before the field existed, and each permission is the union across a buyer's relationships with one seller. A product a buyer can book at no grain or in no buy type does not list for them. Additive: GRAIN_NOT_OFFERED keeps its name and shape and gains details.permitted_grains only when a relationship is what excluded the grain, so an agent can tell "never sold that way" from "not sold that way to you".
2026-08-21A product carries the seller's own reference, product.external_ref. Opaque, never validated, and unique per seller across its products: a publish whose resulting catalog puts one ref on two products is refused 422 DUPLICATE_EXTERNAL_REF naming both, as a whole-request refusal, because uniqueness is a property of the catalog rather than of any one entry. Seller-only: present on the seller's own reads, absent from every buyer-facing catalog read, so no buyer's parse moves. It is not a second product_id: product_id stays the platform's stable handle on every avail, rate entry, and order line, while this is the seller's mutable one, so reorganizing internal codes never invalidates a booked order's key.
2026-08-21agreement.disclosure_overrides is implemented, and the chain it resolves by is stated per field. Specified since the disclosure model landed and unbuilt until now, so this is a gap closing rather than a contract changing. The resolution is override(product_id) ?? override("*") ?? product.disclosure ?? published, per field: an entry that omits avails_policy or pricing_policy is transparent for that field and falls through as though it were absent, which keeps the two postures independent. One product_id twice is refused 422 DUPLICATE_DISCLOSURE_OVERRIDE rather than resolved by array order, and an override naming a product outside catalog_scope is a no-op.
2026-08-20package is renamed product, everywhere, with no alias. The old name described a wrapper the object stopped being; the object is the sellable thing, so it takes the trade's own word. There is no dual-read period and no compatibility alias: package, package_id, and packages[] are refused, because two spellings of one object is the ambiguity the rename exists to end.
2026-08-20A period replaces week, grain, and date on avails and on order lines. One object, {grain, start}, where grain is day, week, or month and each grain owns exactly one start format (2026-10-07, 2026-W41, 2026-10), so no token can be read as two grains. A product declares what it will sell in grains[], defaulting to ["week"], and a line naming a grain the product does not offer is refused 422 GRAIN_NOT_OFFERED. Cards still price by week, which is why a product offering month must price each month's weeks alike: the catalog publish refuses it with 409 GRAIN_CONFLICT otherwise, because a month line has one rate to lock.
2026-08-20The line item is the transacted object, and slot is gone. The ladder is product, then line item, then unit: a line item is one product for one period at one length and one rate, and the units it books are its allocation. The dated instance the old slot named is now a line item whose period is a single day, so day-locked buying is a grain rather than a separate noun. slot_id stops being a documented concept as well as a field.
2026-08-20The pool object and the days-intersect-time overlap rule are both retired, and DAYPART_CONFLICT leaves the error vocabulary. Two Available products covering the same network, days, and hours now publish cleanly: overlap is ordinary, the platform never held the seller's sales plan, and the rule refused honest catalogs while catching nothing a seller had asked it to catch. The shared-capacity object existed only as that rule's declared exception and goes with it. What a product has left is what its own avails say; INVENTORY_UNAVAILABLE no longer carries details.pool_id.
2026-08-20sm_daypart becomes reporting_dayparts[], a closed list of six. early_morning, daytime, primetime, late_night, overnight, weekend: plural because a title running Saturday nights is honestly both primetime and weekend, and closed because a free string was never comparable across sellers. It is descriptive only: nothing validates it against the product's own definition and nothing prices on it. Omitting it means uncategorized.
2026-08-20visible_to retires into product.visibility plus agreement.catalog_scope. The product states the axis (open, the default, or by_agreement); the relationship states who, as "*" or an explicit product list. Two mechanisms with no rule about which won become one axis and one grant, and neither can say what the other says. The relationship record is created implicitly by the first grant, so there is no separate create step, and a seller's external_ref for a buyer is unique within that seller: a duplicate is refused 422 DUPLICATE_EXTERNAL_REF.
2026-08-16An avail states one count, in the denomination its product sells in: units.available beside spots.available. A product whose buy_types is exactly ["audience_guaranteed"] now publishes units.available, the bookable units open in that week, and publishes neither spots (nothing commits to a spot count on inventory sold in units) nor impressions (a per-spot estimate there needs a spots-per-unit conversion the platform does not have). Exactly one count is on any entry and the product decides which, so there is no unit_type field: which key is present says the denomination. Additive on the read for every other product, whose entries are byte-identical. On PUT /v1/me/avails this is a loosening: spots.available was required unconditionally, which no audience-guaranteed entry could satisfy, so that inventory could not round-trip at all; it is now required only where the product sells spots, units.available is required where it does not, and the wrong count is refused per entry as INVALID_ENTRY naming the field. There is no units.total, for the reason there is no spots.total. Nothing week-level is stored or served in impressions: what a unit delivers is the guaranteed_impressions on its rate line, and a week total is the reader's multiplication where the week prices exactly one guaranteed line. The count is published and never enforced: order create still does not test an audience-guaranteed line against it.
2026-08-12unpriceable[] is renamed unpriced[], and UNPRICEABLE_DECLARATION becomes UNPRICED_DECLARATION. Same derivation, same per-axis-value rule, same both-sides coverage; only the name and the warning detail text change. "Unpriceable" read as a property of the product (no price at all, negotiate for one) when the field is a property of the current card (this declared value has no rate entry). The detail string is now stated per axis, because the consequence is: an unpriced spot_length, buy_type, or advertiser_class cannot be ordered, while an unpriced currency withdraws only the audience guarantee in that currency and leaves whatever else the card prices orderable. Splitting the currency axis into its own positive field is under consideration and would get its own row here.
2026-08-11GET /v1/events/feed: a tenant-scoped, resumable read over your whole event stream. Additive. Every event a webhook would deliver to your credential, all six types in their delivery envelopes, resumable from an opaque cursor: recovery after an outage becomes one loop instead of one replay per live order. Per-order replay, seq, and every dedup identity are untouched; filters share the webhook registration's events/statuses semantics; positions are assigned by the event's own commit and streams belong to the organization, not the token. 410 CURSOR_EXPIRED is reserved: preview streams are retained in full, and a retention horizon gets its own row here before it ever takes effect.
2026-08-11The order event's units[] entries publish their full record. Additive: entries gain allocation (the air dates the transition decided), and countered entries gain in_response_to, counter, and locked_rate_id, all of which the platform already recorded. The published stream is now fold-sufficient: everything on a line result exists on some event, so the snapshot is checkably the fold of the stream.
2026-08-11The decide and confirm transitions carry the approval record. Additive, with one named consequence: the decision record ({gate, actor, decision, reason?}) rides the event it produced, the seller view's approvals[] is now that record's projection rather than a separate ledger, and a buyer replaying its own order reads the record too, where the old array was seller-view only. The counterparty to a decision may read who took it, as a signed confirmation names its signer on paper.
2026-08-11placement_receipt.daylocks is a projection, never a second record. The receipt's own facts are seller_order_id and placed_at; its daylocks rows are the same event's units[].allocation flattened to {unit_id, date, units}, ordered by unit_id then date. The air dates are recorded once, so receipt and lines can never disagree.
2026-08-11The spec states its one data-model idiom. No wire change. "Snapshots are truth" and "events are hints" both stay, now with the reason: they are statements about delivery, not the record. The record of an order is its create request, the platform's create-time facts, and an append-only event log; every read, the snapshot included, is the platform's projection of those three, and the order reference now names the source of every field.
2026-08-11class is renamed market_class and collapses to scatter | discounted. Every concept page already said "market class"; only the wire said class, beside advertiser_class, a different axis. upfront is gone because it had no distinct mechanics anywhere: the commitment lives on the agreement (commitment_gross, pacing) and the pricing on the agreement-scoped card. remnant and opportunistic are one value, discounted, because they priced and behaved identically and no seller could classify the difference reliably; the trade's words stay in prose. ?rate_class= is accepted as a deprecated alias for ?market_class= for the remainder of the preview period, its old values mapped onto the collapsed enum.
2026-08-11avail.basis values are renamed measured and modelled. The old actual/projected pair shared the token projected with avail.state, so state: available, basis: projected and state: projected, basis: actual were both expressible with nothing distinguishing them in prose. The axes are unchanged: basis is figure provenance, state is clearability.
2026-08-11The avails read shape matches the write shape for source and stream. The pair is declared once per entry and now reads back once per entry, instead of being repeated inside every audience key, which implied per-audience measurement sources the write shape cannot express.
2026-08-11One cancellation block on all three surfaces, with a typed late_notice. The cancel response, the cancellation event, and the snapshot carry the identical {reason, actor, late_notice, notice?} block; the three-row per-surface table is gone. late_notice is the typed verdict (replacing the redundant within_notice_window boolean), notice carries the figures whenever terms were attached, and substatus is demoted to what the docs always claimed it was: an informational step marker nothing may be recoverable only from.
2026-08-11One expiry governs a plan. POST /me/orders/{id}/plan refuses a plan whose expires_at is later than the earliest quote_expires_at among the quote commits its lines cite: the effective expiry was always the earlier of the two, so the bound is now enforced where the plan is posted instead of surfacing as a STALE_RATE_CARD at confirm.
2026-08-11The capability profile stops describing one fact two ways. rate_management.mode (which wore rate movement and avails movement in one enum) splits: rate_management.rates is frozen_quarterly | live, and avails movement is capabilities.realtime_availability alone, so the contradictory frozen_quarterly-with-live-avails profile is no longer expressible. capabilities.pricing_modes is removed rather than derived: pricing posture is the per-product disclosure.pricing_policy, and the seller-level restatement used a second vocabulary with no value for quote at all.
2026-08-11The effective posting policy snapshots at order create. The override(product_id) ?? override("*") ?? product.posting_policy ?? unstated chain is evaluated once, per line, at create, and frozen by value exactly as cancellation_terms is: both are agreement terms that decide money. Reconciliation reads the frozen result; a later product or agreement change applies to the next order, never a booked one.
2026-08-11Four lifecycle event types collapse into order.status_changed, and the envelope gains prior_status. order.needs_review, negotiation.offer, and order.canceled were the same transitions viewed from the other role, each fully determined by the status the envelope already carried. Both sides now read the same type; webhook registrations gain a statuses[] filter (strictly more precise than the retired names); and the old order.canceled carve-out ("an order you had not yet approved") becomes the platform-computed prior_status field instead of a type name. The five non-lifecycle types stay.
2026-08-11GET /settlement/credit is agreement-scoped, and agreement.credit is gone. Exposure is a live number that gates order creation, so it gets one authoritative surface: the endpoint, now taking a required ?agreement=, rather than also riding inside the slow-moving agreement object clients reasonably cache.
2026-08-11sla speaks one unit. max_decision_latency_seconds (14400) becomes max_decision_latency_minutes (240) beside min_lead_minutes: two durations in two units inside one object is the shape of a real incident.
2026-08-11product.definition is one tagged variant per type. The single field table marked only timezone required while a daypart missing its selling_title was a documented 422; each type now has its own variant with its own requirement set (the occurrences program shape included), in the style the reference already uses elsewhere.
2026-08-11avail.pool is required on pooled products. An optional diagnostic that is sometimes absent can be relied on by nobody; the block is how an agent learns a constraint is shared (so shopping a sibling product will not help), so it is mandatory exactly where it means something and absent everywhere else.
2026-08-11product.billboard stays structured and is named informational. Nothing validates, prices, or settles on it; it stays because it identifies what is being sold at the approval gate, and it earns validation the day a trafficking flow acts on it. Recorded so the posture is a decision rather than an accident.
2026-08-11rate entry.gross_rate_weekly is removed. Nothing read it, its denominator ("a full week of units") was never defined, and the one rule about it (never back-computed) contradicted the canonical example (405.00 beside 4050.00). A seller pricing a weekly block commits an entry whose unit is the week.
2026-08-11avail.impressions.<audience>.total_avail is removed. It was exactly per_spot x spots.available on every audience of the worked example, stale the moment available moved, and read by nothing. Total available impressions is the reader's own arithmetic.
2026-08-11avail.spots.total is removed, on the write and the read; sellout_level is defined precisely. No single denominator was true of a pooled product (the example's total: 80 was the pool's capacity while the product's own cap was 60), and nothing validated against it. spots.available stays as the one unit count orders validate against; sellout_level stays as the yield signal, now defined as the seller's declared sell-through of its own unpublished sales plan, never the complement of available.
2026-08-11line_items[].provider and .network are no longer accepted on writes. Both are the product's own facts, so they are derived from the line's product_id and echoed on every read; a buyer-sent copy could only agree with the catalog or contradict it.
2026-08-11slot_id no longer travels as a field. An order line pins a date with week-or-date (was week-or-slot_id), and a date-grain avail entry carries date alone: the composite {product_id}:{date} string was a second copy of the product id that could disagree with the first (product_id: "acme-prime" with slot_id: "acme-news:..." was expressible). The slot stays a first-class concept; the notation stays in prose.
2026-08-11num_requested leaves the catalog path. On a catalog line it was a second copy of the units the buyer sent, making units: 10, num_requested: 8 expressible and meaningless. It survives only where the buyer named no unit count: proposal and document workflow lines. Read partial acceptance as num_accepted against the line's own units.
2026-08-11The partial status is dropped at both levels. A line accepted below its ask is accepted with num_accepted below units; every consumer already handled accepted and partial identically except for the count, which it read from num_accepted in either case. Order-level partial was never a real status value; an order with a mixed unit set completes like any other.
2026-08-11plan_proposal.minted_products and .quote_refs are removed. Both were filters over line_items the reader already holds: the minted set is the lines carrying minted: true, the quote commits are the set behind the lines' locked_rate_ids.
2026-08-11spots is no longer an audience code. The unit axis and the audience axis are separate: unit counts live in units, spots.available, and a pool's unit, and product.currencies lists audience codes alone (it may be empty on a product trading on unit counts only). impressions: {spots: ...}, one spot per spot, is no longer expressible.
2026-08-11workflow is derived at create, never accepted. The create body carries exactly one of line_items, request, or document, and that member is the workflow; a value sent anyway is ignored. Stored and echoed on every read unchanged, since the plan endpoint's precondition reads it.
2026-08-11settlement_mode is resolved from the presented agreement, never chosen by the request. direct when no agreement is presented; echoed on every read; a sent value that contradicts the resolution is refused 422, because which mode governs money is the agreement's fact. Previously documented as a buyer input with a default and no stated winner on disagreement.
2026-08-11capability_profile.conformance_level is computed from the declared capabilities, never accepted. The old write validated a stated level against the declarations, which is proof it was derived. Still on every read, and still published to buyers as level on GET /v1/sellers.
2026-08-11The derived money figures are marked derived. GET /settlement/credit's available is exactly credit_limit - exposure; pacing's remaining is exactly committed - booked. Both stay, stated as derivations so a disagreement is never treated as meaningful.
2026-08-11One write model on all four publish surfaces. Catalog, rate cards, avails, and file drops now share per-entry partial success with explicit errors[] and warnings[], integrity failures the single whole-request rejection, and an unknown product_id an entry-level error everywhere (it was a hard 422 on rate cards, a silent skip-with-warning on avails, and a row error on file drops). The catalog's version envelope stays atomic: one update, one taxonomy_version. The conventions page now describes what the endpoints actually do.
2026-08-11Declared-but-unpriced combinations are named on both sides. A catalog commit warns per declared combination with no rate entry (UNPRICEABLE_DECLARATION), and the catalog read carries a derived unpriceable[] per product, so inventory a buyer can see but not buy is visible instead of silent. The declaration arrays themselves stay: they bound what a card may price.
2026-08-11Catalog audience_guaranteed lines book in units, everywhere the docs speak. The orders API line-item table said units was forbidden and impressions_goal required on guaranteed lines, contradicting the objects reference and the buy-types page. The rule is now stated identically on every page: every catalog line books in units, and impressions_goal belongs to the proposal and document workflows alone; a catalog line carrying it is refused 422.
2026-08-11rate_basis is emitted, never accepted, and a read carries exactly the cost field the write carried. Writes carry exactly one cost field (gross_rate_unit or gross_rate_cpm on a rate entry, unit_cost or cpm on an order line; both or neither is 422), and the platform derives rate_basis from which one is present. Every read surface still emits it; a value sent on a write is ignored. With that, the derived, informational gross_rate_cpm leaves audience_guaranteed entries: the equivalent CPM is the reader's own arithmetic (gross_rate_unit / guaranteed_impressions x 1000), not a field.
2026-08-11The rate entry's write and read shapes are named and split over week. The reference marked week required while the rates page said to omit it to span the window; both were true of different shapes. A rate card entry input may omit week to span the commit's effective window; the platform materialises it per ISO week at commit, and a week-specific entry wins over a spanning one on the same rate key. Read entries always carry week.
2026-08-11buyer_metadata has one closed key list, closed at the top level. Defined once on the order reference; an unknown top-level key is refused 422 at create because these values are echoed on EDI delivery, where an unrecognised key is silent data loss. Nested objects keep their own contracts: an unrecognised field inside buyer_contact stays valid and is carried verbatim, and orders booked before the closure keep their custom keys on every read. advertiser_class is removed from the list: class prices and lives on the line's rate; it was never a metadata fact.
2026-08-11totals.impressions is a map keyed by audience code, and it is the contracted guarantee. The flattened impressions_<audience> field names in one worked example are gone. The map sums each audience_guaranteed line's guaranteed_impressions times its booked units, seller-sourced from the locked rate entries and frozen at create; it is absent on orders with no guaranteed lines, and nothing settles on it (settlement stays on delivery posts). Per ADR-0019.
2026-08-11One error envelope, defined once. The five-field shape {error_code, error_message, stage, recoverable, details} on Errors is canonical; the order-event reference pointed at it while restating a three-field subset, and now points without restating.
2026-08-11order.taxonomy_version exists; the per-product copy is gone. The selling-titles page promised orders pin the taxonomy version and no such field existed on the order. The order now carries it, pinned by value at create: one seller has exactly one current version at any instant (the atomic update envelope's), so the pin is total however many products or windows the lines span. The product no longer repeats the envelope's version; a reader that used the per-product copy reads the envelope's value, which was always equal to it, and orders booked before the field read back without one.
2026-08-11The rate-cards DAYPART_CONFLICT cause is corrected. The error table said a commit "would leave" products overlapping, which a price publish cannot do. The check is a guard, and the page now says so: a commit is refused when the catalog it prices is already in violation, or when a priced product's inventory_pool fails to resolve for the commit's own effective window. Behavior is unchanged; the stated reason was wrong.
2026-08-01Per-second pricing retired. rate_basis is now per_unit or cpm, and cost_per_second is removed from the rate entry and from the seller-hosted pull contract. Creative length is a key axis of the rate card, so a card prices each length it sells and the platform derives no length's rate from another's. A request carrying rate_basis: "per_second" is refused 422 INVALID_INPUT.