API v2

Introduction

The Squirrel API gives your application the product data and prices that power Squirrel: send a product code, a retailer's product page or a product name, and get back the product and the offers of the retailers you work with, with your own affiliate tracking already in each link.

With the API you can:

Everything runs from your Squirrel account: the retailers and affiliate networks you select there are the ones the API answers with.

Quickstart

  1. Get your API key. Sign in to your Squirrel account and open API Integration (in the Offers menu). Copy the key: it is the bearer token every call sends.
  2. Search for a product. Send its barcode to POST /products/search, as in the samples on the right. Replace [YOUR_ACCESS_TOKEN] with your key.
  3. Read the answer. data holds one list per code you sent, with the product in each country and its offers. The first time you ask for a product, Squirrel answers "We are refreshing our cache" and fetches it in the background: ask again a minute later, or send first_response_live to get a quick first answer straight away.
curl --request POST 'https://api.getsquirrel.co/api/v2/products/search' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'code=4548736132580' \
  --form 'country=GB'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/products/search');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'code' => '4548736132580',
        'country' => 'GB',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/products/search', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    code: '4548736132580',
    country: 'GB',
  }),
});
const result = await response.json();

A product in the cache returns JSON structured like this (shortened):

{
  "execution_time_ms": 41,
  "monthly_quota": 100000,
  "current_usage": 1523,
  "error": false,
  "data": {
    "4548736132580": [
      {
        "name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
        "squirrel_id": 1234567,
        "price": 228.99,
        "rrp": 379,
        "offer_url": "https://www.awin1.com/cread.php?awinmid=0000&awinaffid=[YOUR_AWIN_ID]&clickref=[CLICK_REF]&ued=https%3A%2F%2Fwww.currys.co.uk%2Fproducts%2Fsony-wh-1000xm5",
        "retailer": {
          "id": 10322,
          "name": "Currys PC World",
          "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/currys.png",
          "searched_retailer": false,
          "retailer_domain": "www.currys.co.uk"
        },
        "image": "https://m.media-amazon.com/images/I/31SNSWUACeL._SL160_.jpg",
        "country": "GB"
      }
    ]
  },
  "request_ids": {
    "4548736132580": "0123456789abcdef0123456789abcdef"
  },
  "message": ""
}

That's it. The rest of this reference explains every endpoint, its parameters and its answers. Each endpoint has a sample in curl, PHP and JavaScript: pick the language with the tabs above the code.

Authentication

The API uses a bearer token: send your API key in the Authorization header of every call.

Authorization: Bearer [YOUR_ACCESS_TOKEN]

Your key is on the API Integration page of your Squirrel account. A call without a key, or with a key that is wrong or no longer valid, gets a 401 answer.

Also send Accept: application/json, so that every answer, errors included, comes back as JSON.

Keys limited to statistics. Some accounts have a key for reporting only. Such a key can call Statistics, Insights, CPC campaigns and Countries; every product, webhook, alert, retailer and CSV Master call answers 403 with "Your API key is limited to the stats and insights endpoints. Contact support to enable product search."

Base URL. Every path in this reference is relative to:

https://api.getsquirrel.co/api/v2

Request parameters can be sent as form fields (multipart/form-data or application/x-www-form-urlencoded) or as a JSON body with Content-Type: application/json.

Limits and quotas

LimitValue
Rate limitA number of calls per minute, shared by all the keys of your account group. 60 a minute unless your plan says otherwise. Above it the API answers 429: wait until the next minute before you retry.
Monthly quotaThe number of calls your plan allows in a calendar month, shared by all the keys of your account group. Each answer shows it in monthly_quota, and what the whole group has used in current_usage. When it is used up the API answers 429 until the next month.
Codes or URLs per callAt most 10 in one product search, separated by |.
StatisticsA range of at most 31 days, ending yesterday at the latest; at most 1,000 rows per page.
InsightsAt most 100 transactions per page.
CPC campaignsAt most 200 campaigns in a list; at most 366 rows in one partner-clicks push.

A product search whose answer has no product yet (the "We are refreshing our cache" message, or a quick first response) is not counted in current_usage.

Responses and errors

Every answer is JSON. A successful answer has HTTP status 200 and this wrapper:

{
  "execution_time_ms": 41,
  "monthly_quota": 100000,
  "current_usage": 1523,
  "error": false,
  "data": {},
  "request_ids": {},
  "message": ""
}
FieldDescription
errorfalse on success, true on an error.
dataThe answer itself: an object or a list, described with each endpoint.
messageEmpty on success; on an error, what went wrong.
monthly_quotaThe calls your plan allows this calendar month, for your whole account group.
current_usageThe calls all the keys of your account group have made this calendar month, this one included.
execution_time_msHow long the API took, in milliseconds. Not in every answer.
request_idsOn a product search, the id of each code or URL you sent; it is the id of its webhook. Not in every answer.

Some endpoints add their own fields next to these (for example returned_results and total_results on Insights); some answer with error, data and message only, plus the quota fields.

Response headers worth knowing (product search only):

HeaderDescription
X-Response-VersionWhere the answer came from: cached 1 from Squirrel's cache, cached 0 not from the cache (a new or refreshed request), first-live 1 a quick first response. With several codes in one call it describes the last one.
X-Response-TTLThe cache_ttl used for the request, in minutes.
X-Response-AgeOn an answer from the cache, how old it is, in seconds.

Errors

An error answer looks like this:

{
  "error": true,
  "data": "",
  "message": "Please send no more than 10 products per call",
  "monthly_quota": 100000,
  "current_usage": 1523
}

An error answer has error: true, says what went wrong in message and comes with an HTTP status other than 200. Some errors give the detail in data as a one-item list, for example ["Missing parameter alert_value"]; others only say "There was an error, the Squirrel team has been notified."

Treat any status other than 200 as an error, and read message (and data) for the reason.

StatusMeaning
400Bad request. A parameter is missing or not valid, for example "Please send at least one product to fetch", "Please use only one type of search per request", "One of the provided country ISO is incorrect, please double check your parameters". When the API has no specific message, the answer is "There was an error, the Squirrel team has been notified.", sometimes with the reason in data.
401Unauthorized. The API key is missing, wrong or no longer valid: message is "session.expired.unauthenticated."
403Forbidden. Your key is limited to the stats and Insights endpoints (see Authentication); or the resource belongs to another account (a webhook, a CSV file, an API-links call without the feature); or the address does not exist: "You are not allowed to do this."
404Not found. The resource does not exist or is not yours (a campaign, a CSV file): "Resource not found..."
399Invalid input on Insights and CPC campaigns: a date missing or not valid, a start_date after the end_date, an empty push. The reason is in message. 399 is not a standard HTTP status: check for it explicitly if your HTTP client only treats 4xx and 5xx as errors.
429Too many requests. Either the rate limit, "Too many requests - you have been rate limited. Please slow down your requests." (wait until the next minute), or the monthly quota, "Monthly quota exceeded. Please upgrade your plan or wait until the next billing period."
500Server error. Something failed on Squirrel's side; the team is notified. Try again later.
503Service unavailable. The API is down for maintenance or overloaded. Try again in a few minutes, waiting longer between each try.

Products

Search by code or URL

