catchbuys

catchbuys API documentation

The catchbuys API gives you programmatic access to 14,200+ digital businesses for sale, aggregated daily from 22 marketplaces (Flippa, Empire Flippers, Quiet Light, TrustMRR, and more).

All endpoints live under this base URL:

https://catchbuys.com/api/v1

Responses are JSON. Authentication uses Sanctum personal access tokens, passed as Authorization: Bearer YOUR_API_KEY.

Authentication

Every authenticated endpoint requires a Bearer token. Tokens are issued from your account.

To get a token:

  1. Create a free account.
  2. Visit /api/access.
  3. Click "Create Token", give it a name, copy the displayed token. It is shown only once.

Send the token on every request:

Example header

Authorization: Bearer YOUR_API_KEY

Rate limiting

All authenticated endpoints are throttled to 60 requests per minute per token. Free during the POC.

Each response includes the standard rate-limit headers:

Header Description
X-RateLimit-Limit Maximum requests per minute (60).
X-RateLimit-Remaining Requests remaining in the current window.

Exceeding the limit returns HTTP 429 Too Many Requests. Wait until the window resets and retry.

Tip: use per_page=100 to maximise data per request — pagination is cheaper than re-querying.

Endpoints

GET /api/v1/listings

List listings with optional filters.

Returns a paginated array under data plus a meta object with pagination info.

GET /api/v1/listings/{id}

Get a single listing by ID.

Returns the full listing object including description, total_revenue, growth_30d, customers, first_listed_at.

GET /api/v1/stats

Platform statistics.

Total active listings, count by type and by source.

GET /api/v1/sources

Marketplace catalogue.

Returns slug, label and current count for each source.

GET /api/v1/user

Authenticated user info.

Returns id, name, email.

GET /api/v1/ping

Public health check (no auth required).

Returns { "ok": true, "service": "catchbuys", "time": "..." }.

Query parameters

All parameters apply to GET /api/v1/listings.

Parameter Type Default Description
q string Keyword in listing name (LIKE match).
source string Single source slug.
flippa, empireflippers, quietlight, trustmrr, dealslide, businessesforsale, websiteclosers, daltons, indiemaker, microns, acquirebase, investorsclub, latonas, dotmarket, bpifrance, nicheinvestor, moneynomad, fih, ecommercebrokers, appbusinessbrokers, sellerforce, acquisitionsdirect
sources[] string[] Multiple source slugs (repeat the param).
types[] string[] Business model.
saas, ecom, site, agency, other
industries[] string[] Exact industry match.
mrr_min integer Minimum monthly revenue.
price_min integer Minimum asking price.
price_max integer Maximum asking price.
age_min integer Minimum age in years.
margin_min integer Minimum profit margin (%).
sort string mrr Sort key.
mrr, price, multiple, margin, age, added
dir string desc Sort direction (asc or desc).
per_page integer 25 Results per page (1-100).
page integer 1 Page number (1-indexed).

Response fields

Fields returned for each listing on /api/v1/listings. Some are only included on the single-listing endpoint.

Field Type Nullable Description
Identity
id integer No Internal listing ID.
source string No Source slug (flippa, empireflippers, …).
source_id string Yes Source-side identifier when available.
name string No Listing name as shown on the source.
url string Yes URL of the listing on the source marketplace.
website string Yes Real website URL when disclosed (mostly for TrustMRR / Flippa).
type string Yes saas | ecom | site | agency | other
industry string Yes Industry / niche (source-defined).
language string Yes Listing language (mostly en, fr).
Pricing & financials
asking_price integer Yes Asking price in the listing currency.
currency string Yes USD | EUR | GBP
monthly_revenue integer Yes MRR (where reported).
annual_revenue integer Yes TTM revenue.
annual_profit integer Yes Annual profit / SDE (premium brokers).
profit_margin float Yes Profit margin in percent.
multiple float Yes Asking price ÷ annual_profit when present, else ÷ annual_revenue.
Performance
age_years integer Yes Years since the business was founded (source-reported).
monthly_traffic integer Yes Monthly visitors (where reported).
thumbnail_url string Yes Listing thumbnail URL.
Detail-only fields
description string Yes Long description (single-listing endpoint only).
total_revenue integer Yes All-time revenue (TrustMRR only).
growth_30d float Yes 30-day growth rate (TrustMRR only).
customers integer Yes Customer count (TrustMRR only).
first_listed_at string (ISO 8601) Yes When the source first listed the business (TrustMRR only).
Timestamps
created_at string (ISO 8601) No When catchbuys first imported the listing.
updated_at string (ISO 8601) No Last refresh.

multiple — computed against annual_profit when present (premium brokers like Quiet Light, Empire Flippers, Latonas, AcquisitionsDirect price against SDE / profit), otherwise against annual_revenue. This matches what the source displays and avoids the apples-to-oranges comparison you would get from a naive price ÷ revenue calc.

Pagination

