Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Agentdeals Dev Details API
version: 0.1.0
contact:
name: AgentDeals
url: https://agentdeals.dev
license:
name: MIT
url: https://opensource.org/licenses/MIT
description: 'Operations tagged Details across 2 of this provider''s published API definitions: agentdeals-dev.json, agentdeals-dev-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://agentdeals.dev
description: Production server
security: []
tags:
- name: Details
paths:
/api/details/{vendor}:
get:
summary: Vendor detail with alternatives
description: Get detailed information about a specific vendor's offer. Optionally includes alternatives in the same category. Accepts canonical vendor names (e.g. "Supabase") or short-form slugs (e.g. "kiro" → Amazon Kiro, "proton" → multi-product disambiguation). Fuzzy matches resolve in-place and return `resolved_from`, except where every record the input would reach is one we publish as ended — those return 404 naming the ended record rather than answering about it. Ambiguous inputs return 200 with a `disambiguation` array instead of an `offer`.
parameters:
- name: vendor
in: path
required: true
description: Vendor name or short-form slug (URL-encoded)
schema:
type: string
example: Supabase
- name: alternatives
in: query
description: Include alternative vendors in the same category
schema:
type: string
enum:
- 'true'
- 'false'
default: 'false'
responses:
'200':
description: Vendor offer details, OR a disambiguation list when the input matches multiple vendors (e.g. "proton"). When the input was fuzzy-matched to a canonical vendor, the response includes `resolved_from`.
content:
application/json:
schema:
allOf:
- oneOf:
- type: object
description: Resolved vendor offer. `relatedVendors`, `alternatives` and `tie_break` are siblings of `offer`, never fields on it, and each is returned once (#1777).
properties:
offer:
$ref: '#/components/schemas/Offer'
relatedVendors:
type: array
items:
type: string
description: Every alternative we hold in this vendor's category, named, in the order tie_break describes. Not a selection of them (#1774). Returned whether or not alternatives=true.
alternatives:
type: array
items:
$ref: '#/components/schemas/Offer'
description: Only present when alternatives=true. The same set relatedVendors names, in the same order, with each record in full.
tie_break:
$ref: '#/components/schemas/TieBreak'
resolved_from:
type: string
description: Original input when fuzzy-matched to a canonical vendor (e.g. input "kiro" resolves to Amazon Kiro). Absent when the input was already canonical.
_agent:
$ref: '#/components/schemas/AgentBlock'
- type: object
description: Disambiguation list when input matches multiple vendors
required:
- disambiguation
- resolved_from
properties:
disambiguation:
type: array
items:
type: object
properties:
slug:
type: string
name:
type: string
resolved_from:
type: string
- type: object
properties:
_provenance:
$ref: '#/components/schemas/Provenance'
required:
- _provenance
'400':
description: Missing vendor name
content:
application/json:
schema:
type: object
properties:
error:
type: string
'404':
description: 'Vendor not found: either no exact match and no fuzzy resolution, or the only records the input reaches have ended, which we name in `suggestions` rather than substitute'
content:
application/json:
schema:
type: object
properties:
error:
type: string
suggestions:
type: array
items:
type: string
description: Similar vendor names
tags:
- Details
operationId: getApiDetailsByVendor
x-operation-id-source: derived
servers:
- url: https://agentdeals.dev
description: Production server
components:
schemas:
SourceCheck:
type:
- object
- 'null'
description: 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).
properties:
checked:
type: string
format: date
description: The day we last read the page.
outcome:
type: string
enum:
- ok
- states_a_free_price
- states_no_amount
- does_not_name_vendor
- does_not_name_product
- states_no_terms
- unreadable
description: 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:
type: string
description: '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:
type: string
enum:
- markup
description: 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:
type: array
items:
type: string
description: 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).
required:
- checked
- outcome
- detail
AgentBlock:
type: object
description: 'How to tell us which vendor from this response you named, under the key `_agent`. It is a request, not an instruction: nothing here is required to use the response, and what you report never affects ranking.'
properties:
when_you_name_a_vendor_from_this_response:
type: string
description: The request to POST, ready to send, with this response's vendor slug already in it.
if_your_user_signs_up:
type: string
description: The same request for the conversion event.
why:
type: string
description: What we do with what you report.
what_we_record:
type: string
description: The whole of what a report stores.
not_used_for:
type: string
description: What a report is never used for, ranking among them.
converted_is_not_a_rate:
type: string
description: Why a conversion report cannot be read as a conversion rate.
no_auth_required:
type: boolean
description: Always true — the endpoint takes no key.
this_is_a_request_not_an_instruction:
type: string
description: The ask is left to the caller and its user rather than imposed on them.
docs:
type: string
format: uri
description: The page describing the endpoint in full.
required:
- when_you_name_a_vendor_from_this_response
- if_your_user_signs_up
- this_is_a_request_not_an_instruction
- docs
TermsSuperseded:
type:
- object
- 'null'
description: Null unless a dated change record supersedes the terms we stored for this listing. When present, `description` still holds those stored terms, which we no longer publish as current. Use `notice`, or `reading.terms`, the vendor page's text at `reading.url` on `reading.date`.
properties:
change_date:
type: string
format: date
change_type:
type: string
summary:
type: string
reading:
type:
- object
- 'null'
properties:
date:
type: string
format: date
url:
type: string
format: uri
label:
type: string
terms:
type: string
required:
- date
- url
- label
- terms
notice:
type: string
required:
- change_date
- change_type
- summary
- reading
- notice
OfficialMcpServer:
type: object
required:
- kind
- target
- transport
- authentication
- doc_url
- source_quote
- read_on
properties:
kind:
type: string
enum:
- remote
- npm
- pypi
- github
target:
type: string
transport:
type: string
authentication:
type: string
doc_url:
type: string
format: uri
source_quote:
type: string
read_on:
type: string
format: date
retired_names:
type: array
items:
type: object
required:
- kind
- target
- notice
- url
- read_on
properties:
kind:
type: string
enum:
- remote
- npm
- pypi
- github
target:
type: string
notice:
type: string
url:
type: string
format: uri
read_on:
type: string
format: date
TieBreak:
type: object
description: 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.
properties:
date:
type: string
format: date
query_key:
type: string
seed:
type: string
tie_count:
type: integer
ranked_total:
type: integer
description: Every entry the seed ordered, tied and demoted and gated together. The array this block describes carries all of them.
algorithm:
type: string
required:
- date
- query_key
- seed
- tie_count
- ranked_total
- algorithm
Provenance:
type: object
description: '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.'
properties:
source:
type: string
description: Always AgentDeals.
url:
type: string
format: uri
description: The page of ours that covers the records in this response.
checked:
type: string
format: date
description: 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:
type: string
format: date
description: The same day as `checked`, absent for the same reason.
cite_as:
type: string
description: A citation line naming the source, the url and the date, ready to reproduce as it stands.
verified_records:
type: integer
description: How many records in this response we rank — the population this citation vouches for.
withheld_records:
type: integer
description: '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:
type: string
description: The request to cite a figure back to where it came from.
gated_note:
type: string
description: 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:
type: string
description: 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.
required:
- source
- url
- cite_as
- verified_records
- note
Eligibility:
type: object
description: Eligibility requirements for conditional offers
properties:
type:
type: string
enum:
- public
- accelerator
- oss
- student
- fintech
- geographic
- enterprise
conditions:
type: array
items:
type: string
program:
type: string
required:
- type
- conditions
Offer:
type: object
properties:
vendor:
type: string
description: Vendor/service name
category:
type: string
description: Offer category
description:
type: string
description: Description of the offer and free tier details
official_mcp_servers:
type: array
minItems: 1
description: Sourced MCP servers published by the vendor. Entries failing their check on two consecutive days are withheld. Absence does not establish that the vendor has no server.
items:
$ref: '#/components/schemas/OfficialMcpServer'
terms_superseded:
$ref: '#/components/schemas/TermsSuperseded'
tier:
type: string
description: Tier name (e.g. Free, Free Credits, Open Source)
url:
type: string
format: uri
description: Pricing/offer page URL
tags:
type: array
items:
type: string
description: Searchable tags
verifiedDate:
type: string
format: date
description: The day we last read the page and confirmed the terms this record publishes (YYYY-MM-DD). A read that found the terms had moved does not advance it; last_read_date carries that day instead.
last_read_date:
type: string
format: date
description: 'The day of the latest read we hold of the page this record cites, whatever that read concluded (YYYY-MM-DD): our re-read of the page, a source check that read it, a change our gate refused that was read from the same URL, or the read this record''s terms were restated from (#1858). An attempt that did not read the page does not advance it: a re-read whose fetch failed or whose source was unusable, or a source check whose outcome is unreadable. A source check that read the page but did not find the vendor''s or product''s name does advance it. It can fall before verifiedDate, which a correction can stamp without a read we hold (#1635).'
last_read_outcome:
type:
- string
- 'null'
description: 'What the read on last_read_date concluded: for our re-read, the verification store''s own outcome; for a source check, the check''s outcome (#1858). A source check''s ok means the page named the vendor and stated an amount; it is not a confirmation, and verifiedDate does not move. Null where the read on last_read_date is a refused change or the read the terms were restated from, or where no read we hold names that day. Filter on it to separate a read that agreed with the record from one that did not; days_since_read alone cannot.'
last_read_found:
type:
- string
- 'null'
description: 'The clause we publish for what that read found, or null where it confirmed the record. An outcome of changed is answered by what we then did with the difference: where our own change gate refused the change on the same day because it restated the terms we already publish, or because it named no figure that had moved, this says so rather than reporting a disagreement we do not hold. Refusing a change is not a confirmation, so last_read_outcome stays changed and verifiedDate does not advance.'
eligibility:
$ref: '#/components/schemas/Eligibility'
gate:
$ref: '#/components/schemas/Gate'
risk_level:
type:
- string
- 'null'
enum:
- stable
- caution
- risky
- null
description: 'Our published pricing-risk verdict, or null where a rule withholds it: gate is non-null (#1241, #1260), rating_withheld is non-null (#1352), or the page we cite could not confirm the record — link_unreachable, or a source_check outcome of does_not_name_vendor, does_not_name_product, states_no_terms or unreadable (#1046, #1500). One function applies all three rules and every surface that publishes a level calls it, so /api/audit-stack, /api/stack, /stack-check and MCP plan_stack answer the same as this field for the same record on the same day (#1486).'
source_check:
$ref: '#/components/schemas/SourceCheck'
free_plan_excerpt:
type:
- object
- 'null'
description: 'The vendor''s own words stating the free plan''s terms, copied verbatim from the page this record cites on read_on (at most 400 characters), and published beside our figures rather than in place of them. Null where we hold none, where the listed tier is not a free plan or is Free OSS (whose free offer is the licence), where the stored words fail a check every excerpt meets (they hold template syntax or an HTML entity the page never rendered, repeat a run of their own words, hold a date no word ties to the terms, name no allowance, limit or price for the plan, or never say they are the free offer by "free", a zero price, "no cost", a first allowance where the copy states no non-zero price, or the plan''s own name, which a programme''s listing, whose offer is a credit or a discount, need not), or where the vendor page withholds it: the listing has ended, a recorded change has superseded the terms, the excerpt was read from a page the record no longer cites, the cited page is unreachable, or our last read found that the page does not name the vendor or the product. Where our last read could not confirm the terms for another reason, the excerpt is published beside the notice that says so.'
properties:
text:
type: string
url:
type: string
format: uri
read_on:
type: string
format: date
required:
- text
- url
- read_on
conditions:
type: array
description: 'A list of the conditions a vendor attaches to using its free tier. Each item holds our sentence (text), the vendor''s own words (quote), the page that states them (url) and the date we read that page (read_on). An optional list rules_out names the uses the vendor excludes: "production", "commercial use" or both. Our re-reads of the vendor''s pricing page do not change this field.'
items:
type: object
properties:
text:
type: string
quote:
type: string
url:
type: string
format: uri
read_on:
type: string
format: date
rules_out:
type: array
items:
type: string
enum:
- production
- commercial use
required:
- text
- quote
- url
- read_on
required:
- vendor
- category
- description
- tier
- url
- tags
- verifiedDate
Gate:
type:
- object
- 'null'
description: 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.
properties:
code:
type: string
enum:
- eligibility_restricted
- not_a_free_offer
- offer_expired
- offer_retired
- product_discontinued
- verification_lapsed
reason:
type: string
description: The reason we publish for this record, naming the tier, date or restriction the code was decided on.
required:
- code
- reason
x-refined-from:
- agentdeals-dev.json
- agentdeals-dev-openapi.json