POST/products/search
curl --request POST 'https://api.getsquirrel.co/api/v2/products/search' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'code=4548736132580' \
  --form 'country=GB'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/products/search');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'code' => '4548736132580',
        'country' => 'GB',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/products/search', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    code: '4548736132580',
    country: 'GB',
  }),
});
const result = await response.json();

The above command returns JSON structured like this (two offers shown):

{
  "execution_time_ms": 41,
  "monthly_quota": 100000,
  "current_usage": 1523,
  "error": false,
  "data": {
    "4548736132580": [
      {
        "name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
        "squirrel_id": 1234567,
        "price": 228.99,
        "rrp": 379,
        "offer_url": "https://www.awin1.com/cread.php?awinmid=0000&awinaffid=[YOUR_AWIN_ID]&clickref=[CLICK_REF]&ued=https%3A%2F%2Fwww.currys.co.uk%2Fproducts%2Fsony-wh-1000xm5",
        "retailer": {
          "id": 10322,
          "name": "Currys PC World",
          "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/currys.png",
          "searched_retailer": false,
          "retailer_domain": "www.currys.co.uk"
        },
        "image": "https://m.media-amazon.com/images/I/31SNSWUACeL._SL160_.jpg",
        "make": "Sony",
        "model": "WH-1000XM5",
        "last_updated": "2026-10-26 09:12:44",
        "last_updated_timestamp": 1793005964,
        "all_offers": [
          {
            "id": 100000001,
            "price": 228.99,
            "offer_url": "https://www.awin1.com/cread.php?awinmid=0000&awinaffid=[YOUR_AWIN_ID]&clickref=[CLICK_REF]&ued=https%3A%2F%2Fwww.currys.co.uk%2Fproducts%2Fsony-wh-1000xm5",
            "offer_clean_url": "https://www.currys.co.uk/products/sony-wh-1000xm5",
            "retailer": {
              "id": 10322,
              "name": "Currys PC World",
              "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/currys.png",
              "searched_retailer": false,
              "retailer_domain": "www.currys.co.uk"
            },
            "retailer_id": 10322,
            "affiliate_network_id": 117,
            "api_id": 9,
            "rrp": 0,
            "max_price": 279,
            "is_promoted": 0,
            "image_url": "",
            "category": "Headphones",
            "min_price": 228.99,
            "in_stock_status": true,
            "affiliate_network_name": "[CSV] Awin UK"
          },
          {
            "id": 100000002,
            "price": 229.99,
            "offer_url": "https://www.anrdoezrs.net/links/[YOUR_CJ_ID]/type/dlg/sid/[CLICK_REF]/https://www.argos.co.uk/product/0000000",
            "offer_clean_url": "https://www.argos.co.uk/product/0000000",
            "retailer": {
              "id": 28418,
              "name": "Argos",
              "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/argos.png",
              "searched_retailer": false,
              "retailer_domain": "www.argos.co.uk"
            },
            "retailer_id": 28418,
            "affiliate_network_id": 152,
            "api_id": 12,
            "rrp": 0,
            "max_price": 299.99,
            "is_promoted": 0,
            "image_url": "",
            "category": "Headphones",
            "min_price": 229.99,
            "in_stock_status": true,
            "affiliate_network_name": "[CSV] Commission Junction UK"
          }
        ],
        "used_offers": [],
        "refurbished_offers": [],
        "no_affiliate_offers": [
          {
            "price": 279,
            "retailer": "Very.co.uk - [CSV] Awin UK"
          }
        ],
        "top_offer": {
          "id": 100000001,
          "price": 228.99,
          "offer_url": "https://www.awin1.com/cread.php?awinmid=0000&awinaffid=[YOUR_AWIN_ID]&clickref=[CLICK_REF]&ued=https%3A%2F%2Fwww.currys.co.uk%2Fproducts%2Fsony-wh-1000xm5",
          "retailer_id": 10322,
          "in_stock_status": true
        },
        "description": "Industry-leading noise cancellation, up to 30 hours of battery life and crystal-clear hands-free calling.",
        "product_codes": {
          "EAN": ["4548736132580"],
          "SKU": [],
          "UPC": ["4548736132580"],
          "MPN": ["WH1000XM5B.CE7"]
        },
        "category": "Headphones",
        "cached": 2,
        "country": "GB"
      }
    ]
  },
  "request_ids": {
    "4548736132580": "0123456789abcdef0123456789abcdef"
  },
  "message": ""
}

Finds products by barcode, ASIN, SKU, product page URL or Squirrel id, and returns each product with the offers of the retailers active on your account. Send one of code, urls or squirrel_id.

Parameters

NameTypeRequiredDefaultDescription
codestringone of threeA product code: EAN, UPC, SKU or ASIN (ASINs only for accounts with Amazon). Up to 10 codes separated by |, for example 4548736132580|5060409600602.
urlsstringone of threeA retailer's product page URL. Up to 10 URLs separated by |.
squirrel_idintegerone of threeThe Squirrel id of a product, from an earlier answer or from your Squirrel dashboard.
countrystringoptionalevery countryThe ISO code of one country, for example GB (UK is read as GB). Without it the answer has the product in every country Squirrel covers. A list of countries, or a country Squirrel does not cover, answers 400.
show_with_offers_onlybooleanoptionalfalsetrue to leave out products that have no offers.
cache_ttlintegeroptional120How long, in minutes, an answer may come from Squirrel's cache. An older answer is still returned, and refreshed in the background for your next call. 0 skips the cache: you get the "refreshing" message and the fresh answer on a later call.
generate_webhookbooleanoptionaltrueWhether a new request creates a webhook (when webhooks are active on your account). false does not switch off a webhook that already exists.
first_response_livebooleanoptionalfalsetrue to get a quick first response instead of the "refreshing" message, on a product Squirrel has not answered for you yet.

How the answer is built

Squirrel always answers in milliseconds, so your application never waits on a retailer.

