Keepa can track product changes and notify you. Tracking through our API uses a separate list than your Keepa account. You can only view and manage the products you track through API calls.
Important:
- Each tracked product decreases your token refill rate. Once your refill rate reaches 0, you will not be able to track any more products or make any API requests that require tokens.
- If your API access is terminated, your tracking list will be deactivated and removed after 7 days, unless access is restored.
- If you switch to a lower-tier API plan that doesn't provide enough tokens for your current tracking list, the necessary amount of trackings will be deactivated and slated for removal after 7 days, unless they are reactivated in the interim.
- To manage your tracking expenses separately from other API uses, consider operating two separate Keepa accounts, each with its own API subscription.
Endpoint#
All tracking operations use the /tracking endpoint. The type parameter selects the operation. Add Tracking additionally supports HTTP POST.
To manage your API tracking, use the following commands:
| Operation | type | Token cost | Purpose |
|---|---|---|---|
| Add Tracking | add | 1 per tracking | Add a new tracking to your list. |
| Remove Tracking | remove / removeAll | 0 | Remove a tracking from your list or clear your entire list. |
| Get Tracking | get / list | 0 | Retrieve tracking information for a product on your list or the whole list. |
| Get Notifications | notification | 0 | Retrieve recent notifications. |
| Get Named Lists | listNames | 0 | Retrieve a list of all your named tracking lists. |
| Set Webhook | webhook | 0 | Update your webhook URL to receive push notifications. |
Types of Tracking#
There are two types of tracking:
1. Regular Tracking#
Allows tracking of the following price types:
- Amazon
- New
- Used
- List Price
- Collectible
- Refurbished
- Lightning Deal
- All offer counts
- Sales Rank
2. Marketplace Tracking#
Includes all data covered by Regular Tracking. Additionally tracks:
- Warehouse Deals
- Buy Box New & Used
- New 3rd Party FBA & FBM
- Rating
- Prime Exclusive
- Review Counts
- All Used and Collectible conditions with shipping costs
The first 20 offers of a product will be used for the tracking.
Token Cost#
Each tracked ASIN can have notification rules for multiple Amazon locales and have a different update interval. These factors determine how much your token refill rate is reduced for each tracking:
| Tracking type | Refill rate reduction |
|---|---|
| Regular Tracking | 0.9 tokens per update per locale tracked |
| Marketplace Tracking | 9 tokens per update per locale tracked |
Since the token refill rate is an integer, the tracking reduction rate is rounded. Your current reduction rate is part of every API response in the tokenFlowReduction field alongside other token stats.
Note: Your tokenFlowReduction will be updated every 5 minutes — not immediately after adding or removing trackings.
Token cost examples#
- 2000 Regular Trackings on a single locale with an update interval of 1 hour:
2000 × 0.9 tokens/hour = 1800 tokens/hour = 30 tokens/minute → decreased refill rate: 30 tokens - 700 Marketplace Trackings on a single locale with an update interval of 12 hours:
700 × 9 tokens / 12 hours = 525 tokens/hour = 8.75 tokens/minute → decreased refill rate: 9 tokens
You can track as many products as your token rate allows.
Besides the refill rate reduction, the request itself costs tokens only for Add Tracking (1 token per tracking); all other tracking operations cost 0 tokens.
Named Tracking Lists#
It is possible to use named tracking lists, which act as a logical separation of your tracking objects. This enables you to have multiple, different trackings for the same ASIN. By default, all trackings are managed in an unnamed list.
- All tracking requests (except Set Webhook) support the additional parameter
listto specify the name of the list the request should act on (e.g.,&list=user123). - A list name can be up to 64 characters long.
- You can manage up to 100,000 lists (contact us if you need more).
- Lists are created implicitly with the first added tracking and can be deleted by the Remove All request.
- If a tracking is added to a named list, the tracking object will have the list name set in the
trackingListNamefield. The same applies to notification objects. - You can retrieve a list of all your named lists using the Get Named Lists request.
Operations#
Add Tracking#
Token cost: 1 per tracking
Adds a new tracking to your list. If you already have an existing tracking for the ASIN on your list, it will be overridden, and if it was deactivated, it will be reactivated. You can batch up to 3,000 trackings in a single request to significantly speed up the process of adding multiple trackings.
You can choose between an HTTP GET or POST request.
GET format
POST format
The POST payload must contain a single or an array of tracking creation objects. You can specify up to 3,000 tracking creation objects in a single request in JSON array notation: [object, object, ...].
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | Must be add. |
tracking |
required (GET format only) | The tracking JSON contains an array of tracking creation objects. It must be URL-encoded if the GET format is used. Due to URL length limitations, do not use the GET method for batch requests. |
list |
optional | Name of the named tracking list this request should act on, e.g. &list=user123. |
Response:
- A
trackingsarray field containing the created or updated tracking object(s). - If an error occurred, the
errorfield is set. - If a tracking could not be added in a batch request, the
errorfield will include a comma-separated list of all failed ASINs.
Example
Add a price watch for ASIN B00M0QVG3W on amazon.de (domain 3), notifying via API when the Amazon price drops to 49.99 € (4999 euro cents) or the product comes back in stock:
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
TrackingRequest tracking = new TrackingRequest("B00M0QVG3W", AmazonLocale.DE, 1);
tracking.ttl = 0; // never expires
tracking.expireNotify = false;
tracking.desiredPricesInMainCurrency = true;
// thresholdValues / notifyIf can be set on the tracking object as needed
api.sendRequest(Request.getTrackingAddRequest(tracking))
.done(result -> {
for (Tracking t : result.trackings) System.out.println(t.asin);
})
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
// the PHP client serializes the tracking creation object from an array:
$tracking = [
"asin" => "B00M0QVG3W",
"ttl" => 0,
"expireNotify" => false,
"desiredPricesInMainCurrency" => true,
"mainDomainId" => 3,
"updateInterval" => 1,
"thresholdValues" => [
["thresholdValue" => 4999, "domain" => 3, "csvType" => 0, "isDrop" => true]
],
"notificationType" => [false, false, false, false, false, true, false]
];
$response = $api->sendRequestWithRetry(Request::getTrackingAddRequest($tracking));
if ($response->status == ResponseStatus::OK) {
print_r($response->trackings);
}import requests
tracking = {
"asin": "B00M0QVG3W",
"ttl": 0,
"expireNotify": False,
"desiredPricesInMainCurrency": True,
"mainDomainId": 3,
"updateInterval": 1,
"metaData": "customer-42",
"thresholdValues": [
{"thresholdValue": 4999, "domain": 3, "csvType": 0, "isDrop": True}
],
"notifyIf": [{"domain": 3, "csvType": 0, "notifyIfType": 1}],
"notificationType": [False, False, False, False, False, True, False],
"individualNotificationInterval": -1,
}
r = requests.post(
"https://api.keepa.com/tracking",
params={"key": "YOUR_API_KEY", "type": "add"},
json=[tracking],
)
data = r.json()
print(data["trackings"][0]["asin"], data["trackings"][0]["isActive"])const tracking = {
asin: "B00M0QVG3W",
ttl: 0,
expireNotify: false,
desiredPricesInMainCurrency: true,
mainDomainId: 3,
updateInterval: 1,
metaData: "customer-42",
thresholdValues: [{ thresholdValue: 4999, domain: 3, csvType: 0, isDrop: true }],
notifyIf: [{ domain: 3, csvType: 0, notifyIfType: 1 }],
notificationType: [false, false, false, false, false, true, false],
individualNotificationInterval: -1,
};
const res = await fetch("https://api.keepa.com/tracking?key=YOUR_API_KEY&type=add", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify([tracking]),
});
const data = await res.json();
console.log(data.trackings[0].asin, data.trackings[0].isActive);curl -s -X POST "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=add" \
-H "Content-Type: application/json" \
-d '[{
"asin": "B00M0QVG3W",
"ttl": 0,
"expireNotify": false,
"desiredPricesInMainCurrency": true,
"mainDomainId": 3,
"updateInterval": 1,
"metaData": "customer-42",
"thresholdValues": [
{ "thresholdValue": 4999, "domain": 3, "csvType": 0, "isDrop": true }
],
"notifyIf": [
{ "domain": 3, "csvType": 0, "notifyIfType": 1 }
],
"notificationType": [false, false, false, false, false, true, false],
"individualNotificationInterval": -1
}]'Example response
{
"timestamp": 1753651200000,
"tokensLeft": 4994,
"refillIn": 43000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 1,
"processingTimeInMs": 18,
"trackings": [
{
"asin": "B00M0QVG3W",
"createDate": 7663520,
"isActive": true,
"ttl": 0,
"expireNotify": false,
"mainDomainId": 3,
"updateInterval": 1,
"metaData": "customer-42",
"trackingListName": null,
"thresholdValues": [
{ "thresholdValueCSV": [7663520, 4999], "domain": 3, "csvType": 0, "isDrop": true }
],
"notifyIf": [
{ "domain": 3, "csvType": 0, "notifyIfType": 1 }
],
"notificationType": [false, false, false, false, false, true, false],
"notificationCSV": null,
"individualNotificationInterval": -1
}
]
}
Response shortened for readability; illustrative values. See the Tracking Object for all field semantics.
Remove Tracking#
Token cost: 0
Removes a single tracking from your list.
To remove all your trackings with a single call:
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | remove to remove a single tracking, or removeAll to remove all your trackings with a single call. removeAll can also be used to delete a named list. |
asin |
required (for remove) |
The ASIN of the product you want to remove from your tracking list. |
list |
optional | Name of the named tracking list this request should act on, e.g. &list=user123. |
Response:
- Only token bucket information.
- If you did not have a tracking for the specified ASIN, the
errorfield is set.
Note: Your tokenFlowReduction will not be updated immediately upon removing a tracking.
Example
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
Request r = Request.getTrackingRemoveRequest("B00M0QVG3W");
api.sendRequest(r)
.done(result -> {
System.out.println("tracking removed");
})
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
$response = $api->sendRequestWithRetry(Request::getTrackingRemoveRequest("B00M0QVG3W"));
if ($response->status == ResponseStatus::OK) {
echo "tracking removed";
}import requests
params = {"key": "YOUR_API_KEY", "type": "remove", "asin": "B00M0QVG3W"}
r = requests.get("https://api.keepa.com/tracking", params=params)
data = r.json()
print(data.get("error"), data["tokensLeft"])const res = await fetch("https://api.keepa.com/tracking?key=YOUR_API_KEY&type=remove&asin=B00M0QVG3W");
const data = await res.json();
console.log(data.error ?? "removed", data.tokensLeft);curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=remove&asin=B00M0QVG3W"Example response
{
"timestamp": 1753651260000,
"tokensLeft": 4994,
"refillIn": 43000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 4
}
Illustrative values.
Get Tracking#
Token cost: 0
Retrieves a single tracking from your list.
Retrieve your list
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | get to retrieve a single tracking, or list to retrieve your whole list. |
asin |
required (for get) |
The ASIN of the product you want to retrieve your tracking for. |
asins-only |
optional (list only) |
asins-only=1 returns only the ASINs you track (fast, lightweight). Pagination parameters are ignored in this mode because the call always returns the full list. |
page |
optional (list only) |
Page of the batch you want (first page = 0). |
perPage |
optional (list only) |
Number of tracking records to return in one batch. If you supply this parameter, the highest accepted value is 100,000 — larger values are capped to it. If you omit it, the whole list is returned in one batch (up to 500,000 records). |
list |
optional | Name of the named tracking list this request should act on, e.g. &list=user123. |
Pagination details: The default is page=0 with no page size limit, which returns the whole list in one batch (up to 500,000 records). To page through a larger list, supply perPage (maximum 100,000) and increment page until the response array is empty.
Response:
- A
trackingsfield containing the tracking object(s) (always an array). - If you did not have a tracking for the specified ASIN, the
errorfield is set. - In case of the
listoperation with theasins-onlyparameter, anasinListfield containing a string array of all tracked ASINs.
Example
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
Request r = Request.getTrackingGetRequest("B00M0QVG3W");
api.sendRequest(r)
.done(result -> {
System.out.println(result.trackings[0].asin);
})
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
$response = $api->sendRequestWithRetry(Request::getTrackingGetRequest("B00M0QVG3W"));
if ($response->status == ResponseStatus::OK) {
print_r($response->trackings);
}import requests
params = {"key": "YOUR_API_KEY", "type": "get", "asin": "B00M0QVG3W"}
r = requests.get("https://api.keepa.com/tracking", params=params)
data = r.json()
tracking = data["trackings"][0]
print(tracking["asin"], tracking["isActive"], tracking["updateInterval"])const res = await fetch("https://api.keepa.com/tracking?key=YOUR_API_KEY&type=get&asin=B00M0QVG3W");
const data = await res.json();
const tracking = data.trackings[0];
console.log(tracking.asin, tracking.isActive, tracking.updateInterval);curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=get&asin=B00M0QVG3W"Example response
{
"timestamp": 1753651320000,
"tokensLeft": 4998,
"refillIn": 22000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 6,
"trackings": [
{
"asin": "B00M0QVG3W",
"createDate": 7663520,
"isActive": true,
"ttl": 0,
"expireNotify": false,
"mainDomainId": 3,
"updateInterval": 1,
"metaData": "customer-42",
"trackingListName": null,
"thresholdValues": [
{ "thresholdValueCSV": [7663520, 4999], "domain": 3, "csvType": 0, "isDrop": true }
],
"notifyIf": [
{ "domain": 3, "csvType": 0, "notifyIfType": 1 }
],
"notificationType": [false, false, false, false, false, true, false],
"notificationCSV": [3, 0, 5, 1, 7663890],
"individualNotificationInterval": -1
}
]
}
Response shortened for readability; illustrative values.
Retrieving only the ASINs of your entire list:
curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=list&asins-only=1"
{
"timestamp": 1753651380000,
"tokensLeft": 4998,
"refillIn": 22000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 3,
"asinList": ["B00M0QVG3W", "B0F3GWXLTS", "B07FZ8S74R"]
}
Get Notifications#
Token cost: 0
Retrieves your recent notification objects. A notification will be marked as read once delivered through this call or pushed to your webhook. Notifications are deleted 24 hours after creation. Use this request if you do not want to use push notifications via webhook or if your webhook endpoint was offline.
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | Must be notification. |
since |
required | Retrieve all available notifications that occurred since this date, in KeepaTime minutes. |
revise |
required | Boolean value (0 = false, 1 = true). Whether or not to request notifications already marked as read. |
all |
optional | Boolean value (0 = false, 1 = true). By default a single call returns at most 2,000 notifications, newest first. Set all=1 to lift that limit and return every notification matching the request.Example: &all=1 |
readOnly |
optional | Boolean value (0 = false, 1 = true). If set to 1, the returned notifications are not marked as read, so a later call without revise still returns them. Use this to inspect notifications without consuming them.Example: &readOnly=1 |
list |
optional | Name of the named tracking list this request should act on, e.g. &list=user123. |
Response:
- A
notificationsfield containing notification object(s), sorted by most recent first. Always an array. - At most 2,000 notifications per call unless
all=1is used. If you receive exactly 2,000, request the remainder withall=1or by paging onsince.
Example
Fetch all unread notifications created since KeepaTime minute 7,662,080:
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
Request r = Request.getTrackingNotificationRequest(7662080, false);
api.sendRequest(r)
.done(result -> {
for (Notification n : result.notifications) {
System.out.println(n.asin + " " + n.trackingNotificationCause);
}
})
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
$response = $api->sendRequestWithRetry(Request::getTrackingNotificationRequest(7662080, false));
if ($response->status == ResponseStatus::OK) {
print_r($response->notifications);
}import requests
params = {"key": "YOUR_API_KEY", "type": "notification", "since": 7662080, "revise": 0}
r = requests.get("https://api.keepa.com/tracking", params=params)
data = r.json()
for n in data["notifications"]:
print(n["asin"], n["csvType"], n["trackingNotificationCause"])const res = await fetch("https://api.keepa.com/tracking?key=YOUR_API_KEY&type=notification&since=7662080&revise=0");
const data = await res.json();
for (const n of data.notifications) {
console.log(n.asin, n.csvType, n.trackingNotificationCause);
}curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=notification&since=7662080&revise=0"Example response
{
"timestamp": 1753651440000,
"tokensLeft": 4998,
"refillIn": 12000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 7,
"notifications": [
{
"asin": "B00M0QVG3W",
"title": "Example Product Title",
"image": "61vXG3m1KWL.jpg",
"ttl": 1440,
"createDate": 7663480,
"domainId": 3,
"notificationDomainId": 3,
"csvType": 0,
"trackingNotificationCause": 1,
"currentPrices": [4899, 4750, 4210, 15230, 6999, -1],
"sentNotificationVia": [false, false, false, false, false, true, false],
"metaData": "customer-42",
"trackingListName": null
}
]
}
Response shortened for readability; illustrative values. See the Notification Object for all field semantics.
Get Named Lists#
Token cost: 0
Retrieves a list of all the names of your named lists.
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | Must be listNames. |
Response:
- A
trackingListNamesfield containing the names of all your named lists. Always an array.
Example
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
// type=listNames has no dedicated factory method — build it directly.
// (Request.getTrackingListRequest(false) covers type=list.)
Request r = new Request();
r.path = "tracking";
r.parameter.put("type", "listNames");
api.sendRequest(r)
.done(result -> System.out.println(result))
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
// type=listNames has no dedicated factory method — build it directly.
// (Request::getTrackingListRequest(false) covers type=list.)
$r = new Request();
$r->path = "tracking";
$r->parameter["type"] = "listNames";
$response = $api->sendRequestWithRetry($r);
if ($response->status == ResponseStatus::OK) {
print_r($response);
}import requests
params = {"key": "YOUR_API_KEY", "type": "listNames"}
r = requests.get("https://api.keepa.com/tracking", params=params)
data = r.json()
print(data["trackingListNames"])const res = await fetch("https://api.keepa.com/tracking?key=YOUR_API_KEY&type=listNames");
const data = await res.json();
console.log(data.trackingListNames);curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=listNames"Example response
{
"timestamp": 1753651500000,
"tokensLeft": 4998,
"refillIn": 2000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 3,
"trackingListNames": ["user123", "user456", "watchlist-electronics"]
}
Illustrative values.
Set Webhook#
Token cost: 0
Updates the webhook URL our service will call whenever a notification is triggered. The URL can also be updated and tested via the website.
| Parameter | Required | Description |
|---|---|---|
key |
required | Your private API key. |
type |
required | Must be webhook. |
url |
required | The new webhook URL. |
Note: Set Webhook is the only tracking request that does not support the list parameter.
Push notification details
- A push notification will be an HTTP POST call with a single notification object as its content.
- Your server must respond with a status code of 200 to confirm successful retrieval.
- If delivery fails, a second attempt will be made with a 15-second delay.
Important: The content type of the POST is application/json and not application/x-www-form-urlencoded. If you use PHP, you have to use file_get_contents('php://input') or $HTTP_RAW_POST_DATA to access the content.
Response:
- Only token bucket information if the update was successful.
- If the specified URL is of invalid format, the
errorfield is set.
Example
KeepaAPI api = new KeepaAPI("YOUR_API_KEY");
Request r = Request.getTrackingWebhookRequest("https://example.com/keepa/notify");
api.sendRequest(r)
.done(result -> {
System.out.println("webhook set");
})
.fail(failure -> System.out.println(failure));<?php
use Keepa\KeepaAPI;
use Keepa\API\Request;
use Keepa\API\ResponseStatus;
use Keepa\objects\AmazonLocale;
$api = new KeepaAPI("YOUR_API_KEY");
$response = $api->sendRequestWithRetry(Request::getTrackingWebhookRequest("https://example.com/keepa/notify"));
if ($response->status == ResponseStatus::OK) {
echo "webhook set";
}import requests
params = {
"key": "YOUR_API_KEY",
"type": "webhook",
"url": "https://example.com/keepa/notify",
}
r = requests.get("https://api.keepa.com/tracking", params=params)
data = r.json()
print(data.get("error") or "webhook updated")const webhookUrl = encodeURIComponent("https://example.com/keepa/notify");
const res = await fetch(`https://api.keepa.com/tracking?key=YOUR_API_KEY&type=webhook&url=${webhookUrl}`);
const data = await res.json();
console.log(data.error ?? "webhook updated");curl -s "https://api.keepa.com/tracking?key=YOUR_API_KEY&type=webhook&url=https%3A%2F%2Fexample.com%2Fkeepa%2Fnotify"Example response
{
"timestamp": 1753651560000,
"tokensLeft": 4998,
"refillIn": 55000,
"refillRate": 300,
"tokenFlowReduction": 30.6,
"tokensConsumed": 0,
"processingTimeInMs": 2
}
Illustrative values.