Pricing API Dashboard Log in

BootLeads API

Search and retrieve store profiles over HTTPS with JSON. The API comes with the Enterprise plan and returns the same store data as the search and CSV export.

Base URL
https://bootleads.com/api/v2
Authentication
Authorization: Bearer <key>
Plan
Enterprise
Row balance
+50,000 a day
Saves up to 3,000,000
Rate limit
3 requests a second
Per API key
Pages
Up to 100 stores
Cursors never expire

Quick start

Copy your key from Account settings and send it as a Bearer token. The key works while your own Enterprise subscription is active.

Request
curl https://bootleads.com/api/v2/stores/search \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"platform": ["shopify"], "country": ["US"], "page": {"size": 100}, "fields": ["title", "contact"]}'
Request
import requests

response = requests.post(
    "https://bootleads.com/api/v2/stores/search",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"platform": ["shopify"], "country": ["US"], "page": {"size": 100}, "fields": ["title", "contact"]},
    timeout=60,
)
data = response.json()
print(response.headers["X-Rows-Remaining"], [store["id"] for store in data["results"]])
Request
const response = await fetch("https://bootleads.com/api/v2/stores/search", {
  method: "POST",
  headers: {"Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"},
  body: JSON.stringify({platform: ["shopify"], country: ["US"], page: {size: 100}, fields: ["title", "contact"]}),
});
const data = await response.json();
console.log(response.headers.get("X-Rows-Remaining"), data.results.map((store) => store.id));

The response is shown under POST /stores/search.

Rows and rate limits

  • Every store returned by stores/search or stores/mget is one row off your balance. The balance grows by 50,000 rows at 00:00 UTC each day and holds up to 3,000,000 rows. See how the balance adds up.
  • A request runs while the balance is above zero, so the last page can take it a little below zero. After that, requests return 429 with the code row_quota_exhausted until the next refill.
  • Each API key can make 3 requests per second. Faster requests return 429 with the code rate_limit_exceeded.

Only the stores you get back use rows. Missing ids, empty pages, errors and GET /filters are free.

Response headerMeaning
X-Rows-RemainingRows left after this request.
X-Rows-Refill-AtWhen the next refill happens, in UTC (ISO 8601).
Retry-AfterOn a 429: seconds to wait before trying again.

How the balance adds up

The balance is not a monthly allowance. It grows a little every day you are subscribed, and your billing date does not change it.

  • First purchase: your first Enterprise subscription starts the balance full, at 3,000,000 rows.
  • Every day: at 00:00 UTC the balance grows by 50,000 rows. Rows you don't use carry over until the balance reaches 3,000,000, which is 60 days of refills.
  • Renewals: renewing does not reset the balance or top it up. It keeps growing by the day, as before.
  • If the subscription ends: the balance stops growing after the last day you paid for and keeps the rows you had. When you subscribe again, it grows again from the next day. The days in between add nothing.

For example, one account over four months:

DateWhat happensBalance after
Oct 1 You buy Enterprise. The balance starts full. 3,000,000
Oct 1 Your first sync downloads 2,800,000 stores. 200,000
Oct 2–31 30 refills of 50,000. Your daily sync uses 5,000 a day. 1,550,000
Nov 1 The subscription renews. The balance is not reset or topped up; the daily refill carries on. 1,595,000
Dec 1 The subscription ends. Its last day still gets a refill, and the rows you have left are kept. 2,945,000
Jan 15 You buy Enterprise again. The 45 days without a subscription add nothing. 2,945,000
Jan 16 The daily refill starts again. 2,995,000

Pagination and syncing

Search results come in pages of up to 100 stores, ordered by created_at (newest first unless you set sort.created_at to asc). While has_more is true, pass next_cursor back in page.cursor for the next page; on the last page it is an empty string. A cursor keeps the filters, sort and fields of the request that started it, so send it without filters or sort. Cursors do not expire: you can stop and continue days later. A cursor issued before a change to our filters is rejected with cursor_invalid_or_expired; start a new search.