data has one entry per code, URL or Squirrel id you sent, under that value (a URL with https:// added when you left it out). Each entry is a list: one product per country, or the message, or [].

{
  "execution_time_ms": 12,
  "monthly_quota": 100000,
  "current_usage": 1523,
  "error": false,
  "data": {
    "4548736132580": [
      "We are refreshing our cache with the latest response for you."
    ]
  },
  "request_ids": {
    "4548736132580": "0123456789abcdef0123456789abcdef"
  },
  "message": ""
}

First response live

On a product Squirrel has not answered for you yet, first_response_live=true returns a quick preliminary answer instead of the "refreshing" message:

Squirrel still processes the request as usual: the full answer, with the squirrel_id and your own tracking links, follows from the cache on a later call or through the webhook. Without a quick match you get the usual "refreshing" message. It answers barcodes, ASINs and product page URLs, not squirrel_id searches.

The Squirrel team can also switch it on for every call of your account: ask your account manager.

Product object

FieldDescription
nameThe product name in that country.
squirrel_idSquirrel's id for the product on your account: the id the Squirrel widget and plugins use.
priceThe price of the best offer, in the country's currency (prices carry no currency: they belong to the country).
rrpThe recommended retail price, when known.
offer_urlThe link of the best offer, with your affiliate tracking.
retailerThe retailer of the best offer.
imageThe product image URL.
makeThe brand.
modelThe model.
last_updatedWhen the product's offers were last updated (YYYY-MM-DD HH:MM:SS), or null.
last_updated_timestampThe same as a Unix timestamp, or null.
all_offersThe new-condition offers for the product in that country, cheapest first, one per retailer, 20 at most. Empty when there is none.
used_offersOffers for the product used.
refurbished_offersOffers for the product refurbished.
no_affiliate_offersOffers of retailers you have not activated on your account (price and retailer only). Activate them in your Squirrel account to get their links.
top_offerThe best offer, or empty when there is none.
descriptionThe product description, as a retailer gives it.
product_codesThe product's codes in that country: EAN, SKU, UPC and MPN lists, and ASIN for accounts with Amazon.
categoryThe Squirrel category of the product.
countryThe ISO code of the country this product information is for.

Offer object

FieldDescription
idSquirrel's id for the offer.
priceThe price, in the country's currency.
offer_urlThe link to the offer, with your affiliate tracking.
offer_clean_urlThe direct link to the retailer's product page, when known.
retailerThe retailer of the offer.
retailer_idSquirrel's id for the retailer.
affiliate_network_idSquirrel's id for the affiliate network.
affiliate_network_nameThe name of the affiliate network.
api_idSquirrel's id for the feed the offer came from.
categoryThe product category the retailer gives.
rrpThe recommended retail price the retailer gives, or 0.
image_urlThe retailer's own image for the product, when it gives one.
max_priceThe highest price Squirrel saw for this offer over the last 90 days.
min_priceThe lowest price Squirrel saw for this offer over the last 90 days.
is_promoted1 when the offer is promoted on your account.
in_stock_statustrue when the product is in stock. By default Squirrel leaves out-of-stock offers out; you can change that in your account.

Retailer object

FieldDescription
idSquirrel's id for the retailer.
nameThe retailer's name.
logo_urlSquirrel's image of the retailer's logo.
searched_retailertrue when the product was searched by a URL of this retailer.
retailer_domainThe retailer's website.
lowest_commission_rate, highest_commission_rate, commission_ratesThe retailer's commission rates, for accounts with retailer commissions.

Search by name

POST/products/find
curl --request POST 'https://api.getsquirrel.co/api/v2/products/find' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'term=sony wh-1000xm5' \
  --form 'country=GB' \
  --form 'size=2'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/products/find');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'term' => 'sony wh-1000xm5',
        'country' => 'GB',
        'size' => '2',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/products/find', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    term: 'sony wh-1000xm5',
    country: 'GB',
    size: '2',
  }),
});
const result = await response.json();

The above command returns JSON structured like this (one product shown):

{
  "execution_time_ms": 0,
  "monthly_quota": 100000,
  "current_usage": 1524,
  "error": false,
  "data": {
    "term": "sony wh-1000xm5",
    "country": "GB",
    "search_mpn_sku": false,
    "took_ms": 212,
    "cached": false,
    "offers_partial": false,
    "notice": "",
    "products": [
      {
        "name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
        "squirrel_id": null,
        "price": 228.99,
        "rrp": null,
        "offer_url": "https://sqrlinks.ai/[YOUR_CLIENT_ID]?url=aHR0cHM6Ly93d3cuY3VycnlzLmNvLnVrL3Byb2R1Y3RzL3Nvbnktd2gtMTAwMHhtNQ%3D%3D",
        "retailer": {
          "id": 10322,
          "name": "Currys PC World",
          "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/currys.png",
          "searched_retailer": false,
          "retailer_domain": "www.currys.co.uk"
        },
        "image": "https://m.media-amazon.com/images/I/31SNSWUACeL._SL160_.jpg",
        "make": "Sony",
        "model": null,
        "last_updated": null,
        "last_updated_timestamp": null,
        "all_offers": [
          {
            "id": "currys-0000000",
            "price": 228.99,
            "offer_url": "https://sqrlinks.ai/[YOUR_CLIENT_ID]?url=aHR0cHM6Ly93d3cuY3VycnlzLmNvLnVrL3Byb2R1Y3RzL3Nvbnktd2gtMTAwMHhtNQ%3D%3D",
            "offer_clean_url": "https://www.currys.co.uk/products/sony-wh-1000xm5",
            "retailer_id": 10322,
            "affiliate_network_id": 117,
            "affiliate_network_name": "[CSV] Awin UK",
            "in_stock_status": true
          }
        ],
        "used_offers": [],
        "refurbished_offers": [],
        "no_affiliate_offers": [],
        "description": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
        "product_codes": {
          "EAN": ["4548736132580"],
          "SKU": [],
          "UPC": ["4548736132580"],
          "MPN": []
        },
        "category": null,
        "cached": 2,
        "country": "GB",
        "key": "4548736132580",
        "score": 31.4,
        "gtins": ["4548736132580"],
        "from_offer": false
      }
    ],
    "timings": {
      "boot_ms": 18,
      "pre_search_ms": 2,
      "search_ms": 96,
      "offers_ms": 74,
      "context_ms": 9,
      "build_ms": 6,
      "total_ms": 212
    }
  },
  "request_ids": [],
  "message": ""
}

Searches Squirrel's product catalogue by product name and answers straight away with the products found and their current offers, best match first. A barcode, an ASIN or a product page URL in term is looked up exactly.

Unlike search by code, the answer is never deferred: it comes from Squirrel's product search, with offer links through Squirrel Links and no squirrel_id. Answers are cached for 10 minutes.

Parameters

NameTypeRequiredDefaultDescription
termstringrequiredWhat to search for: a product name such as sony wh-1000xm5, a barcode, an ASIN or a product page URL. At most 1,000 characters (the first 200 are searched).
countrystringoptionalGBThe ISO code of one country (UK is read as GB).
sizeintegeroptional10How many products to return, 1 to 20.
search_mpn_skubooleanoptionalfalsetrue to first look for offers whose MPN or SKU is exactly term (for a term of 64 characters or less), before the name search.

Answer

FieldDescription
termThe term as searched (lower case, single spaces).
countryThe country searched.
productsThe products found: product objects plus key, score (how well it matches), gtins (its barcodes) and from_offer. Offers have the fields of the offer object; their id is a string.
noticeEmpty, or why the answer is incomplete: "The search took too long, please try again", "We do not have this product page", "No product search index for this country yet", or how the MPN/SKU lookup went.
offers_partialtrue when some offers could not be loaded in time.
cachedtrue when the answer came from the 10-minute cache.
took_ms, timingsHow long the search took, in milliseconds.

Search by text

POST/terms/search
curl --request POST 'https://api.getsquirrel.co/api/v2/terms/search' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'term=Apple iPhone 16' \
  --form 'country=GB'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/terms/search');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'term' => 'Apple iPhone 16',
        'country' => 'GB',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/terms/search', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    term: 'Apple iPhone 16',
    country: 'GB',
  }),
});
const result = await response.json();

The above command returns JSON structured like this (two products shown):

