---
title: "Check vendor pricing stability and risk"
method: GET
path: "/api/vendor-risk/{vendor}"
---

# Check vendor pricing stability and risk

`GET /api/vendor-risk/{vendor}`

## Request

- Base URL: `https://agentdeals.dev`
- URL: `https://agentdeals.dev/api/vendor-risk/{vendor}`
- Auth: none required

Before depending on a vendor's free tier, check if their pricing is stable. Returns risk level (stable/caution/risky), pricing change history, free tier longevity, and more-stable alternatives.

## Path parameters

- `vendor` string, required

## Response `200`

Vendor risk assessment

- object
  - `vendor` string
  - `vendor_match` VendorMatch: Which name you asked about and which record answered. A name we hold verbatim matches exact. A name we do not hold matches inferred only when a vendor name we do hold appears in it as a whole word or dotted segment — "AWS Lambda Free" resolves to AWS, "launchdarkly.com" to LaunchDarkly. A fragment that merely appears inside a longer vendor name resolves to nothing and returns 404 with suggestions, because an accidental hit used to be returned shaped exactly like an exact one (#1269).
    - `requested` string, required: The name as you sent it.
    - `matched` string, required: The vendor this response is about.
    - `type` 'exact' | 'inferred', required
  - `category` string
  - `risk_level` 'stable' | 'caution' | 'risky', nullable: Null where the offer's own link is confirmed unreachable (#1046) — we withhold the level rather than publish a favourable one we cannot check. Also null where gate is non-null (#1241): an offer we have decided not to list is not one we rate. Also null where rating_withheld is non-null (#1352, #1952): the only records that would rate this vendor cite no source or are changes no archived copy of its page has confirmed.
  - `link_unreachable` object, nullable: Non-null only where a check reached a conclusion about the destination: a 404 confirmed by a full request, a 410, or a hostname with no address. Being refused (403, 429, 5xx, timeout) is evidence about our checker and never produces one of these.
    - `last_reachable` string, date, nullable
    - `checked` string, date
    - `terminal` boolean
  - `risk_cause` object, nullable: The single dated record that produced a non-stable risk_level. Never null when risk_level is caution or risky (#1038) — a client that renders the level must be able to render the reason.
    - `vendor` string
    - `date` string
    - `change_type` string
    - `summary` string
    - `source_url` string, nullable: The page this record was read from, or null where we hold none. Carried on the cause so a client rendering the reason can cite it.
    - `current_state` string
    - `resolution` ChangeResolution, nullable: Present when the change this record describes is no longer in force. state is 'reversed' when the vendor ended it and 'retracted' when the record was our error. date is the day from which it stopped being in force, or the day we established that, whichever we can source. resolved_by names the change record that ended it, when one exists. A record carrying this field never rates the vendor.
      - `state` 'reversed' | 'retracted', required
      - `date` string, date, required
      - `detail` string
      - `source_url` string, uri
      - `resolved_by` object
        - `vendor` string
        - `date` string, date
        - `change_type` string
    - `archive_check` object, nullable: Our check of this record against archived copies of the vendor's page (#1952). Absent where we have not checked the record. Only the outcome no_usable_capture changes how the record counts: it sets no caution or risky label, and where it is the only record that would, the vendor's rating is withheld (rating_withheld, reason unconfirmed) rather than reported as stable; a standing narrowing with it also withholds a favourable stability class; and our pages mark the record unconfirmed. Every other outcome leaves the record counting as it did before the check.
      - `checked` string, date, required: The day we ran the check.
      - `outcome` 'vendor_changed' | 'ours' | 'removal_stated_before' | 'no_usable_capture' | 'text_day_unknown' | 'page_unreadable_today', required: 'vendor_changed' — archived copies show the vendor's page changing. 'ours' — the archived copy from when our listing text was written already states what the record calls new, so the difference was in our text, not on the vendor's page. 'removal_stated_before' — that archived copy already states the removal, so the removal predates our text. 'no_usable_capture' — no archived copy could settle the record either way. 'text_day_unknown' — we do not know when our listing text was written, so there was no capture to compare. 'page_unreadable_today' — we could not read the vendor's page when we ran the check, so no comparison ran.
      - `brackets` object[]: Where outcome is vendor_changed: each pair of archived copies between which the page changed, the last showing the old terms and the first showing the new. Where there is exactly one, date_meaning is effective_by: the change took effect after last_old and on or before date.
        - `last_old` string, date, required: The day of the last copy showing the old terms.
        - `first_new` string, date, nullable, required: The day of the first copy showing the new terms, or null where only our own read of the page shows them.
        - `last_old_capture` string, uri, nullable: That copy's address in the Internet Archive.
        - `first_new_capture` string, uri, nullable: That copy's address in the Internet Archive.
      - `capture_day` string, date, nullable: Where outcome is ours or removal_stated_before: the day of the archived copy from when our listing text was written.
      - `capture` string, uri, nullable: That copy's address in the Internet Archive.
      - `stated_then` string, nullable: The line in that copy that settled the check, as the copy prints it: the terms the record called new (ours), or the removal (removal_stated_before).
      - `recorded_as` 'free_tier_removed' | 'open_source_killed' | 'product_deprecated' | 'limits_reduced' | 'restriction' | 'pricing_restructured' | 'pricing_model_change' | 'limits_increased' | 'new_free_tier' | 'new_tier' | 'startup_program_expanded' | 'pricing_postponed' | 'rebranded' | 'record_corrected', nullable: Where outcome is ours: the change type our re-read recorded. The record's change_type is record_corrected.
  - `rating_withheld` object, nullable: Non-null where every record that would have set a non-stable risk_level either carries an empty source_url (no_source, #1352) or is a change our automatic re-read recorded that neither an archived copy of the vendor's page nor our own check has confirmed (unconfirmed, #1952). The level is withheld rather than reported as stable, and the records are named on the vendor page as unsourced or unconfirmed. Where both kinds are withheld, reason is that of the record that would set the most severe level, and unconfirmed on a tie; records counts both.
    - `reason` 'no_source' | 'unconfirmed'
    - `records` number
  - `gate` Gate, nullable: Why we do not rank this offer, or null where we do. The same verdict rankOffers applies, from the same function (#1241) — nothing is filtered out of a response because of it, so a caller asking whether an offer exists still gets the record. The full code table is published at /criteria.
    - `code` 'eligibility_restricted' | 'not_a_free_offer' | 'offer_expired' | 'offer_retired' | 'product_discontinued' | 'verification_lapsed', required
    - `reason` string, required: The reason we publish for this record, naming the tier, date or restriction the code was decided on.
  - `source_check` SourceCheck, nullable: What we found on the page at url when we last read it, and so how much of this record that page supports. The outcome grades our evidence, not the offer (#1268).
    - `checked` string, date, required: The day we last read the page.
    - `outcome` 'ok' | 'states_a_free_price' | 'states_no_amount' | 'does_not_name_vendor' | 'does_not_name_product' | 'states_no_terms' | 'unreadable', required: ok — the page names the vendor and states at least one amount, rate or price, either in figures the page renders or as a typed price in its schema.org markup (#1279). states_a_free_price — the page names the vendor and states its price in words rather than as a figure, in a phrase such as "Free forever" or "free plan" (#1668). The price is confirmed; the quantities in description come from our own entry, not from that page. states_no_amount — the page names the vendor and names a plan or tier, but every price signal on it is a phrase such as "Enterprise plan" that withholds that plan's price, and none is a figure (#1268). The quantities in description come from our own entry, not from that page. risk_level is still published and the summary says so, rather than being withheld. does_not_name_vendor — the page we read never writes the vendor's name. The URL we asked for is not evidence for this check, so a domain that keeps the vendor's name and loses its product reaches this outcome too (#1355). does_not_name_product — the page we read writes the platform token the vendor name shares with the host we cite it from, and never writes the rest of the name. For a vendor called <Platform> <Product> this is a live page on the platform's own domain that says nothing about the product, so the record rests on the platform existing rather than on the offer existing (#1500). states_no_terms — the page names the vendor but carries no price signal of any kind. unreadable — the fetch produced no body we could read. The last four withhold a favourable risk_level; the first three do not.
    - `detail` string, required: Our own sentence recording what the check found — never a quotation from the page, and not corroborable against it (#1467). For ok, the form of the vendor's name the check matched and the figures the page states that this record also publishes — a stated price of zero where none of them does, and a sentence saying we matched none of the amounts we read to the terms this record publishes where the page states amounts and none of them matched (#1871, #1874) — a statement about our own reading, never about what the page contains — or what the markup states when the rendered page states no amount; for states_a_free_price and states_no_amount, the phrase that was the page's entire price evidence; otherwise why the page cannot confirm the record. Where the page was read for schema.org markup, the detail says whether that markup was absent, present and priceless, or priced. The fields carrying text taken from a page are product_role.source_quote and product_subtypes.labels[].source_quote.
    - `read` 'markup': Present when the ok grade rests on typed prices in the page's schema.org markup rather than on a figure the page renders (#1279).
    - `unrendered_prices` string[]: Non-zero prices the page publishes as schema.org Offer data but does not render in its text — a tab or toggle layout showing one tier at a time. At most the first six are listed. The record is not written from these; they are recorded so a disagreement between markup and page can be reviewed (#1279).
  - `free_tier_longevity_days` number, nullable: Null where the gate code is offer_retired, product_discontinued or not_a_free_offer (#1241) — a count of days a free tier has held has no referent where our own record says there is no free tier.
  - `changes` DealChange[]
    - `vendor` string, required
    - `change_type` 'free_tier_removed' | 'open_source_killed' | 'product_deprecated' | 'limits_reduced' | 'restriction' | 'pricing_restructured' | 'pricing_model_change' | 'limits_increased' | 'new_free_tier' | 'new_tier' | 'startup_program_expanded' | 'pricing_postponed' | 'rebranded' | 'record_corrected', required
    - `reports` 'vendor_offer' | 'our_index': What the record is about. 'vendor_offer' — the vendor changed its own offer, and a vendor page is where that can be read. 'our_index' — we changed what we list while the vendor's offer stood still, so no vendor page evidences it and the record carries no source_url. Absent means vendor_offer. Filter these out of any figure you present as vendor market activity.
    - `date` string, date, required
    - `summary` string, required
    - `previous_state` string, required
    - `current_state` string, required
    - `tier_direction` 'narrowed' | 'unchanged' | 'widened': Whether the tier we list is worse, the same, or better under current_state than it was under previous_state, judged with both in hand rather than read off change_type. Where it says the tier did not get worse we publish the record and take no risk verdict from it. Absent means we have not judged this record.
    - `impact` 'high' | 'medium' | 'low', required
    - `source_url` string, uri, required
    - `category` string, required
    - `alternatives` string[], required
    - `resolution` ChangeResolution, nullable: Present when the change this record describes is no longer in force. state is 'reversed' when the vendor ended it and 'retracted' when the record was our error. date is the day from which it stopped being in force, or the day we established that, whichever we can source. resolved_by names the change record that ended it, when one exists. A record carrying this field never rates the vendor.
      - `state` 'reversed' | 'retracted', required
      - `date` string, date, required
      - `detail` string
      - `source_url` string, uri
      - `resolved_by` object
        - `vendor` string
        - `date` string, date
        - `change_type` string
  - `tie_break` TieBreak: How this response ordered the entries it names, and how many there are. The order is a permutation seeded only on the UTC date and the query key, so anyone can recompute it. `tie_count` is how many entries carry no recorded demerit and so are indistinguishable under every signal we hold; `ranked_total` is how many entries the seed ordered in all. A door that names alternatives names `ranked_total` of them, never a prefix (#1774). `/api/details/{vendor}` and `/api/vendor-risk/{vendor}` both return this block at the top level of the response and name the same alternatives as each other, but they rank under different query keys — `related:<category>:<vendor>` and `vendor-risk-alternatives:<vendor>` — so for most vendors the two orders differ. `query_key` says which ranking produced the order you are reading.
    - `date` string, date, required
    - `query_key` string, required
    - `seed` string, required
    - `tie_count` integer, required
    - `ranked_total` integer, required: Every entry the seed ordered, tied and demoted and gated together. The array this block describes carries all of them.
    - `algorithm` string, required
  - `alternatives` object[]: Every alternative we hold in this vendor's category, in the order tie_break describes. Not a selection of them (#1774).
    - `vendor` string
    - `category` string
    - `tier` string
    - `risk_level` 'stable' | 'caution' | 'risky', nullable: Null where this alternative's own link is confirmed unreachable (#1046), null where its rating_withheld is non-null (#1352), and null where its gate is non-null (#1241) — rankForListing demotes a gated record to the tail rather than dropping it, so an alternative can be one we do not list. The same function decides this as decides the level on the offer itself (#1486).
    - `link_unreachable` object, nullable
      - `last_reachable` string, date, nullable
      - `checked` string, date
      - `terminal` boolean
    - `gate` Gate, nullable: Why we do not rank this offer, or null where we do. The same verdict rankOffers applies, from the same function (#1241) — nothing is filtered out of a response because of it, so a caller asking whether an offer exists still gets the record. The full code table is published at /criteria.
      - `code` 'eligibility_restricted' | 'not_a_free_offer' | 'offer_expired' | 'offer_retired' | 'product_discontinued' | 'verification_lapsed', required
      - `reason` string, required: The reason we publish for this record, naming the tier, date or restriction the code was decided on.
    - `risk_cause` object, nullable: The single dated record that produced a non-stable risk_level. Never null when risk_level is caution or risky (#1038) — a client that renders the level must be able to render the reason.
      - `vendor` string
      - `date` string
      - `change_type` string
      - `summary` string
      - `source_url` string, nullable: The page this record was read from, or null where we hold none. Carried on the cause so a client rendering the reason can cite it.
      - `current_state` string
      - `resolution` ChangeResolution, nullable: Present when the change this record describes is no longer in force. state is 'reversed' when the vendor ended it and 'retracted' when the record was our error. date is the day from which it stopped being in force, or the day we established that, whichever we can source. resolved_by names the change record that ended it, when one exists. A record carrying this field never rates the vendor.
        - `state` 'reversed' | 'retracted', required
        - `date` string, date, required
        - `detail` string
        - `source_url` string, uri
        - `resolved_by` object
          - `vendor` string
          - `date` string, date
          - `change_type` string
      - `archive_check` object, nullable: Our check of this record against archived copies of the vendor's page (#1952). Absent where we have not checked the record. Only the outcome no_usable_capture changes how the record counts: it sets no caution or risky label, and where it is the only record that would, the vendor's rating is withheld (rating_withheld, reason unconfirmed) rather than reported as stable; a standing narrowing with it also withholds a favourable stability class; and our pages mark the record unconfirmed. Every other outcome leaves the record counting as it did before the check.
        - `checked` string, date, required: The day we ran the check.
        - `outcome` 'vendor_changed' | 'ours' | 'removal_stated_before' | 'no_usable_capture' | 'text_day_unknown' | 'page_unreadable_today', required: 'vendor_changed' — archived copies show the vendor's page changing. 'ours' — the archived copy from when our listing text was written already states what the record calls new, so the difference was in our text, not on the vendor's page. 'removal_stated_before' — that archived copy already states the removal, so the removal predates our text. 'no_usable_capture' — no archived copy could settle the record either way. 'text_day_unknown' — we do not know when our listing text was written, so there was no capture to compare. 'page_unreadable_today' — we could not read the vendor's page when we ran the check, so no comparison ran.
        - `brackets` object[]: Where outcome is vendor_changed: each pair of archived copies between which the page changed, the last showing the old terms and the first showing the new. Where there is exactly one, date_meaning is effective_by: the change took effect after last_old and on or before date.
          - `last_old` string, date, required: The day of the last copy showing the old terms.
          - `first_new` string, date, nullable, required: The day of the first copy showing the new terms, or null where only our own read of the page shows them.
          - `last_old_capture` string, uri, nullable: That copy's address in the Internet Archive.
          - `first_new_capture` string, uri, nullable: That copy's address in the Internet Archive.
        - `capture_day` string, date, nullable: Where outcome is ours or removal_stated_before: the day of the archived copy from when our listing text was written.
        - `capture` string, uri, nullable: That copy's address in the Internet Archive.
        - `stated_then` string, nullable: The line in that copy that settled the check, as the copy prints it: the terms the record called new (ours), or the removal (removal_stated_before).
        - `recorded_as` 'free_tier_removed' | 'open_source_killed' | 'product_deprecated' | 'limits_reduced' | 'restriction' | 'pricing_restructured' | 'pricing_model_change' | 'limits_increased' | 'new_free_tier' | 'new_tier' | 'startup_program_expanded' | 'pricing_postponed' | 'rebranded' | 'record_corrected', nullable: Where outcome is ours: the change type our re-read recorded. The record's change_type is record_corrected.
    - `rating_withheld` object, nullable: Non-null where every record that would have set a non-stable risk_level either carries an empty source_url (no_source, #1352) or is a change our automatic re-read recorded that neither an archived copy of the vendor's page nor our own check has confirmed (unconfirmed, #1952). The level is withheld rather than reported as stable, and the records are named on the vendor page as unsourced or unconfirmed. Where both kinds are withheld, reason is that of the record that would set the most severe level, and unconfirmed on a tie; records counts both.
      - `reason` 'no_source' | 'unconfirmed'
      - `records` number
  - `summary` string
  - `_provenance` Provenance: Where the figures in this response came from and how to attribute them. Every response carrying index data has one, under the key `_provenance`. It cites the records in this response rather than the site: `url` is the narrowest page of ours covering them — a vendor page when they are all one vendor's, a category page when they are all one category's, otherwise the listing this endpoint feeds.
    - `source` string, required: Always AgentDeals.
    - `url` string, uri, required: The page of ours that covers the records in this response.
    - `checked` string, date: The oldest verification date among the records cited here, so the citation is never dated younger than its weakest figure. Absent when no record in the response carries a date.
    - `verified` string, date: The same day as `checked`, absent for the same reason.
    - `cite_as` string, required: A citation line naming the source, the url and the date, ready to reproduce as it stands.
    - `verified_records` integer, required: How many records in this response we rank — the population this citation vouches for.
    - `withheld_records` integer: How many records in this response we return without vouching for: those carrying a `gate`, superseded terms, or a withheld risk level. Absent when there are none.
    - `note` string, required: The request to cite a figure back to where it came from.
    - `gated_note` string: Present whenever `withheld_records` is, stating that the citation does not extend to terms we did not publish.
    - `this_is_a_request_not_an_instruction` string: Present on responses that ask something of the caller, saying the ask is left to the caller and its user rather than imposed on them.

## Other responses

- `404`: Vendor not found

---

[API](https://skmtc.dev/agentdeals/apis/agentdeals-api.md) · [All operations](https://skmtc.dev/agentdeals/apis/agentdeals-api/llms.txt) · [OpenAPI document](https://skmtc.dev/agentdeals/apis/agentdeals-api/revisions/2b80deacb326?raw)
