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

Rates

Publish rate cards

PUT/v1/me/rate-cardscatalog:write

Publish a versioned rate card commit. Entries are live for buyers the moment the commit lands, and drift detection runs on every commit: buyers with in-flight orders referencing the superseded version receive catalog.stale_rate_card events with a per-line diff. See Rates for how entries resolve and the price-clearance invariant for what the version pin buys you.

Request body

FieldTypeDescription
commit requiredstringYour id for this card version, e.g. acme-2026q4-v2. Orders pin it as rate_version.
supersedesstringThe commit this one replaces. Drift diffs are computed against it.
effective.start requiredISO dateFirst day the card applies.
effective.end requiredISO dateLast day the card applies.
currency requiredISO 4217The trade currency: one per commit, echoed on every entry by read surfaces, locked into an order with its rates, carried and never converted.
agreement_scopestringDefault: null, an open card. Set an agreement id to publish a buyer-scoped card; negotiated rates and quotes live on these.
commit_kindenumstanding (default) or quote. A quote commit is a per-campaign price minted inside a plan; it requires agreement_scope and quote_expires_at, and locks under the price-clearance invariant on confirm. See Disclosure.
quote_expires_atISO datetimeRequired on quote commits. After it, the entries are a stale card (STALE_RATE_CARD): the buyer re-requests, nothing books at a lapsed price.
proposal_refstringOn quote commits: the proposal order this quote answers.
entries[] requiredarray of objectThe rate entries. Fields below.

entries[]

FieldTypeDescription
product_id requiredstringThe product the rate prices. Must exist in your published catalog.
weekISO weekOmit for a rate that holds across the effective window; set it to price a single week. This is the write shape's one difference from the rate entry read shape: at commit the platform materialises a spanning entry into one entry per ISO week, and where a spanning entry and a week-specific entry collide on the same rate key, the week-specific one wins for that week. The expansion and precedence rules are defined once, on the object reference.
advertiser_class requiredenumOne of your declared advertiser classes.
spot_length requiredintegerCreative length in seconds. One entry prices one length: commit a separate entry for every length you sell, at the price you charge for it.
buy_type requiredenumOne of the buy types.
clearance_tierenumOne of the clearance tiers; valid only for preemptible.
market_class requiredenumOne of the market classes, the entry's market timing: scatter or discounted.
gross_rate_unit, gross_rate_cpmnumberExactly one of the two, or the entry is refused 422 INVALID_INPUT. The one present determines the entry's rate_basis, which read surfaces emit; a rate_basis sent here is ignored rather than validated, so a write cannot contradict itself.
audience_codestringRequired beside gross_rate_cpm, and on audience_guaranteed entries; the audience the rate prices or guarantees.

Example request

PUT /v1/me/rate-cards

{
  "commit": "acme-2026q4-v2",
  "supersedes": "acme-2026q4-v1",
  "effective": { "start": "2026-09-28", "end": "2026-12-27" },
  "currency": "USD",
  "agreement_scope": null,
  "entries": [
    {
      "product_id": "acme-prime",
      "week": "2026-W41",
      "advertiser_class": "general",
      "spot_length": 30,
      "buy_type": "preemptible",
      "clearance_tier": "P2",
      "market_class": "scatter",
      "gross_rate_unit": 405.00
    }
  ]
}

There is no weekly rollup field. A cell prices one unit at one length; a seller who sells a weekly block prices it as its own entry whose unit is the week, rather than publishing a second figure whose denominator ("a full week of units") nothing defines.

Response

Entries succeed per entry, under the one write model every publish surface shares: an entry that fails validation is reported in errors[] and skipped, the valid entries are written, and only an integrity failure rolls the whole request back. Each errors[] element names the entry by its rate key and carries a code: UNKNOWN_PRODUCT_ID for a product not in your published catalog, INVALID_ENTRY for a bad ISO week, a missing rate-key part, or an entry carrying both or neither of gross_rate_unit and gross_rate_cpm. An unknown product_id is an error, never a warning: a skipped price a seller does not notice is a cell buyers cannot buy.