{
  "execution_time_ms": 64,
  "monthly_quota": 100000,
  "current_usage": 1525,
  "error": false,
  "data": [
    {
      "name": "Apple iPhone 16 128GB - Ultramarine",
      "squirrel_id": null,
      "rrp": 799,
      "image": "https://m.media-amazon.com/images/I/313JYjxMQ-L._SL160_.jpg",
      "make": "Apple",
      "model": "iPhone 16",
      "used_offers": [],
      "refurbished_offers": [],
      "no_affiliate_offers": [],
      "description": "",
      "product_codes": {
        "ASIN": null,
        "EAN": ["0195949822445"],
        "SKU": [],
        "UPC": ["195949822445"],
        "MPN": []
      },
      "category": "Mobile phones",
      "country": "GB"
    },
    {
      "name": "Apple iPhone 16 128 GB - Black",
      "squirrel_id": null,
      "rrp": 799,
      "image": "https://m.media-amazon.com/images/I/31O+YeW1FxL._SL160_.jpg",
      "make": "Apple",
      "model": "iPhone 16",
      "used_offers": [],
      "refurbished_offers": [],
      "no_affiliate_offers": [],
      "description": "",
      "product_codes": {
        "ASIN": null,
        "EAN": ["0195949821905"],
        "SKU": [],
        "UPC": ["195949821905"],
        "MPN": []
      },
      "category": "Mobile phones",
      "country": "GB"
    }
  ],
  "request_ids": [],
  "message": ""
}

Searches products by free text and returns the matching products without offers: use it to find a product's codes, then search by code for its prices. To get products and offers in one call, use search by name.

Parameters

NameTypeRequiredDefaultDescription
termstringrequiredThe text to search for.
countrystringoptionalGBThe ISO code of one country. A list of countries answers 400.

Answer

data is a list of products, with these fields of the product object: name, squirrel_id, rrp, image, make, model, description, product_codes, category and country. The offer lists are always empty here. No product found gives an empty list.

Webhooks

