Findcheap API
The Findcheap API has two capabilities:
- Extract — takes a product page URL as input and returns structured product data from the page.
- Search — takes a product page URL as input and searches the internet to find where the product is available for a lower price. For some products, the agent only returns top-level data, and a subsequent call to the Resolve endpoint is needed if further details are desired.
Every call is an HTTPS POST with a JSON body and a JSON answer.
Authentication
Every request carries an API key in the Authorization header. Keys begin with fc_live_ and are issued on request from the access page.
Authorization: Bearer fc_live_xxxxxxxxxxxxxxxxA request without a valid key is answered 401. The key goes in the header only; it is not accepted in the URL.
Markets
Every call runs in a market, the country where the shopper is located. The agent reads the product page from the country specified in the market param. This is important to note because many stores show different product details to users from different countries (ex. different prices and currencies).
Set it with the market field. If left unspecified, the agent uses the page’s TLD to determine its market (.in → India, .co.uk → UK, .com.au → Australia, .ca → Canada); all other addresses default to the US.
The table below shows each market, its corresponding currency, and the primary marketplaces the agent searches in that market when Search is called.
| market | Currency | Marketplaces Search compares against |
|---|---|---|
| US | USD | Amazon, Walmart, eBay, AliExpress, Shein, and other sites |
| CA | CAD | Amazon, AliExpress, other sites |
| AU | AUD | Amazon, eBay, AliExpress, other sites |
| IN | INR | Amazon, Flipkart, other sites |
| GLOBAL | USD | Amazon, eBay, AliExpress, other sites; the page is fetched from the store's own country when its address names one |
Extract
The Extract endpoint takes a product page URL as input and returns structured product identity data. It answers within 30 seconds, and has a median response time of 4 seconds. A page that cannot be reached in time answers 502 unreachable, and a page that is reached but cannot be read in time answers 504 timeout.
Request body
The product page’s address, https only. A short link or a share link is followed to the page it names.
"US", "CA", "AU", "IN" or "GLOBAL". If omitted, the market is inferred from the URL; otherwise, it defaults to US (see Markets).
Response
| Field | Type | Meaning |
|---|---|---|
| is_product_page | boolean | true when the address is one product's own page. false for a home page, a category or search page, a cart, an article, or a page that is gone; the other fields are then null. |
| url | string | The page the read ended on, after any redirects. |
| market | string | The market the page was read in. |
| title | string | null | The product's name as the page states it. |
| image | string | null | The image address of the main product photo. |
| price | number | null | The standing price a shopper in the market pays: not a first-order, coupon or membership price. null when the page does not show one. |
| currency | string | null | ISO 4217 code of price. |
| brand | string | null | The brand, when the page names it. |
| in_stock | boolean | null | true when the page says the product can be bought now, false when it is out of stock or discontinued, null when the page does not say. |
| reason | string | null | Why a read fell short: gone (the store says the page no longer exists), not_a_product_page, incomplete (a product page whose title or photo could not be read), price_in_another_currency (the price shown was another market's version and was withheld). null on a clean read. |
A page that could not be fetched at all is not a 200: it is a 502 unreachable, so you can tell “gone” from “try again later”.
Example call
# -H adds a header (your key, then the body's format); -d is the JSON body.
# market is the shopper's country; omit it and the address decides (see Markets).
curl https://api.findcheap.ai/v1/extract \
-H "Authorization: Bearer $FINDCHEAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.wearcomet.com/products/astra-burnt-toast", "market": "IN"}'Example response
{
"is_product_page": true,
"url": "https://www.wearcomet.com/products/astra-burnt-toast",
"market": "IN",
"title": "Astra BURNT TOAST",
"image": "https://www.wearcomet.com/cdn/shop/files/...",
"price": 4899.0,
"currency": "INR",
"brand": "wearcomet",
"in_stock": true,
"reason": null
}Search
The Search endpoint takes a product page URL as input and searches the internet to find where the product is available for a lower price. It mirrors the functionality of the Chrome extension. It has a maximum budget of 90 seconds, but usually completes much sooner; a search that times out returns 504 timeout.
First, the agent calls Extract to read the product page. Then, it searches the internet according to the marketplaces specified in the request. Results are sorted into two arrays, each containing up to 10 results:
same_product— the same brand and model as the original product. Sorted by cheapest first.similar_products— products that don’t match the same brand and model of the original product, but would be considered acceptable equivalents. Sorted by cheapest first.
Request body
The product page’s address.
The region that was used to read the original product page, and search for cheaper results. Search results are displayed in this market’s currency.
Tells the agent which marketplaces to search. Only marketplaces available in the consumer’s market count (see Markets). If omitted, the agent autonomously chooses which marketplaces to search for best results, as it does in the Chrome extension.
Accepted values: "amazon", "walmart", "ebay", "aliexpress", "shein", "flipkart", "other_sites". An unknown name is a 400.
If condition is set to "used", the agent searches for where the product can be bought secondhand. This search mode is not available in India, and is refused for grocery and consumable products.
Response
| Field | Type | Meaning |
|---|---|---|
| product | object | The page as read by the Extract call. |
| same_product | card[] | A list of up to 10 alternative locations where the original product is available, sorted by lowest price first. |
| similar_products | card[] | A list of up to 10 places where similar products are available, sorted by lowest price first. |
| page_is_cheapest | boolean | true when the original page already has the lowest price Findcheap found for the product. |
| product_name | string | null | The name of the product in a usable format, e.g. "JLab JBuds Lux ANC". |
Card structure
Every listing in same_product and similar_products has the following shape:
| Field | Type | Meaning |
|---|---|---|
| title | string | The listing's title. |
| price | number | The listing's price, in the market's currency. |
| currency | string | ISO 4217 code. |
| url | string | null | The listing’s direct link. null for a listing that carries resolve_id; see Resolve. |
| resolve_id | string | null | Present when url is null; see Resolve. |
| image | string | The image address of the listing's main product photo. |
| retailer | string | Where the product is being sold. |
| rating | number | null | The retailer's own rating figure: a review average out of 5 on most, the seller's feedback percentage on eBay. |
| ratings_count | number | null | Reviews or feedback entries behind rating. |
| over_page_price | boolean | null | Only included for same_product cards. true when the card is greater than or equal to the original page's price (pack-adjusted), false when cheaper, null when the page's price was unknown. |
| unit_size | string | null | Pack or size context for multi-pack products, e.g. 12 oz. |
| pack_count | number | null | Units per pack, when detected. |
| normalized_price | number | null | Per-unit price, for comparing different pack sizes. |
| coupon | string | null | The best coupon for the listing, if any are available. Discounts with strings attached are ignored (ex. “subscribe and save!”). |
| promo_code | string | null | The best promo code for the listing, if any are available. Promo codes with strings attached are ignored. |
| price_before_discount | number | null | The listing price before the coupon or promo code was applied (the main price field is the price after applying the discount). null when there is no discount. |
| discount_type | "percent" | "fixed" | null | Whether the discount is a percentage or fixed amount. |
| discount_value | number | null | 6 for 6% off; 30 for 30 off in the card's currency. |
When the address is not a product page, Search answers 422 not_searchable and names the reason, so one call tells you both whether the page sells something and what Findcheap found.
Example call
# -H adds a header (your key, then the body's format);
# -d is the JSON body. A search takes 20–45 s.
curl https://api.findcheap.ai/v1/search \
-H "Authorization: Bearer $FINDCHEAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.jlab.com/products/star-spangled-jbuds-lux-anc-headphones"}'Example response
{
"product": {
"title": "Star Spangled JBuds Lux ANC Headphones",
"price": 99.99, "currency": "USD", "brand": "JLab",
"image": "https://www.jlab.com/cdn/shop/files/...",
"url": "https://www.jlab.com/products/star-spangled-jbuds-lux-anc-headphones",
"market": "US", "in_stock": true
},
"same_product": [
{
"title": "JLab JBuds Lux ANC Over Ear Headphones Bundle with Go Air Pop+ True Wireless Earbuds, Active Noise Cancelling, Bluetooth",
"price": 67.5, "currency": "USD",
"url": "https://www.amazon.com/dp/B0FWTKRMFV",
"image": "https://m.media-amazon.com/images/I/51KhhANNLVL._AC_SL500_.jpg",
"retailer": "Amazon", "rating": 4.3, "ratings_count": 79,
"over_page_price": false
}
],
"similar_products": [
{
"title": "JLab Kids Headphones Active Noise Cancelling Wireless Wired Blue Red",
"price": 5.99, "currency": "USD",
"url": "https://www.ebay.com/itm/377483228740",
"image": "https://i.ebayimg.com/images/g/-QQAAeSw~ttqoREo/s-l500.jpg",
"retailer": "eBay", "rating": 99.5, "ratings_count": 29294
}
],
"page_is_cheapest": false,
"product_name": "JLab JBuds Lux ANC"
}Resolve
For some results, the Search endpoint only returns top-level data, to maximize search efficiency. These results carry a resolve_id instead of a url. Calling the Resolve endpoint with a resolve_id returns a direct link to the corresponding product on the merchant’s website and the merchant’s name.
An id is valid for up to two hours after the search that produced it. In rare cases, it may expire sooner, so it is advised to resolve any desired ids shortly after the search has completed.
Request body
The card’s resolve_id.
Response
| Field | Type | Meaning |
|---|---|---|
| resolved | boolean | true when url holds the retailer's link. |
| url | string | null | The product's direct link on the merchant's website, tracking parameters stripped. null when not resolved. |
| retailer | string | null | The merchant's name, e.g. "B&H Photo". null when not resolved. |
An id that is unknown, expired, or could not be resolved answers { "resolved": false, "url": null, "retailer": null } with a 200; it is never an error.
Example call
# The id is a Search card's resolve_id (a card whose url is null).
curl https://api.findcheap.ai/v1/resolve \
-H "Authorization: Bearer $FINDCHEAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id": "7c1f0e2b4d9a4f3e8b6a2c5d9e1f0a3b"}'Example response
{ "resolved": true, "url": "https://www.bhphotovideo.com/c/product/...", "retailer": "B&H Photo" }Errors
Every error is an HTTP status with one JSON shape:
{
"error": {
"type": "not_searchable",
"message": "The address is not a readable product page (not_a_product_page)."
}
}| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request | A malformed body or a missing field; the message names it. |
| 400 | invalid_url | The address is not an https page address. |
| 400 | unknown_market | market is not one of US, CA, AU, IN, GLOBAL. |
| 401 | unauthorized | No key, or a key that is not valid. |
| 422 | not_searchable | Search: the address is not a readable product page; the message names the reason. |
| 422 | refused | Search: a used-condition search on grocery or consumable products, or one with no determinable brand. |
| 429 | rate_limited | This key is over one of its limits. Retry after the number of seconds in the Retry-After header. |
| 502 | unreachable | The page could not be fetched right now; the store refused or did not answer. A retry later may succeed. |
| 504 | timeout | The call ran past its time limit on our side: 30 seconds for Extract (the page was reached but not read in time), 90 seconds for Search. Safe to retry. |
| 500 | search_failed | Something failed on our side. Safe to retry with backoff. |
Rate limits
The rate limits for each plan are listed on the developers page.