Keeping a copy in sync

Every page also returns resume_cursor, a bookmark just after the last store on the page, and the last page has one too. What it brings back depends on the sort order:

  • Oldest first (sort.created_at set to asc): it is a sync bookmark. Pass it later and you get the stores added since that match the cursor's filters. Use this to keep a copy of the data.
  • Newest first (the default): it carries on towards older stores. It picks up a run you stopped halfway, but it never brings newer stores. To check for new stores in this order, set created_at.min to the newest created_at you already have, and drop the ids you have seen.

Stores that match your filters later are not returned

The bookmark follows the date a store was added (created_at), while filters are checked against each store as it is now. A password-protected store that opens, or a store whose category, followers or Shopify registration country we fill in later, sits behind your bookmark. Sync with filters that do not change once a store is added, such as platform, include password_protected in status, and apply the rest of your filters on your side.

  1. First run: send platform and status with sort.created_at set to asc, page until has_more is false, and save resume_cursor with each page you store.
  2. A whole platform can hold more stores than your row balance. When a request returns row_quota_exhausted, stop and continue from the saved cursor after the next refill.
  3. Every day after: send the saved cursor, page to the end and save the new resume_cursor.
  4. Store results by id: a store can come back more than once.
  5. Every few days, send the ids of stores you hold as password_protected to /stores/mget to see which have opened.
Daily sync in Python
import time

import requests

API = "https://bootleads.com/api/v2/stores/search"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
FIRST_RUN = {
    "platform": ["shopify"],
    "status": ["active", "password_protected"],
    "sort": {"created_at": "asc"},
    "page": {"size": 100},
}


def sync(bookmark=None):
    """Page from the bookmark to the end; returns the bookmark for the next run."""
    body = {"page": {"size": 100, "cursor": bookmark}} if bookmark else FIRST_RUN
    while True:
        resp = requests.post(API, json=body, headers=HEADERS, timeout=60)
        if resp.status_code == 429:
            if resp.json()["error"]["code"] == "row_quota_exhausted":
                return bookmark  # out of rows: run again after the refill
            time.sleep(int(resp.headers.get("Retry-After", 1)))  # too fast: wait, then retry
            continue
        resp.raise_for_status()
        data = resp.json()
        bookmark = data["resume_cursor"]
        save(data["results"], bookmark)  # upsert by id and keep the bookmark together
        if not data["has_more"]:
            return bookmark
        body = {"page": {"size": 100, "cursor": data["next_cursor"]}}
Other ways a store can slip past a bookmark

Pages are not a frozen snapshot. Stores that close while you page drop out of later pages, and stores added meanwhile show up at the end of an ascending run. A store reaches search a few minutes after it is added, and when we merge two addresses of one store or a store moves to another platform it keeps the earlier created_at; either way it can land just before your bookmark. Rerun the full sync now and then if you need every one.

Endpoints

POST/stores/mget

One row per store found

Get up to 50 stores by domain, in the order you ask for them. Domains we do not have come back in missing_ids and do not use rows. Takes fields too.

Request
curl https://bootleads.com/api/v2/stores/mget \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["example-store.com", "unknown-store.com"], "fields": ["platform", "product"]}'
Response
{
  "missing_ids": ["unknown-store.com"],
  "results": [
    {
      "id": "example-store.com",
      "created_at": "2025-01-15T10:30:00+00:00",
      "platform": "shopify",
      "product": {"count": 128, "avg_price": 39.99, "avg_price_usd": 39.99, "currency": "USD"}
    }
  ]
}

GET/filters

Free

Every filter with how to write it, and for list filters the values you can send, with approximate store counts refreshed hourly. Send the key of a value, not its label: labels can change. Add ?key=apps to get one filter.

Request
curl "https://bootleads.com/api/v2/filters?key=country" -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "filters": [
    {
      "key": "country",
      "type": "array",
      "description": "Where the store is based, not where it sells ...",
      "docs_url": "https://bootleads.com/data/filters/#filter-country",
      "values": [{"key": "US", "label": "United States", "count": 2154021}, ...]
    }
  ]
}

