# Compare purchase options

Find offers for the same product, compatible alternatives, or models with specification upgrades and downgrades.

## What this endpoint does

Endpoint

```
POST https://mcp.gaisho.co/v1/tools/find_deals
```

Compare purchase options for a reference product. Choose between offers for that product, alternative models with compatible key specifications, and models with upgrades or downgrades. Results connect each price to its seller and purchase link and explain the important differences.

- Find other places to buy the product selected from search.
- Find alternative models under a shopper’s budget while keeping required specifications.
- Explore what changes when moving to a model with more or fewer capabilities.

## Before you call

- same_product is the default. Send requirements to search_products first, then pass the selected product_id without query. Do not attach query to a same_product call.
- For dupe or ladder, keep the reference item in product and additional requirements in query. Alternatives are not automatically cheaper: explicitly request cheaper options only when the shopper asks.
- Preserve stated constraints such as battery life no worse, keep the capacity, or capacity may be smaller. Mark preferences as preferred. Do not silently loosen requirements to get more results.
- A budget is optional. Rating, review-count and ordering requests are not filters here; read the reported unresolved requirements.

Send a JSON object as the request body and authenticate with Authorization: Bearer <API key>. Amounts are in USD unless the result explicitly says otherwise.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| product | string | Required | Product URL, ASIN, GTIN, ID, model or description. Pass URLs and IDs alone. Must contain non-whitespace text; at most 2,000 characters (enforced by the service). |
| mode | string | Optional | same_product for the selected product; dupe for alternatives; ladder for specification upgrades or downgrades. One of: same_product, dupe, ladder. Default: same_product. |
| query | string | Optional | Additional requirements for dupe or ladder only. Omit for same_product; send those requirements to search_products first. Up to 2000 characters. |

## Example request

Set GAISHO_API_KEY in your server or local environment. These examples use a JSON POST request; Python uses the standard library and JavaScript uses Node.js fetch. The 60-second client timeout in Python and JavaScript is configurable, not a service latency guarantee.

cURL

```
curl --fail-with-body 'https://mcp.gaisho.co/v1/tools/find_deals' \
  -H "Authorization: Bearer $GAISHO_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{
  "product": "PRODUCT_ID_FROM_SEARCH",
  "mode": "same_product"
}'
```

Python

```
import json
import os
import urllib.error
import urllib.request

arguments = json.loads(r'''{
  "product": "PRODUCT_ID_FROM_SEARCH",
  "mode": "same_product"
}''')
request = urllib.request.Request(
    "https://mcp.gaisho.co/v1/tools/find_deals",
    data=json.dumps(arguments).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + os.environ["GAISHO_API_KEY"],
        "Content-Type": "application/json",
    },
    method="POST",
)
try:
    with urllib.request.urlopen(request, timeout=60) as response:
        payload = json.load(response)
    print(json.dumps(payload, indent=2))
except urllib.error.HTTPError as error:
    print(error.read().decode("utf-8"))
    raise
```

JavaScript