GET/webhooks/{request_id}Enabled on request
curl 'https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef?with_data=1' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$url = 'https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef?' . http_build_query([
    'with_data' => '1',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = 'https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef?' + new URLSearchParams({
  with_data: '1',
});
const response = await fetch(url, {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 9,
  "monthly_quota": 100000,
  "current_usage": 1526,
  "error": false,
  "data": {
    "squirrel_id": 1234567,
    "request_hash": "0123456789abcdef0123456789abcdef",
    "created_at": "2026-10-20 14:03:11",
    "active": 1,
    "last_webhook_at": "2026-10-26 08:00:05",
    "content": {
      "4548736132580": [
        {
          "name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
          "squirrel_id": 1234567,
          "price": 228.99,
          "country": "GB"
        }
      ]
    }
  },
  "request_ids": [],
  "message": ""
}

With webhooks, Squirrel sends your server the new answer of a product search whenever its product or offers change, so you don't have to ask again. They are part of the Squirrel Expert plan: ask a Squirrel team member to set them up on your account (the address they are sent to included).

Every product search creates a webhook for each code or URL, unless you send generate_webhook=false. Its id is the request_ids value of that code or URL in the search's answer. These endpoints check a webhook, and pause or restart it.

Check a webhook

GET /webhooks/{request_id}

NameTypeRequiredDefaultDescription
request_idstring (path)requiredThe webhook's id: the request_ids value from the product search.
with_databooleanoptionalSend it (any value) to get the webhook's last content in content, while Squirrel still has it cached.
FieldDescription
squirrel_idThe Squirrel id of the product the webhook is about.
request_hashThe webhook's id.
created_atWhen the webhook was created.
active1 when the webhook is active and sent on every update, 0 when it is paused.
last_webhook_atWhen the webhook was last sent.
contentWith with_data: the webhook's last content, the same as the product search's data.

Pause a webhook

DELETE/webhooks/{request_id}
curl --request DELETE 'https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef', {
  method: 'DELETE',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

Pauses the webhook: data is "The selected webhook was disabled."

Restart a webhook

PUT/webhooks/{request_id}
curl --request PUT 'https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/webhooks/0123456789abcdef0123456789abcdef', {
  method: 'PUT',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

Makes a paused webhook active again: data is "The selected webhook was activated."

An id that does not exist answers 400, and an id of another account answers 403, both with the message "There was an error, the Squirrel team has been notified."

Alerts

POST/alertsEnabled on request
curl --request POST 'https://api.getsquirrel.co/api/v2/alerts' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'squirrel_id=1234567' \
  --form 'country=GB' \
  --form 'alert_type_id=2' \
  --form 'alert_method_id=1' \
  --form 'value_type=percentage' \
  --form 'alert_value=10' \
  --form 'user_name=Alex Example' \
  --form 'user_email=reader@example.com' \
  --form 'first_email=1'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/alerts');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'squirrel_id' => '1234567',
        'country' => 'GB',
        'alert_type_id' => '2',
        'alert_method_id' => '1',
        'value_type' => 'percentage',
        'alert_value' => '10',
        'user_name' => 'Alex Example',
        'user_email' => 'reader@example.com',
        'first_email' => '1',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/alerts', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    squirrel_id: '1234567',
    country: 'GB',
    alert_type_id: '2',
    alert_method_id: '1',
    value_type: 'percentage',
    alert_value: '10',
    user_name: 'Alex Example',
    user_email: 'reader@example.com',
    first_email: '1',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 57,
  "monthly_quota": 100000,
  "current_usage": 1527,
  "error": false,
  "data": {
    "id": 98765,
    "name": "Alex Example",
    "squirrel_id": 1234567,
    "squirrel_page_url": "https://www.example.com/reviews/sony-wh-1000xm5",
    "alert_type_id": 2,
    "alert_method_id": 1,
    "alert_method_value": "reader@example.com",
    "created_at": "2026-10-26 10:15:00",
    "updated_at": "2026-10-26 10:15:00",
    "active": 1,
    "country": "GB"
  },
  "request_ids": [],
  "message": ""
}

With Squirrel Notify, your readers can ask to be told by email when a product's price drops or when it is back in stock. The API creates, checks and stops these alerts.

Create an alert

POST /alerts

NameTypeRequiredDefaultDescription
squirrel_idintegerrequiredThe Squirrel id of the product, from a product search or your Squirrel dashboard. It must be a product of your account.
countrystringrequiredThe ISO code of the country.
alert_type_idintegerrequired1 back in stock, 2 price drop.
alert_valuestringrequiredThe price drop that triggers the alert: a percentage or a price, as value_type says (for example 10).
value_typestringrequiredpercentage: the alert fires when the price falls by alert_value percent from today's best price. price: when it falls to alert_value or below.
retailer_idintegeroptionalany retailerFollow one retailer's offer only. It must sell the product in that country.
alert_method_idintegeroptionalHow the alert is sent: 1 email, 2 webhook.
user_namestringoptionalThe name of the person the alert is for.
user_emailstringoptionalThe email address the alert is sent to.
first_emailintegeroptional01 to send the person an email confirming the alert. It is always sent when alert_method_id is given.

A price-drop alert needs at least one active offer for the product in that country.

Alert object

FieldDescription
idThe alert's id: use it to check or stop the alert.
nameThe name of the person the alert is for.
squirrel_idThe product the alert follows.
squirrel_page_urlThe page the alert was asked from (the Referer of the call), when known.
countryThe ISO code of the alert's country (on creation).
alert_type_id1 back in stock, 2 price drop.
alert_method_id1 email, 2 webhook.
alert_method_valueThe email address the alert is sent to.
created_atWhen the alert was created.
updated_atWhen the alert last changed.
active1 active, 0 stopped.
retailerThe retailer the alert follows, when it follows one (id, name, logo_url).

Check an alert

GET/alerts/{alert_id}
curl 'https://api.getsquirrel.co/api/v2/alerts/98765' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/alerts/98765');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/alerts/98765', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

Returns the alert object in data.

Stop an alert

DELETE/alerts/{alert_id}
curl --request DELETE 'https://api.getsquirrel.co/api/v2/alerts/98765' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/alerts/98765');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/alerts/98765', {
  method: 'DELETE',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

Stops the alert: data is "The selected alert was disabled."

Errors. A missing parameter, a product that is not yours, a country Squirrel does not cover, a retailer that does not sell the product or a product with no active offers answers 400, with the reason in data, for example ["Missing parameter alert_value"] or ["Invalid retailer for this squirrel"].

Retailers

GET/retailers/list
curl 'https://api.getsquirrel.co/api/v2/retailers/list' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/retailers/list');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/retailers/list', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 22,
  "monthly_quota": 100000,
  "current_usage": 1528,
  "error": false,
  "data": [
    {
      "id": 56,
      "name": "Amazon UK",
      "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/amazon-uk-compressor.png",
      "retailer_domain": "www.amazon.co.uk",
      "affiliate_network_id": 12,
      "affiliate_network_name": "Amazon UK",
      "country": "UK",
      "relationship": "Direct",
      "lowest_commission_rate": null,
      "highest_commission_rate": null,
      "commission_rates": []
    },
    {
      "id": 28418,
      "name": "Argos",
      "logo_url": "https://squirrels-live.getsquirrel.co/assets/images/retailers/argos.png",
      "retailer_domain": "www.argos.co.uk",
      "affiliate_network_id": 152,
      "affiliate_network_name": "[CSV] Commission Junction UK",
      "country": "UK",
      "relationship": "Direct",
      "lowest_commission_rate": null,
      "highest_commission_rate": null,
      "commission_rates": []
    }
  ],
  "request_ids": [],
  "message": ""
}

Lists the retailers active on your account, by name. No parameters.

FieldDescription
idSquirrel's id for the retailer.
nameThe retailer's name.
logo_urlSquirrel's image of the retailer's logo.
retailer_domainThe retailer's website.
affiliate_network_idSquirrel's id for the affiliate network you work with this retailer through.
affiliate_network_nameThe affiliate network's name.
countryThe ISO code of the affiliate network's country.
relationshipDirect, or the name of the clean-links account the retailer is linked through.
lowest_commission_rate, highest_commission_rate, commission_ratesKept for compatibility: always empty here.

Countries

GET/utils/countries
curl 'https://api.getsquirrel.co/api/v2/utils/countries' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/utils/countries');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/utils/countries', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 4,
  "monthly_quota": 100000,
  "current_usage": 1529,
  "error": false,
  "data": [
    { "id": 1, "iso": "GB", "name": "United Kingdom" },
    { "id": 2, "iso": "US", "name": "United States" },
    { "id": 13, "iso": "AU", "name": "Australia" },
    { "id": 38, "iso": "CA", "name": "Canada" },
    { "id": 73, "iso": "FR", "name": "France" },
    { "id": 80, "iso": "DE", "name": "Germany" },
    { "id": 99, "iso": "IN", "name": "India" },
    { "id": 105, "iso": "IT", "name": "Italy" },
    { "id": 150, "iso": "NL", "name": "Netherlands" },
    { "id": 199, "iso": "ES", "name": "Spain" },
    { "id": 205, "iso": "SE", "name": "Sweden" }
  ],
  "request_ids": [],
  "message": ""
}

Lists the countries Squirrel covers: the values country takes in the other endpoints. No parameters. Any key can call it, including keys limited to statistics.

FieldDescription
idSquirrel's id for the country.
isoThe country's ISO code.
nameThe country's name.

Statistics

Impressions

POST/stats/impressions
curl --request POST 'https://api.getsquirrel.co/api/v2/stats/impressions' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'start_date=2026-10-01' \
  --form 'end_date=2026-10-25' \
  --form 'page=1' \
  --form 'per_page=3'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/stats/impressions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'start_date' => '2026-10-01',
        'end_date' => '2026-10-25',
        'page' => '1',
        'per_page' => '3',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/stats/impressions', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    start_date: '2026-10-01',
    end_date: '2026-10-25',
    page: '1',
    per_page: '3',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 88,
  "monthly_quota": 100000,
  "current_usage": 1530,
  "error": false,
  "data": {
    "total_results": 17969,
    "page": 1,
    "total_pages": 5990,
    "stats": [
      {
        "date": "2026-10-01",
        "count": "4",
        "squirrel_id": "1234567",
        "url": "https://www.example.com/reviews/sony-wh-1000xm5",
        "render_type": ""
      },
      {
        "date": "2026-10-01",
        "count": "1",
        "squirrel_id": "1234568",
        "url": "https://www.example.com/best/headphones",
        "render_type": "tbc"
      },
      {
        "date": "2026-10-01",
        "count": "2",
        "squirrel_id": "1234569",
        "url": "https://www.example.com/",
        "render_type": ""
      }
    ]
  },
  "request_ids": [],
  "message": ""
}

The raw impressions of the Squirrel widgets on your account, per day, product and page.

Parameters

NameTypeRequiredDefaultDescription
start_datedaterequiredThe first day, YYYY-MM-DD.
end_datedaterequiredThe last day, YYYY-MM-DD. It must be yesterday or earlier, not before start_date, and at most 31 days after it.
pageintegeroptional1The page to return, from 1 to total_pages.
per_pageintegeroptional1000Rows per page, at most 1,000.

Answer

FieldDescription
total_resultsThe number of rows for the dates.
pageThe page returned.
total_pagesThe number of pages at this per_page.
statsThe rows.

Each row:

FieldDescription
dateThe day, YYYY-MM-DD.
countHow many impressions.
squirrel_idThe product's Squirrel id.
urlThe page the widget was on, as the browser reported it. Some pages only give your domain (AMP pages, for example).
render_typeThe kind of impression: empty for a normal one, tbc when the widget had no offers to show.

Clicks

POST/stats/clicks
curl --request POST 'https://api.getsquirrel.co/api/v2/stats/clicks' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'start_date=2026-10-01' \
  --form 'end_date=2026-10-25' \
  --form 'page=1' \
  --form 'per_page=2'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/stats/clicks');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'start_date' => '2026-10-01',
        'end_date' => '2026-10-25',
        'page' => '1',
        'per_page' => '2',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/stats/clicks', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    start_date: '2026-10-01',
    end_date: '2026-10-25',
    page: '1',
    per_page: '2',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 75,
  "monthly_quota": 100000,
  "current_usage": 1531,
  "error": false,
  "data": {
    "total_results": 7,
    "page": 1,
    "total_pages": 4,
    "stats": [
      {
        "date": "2026-10-05",
        "count": "1",
        "squirrel_id": "1234567",
        "url": "https://www.example.com/reviews/sony-wh-1000xm5",
        "offer_type": "default",
        "retailer": "Currys PC World"
      },
      {
        "date": "2026-10-05",
        "count": "2",
        "squirrel_id": "1234568",
        "url": "https://www.example.com/best/headphones",
        "offer_type": "default",
        "retailer": "Argos"
      }
    ]
  },
  "request_ids": [],
  "message": ""
}

The raw clicks on the offers of the Squirrel widgets on your account, per day, product, page and retailer. The parameters, limits and answer are the same as for impressions, with these fields in each row instead of render_type:

FieldDescription
offer_typeThe kind of offer clicked: default a retailer's offer, tbc a "Check price here" offer, tds a mobile deal (TDS plugin).
retailerThe name of the retailer clicked.

Errors. Missing or invalid dates, a range over 31 days, an end_date after yesterday or a per_page over 1,000 answer 400, with the reason in data, for example ["The following errors occurred: The interval between start and end dates cannot exceed 31 days"].

Insights

Transactions

POST/insights/transactions
curl --request POST 'https://api.getsquirrel.co/api/v2/insights/transactions' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'start_date=2026-10-01' \
  --form 'end_date=2026-10-25' \
  --form 'limit=1' \
  --form 'offset=1'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/insights/transactions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'start_date' => '2026-10-01',
        'end_date' => '2026-10-25',
        'limit' => '1',
        'offset' => '1',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/insights/transactions', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    start_date: '2026-10-01',
    end_date: '2026-10-25',
    limit: '1',
    offset: '1',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "execution_time_ms": 0,
  "monthly_quota": 100000,
  "current_usage": 1532,
  "returned_results": 1,
  "total_results": 1582,
  "error": false,
  "data": [
    {
      "id": 5550001,
      "anchor": "https://www.currys.co.uk/products/sony-wh-1000xm5",
      "country_id": "UK",
      "squirrel_id": 1234567,
      "commissions_gross": "6.87",
      "commissions_net": "6.87",
      "content_type": "reviews",
      "merchant_name": "Currys PC World",
      "network_name": "Awin",
      "order_id": "[ORDER_ID]",
      "page": "https://www.example.com/reviews/sony-wh-1000xm5",
      "platform": "mobile",
      "product_name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
      "productprice": "228.99",
      "referrer_url": "https://www.google.com/",
      "sales_gross": "228.99",
      "sales_net": "228.99",
      "sub_vertical": null,
      "transaction_full_date": "2026-10-14 18:22:05",
      "transaction_status": "Pending",
      "utm_campaign": null,
      "utm_content": null,
      "utm_medium": "organic",
      "utm_source": "google",
      "utm_term": null,
      "vertical": "Audio",
      "updated_at": "2026-10-15 06:01:21",
      "is_member": 0,
      "is_bounty": 0
    }
  ],
  "message": ""
}

