> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# Price Comparison API

> Compare the BeatAPI price of one model configuration with the public prices other platforms list for it, with no API key.

# Price Comparison API

`POST /v1/price-comparisons` compares the BeatAPI price of one published pricing spec with the public prices other platforms list for the same spec. It needs no API key and is not billed.

The comparison is neutral. BeatAPI is ranked by the same rules as every other platform, and when another platform is cheaper the answer says so: `beatapi_position` is `not_lowest`.

## Quick start

```bash
curl https://api.beatapi.io/v1/price-comparisons \
  -H "Content-Type: application/json" \
  -d '{"model":"Nano Banana Pro"}'
```

With only `model`, the model's common configuration is compared with every platform that has a stored quote for it. Add `spec` to choose another configuration, and `providers` to choose the platforms.

## Request

| Field       | Type      |                   Required | Description                                                                                                                                                                     |
| ----------- | --------- | -------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`     | string    | Yes, unless `spec` is sent | The model's display name (`Nano Banana Pro`), its page slug, the shared prefix of its spec IDs, or one spec ID. Matching ignores case, and dots and dashes are interchangeable. |
| `spec`      | string    |                         No | One spec ID of that model from `GET /v1/pricing`, such as `nano-banana-pro-4k`. Without it the common configuration is compared.                                                |
| `providers` | string\[] |                         No | Platform IDs to compare, at most 20. Without it every platform with a stored quote for the spec takes part, BeatAPI included.                                                   |

`model` and `spec` are each at most 200 characters. When `model` is empty, `spec` is used to find the model.

### The common configuration

Without `spec`, one configuration is chosen by its shape, never by which platform wins:

* Text models: input tokens.
* Video models: the first 720p spec without reference video or video-to-video input.
* Image models: the first plain image spec, for example `nano-banana-pro-1k`.

When `model` is itself a spec ID, that spec is compared.

### Spec IDs

Spec IDs come from the anonymous price list: each entry of `data.groups[].specs[]` in `GET https://api.beatapi.io/v1/pricing` has an `id`. Every comparison also returns the model's `available_specs`.

### Platform IDs

Platform IDs are lowercase, as returned in `quotes[].provider`; `beatapi` is BeatAPI. When you send `providers`, only the listed platforms are compared, so include `beatapi` to rank BeatAPI. A listed platform that has no quote for this spec is returned as `pending_verification`. An ID the comparison table does not know is rejected with `400`.

## Response

A successful call returns `200` with the result in `data`, the same envelope as `GET /v1/pricing`. The response is not cached (`Cache-Control: no-store`).

The example below uses prices checked on 2026-11-11. Prices, dates and the `data_version` value change over time; call the endpoint for current figures.

```bash
curl https://api.beatapi.io/v1/price-comparisons \
  -H "Content-Type: application/json" \
  -d '{"model":"Nano Banana Pro","spec":"nano-banana-pro-1k","providers":["beatapi","atlascloud","fal","google","higgsfield","wavespeed"]}'
```