Filters

Filters are top-level keys of the search body. Each one narrows the results; several values in one filter match a store that has any of them. Ranges take {"min": .., "max": ..}, both inclusive and either optional; dates are ISO 8601 and use UTC. The descriptions are the ones in the store search, so the filter reference shows the same text.

Only active stores come back unless you set status. List filters take the value keys from GET /filters. Filters with a fixed set of lowercase values, such as status, reject a value they don't know with 422. In the others case does not matter, except in theme and province names; UK works as GB, and a value we don't have, such as a misspelt country, matches no store. Where a description mentions Unknown, send unknown for stores without a value.

Basics

platform

Platform

The ecommerce platform the store runs on. Smaller platforms are recorded under their own name, such as `opencart`, but only those with enough stores are listed as values; `custom` means storefront software built in-house.

Values: GET /filters?key=platform

In the filter reference

Array of values

country

HQ Country

Where the store is based, not where it sells: many stores ship worldwide. Shopify uses the registered country; other platforms are inferred from store settings, phone, domain and currency. Send `unknown` for stores with no detected country.

Values: GET /filters?key=country

In the filter reference

Array of values

province

State / Province

Shopify only

The state, province or region the merchant registered with Shopify, such as California or Ontario. Shopify only asks for one in some countries. Write each value as country code and name, e.g. US:California.

Values: GET /filters?key=province

In the filter reference

Array of values

category

Category

A top-level category; it also matches stores in any of its subcategories. Use `subcategory` for a single subcategory. Assigned by an AI model; send `unknown` for stores it has not classified yet. Category and subcategory values combine: a store in any of them matches.

Values: GET /filters?key=category

In the filter reference

Array of values

subcategory

Category

A subcategory. Assigned by an AI model; send `unknown` for stores it has not classified yet. Category and subcategory values combine: a store in any of them matches.

Values: GET /filters?key=subcategory

In the filter reference

Array of values

status

Store Status

Whether visitors can open the store. Password-protected stores are usually not launched yet or temporarily closed. Only active stores are returned unless you set it.

  • active
  • password_protected
In the filter reference

Array of values

Products

ships_to

Ships To

Shopify only

Countries the store delivers to, from its Shopify shipping settings. `*` (Worldwide) matches stores that ship everywhere.

Values: GET /filters?key=ships_to

In the filter reference

Array of values

Contact & social

social_platforms

Social Platforms

Stores that link to a profile on any of the chosen networks. Send `unknown` for stores with no social profile.

Values: GET /filters?key=social_platforms

In the filter reference

Array of values

Advertising

meta_ads

Meta Ads

Whether the store advertises on Facebook and Instagram: started an ad in the last 30 days, has run ads at some point, or never seen advertising.

  • active
  • ever
  • never
In the filter reference

One value

tracker_platforms

Tracking Pixels

Stores with a tracking pixel from any of the chosen vendors. Send `unknown` for stores with no pixel at all.

Values: GET /filters?key=tracker_platforms

In the filter reference

Array of values

Business & traits

business_model

Business Model

How the store fulfils orders: dropshipping, print on demand or cash on delivery. Dropshipping is read from a store's category links, Shopify apps and product vendors, print on demand from its Shopify apps and product vendors, and cash on delivery from page content.

  • dropshipping
  • pod
  • cod
In the filter reference

Array of values

traits

Traits

Stores that match every chosen trait. Shopify Plus and Offers Installments (Shop Pay Installments) apply to Shopify stores only.

  • shopify_plus
  • has_mobile_app
  • platform_changed
  • offers_installments
In the filter reference

Array of values

previous_platforms

Migrated From

The platform a store ran on before its current one. Only switches seen since we started tracking the store are known.

Values: GET /filters?key=previous_platforms

In the filter reference

Array of values

pages

Store Pages

Stores that link to the chosen page types, such as wholesale, affiliate or shipping.