The transactions (sales) collected on your Squirrel Insights account.

Parameters

NameTypeRequiredDefaultDescription
start_datedaterequiredThe start, YYYY-MM-DD or YYYY-MM-DD HH:MM:SS. Without a time, 00:00:00.
end_datedaterequiredThe end, in the same formats. Without a time, 23:59:59. Not before start_date.
limitintegeroptional100Transactions per page, 1 to 100.
offsetintegeroptional1The page number, starting at 1 (not a row offset): page 2 with limit=100 returns rows 101 to 200.
show_publisher_statsbooleanoptional01 to include the transactions of every website of your account group, not only this key's.

Answer

FieldDescription
returned_resultsThe number of transactions in this page.
total_resultsThe number of transactions for the dates.
dataThe transactions.

Each transaction:

FieldDescription
idSquirrel's id for the transaction.
anchorThe link that was clicked.
country_idThe country of the sale.
squirrel_idThe product's Squirrel id, when the click came from a Squirrel widget.
commissions_gross, commissions_netThe commission, before and after deductions.
sales_gross, sales_netThe sale amount, before and after deductions.
productpriceThe price of the product sold.
content_typeThe kind of page the click came from (for example reviews).
merchant_nameThe retailer.
network_nameThe affiliate network.
order_idThe retailer's order reference.
pageThe page the click came from.
platformThe reader's device, for example mobile.
product_nameThe product sold.
referrer_urlWhere the reader came to your page from.
transaction_full_dateWhen the sale happened.
transaction_statusThe network's status: for example Pending, Approved, Rejected.
utm_campaign, utm_content, utm_medium, utm_source, utm_termThe UTM values of the visit.
vertical, sub_verticalThe page's vertical and sub-vertical.
updated_atWhen the transaction last changed.
is_member1 when the reader was a member of your site.
is_bounty1 for a bounty transaction.

Errors. Missing or invalid dates, or a start_date after end_date, answer with HTTP status 399 and the reason in message, for example "start_date and end_date are required". A key with no Insights account answers 399 with "You do not have access to insights".

CPC campaigns

