---
title: "Get free-tier stack candidates per role"
method: GET
path: "/api/stack"
---

# Get free-tier stack candidates per role

`GET /api/stack`

## Request

- Base URL: `https://agentdeals.dev`
- URL: `https://agentdeals.dev/api/stack`
- Auth: none required

For each role in your project type (hosting, database, auth, …) returns the set of free-tier offers whose terms we can stand behind today — deliberately not a single pick, because under every signal we record dozens of them tie. Each candidate carries its demerits, with the recorded fact and date behind each, plus any recorded changes we disclose but do not rank on. This endpoint does NOT model technical fit between a product and a role; the caller must apply that. The method is published at /criteria, and every role returns the tie_break seed so its order can be recomputed independently.

## Query parameters

- `use_case` string, required
- `requirements` string

## Response `200`

Candidate sets per role, with the evidence behind each

- object
  - `use_case` string
  - `stack` object[]
    - `role` string
    - `category` string
    - `candidates` object[]: The rotated tied set for this role, capped at 8. Not ordered by merit — every member is indistinguishable under our criteria.
      - `vendor` string
      - `tier` string
      - `description` string
      - `terms_superseded` TermsSuperseded, nullable: 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`.
        - `change_date` string, date, required
        - `change_type` string, required
        - `summary` string, required
        - `reading` object, nullable, required
          - `date` string, date, required
          - `url` string, uri, required
          - `label` string, required
          - `terms` string, required
        - `notice` string, required
      - `conditions` object[]: 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.
        - `text` string, required
        - `quote` string, required
        - `url` string, uri, required
        - `read_on` string, date, required
        - `rules_out` string[]
      - `url` string, uri
      - `verified_date` string, date
      - `last_read_date` string, date: The day of the latest read we hold of this vendor's cited page, whatever that read concluded: our re-read, a source check that read the page, a change our gate refused that was read from the same URL, or the read its terms were restated from (#1858). It can fall before verified_date, because verified_date is a stamp the record carries rather than a confirmation the store can always source (#1635).
      - `risk_level` 'stable' | 'caution' | 'risky', nullable: The same level /api/offers publishes for this record, from the same function (#1486). Null where we withhold it rather than publish a favourable one we cannot stand behind. A null always arrives with the field naming the rule that withheld it, and with level_withheld_because.
      - `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` object, nullable: Non-null for an offer we have decided not to list (#1241). A candidate set is drawn from ungated offers, so this is null in practice and is carried because the level's rules are one set.
      - `source_check` object, nullable: Our last read of the page we cite. An outcome other than ok withholds a favourable level.
      - `level_withheld_because` string, nullable: The sentence naming the rule that withheld the level, or null where a level is published.
      - `stability` 'stable' | 'watch' | 'volatile' | 'improving', nullable: Null where a favourable class would rest on records we cannot stand behind (#1561). Withheld by every reason that withholds risk_level: an unreachable pricing page, a page stating no amount, tier or rate we can read, a refused read, a narrowing that cites no source or that no archived copy of the vendor's page has confirmed, or a gated listing. A null always arrives with stability_withheld_because.
      - `stability_withheld` object, nullable: Non-null where a standing narrowing for this vendor cites no source (no_source) 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), so a favourable stability class is withheld rather than published. Where both kinds stand, reason is unconfirmed; records counts both.
        - `reason` 'no_source' | 'unconfirmed'
        - `records` number
      - `stability_withheld_because` string, nullable: The code naming the rule that withheld the stability class, or null where a class is published.
      - `link_unreachable` object, nullable: Non-null where the offer's own link has not resolved for us (#1046).
        - `last_reachable` string, nullable
        - `checked` string
        - `terminal` boolean
      - `demerits` object[]: Empty when we hold nothing against the offer.
        - `code` string
        - `points` integer
        - `reason` string
        - `date` string
        - `about_us` boolean: True when the demerit describes a limit of ours rather than a fact about the vendor.
      - `disclosures` object[]: Recorded changes shown but never ranked on.
        - `code` string
        - `date` string
        - `summary` string
    - `tie_count` integer: How many offers tie at zero demerits in this role.
    - `eligible_count` integer
    - `demoted_count` integer
    - `excluded_count` integer
    - `reason` 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
  - `method` object
    - `policy` string
    - `not_modelled` string
    - `criteria_url` string
  - `total_monthly_cost` string
  - `limitations` string[]
  - `upgrade_path` 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

- `400`: Missing use_case parameter

---

[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)