Values: GET /filters?key=pages

In the filter reference

Array of values

Technologies & apps

technologies

Technologies

Stores running any of the chosen tools, detected on the store's homepage across all platforms: email, reviews, subscriptions, support, search, ads and more.

Values: GET /filters?key=technologies

In the filter reference

Array of values

technology_category

Technology Category

Stores running any tool in the chosen categories. Combined with technologies, a store must match both.

Values: GET /filters?key=technology_category

In the filter reference

Array of values

theme

Shopify Theme

Shopify only

The theme the storefront is built on, such as Dawn, Prestige or the retired Debut. Custom themes keep whatever name their developer gave them.

Values: GET /filters?key=theme

In the filter reference

Array of values

Domain

Response fields

Each store is a JSON object keyed by its domain in id; fields without a value are left out or come back empty. To get less, pass fields with any of the top-level keys below; id and created_at always come back.

  • platform
  • title
  • description
  • country
  • language
  • status
  • categories
  • is_dropshipping
  • is_cod
  • is_print_on_demand
  • has_ios_app
  • has_android_app
  • updated_at
  • product
  • apps
  • technologies
  • contact
  • social
  • fb_ads
  • shopify

The figure on the right is the share of stores that have the field.

Store basics

Domain

Observed

  • id

The store's website address. Each domain is one store.

100% of active stores

Platform

Derived

  • platform

The ecommerce platform the store runs on. Besides the 6 main platforms, stores on 53 smaller platforms such as OpenCart, Shopware and Cafe24 are recorded under their own name. Custom-built means the store runs storefront software built in-house, or on a platform we don't recognise yet.

100% of active stores

Status

Derived

  • status

Whether the store is open or behind a password page.

100% of active stores

Title

Observed

  • title

The store name shown in the browser tab.

99.4% of active stores

Description

Observed

  • description

The short description the store gives search engines.

57.8% of active stores

Myshopify Domain

Observed · Shopify only

  • shopify.myshopify

The store's permanent myshopify.com address.

99.8% of Shopify stores

Geography & classification

HQ Country

Inferred

  • country

Where the store is based, not where it sells.

87.7% of active stores

HQ Location

Observed · Shopify only

  • shopify.registration_country
  • shopify.province
  • shopify.city

City, state or province, and country the store registered with Shopify.

99.2% of Shopify stores

Language

Inferred

  • language

The main language of the storefront.

99.8% of active stores

Category

Inferred

  • categories

Category and subcategory from a taxonomy of 29 categories and 159 subcategories.

89.5% of active stores

Tags

Inferred

  • is_dropshipping
  • is_print_on_demand
  • is_cod
  • has_ios_app
  • has_android_app
  • shopify.offers_installments

Yes/no flags for dropshipping, print on demand, cash on delivery, an iOS or Android app, and installment payments such as Shop Pay Installments (Shopify only).

16.2% of active stores

Ships To

Observed · Shopify only

  • shopify.ships_to_countries

Countries the store delivers to. Stores that ship everywhere show Worldwide.

98.3% of Shopify stores

Products & pricing

Product Count

Observed

  • product.count

Number of products in the catalog.

93.6% of active stores

Avg Price (USD)

Derived

  • product.avg_price_usd

Average product price, converted to US dollars.

78.5% of active stores

Avg Price

Derived

  • product.avg_price

Average product price in the store's own currency.

78.8% of active stores

Currency

Observed

  • product.currency

The currency the store sells in.

94.1% of active stores

Payment Methods

Observed · Shopify only

  • shopify.payment_methods

Wallets offered at checkout: Shop Pay, PayPal, Apple Pay, Google Pay and Amazon Pay.

78.1% of Shopify stores

Contact

Email

Observed

  • contact.email

Email addresses on the store's own domain.

35.5% of active stores

Phone

Observed

  • contact.phone

Phone numbers in international format.

21.0% of active stores

WhatsApp

Observed

  • contact.whatsapp

WhatsApp numbers from chat links and buttons.