GET/campaignsEnabled on request
curl 'https://api.getsquirrel.co/api/v2/campaigns?status=active' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$url = 'https://api.getsquirrel.co/api/v2/campaigns?' . http_build_query([
    'status' => 'active',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = 'https://api.getsquirrel.co/api/v2/campaigns?' + new URLSearchParams({
  status: 'active',
});
const response = await fetch(url, {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this (one campaign shown):

{
  "error": false,
  "data": {
    "campaigns": [
      {
        "id": 321,
        "name": "Autumn headphones push",
        "status": "active",
        "source": "squirrel",
        "commerce_type": "cpc",
        "start_at": "2026-10-01 00:00:00",
        "end_at": "2026-10-31 23:59:59",
        "timezone": "UTC",
        "currency": "GBP",
        "cpc_rate": 0.35,
        "budget": 3500,
        "target_clicks": 10000,
        "clicks_delivered": 6420,
        "clicks_remaining": 3580,
        "partner_clicks": 6388,
        "revenue_delivered": 2247,
        "revenue_remaining": 1253,
        "orders": 41,
        "sales_value": 9388.6,
        "commission_pending": 281.66,
        "commission_approved": 120.4,
        "pace_status": "behind",
        "required_daily": 325.45,
        "projected_total": 10120,
        "utm_campaign": "cmp_321"
      }
    ]
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1533
}

CPC campaigns are the pay-per-click campaigns your account runs with Squirrel. They are created in the Squirrel dashboard; the API reads them and their daily figures, receives the clicks your partners counted, and creates tracking links.

Every campaign call answers 404 ("Resource not found...") for a campaign that does not exist or is not yours. Invalid input answers with HTTP status 399 and the reason in message.

List campaigns

GET /campaigns

NameTypeRequiredDefaultDescription
statusstringoptionalevery status but archivedOnly the campaigns with this status, for example active or archived.

Returns your campaigns, newest first, 200 at most, in data.campaigns:

FieldDescription
idThe campaign's id.
nameThe campaign's name.
statusThe campaign's status.
sourceWhere the campaign runs.
commerce_typeThe kind of campaign.
start_at, end_atWhen the campaign starts and ends.
timezoneThe campaign's time zone (UTC by default).
currencyThe currency of every amount.
cpc_rateThe price of one click.
budgetThe budget: the one set, or target_clicks × cpc_rate.
target_clicksThe clicks to deliver.
clicks_deliveredThe billable clicks delivered so far.
clicks_remainingThe clicks still to deliver.
partner_clicksThe clicks your partners reported.
revenue_delivered, revenue_remainingThe click revenue delivered so far, and still to deliver.
orders, sales_valueThe orders and sales the campaign brought.
commission_pending, commission_approvedThe commission on those sales, pending and approved.
pace_statusHow the campaign is pacing against its target: ahead, on_track, behind, at_risk or done; null until it is computed or when the campaign has no target.
required_daily, projected_totalThe clicks a day it needs to reach its target, and the clicks it is projected to reach.
utm_campaignThe utm_campaign value of the campaign's links.

Campaign details

GET/campaigns/{id}
curl 'https://api.getsquirrel.co/api/v2/campaigns/321' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/campaigns/321');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/campaigns/321', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns the campaign's fields and these (shortened):

{
  "error": false,
  "data": {
    "id": 321,
    "name": "Autumn headphones push",
    "status": "active",
    "destination": {
      "version": 2,
      "primary_url": "https://www.example-retailer.com/headphones",
      "overflow_url": "https://www.example-retailer.com/audio",
      "since": "2026-10-10 09:00:00"
    },
    "placements": [
      {
        "id": 77,
        "platform": "newsletter",
        "label": "Newsletter",
        "active": true,
        "link": "https://sqrlinks.ai/[YOUR_CLIENT_ID]/c/autumn-headphones-newsletter-a1b2?utm_source=newsletter&utm_medium=email&utm_campaign=cmp_321"
      }
    ]
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1534
}

The campaign, with the fields of the list and:

FieldDescription
destinationWhere the campaign's clicks go: primary_url, overflow_url (once the target is reached), the rule's version and since when it applies; null when none is set.
placementsThe campaign's placements: id, platform, label, active and its tracking link (null for campaigns that do not use Squirrel links).

Daily stats

GET/campaigns/{id}/stats
curl 'https://api.getsquirrel.co/api/v2/campaigns/321/stats?start_date=2026-10-20&end_date=2026-10-25' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$url = 'https://api.getsquirrel.co/api/v2/campaigns/321/stats?' . http_build_query([
    'start_date' => '2026-10-20',
    'end_date' => '2026-10-25',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = 'https://api.getsquirrel.co/api/v2/campaigns/321/stats?' + new URLSearchParams({
  start_date: '2026-10-20',
  end_date: '2026-10-25',
});
const response = await fetch(url, {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this (one day shown):

{
  "error": false,
  "data": {
    "campaign_id": 321,
    "start_date": "2026-10-20",
    "end_date": "2026-10-25",
    "currency": "GBP",
    "days": [
      {
        "date": "2026-10-20",
        "placement_id": 0,
        "squirrel_clicks": 312,
        "billable_clicks": 298,
        "bonus_clicks": 6,
        "filtered_clicks": 8,
        "partner_clicks": 301,
        "impressions": 15420,
        "pageviews": 9812,
        "cpc_revenue": 104.3,
        "orders": 2,
        "sales_value": 458.98,
        "commission_pending": 13.77,
        "commission_approved": 0,
        "commission_rejected": 0
      }
    ]
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1535
}

The campaign's figures per day. Each day has a row with placement_id 0 for the whole campaign, and one row per placement.

NameTypeRequiredDefaultDescription
start_datedateoptional30 days agoThe first day, YYYY-MM-DD.
end_datedateoptionaltodayThe last day, YYYY-MM-DD, not before start_date.
FieldDescription
squirrel_clicksThe clicks Squirrel counted.
billable_clicksThe clicks billed.
bonus_clicksOverflow and over-delivery clicks, beyond the billable ones.
filtered_clicksClicks filtered out as not valid.
partner_clicksThe clicks your partners reported for the day.
impressions, pageviewsThe impressions and page views reported.
cpc_revenueThe click revenue of the day.
orders, sales_valueThe orders and sales of the day.
commission_pending, commission_approved, commission_rejectedThe commission on those sales, by status.

Partner clicks

GET/campaigns/{id}/partner-clicks
curl 'https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks?start_date=2026-10-20&end_date=2026-10-25' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$url = 'https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks?' . http_build_query([
    'start_date' => '2026-10-20',
    'end_date' => '2026-10-25',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = 'https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks?' + new URLSearchParams({
  start_date: '2026-10-20',
  end_date: '2026-10-25',
});
const response = await fetch(url, {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "error": false,
  "data": {
    "campaign_id": 321,
    "rows": [
      {
        "date": "2026-10-20",
        "placement_id": 77,
        "source": "api",
        "clicks": 301,
        "impressions": 15420,
        "orders": null,
        "revenue": null,
        "imported_at": "2026-10-21 07:00:12"
      }
    ]
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1536
}

The clicks your partners reported for the campaign, per day and placement, with where each row came from (source) and when it was received (imported_at). It takes the same start_date and end_date as daily stats.

Push partner clicks

POST/campaigns/{id}/partner-clicksRequires the API links feature
curl --request POST 'https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --header 'Content-Type: application/json' \
  --data '{"rows": [{"date": "2026-10-25", "placement_id": 77, "clicks": 288, "impressions": 14980, "ref": "daily-report-2026-10-25"}]}'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'rows' => [
            [
                'date' => '2026-10-25',
                'placement_id' => 77,
                'clicks' => 288,
                'impressions' => 14980,
                'ref' => 'daily-report-2026-10-25',
            ],
        ],
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/campaigns/321/partner-clicks', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    rows: [
      {
        date: '2026-10-25',
        placement_id: 77,
        clicks: 288,
        impressions: 14980,
        ref: 'daily-report-2026-10-25',
      },
    ],
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "error": false,
  "data": {
    "saved": 1,
    "errors": []
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1537
}

Sends the clicks your partner counted, one row per day and placement. A row for a day and placement already sent replaces it.

NameTypeRequiredDefaultDescription
rowsarrayrequiredThe rows, 366 at most per call (any beyond are ignored).
rows[].datedaterequiredThe day, YYYY-MM-DD.
rows[].clicksintegerrequiredThe clicks of the day.
rows[].placement_idintegeroptional0The placement, from campaign details; 0 for the whole campaign.
rows[].impressionsintegeroptionalThe impressions of the day.
rows[].ordersintegeroptionalThe orders of the day.
rows[].revenuenumberoptionalThe revenue of the day.
rows[].refstringoptionalYour own reference for the row (180 characters at most).

The answer gives the number of rows saved, and in errors each row that was not, for example "row 2: unknown placement_id". A bad row does not stop the others.

POST/campaigns/{id}/linksRequires the API links feature
curl --request POST 'https://api.getsquirrel.co/api/v2/campaigns/321/links' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'platform=newsletter' \
  --form 'label=October newsletter'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/campaigns/321/links');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'platform' => 'newsletter',
        'label' => 'October newsletter',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/campaigns/321/links', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    platform: 'newsletter',
    label: 'October newsletter',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "error": false,
  "data": {
    "placement_id": 78,
    "platform": "newsletter",
    "label": "October newsletter",
    "link": "https://sqrlinks.ai/[YOUR_CLIENT_ID]/c/autumn-headphones-newsletter-c3d4?utm_source=newsletter&utm_medium=email&utm_campaign=cmp_321"
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1538
}

Creates a new placement for the campaign, with its own tracking link.

NameTypeRequiredDefaultDescription
platformstringoptionalotherWhere the link will be used: onsite, facebook, instagram, tiktok, x, apple_news, newsletter, app or other.
labelstringoptionalthe platform's nameA name for the placement (180 characters at most).

CSV Master

GET/csv-master/list-filesEnabled on request
curl 'https://api.getsquirrel.co/api/v2/csv-master/list-files' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/csv-master/list-files');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/csv-master/list-files', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "error": false,
  "data": [
    {
      "id": 12,
      "csv_master_id": 3,
      "master_name": "My Product Feed",
      "file_type": "master",
      "created_at": "2026-10-25 06:00:00",
      "last_download": "2026-10-25 10:15:42"
    },
    {
      "id": 13,
      "csv_master_id": 3,
      "master_name": "My Product Feed",
      "file_type": "diff",
      "created_at": "2026-10-26 06:00:00",
      "last_download": null
    }
  ],
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1539
}

CSV Master lets you download the CSV export files prepared for your account. There are two types of files:

Files are generated on a schedule the Squirrel team sets, and can be downloaded once ready, in two steps:

  1. Call List files to see which files are available.
  2. Call Download file with a file_id to get a download link valid for 15 minutes.
  3. Open the link (a GET that follows redirects): it sends you to the file.

The link is signed: it does not show where the file is stored, and it stops working when it expires or if it is changed.

List files

GET /csv-master/list-files

Returns the files you can download, newest first. No parameters.

FieldDescription
idThe file's id: the file_id of Download file.
csv_master_idThe id of the CSV Master feed the file belongs to.
master_nameThe feed's name.
file_typemaster (full export) or diff (changes only).
created_atWhen the file was generated.
last_downloadWhen your account last downloaded it, or null.

Download file

POST/csv-master/download-file
curl --request POST 'https://api.getsquirrel.co/api/v2/csv-master/download-file' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --form 'file_id=12'
<?php
$token = '[YOUR_ACCESS_TOKEN]';

$ch = curl_init('https://api.getsquirrel.co/api/v2/csv-master/download-file');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'file_id' => '12',
    ]),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://api.getsquirrel.co/api/v2/csv-master/download-file', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer [YOUR_ACCESS_TOKEN]',
  },
  body: new URLSearchParams({
    file_id: '12',
  }),
});
const result = await response.json();

The above command returns JSON structured like this:

{
  "error": false,
  "data": {
    "url": "https://api.getsquirrel.co/api/v2/csv-master/fetch?token=[SIGNED_TOKEN]",
    "expires_in_minutes": 15
  },
  "message": "",
  "monthly_quota": 100000,
  "current_usage": 1540
}
NameTypeRequiredDefaultDescription
file_idintegerrequiredThe id of the file, from List files.
FieldDescription
urlThe download link. Open it with a GET that follows redirects; it needs no API key.
expires_in_minutesHow long the link works: 15.
StatusMeaning
400file_id is missing.
403The file is not one of your account's files, or your account has no CSV Master.
404The file does not exist or is not ready.
500The link could not be made: try again or contact support.

An expired or changed download link answers 400 when you open it.

AI assistants (MCP)

Squirrel runs two Model Context Protocol (MCP) servers, so AI assistants such as Claude and ChatGPT can search products and prices with Squirrel. Both are read-only and use MCP's Streamable HTTP transport.

Product search MCP

POSThttps://mcp.getsquirrel.co/mcp
curl 'https://mcp.getsquirrel.co/mcp' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json, text/event-stream' \
  --data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_products","arguments":{"query":"sony wh-1000xm5","country":"GB","max_products":1,"max_offers":3}}}'

The tool's structured answer looks like this (shortened):

{
  "query": "sony wh-1000xm5",
  "country": "GB",
  "currency": "GBP",
  "products": [
    {
      "name": "Sony WH-1000XM5 Premium Wireless Noise Cancelling Headphones",
      "brand": "Sony",
      "image": "https://m.media-amazon.com/images/I/31SNSWUACeL._SL160_.jpg",
      "barcodes": ["4548736132580"],
      "offers": [
        { "retailer": "Currys Business", "price": 228.99, "currency": "GBP", "in_stock": true, "link": "https://sqrlinks.ai/27/l/[CODE]/[TAG]" },
        { "retailer": "Currys", "price": 229, "currency": "GBP", "in_stock": true, "link": "https://sqrlinks.ai/27/l/[CODE]/[TAG]" },
        { "retailer": "Argos", "price": 229.99, "currency": "GBP", "in_stock": true, "link": "https://sqrlinks.ai/27/l/[CODE]/[TAG]" }
      ]
    }
  ],
  "notice": null,
  "offers_incomplete": false
}

A public MCP server that anyone can add to an AI assistant, with no account or key. It has one tool, search_products: find products by name, barcode, ASIN, model number or product page URL, and get each product's current offers in one country, cheapest first, in stock first, each with a link to the retailer.

Address: https://mcp.getsquirrel.co/mcp

Add it to an assistant: in Claude, open Settings → Connectors → Add custom connector and paste the address; in ChatGPT, add it as a connector (developer mode). Any MCP client that supports remote servers over Streamable HTTP can use it.

ArgumentTypeRequiredDefaultDescription
querystringrequiredA product name, barcode, ASIN, model number or product page URL; 1,000 characters at most.
countrystringoptionalGBThe shopper's country: GB, US, AU or CA.
max_productsintegeroptional5Products to return, 1 to 10.
max_offersintegeroptional5New-condition offers per product, 1 to 20.

Limits: 20 searches a minute from one address, and 120 a minute in total; above that the tool answers "Too many searches from this address: at most 20 a minute." or asks to try again in a minute.

Publishers MCP

POSThttps://mcp.getsquirrel.co/mcp/publisherEnabled on request

The Publishers MCP connects an AI assistant to your Squirrel account, so you can ask it about your own websites in plain words: "How did my commission do last week compared with the week before?", "Which retailers got the most clicks on my site this month?"

Address: https://mcp.getsquirrel.co/mcp/publisher

Connect: add the address as a custom connector in your assistant. The first time, the assistant opens a Squirrel sign-in page: sign in with your Squirrel dashboard account and approve the assistant. You can disconnect it from the AI assistants section of your dashboard's Integration pages. It does not use your API key.

What the assistant can do, for the websites your Squirrel user can see:

ToolsWhat they give
list_my_websites, get_helpYour websites, and what the assistant can ask.
search_productsThe product search above, with the links of your website.
insights_summary, insights_trend, insights_breakdown, insights_top, insights_compareYour Insights figures (clicks, commission, revenue, sales, orders and your Google Analytics traffic), totals, trends by day, week or month, breakdowns by retailer, network, country, page, product, author, link or UTM, top lists and period comparisons. Data until yesterday, up to two years back.
offers_overview, offers_widgets, offers_retailers, offers_traffic, offers_squirrelizers, offers_playlists, offers_marketplace_revenueYour Squirrel widgets' impressions and clicks by widget, retailer, page and day, and your marketplace revenue by month. Each needs the matching permission on your Squirrel user.

Every tool only reads: the assistant cannot change anything on your account. Calls are limited to 60 a minute per user (10 product searches a minute).

Changelog

What changed in the API and in this reference, newest first. Changes to the API are added here when they ship.

2026-10-26

New in the reference

Corrected

Removed