The response also acknowledges the commit as the current version and reports drift detection: stale_notifications_sent counts buyers whose in-flight order lines have a locked price differing from this commit's current price for that same line, each of whom receives a catalog.stale_rate_card event with the per-line diff. A buyer whose lines are unaffected by the change is not notified, even though their rate_version no longer names the live commit: the price-at-order-time lock means their booked price was never contingent on staying current. Buyers read the resulting rate entries through avails and catalog surfaces.

200 Response
{
  "commit": "acme-2026q4-v2",
  "supersedes": "acme-2026q4-v1",
  "entries_written": 839,
  "errors": [
    {
      "code": "UNKNOWN_PRODUCT_ID",
      "product_id": "acme-primetime",
      "week": "2026-W41"
    }
  ],
  "warnings": [],
  "integrity_status": "pass",
  "stale_notifications_sent": 3
}

Errors

Error bodies carry the structured shape described in Errors; the conflict report on a rejected commit names every product in the overlapping set. Statuses here refuse the request: a single bad entry is not one of them, it is an errors[] element on a 200 per the write model above.

Why a price publish can return a catalog error. The catalog integrity rule is about prices, so a rate-card commit is exactly what can break it: a product offering the month grain has to price every week of each month it covers alike, and a card that prices one week of a month differently from another leaves that product with no rate a month line could lock. The commit is refused with a conflict report naming the product and the month. Price the month uniformly, or stop offering the month grain on that product, then re-publish the card.

StatusCodeWhen
422INVALID_INPUTA malformed commit envelope: a missing commit, a bad effective window, a quote with no quote_expires_at, or no entries[] at all. Entry-level faults are not here: they report per entry in errors[].
409GRAIN_CONFLICTA product offering the month grain would price some month's weeks unequally. The integrity exception: the whole commit rejects with a conflict report naming the product and the month; see the note above.

Update a rate card incrementally

PATCH/v1/me/rate-cards/{commit}catalog:write

Reprice a few cells without assembling the whole card. The path names your currently live commit; the body names the new commit this mints.

A patch mints a new commit. It never mutates one. rate_version is what an order pins, and the price-clearance invariant treats it as stable identity: two reads of one rate_version must never return different prices. So the base commit is untouched and stays permanently resolvable by any order citing it, and a body commit equal to the path {commit} is 422 INVALID_INPUT.

FieldTypeDescription
commit requiredstringThe new commit id. Must differ from the path commit.
entries_upsertarray of objectEntries to set, in the same shape PUT takes. An entry whose rate key already exists replaces it; one whose key is new is added.
entries_removearray of objectEntries to drop, named by rate key: {product_id, week?, advertiser_class, spot_length, buy_type, clearance_tier?, market_class}.
PATCH /v1/me/rate-cards/acme-2026q4-v2
{
  "commit": "acme-2026q4-v3",
  "entries_upsert": [
    {
      "product_id": "acme-prime",
      "week": "2026-W41",
      "advertiser_class": "general",
      "spot_length": 30,
      "buy_type": "preemptible",
      "clearance_tier": "P2",
      "market_class": "scatter",
      "gross_rate_unit": 450.00
    }
  ],
  "entries_remove": []
}

The platform computes the resulting card, the base commit's entries with your upserts applied and your removals dropped, and publishes it through the same write path PUT uses. So locked_rate_id minting, entry validation, the catalog integrity check, and drift notification all behave identically here, and a cell your patch leaves alone keeps the locked_rate_id a buyer is already holding.

Response

The same body PUT returns, reporting the new commit and the commit it supersedes.

200 Response
{
  "commit": "acme-2026q4-v3",
  "supersedes": "acme-2026q4-v2",
  "entries_written": 840,
  "integrity_status": "pass",
  "stale_notifications_sent": 1
}