```json
{
  "data": {
    "status": "ready",
    "model": "Nano Banana Pro",
    "spec": {
      "id": "nano-banana-pro-1k",
      "label": "1 image · 1K",
      "label_zh": "1 张图片 · 1K",
      "category": "image",
      "unit": "image"
    },
    "available_specs": ["nano-banana-pro-1k", "nano-banana-pro-2k", "nano-banana-pro-4k"],
    "questions": [],
    "quotes": [
      {
        "provider": "beatapi",
        "status": "quoted",
        "provider_model_id": "Nano Banana Pro",
        "amount": "0.05",
        "currency": "USD",
        "quote_kind": "fixed",
        "matched_configuration": "1 image · 1K",
        "source_urls": ["https://api.beatapi.io/v1/pricing", "https://beatapi.io/nano-banana-pro-api"],
        "checked_at": "2026-11-11T10:00:00Z",
        "expires_at": null,
        "freshness_status": "fresh",
        "rank_eligible": true,
        "exclusion_reason": null
      },
      {
        "provider": "atlascloud",
        "status": "quoted",
        "provider_model_id": "google/nano-banana-pro/text-to-image",
        "amount": "0.14",
        "currency": "USD",
        "quote_kind": "estimate",
        "matched_configuration": "1k · 1:1 · 1 张 · 文生图 · 不开 web/image search · thinking 默认",
        "source_urls": ["https://www.atlascloud.ai/models/google/nano-banana-pro/text-to-image"],
        "checked_at": "2026-11-11T08:59:27Z",
        "expires_at": "2026-12-11T08:59:27Z",
        "freshness_status": "fresh",
        "rank_eligible": false,
        "exclusion_reason": "Estimated by the provider's calculator, not a fixed price."
      },
      {
        "provider": "fal",
        "status": "quoted",
        "provider_model_id": "fal-ai/nano-banana-pro",
        "amount": "0.15",
        "currency": "USD",
        "quote_kind": "fixed",
        "matched_configuration": "Nano Banana Pro text-to-image resolution=1K, no web search",
        "source_urls": ["https://fal.ai/models/fal-ai/nano-banana-pro"],
        "checked_at": "2026-11-11T08:50:26Z",
        "expires_at": "2026-12-11T08:50:26Z",
        "freshness_status": "fresh",
        "rank_eligible": true,
        "exclusion_reason": null
      },
      {
        "provider": "google",
        "status": "quoted",
        "provider_model_id": "gemini-3-pro-image",
        "amount": "0.1344",
        "currency": "USD",
        "quote_kind": "formula",
        "matched_configuration": "gemini-3-pro-image text-to-image 1K 1:1, Standard paid tier, 1 output image",
        "source_urls": ["https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image"],
        "checked_at": "2026-11-11T08:50:17Z",
        "expires_at": "2026-12-11T08:50:17Z",
        "freshness_status": "fresh",
        "rank_eligible": true,
        "exclusion_reason": null
      },
      {
        "provider": "higgsfield",
        "status": "unsupported_configuration",
        "amount": null,
        "currency": "USD",
        "source_urls": ["https://open.higgsfield.ai/pricing"],
        "checked_at": "2026-11-11T08:48:09Z",
        "expires_at": "2026-12-11T08:48:09Z",
        "freshness_status": "fresh",
        "rank_eligible": false,
        "exclusion_reason": "The provider does not offer this model or configuration."
      },
      {
        "provider": "wavespeed",
        "status": "quoted",
        "provider_model_id": "google/nano-banana-pro/text-to-image",
        "amount": "0.126",
        "currency": "USD",
        "quote_kind": "fixed",
        "matched_configuration": "1k · 1:1 · 1 张 · 文生图 · 不开 web/image search",
        "source_urls": ["https://wavespeed.ai/models/google/nano-banana-pro/text-to-image"],
        "checked_at": "2026-11-11T08:52:36Z",
        "expires_at": "2026-12-11T08:52:36Z",
        "freshness_status": "fresh",
        "rank_eligible": true,
        "exclusion_reason": null
      }
    ],
    "summary": {
      "winner_providers": ["beatapi"],
      "lowest_amount": "0.05",
      "ranking_scope": ["beatapi", "wavespeed", "google", "fal"],
      "excluded_providers": ["atlascloud", "higgsfield"],
      "coverage_complete": true,
      "beatapi_position": "lowest",
      "conclusion": "beatapi has the lowest price of 4 verified providers at $0.05.",
      "benchmarks": {
        "provider_count": 4,
        "mean_scope": ["beatapi", "wavespeed", "google", "fal"],
        "mean_amount": "0.1151",
        "second_provider_amount": "0.126",
        "second_provider_providers": ["wavespeed"],
        "lowest_below_second_percentage": "60.32",
        "lowest_below_mean_percentage": "56.56"
      }
    },
    "data_version": "quotes-684-1794391200",
    "generated_at": "2026-11-11T10:00:00Z"
  }
}
```

How the figures in this example follow from the quotes:

* Four quotes are ranked: 0.05, 0.126, 0.1344 and 0.15. The estimate from `atlascloud` and the unsupported configuration at `higgsfield` are listed but not ranked.
* Lowest: 0.05 (`beatapi`). Second position: 0.126 (`wavespeed`). (0.126 − 0.05) ÷ 0.126 = 60.32%.
* Mean: (0.05 + 0.126 + 0.1344 + 0.15) ÷ 4 = 0.1151. (0.1151 − 0.05) ÷ 0.1151 = 56.56%.

### Top-level fields

| Field              | Description                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `status`           | `needs_clarification`, `ready`, `partial` or `unavailable` (see below).                                 |
| `model`            | The model that was compared, as named in the price list.                                                |
| `model_candidates` | Only with `needs_clarification`: models to send back as `model`, at most 10.                            |
| `spec`             | What was compared: `id`, `label`, `label_zh`, `category` and `unit`. `null` with `needs_clarification`. |
| `available_specs`  | Every spec ID of the model, for a follow-up call with `spec`.                                           |
| `questions`        | What to send next when the request is ambiguous; otherwise empty.                                       |
| `quotes`           | One entry per platform, `beatapi` first and the rest by ID.                                             |
| `summary`          | The ranking and the four metrics. `null` with `needs_clarification`.                                    |
| `data_version`     | Changes whenever the stored quotes change. Compare it between calls; do not parse it.                   |
| `generated_at`     | When the answer was computed, in UTC.                                                                   |

| `status`              | Meaning                                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `needs_clarification` | The model was not recognised, or more than one model matches. `quotes` is empty and `summary` is `null`; send one of `model_candidates`.               |
| `ready`               | Every listed platform has a final answer for the spec.                                                                                                 |
| `partial`             | At least one quote is `pending_verification`, `source_conflict`, `stale` or `incomplete_price`. The ranking covers only the quotes that can be ranked. |
| `unavailable`         | Nothing to compare. Handle it like an answer without a ranking.                                                                                        |

### Units

| `spec.unit` | Every amount is the price of                                            |
| ----------- | ----------------------------------------------------------------------- |
| `image`     | One image of the spec.                                                  |
| `video`     | One complete video of the length the spec label states, not one second. |
| `1M_tokens` | One million tokens of the spec, for example input tokens.               |

All amounts are in USD and are decimal strings, so a small price is never rounded to zero. `null` means there is no trustworthy price; `"0"` would mean free.

### Quotes