Listing endpoints return a flat data array plus a meta object describing the page.

meta fields

totalTotal number of matching rows.
pageCurrent page (1-indexed).
per_pageItems per page.
last_pageFinal page number.
has_moretrue if another page exists after this one.
{
  "data": [ ... ],
  "meta": {
    "total": 9384,
    "page": 1,
    "per_page": 25,
    "last_page": 376,
    "has_more": true
  }
}

Code examples

Copy-paste starters in three languages. Replace YOUR_API_KEY with the token you generated at /api/access.

# List SaaS listings with $5k+ MRR, sorted by lowest multiple
curl "https://catchbuys.com/api/v1/listings?types[]=saas&mrr_min=5000&sort=multiple&dir=asc&per_page=10" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

# Get a single listing by ID
curl "https://catchbuys.com/api/v1/listings/12345" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

# Filter by industry and price range
curl "https://catchbuys.com/api/v1/listings?industries[]=Health&price_max=500000&age_min=3" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"

# Multiple sources at once
curl "https://catchbuys.com/api/v1/listings?sources[]=flippa&sources[]=empireflippers&mrr_min=10000" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://catchbuys.com/api/v1';

// List listings with filters
const params = new URLSearchParams();
params.append('types[]', 'saas');
params.append('mrr_min', '5000');
params.append('per_page', '10');

const response = await fetch(`${BASE_URL}/listings?${params}`, {
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Accept': 'application/json'
  }
});

const { data, meta } = await response.json();
console.log(`Found ${meta.total} listings`);

// Get a single listing
const listing = await fetch(`${BASE_URL}/listings/12345`, {
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Accept': 'application/json'
  }
}).then(r => r.json());
import requests

API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://catchbuys.com/api/v1'

headers = {
    'Authorization': f'Bearer {API_KEY}',
    'Accept': 'application/json'
}

# List listings with filters
response = requests.get(f'{BASE_URL}/listings', headers=headers, params={
    'types[]': 'saas',
    'mrr_min': 5000,
    'sort': 'multiple',
    'dir': 'asc',
    'per_page': 25
})
data = response.json()
print(f"Found {data['meta']['total']} listings")

for listing in data['data']:
    print(f"{listing['name']} — MRR: {listing['monthly_revenue']} — Asking: {listing['asking_price']} ({listing['multiple']}x)")

# Get a single listing
listing = requests.get(f'{BASE_URL}/listings/12345', headers=headers).json()

Errors

Standard HTTP status codes. Errors return a JSON body with a message field.

Code Meaning Description
200 OK Request succeeded.
401 Unauthenticated Missing or invalid Bearer token.
403 Forbidden Token does not have access to the resource.
404 Not found Listing or endpoint does not exist.
422 Validation error A query parameter is malformed.
429 Too many requests Rate limit exceeded — wait for the window to reset.

Example error bodies

// 401 Unauthenticated
{
  "message": "Unauthenticated."
}

// 429 Too Many Requests
{
  "message": "Too Many Attempts."
}

MCP server

catchbuys also exposes an MCP server so AI agents (Claude Desktop, Claude Code, Cursor, Windsurf, custom) can query the inventory in plain English. Same Bearer token as the REST API.

Once configured, an agent can answer prompts like:

"Find SaaS listings under $500k with at least 3 years of history"
"Show me Amazon FBA brands listed in the last 7 days, sorted by lowest multiple"

Setup

Add this to your MCP client config.

Claude Code / Cursor (~/.claude.json or .cursor/mcp.json)

{
  "mcpServers": {
    "catchbuys": {
      "type": "http",
      "url": "https://catchbuys.com/mcp/catchbuys",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "catchbuys": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://catchbuys.com/mcp/catchbuys",
        "--header",
        "Authorization: Bearer YOUR_API_KEY"
      ]
    }
  }
}

Claude Desktop requires Node.js for npx mcp-remote.

Learn more about the MCP server

Troubleshooting

401 Unauthenticated

Your token is missing or invalid.

  • Make sure the header format is exactly: Authorization: Bearer YOUR_TOKEN (with "Bearer " prefix and a space).
  • Copy the full token including the number prefix and pipe character (e.g. 42|abc...).
  • Tokens are shown only once at creation. If lost, revoke and create a new one at /api/access.

429 Too Many Requests

You exceeded 60 requests/minute.

  • Wait until the rate-limit window resets (max 60s) and retry.
  • Use per_page=100 to pull 4× more rows per call.
  • Cache results client-side when scanning the entire catalogue.

MCP server disconnected

If your MCP client shows "Server disconnected" for catchbuys:

  • Check that your token is still valid (create a new one if needed).
  • Restart the MCP client completely (Cmd+Q on Mac for Claude Desktop, not just close the window).
  • Verify your config file syntax — JSON parsers are unforgiving about trailing commas.

Still stuck? Drop us a line — include the failing request and the response body so we can reproduce.