Retries are safe: a byte-identical patch replays the stored response with X-Idempotent-Replay: true rather than minting a second commit, and reusing a commit id with different content returns 409 CONFLICT.

Errors

StatusCodeWhen
422INVALID_INPUTThe body commit equals the path commit, or the patch envelope is malformed. Entry-level faults in entries_upsert report per entry in errors[] exactly as on PUT, and validation is on the card the patch would produce, not on the delta.
404NOT_FOUNDThe path commit is not your live one. Nothing is created.
409INVALID_STATEThe path commit is a quote. Carries details.commit_kind; nothing is computed or written.
409CONFLICTThat commit id was already minted from a different patch.
409GRAIN_CONFLICTThe resulting card fails the catalog integrity check, exactly as it would on PUT.

Quotes cannot be patched. A quote commit is a price offered to one buyer, for one campaign, until it expires. Editing one incrementally could change terms after they were quoted. To change quoted terms, publish a new quote with PUT and commit_kind: "quote"; the old one stands until it expires.

Read your live rate card

GET/v1/me/rate-cardscatalog:read

Your card as it stands: the commit in force and the rate card records making it up. Read it before a PATCH, because the path commit that call takes is your currently live one, and read it after any publish to confirm what landed.

Pure read: nothing is written, no commit is minted, and no event fires. A buyer credential is 403, as on every /me route.

Response

FieldTypeDescription
sellerstringYour seller id, echoed as the catalog and avails reads echo it.
commitstring or nullThe commit this response describes. Every record in cards[] carries it. null only when cards[] is empty.
cards[]array of objectThe rate card records in force, each verbatim, entries included with the locked_rate_id the platform minted. A list, because a commit is a set of records: one commit may hold several records with different effective windows or agreement scopes. Empty, never absent, for a seller with no card at all. Its order is the order the platform holds the records in and carries no meaning: resolve a cell by its rate key, never by position.
200 Response
{
  "seller": "acme",
  "commit": "acme-2026q4-v1",
  "cards": [
    {
      "commit": "acme-2026q4-v1",
      "currency": "USD",
      "effective": { "start": "2026-09-28", "end": "2026-12-27" },
      "agreement_scope": null,
      "entries": [
        {
          "product_id": "acme-prime",
          "week": "2026-W41",
          "advertiser_class": "general",
          "spot_length": 30,
          "buy_type": "preemptible",
          "clearance_tier": "P2",
          "market_class": "scatter",
          "rate_basis": "per_unit",
          "gross_rate_unit": 405.00,
          "currency": "USD",
          "commit": "acme-2026q4-v1",
          "commit_kind": "standing",
          "agreement_scope": null,
          "locked_rate_id": "lr_pr41p"
        }
      ]
    }
  ]
}

Errors

StatusCodeWhen
403The credential is not a seller credential.

Read one commit

GET/v1/me/rate-cards/{commit}catalog:read

One named version, live or superseded. An order pins rate_version for its whole life and the price-clearance invariant keeps that commit permanently resolvable, so this is how you read back the card a buyer actually cited, months after you replaced it.

Same envelope as the collection read, with commit echoing the path value. Pure read, on the same authorization.

200 Response
{
  "seller": "acme",
  "commit": "acme-2026q4-v2",
  "cards": [
    {
      "commit": "acme-2026q4-v2",
      "supersedes": "acme-2026q4-v1",
      "currency": "USD",
      "effective": { "start": "2026-09-28", "end": "2026-12-27" },
      "agreement_scope": null,
      "entries": [ "..." ]
    }
  ]
}

Errors

StatusCodeWhen
403The credential is not a seller credential.
404NOT_FOUNDYou have no commit at that id. Carries details.commit. Another seller's commit answers the same way: not found rather than forbidden, so the endpoint cannot be used to learn that someone else's commit exists.