| Field                       | Description                                                                                                                                                                         |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`                  | Platform ID.                                                                                                                                                                        |
| `status`                    | `quoted`, `stale`, `incomplete_price`, `unsupported_configuration`, `no_public_price`, `source_conflict` or `pending_verification`.                                                 |
| `provider_model_id`         | The platform's own model or endpoint name, when known.                                                                                                                              |
| `amount`                    | The full price for one unit, or `null`.                                                                                                                                             |
| `quote_kind`                | `fixed` (a listed price), `formula` (computed from listed rates) or `estimate` (a calculator estimate).                                                                             |
| `matched_configuration`     | The configuration the price was read for, as written when it was checked.                                                                                                           |
| `note`                      | An optional remark, such as a time-of-day multiplier on the BeatAPI price.                                                                                                          |
| `source_urls`               | Public pages the price was read from.                                                                                                                                               |
| `checked_at` / `expires_at` | When the price was checked, and when it stops being ranked (30 days later). The BeatAPI quote is read live, so its `checked_at` is the time of the call and `expires_at` is `null`. |
| `freshness_status`          | `fresh`, `stale` or `unknown`.                                                                                                                                                      |
| `rank_eligible`             | Whether the quote takes part in the ranking.                                                                                                                                        |
| `exclusion_reason`          | Why the quote is listed but not ranked; `null` when it is ranked.                                                                                                                   |

A quote is ranked only when it has an amount, was checked within the last 30 days, matches the spec exactly and is not an estimate. Every other quote stays in `quotes` with its `exclusion_reason`, so you can see who was left out and why.

The BeatAPI quote is the live retail price from `GET /v1/pricing`, so a BeatAPI price change shows up in the next comparison. Other platforms' prices are checked by hand against their public pages.

### Summary and the four metrics

| Field                                       | Description                                                                                                                                                                                              |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `winner_providers` / `lowest_amount`        | The lowest ranked price and every platform at that price. A tie lists all of them.                                                                                                                       |
| `benchmarks.lowest_below_second_percentage` | How far the lowest price is below the price in second position. Ties count, so a tie for lowest gives `"0.00"`. `second_provider_amount` and `second_provider_providers` show that price and who has it. |
| `benchmarks.mean_amount`                    | The mean of every ranked price, BeatAPI included, one price per platform (`mean_scope`).                                                                                                                 |
| `benchmarks.lowest_below_mean_percentage`   | How far the lowest price is below that mean.                                                                                                                                                             |
| `benchmarks.provider_count`                 | How many platforms are ranked.                                                                                                                                                                           |
| `ranking_scope` / `excluded_providers`      | Ranked platforms from lowest to highest, and the listed platforms that are not ranked.                                                                                                                   |
| `coverage_complete`                         | `false` when the answer is `partial`.                                                                                                                                                                    |
| `beatapi_position`                          | `lowest`, `tied_lowest`, `not_lowest`, `insufficient_comparison` or `not_compared`.                                                                                                                      |
| `conclusion`                                | One English sentence that states the result.                                                                                                                                                             |

Percentages are strings with two decimal places, computed from unrounded amounts and rounded half up at the end. A metric is `null` when fewer than two platforms are ranked or its denominator is zero; it is never `NaN`. `mean_amount` is rounded to 8 decimal places.

| `beatapi_position`        | Meaning                                                                           |
| ------------------------- | --------------------------------------------------------------------------------- |
| `lowest`                  | BeatAPI alone has the lowest ranked price.                                        |
| `tied_lowest`             | BeatAPI shares the lowest price with at least one other platform.                 |
| `not_lowest`              | Another platform is cheaper.                                                      |
| `insufficient_comparison` | BeatAPI is ranked but no other platform is, so there is nothing to compare.       |
| `not_compared`            | BeatAPI is not ranked, for example because `providers` did not include `beatapi`. |

## When the model is ambiguous

A name that matches several models returns `200` with `needs_clarification`:

```bash
curl https://api.beatapi.io/v1/price-comparisons \
  -H "Content-Type: application/json" \
  -d '{"model":"banana pro"}'
```

```json
{
  "data": {
    "status": "needs_clarification",
    "model_candidates": ["Nano Banana Pro", "Nano Banana Pro Standard"],
    "spec": null,
    "questions": ["Which model do you want to compare? Send one of model_candidates as model."],
    "quotes": [],
    "summary": null,
    "data_version": "quotes-684-1794391200",
    "generated_at": "2026-11-11T10:00:00Z"
  }
}
```

Send one candidate unchanged as `model`. An exact name, slug or spec ID always wins over a partial match, so `Nano Banana Pro` resolves to one model. A name that matches no model also returns `needs_clarification`, without `model_candidates`.

## Errors

Errors use the standard [error object](/errors). A request you must change returns `400 bad_request` with a message that names the problem:

| Cause                               | `message`                                                                                      |
| ----------------------------------- | ---------------------------------------------------------------------------------------------- |
| The body is not valid JSON          | `Invalid price comparison request.`                                                            |
| `spec` does not belong to the model | `spec "nano-banana-2-1k" is not a spec of Nano Banana Pro; see GET /v1/pricing for its specs.` |
| An unknown platform in `providers`  | `unknown provider "example".`                                                                  |
| A platform listed twice             | `provider "fal" is listed twice.`                                                              |
| `model` or `spec` is too long       | `model and spec must be at most 200 characters.`                                               |
| More than 20 platforms              | `providers may list at most 20 entries.`                                                       |

Do not resend a `400` unchanged. A `500 internal_error` means the comparison could not be computed; retry once, then contact support with `request_id`.