Undrop API
Search Undrop from your own programs: send a product's name, a store or image link, or photos, and get back where it sells for less.
Get a keyGet a key
Create a key on your account page, in the API section. You need an account with a confirmed email address.
You can have 3 keys at a time. A key is shown once, when you create it: copy it then. You can revoke a key from the same page; programs that use it stop working at once.
Authentication
Send the key in the Authorization header of every request: Authorization: Bearer ud_….
Use the key from your server only. Never put it in front-end code, a web page or an app you hand out: anyone could read it there. The API sends no CORS headers, so browsers can't call it anyway. If a key leaks, revoke it and create another.
Start a search
POST https://undrop.app/api/public/v1/searches takes what the site takes. Each search runs like one made on the site, at the standard level: the advanced analysis isn't offered through the API.
curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "wireless earbuds with charging case", "country": "US"}'curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://shop.example/products/neck-fan", "country": "US"}'curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-F "images=@ad-screenshot.jpg" \
-F country=US \
-F referencePrice=39.99curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "repair",
"part": "door seal",
"brand": "Bosch",
"model": "WAT28400",
"imageUrls": ["https://example.com/rating-plate.jpg"]
}'curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-F kind=repair \
-F "part=door seal" \
-F "images=@rating-plate.jpg" \
-F "images=@old-part.jpg"| Field | What it takes |
|---|---|
query | The product's name, 2 to 200 characters. |
url | An http(s) link to a store page, a video or an image. |
images | Image files, as multipart form data: up to 8 for a price search, 3 for a part, 4.4 MB in all. |
kind | repair to look for a replacement part. Leave it out for a price search. |
part, brand, model | A part search's words, each optional, up to 120 characters. Send at least a photo or one of them. |
imageUrls | A part's photos by their http(s) links, in JSON (up to 3): we fetch them as an upload. |
country | Optional: two capital letters, such as US: where the listings are compared for. Otherwise your account's country, or where the request comes from. |
currency | Optional: three capital letters, such as USD. Otherwise your account's currency, or the country's. |
referencePrice | Optional, for a price search: the price you saw, a number above 0. Savings are counted from it. |
Get the results
A search takes from a few seconds to about a minute and a half. The POST answers at once with 202 Accepted: the search, its status running, and its address in the Location header.
HTTP/1.1 202 Accepted
Location: https://undrop.app/api/public/v1/searches/0199de4a-6f3b-7c21-9a8e-5b2d1c0f4e37
Retry-After: 3
Content-Type: application/json; charset=utf-8
{"id": "0199de4a-6f3b-7c21-9a8e-5b2d1c0f4e37", "kind": "price", "status": "running", …, "results": []}To get the results in one call, add Prefer: wait: the answer waits until the search ends, 110 seconds at most, and comes with 200 OK. Prefer: wait=30 waits 30 seconds at most. If the search is still running when the wait ends, the answer is 202, as without it.
curl https://undrop.app/api/public/v1/searches \
-H "Authorization: Bearer $UNDROP_API_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: wait" \
-d '{"query": "wireless earbuds with charging case", "country": "US"}'Otherwise, read the search at its address until its status is no longer running. While it runs, the answer carries Retry-After: 3: wait that many seconds before you read it again. A key reads only its own searches: any other id answers 404.
curl https://undrop.app/api/public/v1/searches/0199de4a-6f3b-7c21-9a8e-5b2d1c0f4e37 \
-H "Authorization: Bearer $UNDROP_API_KEY"The answer
A search, in JSON. Its results are the paid listings, in the site's order: the closest match first, then the lowest price.
| Field | Type | What it is |
|---|---|---|
id | string | The search's id. |
kind | string | price, or repair for a replacement part. |
status | string | running, then succeeded, partial (some sources didn't answer in time), failed, refused (content we don't search) or not_product (no product found). |
country | string | Where the listings are compared for, in two letters. |
currency | string | The currency prices are converted to, in three letters. |
createdAt, completedAt | string, null | ISO 8601 dates in UTC; completedAt is null while the search runs. |
error | object, null | { "code": … } when the search ended without results: one of the codes under Errors. |
results | array | The paid listings; empty while the search runs. |
Each result:
| Field | Type | What it is |
|---|---|---|
id | string | The listing's id. |
title | string | The listing's title. |
source | string | The marketplace, such as aliexpress, temu or amazon. |
store | string | The store's name, as the listing gives it. |
price | object, null | amount and currency, in the search's currency when it could be converted. Null when the price can't be shown: Amazon's never are. |
imageUrl | string, null | The listing's photo. |
link | string | Undrop's link to the listing. Use it exactly as given. |
match | string | How close it is to the product: same, variant (another option of it), similar, different or unverified. |
isSupplier | boolean | Sold on a supplier marketplace, such as AliExpress or Alibaba, rather than by a retailer. |
savingsPct | number, null | How much less it costs than the price seen, in percent, for the same product or another option of it. |
part | object | A part search only: origin (oem, compatible, used or unknown), verified (an original we can stand behind) and maker. |
{
"id": "0199de4a-6f3b-7c21-9a8e-5b2d1c0f4e37",
"kind": "price",
"status": "succeeded",
"country": "US",
"currency": "USD",
"createdAt": "2026-10-13T14:02:11.482Z",
"completedAt": "2026-10-13T14:02:39.907Z",
"error": null,
"results": [
{
"id": "0199de4a-8d20-7e1f-b3c4-2a6e9f1d0c58",
"title": "Wireless earbuds with charging case, Bluetooth 5.3",
"source": "aliexpress",
"store": "AliExpress",
"price": {
"amount": 8.49,
"currency": "USD"
},
"imageUrl": "https://images.example/earbuds-white.jpg",
"link": "https://undrop.app/go/0199de4a-8d20-7e1f-b3c4-2a6e9f1d0c58",
"match": "same",
"isSupplier": true,
"savingsPct": 71.6
},
{
"id": "0199de4a-8d20-7e1f-b3c4-31f07a4be912",
"title": "Wireless Earbuds, Bluetooth Headphones with Charging Case",
"source": "amazon",
"store": "Amazon",
"price": null,
"imageUrl": "https://images.example/earbuds-black.jpg",
"link": "https://undrop.app/go/0199de4a-8d20-7e1f-b3c4-31f07a4be912",
"match": "variant",
"isSupplier": false,
"savingsPct": null
}
]
}Limits
Each key can start 20 searches a day, counted from midnight UTC, and 10 searches an hour. A search that fails or finds nothing doesn't count against the day. Reading searches has its own limit, 100 reads an hour, so a key out of searches still gets its results. A key can have 3 searches running at once.
These are today's limits for every key, and they may change. Need more for a project? Write to fcarreau@gmail.com: we can raise a key's own limits.
Over a limit, the API answers 429 Too Many Requests, with Retry-After (the seconds to wait) and the reason in details.reason: hourly_limit or daily_quota.
{
"error": {
"code": "RATE_LIMITED",
"message": "This key has used today's 20 searches (UTC).",
"details": {
"reason": "daily_quota",
"limit": 20,
"used": 20,
"retryAfterSeconds": 35940
}
}
}Errors
An error answers { "error": { "code", "message", "details" } }. Rely on the code: the message is written for people, and may change.
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | A field is missing or wrong, or advanced was asked for. |
| 400 | URL_NOT_ALLOWED | The link isn't a public http(s) address. |
| 401 | UNAUTHORIZED | No key, or a key that isn't valid or was revoked. |
| 403 | FORBIDDEN | The key's account is suspended. |
| 404 | NOT_FOUND | No search with that id for this key. |
| 409 | SEARCH_IN_PROGRESS | The key already has 3 searches running. |
| 413 | PAYLOAD_TOO_LARGE | The upload is over 4.4 MB. |
| 415 | UNSUPPORTED_MEDIA | A photo link doesn't lead to an image. |
| 422 | URL_UNREACHABLE | We couldn't open a photo's link. |
| 429 | RATE_LIMITED | Over the hourly or the daily limit: see Limits. |
| 503 | MAINTENANCE | Searches are paused for maintenance. |
| 503 | COST_CAP_REACHED | The service has reached its capacity for the day. |
| 500 | INTERNAL | Something broke on our side. Try again later. |
A search that ends without results says why in error.code, such as URL_UNREACHABLE, NOT_A_PRODUCT, REFUSED or ANALYSIS_UNAVAILABLE.
Changelog
v1, October 2026: the first version. Searches by name, link, photos and replacement part; answers with paid links only.