9.3% of active stores

Social reach

Total Followers

Estimated

  • social.total_followers

Followers across the store's Facebook, Instagram, YouTube, Pinterest and TikTok profiles.

30.0% of active stores

Facebook Followers

Estimated

  • social.followers.facebook

Followers of the store's Facebook page.

20.0% of active stores
  • social.followers.instagram

Followers of the store's Instagram account.

23.8% of active stores

TikTok Followers

Estimated

  • social.followers.tiktok

Followers of the store's TikTok account.

5.5% of active stores

YouTube Followers

Estimated

  • social.followers.youtube

Subscribers to the store's YouTube channel.

7.0% of active stores
  • social.followers.pinterest

Followers of the store's Pinterest profile.

2.6% of active stores

Facebook Link

Observed

  • social.link.facebook
  • social.platforms

The store's Facebook page.

38.4% of active stores

Instagram Link

Observed

  • social.link.instagram
  • social.platforms

The store's Instagram profile.

46.9% of active stores

TikTok Link

Observed

  • social.link.tiktok
  • social.platforms

The store's TikTok profile.

11.1% of active stores

YouTube Link

Observed

  • social.link.youtube
  • social.platforms

The store's YouTube channel.

13.1% of active stores

Twitter Link

Observed

  • social.link.twitter
  • social.platforms

The store's X (Twitter) profile.

8.2% of active stores

Pinterest Link

Observed

  • social.link.pinterest
  • social.platforms

The store's Pinterest profile.

4.4% of active stores

LinkedIn Link

Observed

  • social.link.linkedin
  • social.platforms

The store's LinkedIn page.

6.4% of active stores

Ads & technology

  • fb_ads.count

Meta (Facebook and Instagram) ads the store started in the last 30 days.

4.1% of active stores

Last Meta Ad

Observed

  • fb_ads.last_start_date

Start date of the store's most recent Meta ad.

8.2% of active stores

Shopify Apps

Inferred · Shopify only

  • apps.active

Shopify apps running on the store, with name, icon and App Store link.

57.1% of Shopify stores

Technologies

Inferred

  • technologies.active
  • technologies.checked_at

Third-party tools running on the store, in 13 categories, and when the store was last checked for them.

62.7% of active stores

Shopify Theme

Observed · Shopify only

  • shopify.theme_schema_name

The Shopify theme the store uses.

97.1% of Shopify stores

Dates

First Seen

Observed

  • created_at

When we first found the store.

100% of active stores

Last Updated

Observed

  • updated_at

When we last checked the store.

100% of active stores

Errors

Errors return a status code and a JSON body with a stable code and a readable message:

Error
{"error": {"code": "row_quota_exhausted", "msg": "Row balance is used up. It refills at 00:00 UTC."}}
StatusCodeMeaningWhat to do
400invalid_parametersThe request cannot be run, for example a cursor sent together with filters.Fix the request
400cursor_invalid_or_expiredThe cursor is not one we issued, or was issued before a change to our filters.Start a new search
401authentication_failedMissing or unknown API key.Check the key
402subscription_expiredThe Enterprise subscription has ended.Renew the plan. If it renews automatically, retry in a few minutes: the renewal can take a moment to arrive.
403api_permission_deniedThe account's own plan does not include the API.Use an Enterprise account's key
403account_disabledThe account has been disabled.Contact us
404not_foundNo such endpoint.Check the path
422validation_errorA field is unknown or has the wrong type; the message names it.Fix the field
429rate_limit_exceededMore than the per-second limit. Retry after Retry-After
429row_quota_exhaustedThe row balance is used up until the next refill. Retry after Retry-After
500internal_server_errorSomething failed on our side. Retry with backoff

Terms of use

The API is covered by our terms of service. You may build our data into your own products, but you may not resell the raw data or rebuild a service like BootLeads. Keep your API key private: do not share it or ship it in software other people use.

Get an API key

The API is included with the Enterprise plan. Your key is on the Account settings page.