```
// Node.js 18+; keep this code and the key on your server.
const response = await fetch("https://mcp.gaisho.co/v1/tools/find_deals", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GAISHO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "product": "PRODUCT_ID_FROM_SEARCH",
  "mode": "same_product"
}),
  signal: AbortSignal.timeout(60_000),
});
const body = await response.text();
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${body}`);
}
const payload = JSON.parse(body);
console.log(payload);
```

## Common request patterns

### Find lower-priced alternatives

Retain the shopper’s required runtime; a lower price does not authorize a weaker specification.

JSON request body

```
{
  "product": "PRODUCT_ID_FROM_SEARCH",
  "mode": "dupe",
  "query": "cheaper than the reference, battery life no worse"
}
```

### Explore specification changes

Only include permission to reduce a specification if the shopper gave it.

JSON request body

```
{
  "product": "PRODUCT_ID_FROM_SEARCH",
  "mode": "ladder",
  "query": "under $300, capacity may be smaller"
}
```

## Response fields

The HTTP response contains tool and a top-level summary. The other payload fields are inside result.

| Field | Type | Description |
| --- | --- | --- |
| tool | string | The tool called: find_deals. |
| summary | string | Readable comparison summary. |
| result.results | array | Purchase options with identifiers, title, URL, price, currency, seller, coupon and differences. |
| result.comparison | object | Counts of products, offers and retailers compared. |
| result.coverage | string | Scope and limitations of the comparison. |
| result.web_candidates | array, optional | Unverified webpage leads, when available; check details before recommending. |
| result.results[].product_id | string | Product identifier for further research. |
| result.results[].offer_id | string \| null | Stored listing ID; null for an option that must be followed by URL. |
| result.results[].title / url | string | Listing title and purchase URL. |
| result.results[].price / currency | number / string | Quoted amount and currency; keep the quantity and coupon terms with it. |
| result.results[].seller | object | Seller platform, ID and name; individual values can be unavailable. |
| result.results[].coupon | object \| null | Coupon information, including the applicable discount when known. |
| result.results[].differences | string[] | Differences, quantity information and unverified requirements. |
| result.comparison.product_count / offer_count / retailer_count | integer | Counts for the comparison, not just the returned rows. |
| result.reference | object, optional | Reference product and price when included in an alternative comparison. |
| result.reference.product_id / title | string / string \| null | Reference product identity, when reference is present. |
| result.reference.price / currency | number / string | Reference quote and its currency, usually USD; price covers the reference quantity. |
| result.results[].price_delta | number, optional | Price difference in reference.currency after converting the option to the reference quantity. Negative means cheaper; positive means more expensive. Not a percentage or necessarily price minus reference.price. Omitted when a comparable difference is unavailable. |
| result.results[].direction | string, optional | Ladder only: up for a specification upgrade, down for a downgrade, mixed for changes in both directions. This describes specifications, not price or an overall recommendation. |
| result.set_aside | array, optional | Listings excluded from the same-product options, with reasons when available. |

## Example response

Examples are illustrative and may omit optional detail fields. Prices are not live quotes; identifiers and example.com URLs are placeholders to replace, not usable product records.

HTTP 200 · illustrative, shortened

```
{
  "tool": "find_deals",
  "summary": "Compared prices for 1 product, 1 eligible offer and 1 retailer. Returned 1 offer. Prices exclude tax and shipping.",
  "result": {
    "results": [
      {
        "product_id": "PRODUCT_ID_FROM_SEARCH",
        "offer_id": "OFFER_ID_FROM_SEARCH",
        "title": "Example cordless vacuum",
        "url": "https://example.com/vacuum",
        "price": 179.99,
        "currency": "USD",
        "seller": {
          "platform": "example.com",
          "id": "EXAMPLE_SELLER_ID",
          "name": "Example Store"
        },
        "coupon": null,
        "differences": [
          "Quantity: 1 item; unit price: 179.99 USD/item."
        ]
      }
    ],
    "comparison": {
      "product_count": 1,
      "offer_count": 1,
      "retailer_count": 1
    },
    "coverage": "Compared prices for 1 product, 1 eligible offer and 1 retailer. Returned 1 offer. Prices exclude tax and shipping."
  }
}
```

## How to read the result

- result.results contains purchase options. Each option’s price, currency, coupon, seller, URL and differences belong together. Read differences even in same_product mode: unspecified package sizes or options may vary.
- The quoted price excludes tax and shipping and already deducts the applicable coupon.discount. The coupon may still require application and eligibility. A null discount was not deducted; a null coupon does not prove there is no promotion.
- comparison counts the products, eligible offers and known retailers compared for the request. These counts can exceed the returned rows; they do not measure every seller on the internet.
- coverage describes scope and limitations. web_candidates, when present, are unverified page leads rather than priced offers. An option with offer_id null is followed using its purchase URL; do not send it to get_offer without a stored offer ID.

## Empty results and errors

| Situation | How to handle it |
| --- | --- |
| No eligible purchase options | Read coverage and any excluded conditions; do not present an unverified webpage lead as a confirmed offer. |
| A requested feature is unverified | Explain the unresolved condition and check product or listing details before recommending. |
| A different version or package appears | Read differences and set_aside when present. Check the exact variant and quantity instead of comparing totals blindly. |
| query supplied with same_product | Move those requirements to search_products, select the product, then call without query. |

A successful request can return no candidates, missing information or an unavailable assessment. Check the result before recommending a product. For request failures, use the error code and message; retrying the same invalid input will not fix it.

- [Error handling and retries](https://gaisho.co/docs/api#errors): HTTP statuses, structured errors, rate limits and quota responses.

## What to call next

- [Get offer details](https://gaisho.co/docs/api/get_offer): Read a listing’s purchase URL, seller, price, coupon terms and stated product information.
- [Check a price](https://gaisho.co/docs/api/vet_price): Assess whether a USD price is reasonable for a stated product or shopping requirement.
- [Check a seller](https://gaisho.co/docs/api/vet_seller): Assess a seller or online store and return a trust assessment with supporting facts.

- [Use this tool through MCP](https://gaisho.co/docs/mcp/find_deals): The same capability through the other connection.
- [REST API conventions](https://gaisho.co/docs/api): Authentication, response envelopes and request handling.
