REST API reference
Live, enriched data on US local businesses. Version 1.0.0. Download the OpenAPI 3.1 spec.
Aggregates are free and never return names, IDs or coordinates. Record-level endpoints bill prepaid credits: 3 credits ($0.03) per standard record, 10 credits ($0.10) per hot record (new or changed in the last 30 days), charged once per unique record per calendar month. Paid responses include `credits_used` and `balance_remaining`. Over-rate requests answer 429 with `{"error":"slow_down","retry_after":N}`.
Conventions
- Base URL
https://opsblu.com. All paths are under/v1. JSON in, JSON out (exports download CSV). - Auth:
Authorization: Bearer ob_live_…. Endpoints marked public need no credential. - Slugs are lowercase-hyphenated:
oregon,portland,hvac-contractors.GET /v1/categoriesis the vocabulary. - Booleans in query strings accept
true/falseor1/0. - Aggregate counts under 5 are the string
"<5"(type Count). Treat it as "some, fewer than five". - Timestamps are RFC 3339 UTC.
Try it: free endpoints work in your terminal right now. For paid endpoints, create a key at /app/api-keys; your first key adds 750 free credits.
Aggregates
Free. Counts only, small counts rendered as "<5".
GET /v1/coverage public
Collection-wide counters and covered cities
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Coverage |
curl \
"https://opsblu.com/v1/coverage" GET /v1/stats public
Aggregate stats for a state, city or city × category
Parameters
| Name | In | Type | Description |
|---|---|---|---|
state | query | string | |
city | query | string | |
category | query | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Stats |
404 | Not found | Error |
curl \
"https://opsblu.com/v1/stats?state=oregon&city=portland&category=dentists" POST /v1/stats/polygon public
Aggregate stats inside a drawn boundary
The polygon is snapped to H3 cells server-side. Minimum area ~0.5 km². Hex counts under 5 come back as "<5".
Request body application/json, required
| Field | Type | Description |
|---|---|---|
geojsonrequired | object | |
categories | string[] | |
persona | "web" | "seo" | "ppc" | "opening" |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Stats |
curl -X POST \
-H "Content-Type: application/json" -d '{"geojson":{},"categories":[],"persona":"web"}' \
"https://opsblu.com/v1/stats/polygon" GET /v1/categories public
Controlled category vocabulary with national counts
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
curl \
"https://opsblu.com/v1/categories" Businesses
Search is free (names + signals). Opening a record bills credits.
GET /v1/businesses
Search businesses (free: names + signals, no contacts)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
state | query | string | |
city | query | string | |
zip | query | string | |
category | query | string | |
q | query | string | Name contains |
no_website | query | boolean | |
cwv_fail | query | boolean | |
platform | query | "wordpress" | "wix" | "squarespace" | "shopify" | "other" | |
has_ads | query | boolean | |
has_analytics | query | boolean | |
server_side | query | boolean | |
new_days | query | integer | |
changed_days | query | integer | |
has_phone | query | boolean | |
has_email | query | boolean | |
hot | query | boolean | |
cursor | query | string | |
limit | query | integerdefault 25 |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | BusinessPage |
429 | Rate limited. Back off for retry_after seconds. | Error |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/businesses?state=oregon&city=portland&zip=97201" GET /v1/businesses/{id}
Open a full record (billed once per month)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
401 | Sign in or pass an API key | Error |
402 | Insufficient credits or spend cap reached | Error |
404 | Not found | Error |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/businesses/ob_por_den_0007" Changes
What changed in a territory since a timestamp.
GET /v1/changes
Change events since a timestamp
Parameters
| Name | In | Type | Description |
|---|---|---|---|
since | query | string (date-time) | Default: 7 days ago |
territory_id | query | string | |
types | query | string | Comma list: new_business, site_launched, platform_changed, pixel_added, pixel_removed, closed |
state | query | string | |
city | query | string | |
category | query | string | |
limit | query | integerdefault 200 |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/changes?since=2026-10-21T00:00:00Z&territory_id=ter_3a…&types=new_business" Exports
Two-step: create (estimate) then confirm (charge).
GET /v1/exports
List exports
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/exports" POST /v1/exports
Create an export and get a credit estimate (nothing charged yet)
Request body application/json, required
| Field | Type | Description |
|---|---|---|
filters | Filters | |
ids | string[] |
Responses
| Code | Description | Body |
|---|---|---|
201 | Created, status pending_confirm | Export |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"filters":{},"ids":[]}' \
"https://opsblu.com/v1/exports" GET /v1/exports/{id}
Get an export
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Export |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/exports/ob_por_den_0007" POST /v1/exports/{id}/confirm
Confirm and charge; makes the CSV available
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | Ready | Export |
402 | Insufficient credits or spend cap reached | Error |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/exports/ob_por_den_0007/confirm" GET /v1/exports/{id}/download
Download CSV
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | CSV | text/csv |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/exports/ob_por_den_0007/download" Tools
Report card, removal requests, sample pages.
POST /v1/report-card public
Score a business website
Request body application/json, required
| Field | Type | Description |
|---|---|---|
url | string | |
business | string | |
city | string |
Responses
| Code | Description | Body |
|---|---|---|
201 | Created | object |
curl -X POST \
-H "Content-Type: application/json" -d '{"url":"https://example.com","business":"","city":"portland"}' \
"https://opsblu.com/v1/report-card" GET /v1/report-card/{id} public
Get a report card
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | ReportCard |
curl \
"https://opsblu.com/v1/report-card/ob_por_den_0007" POST /v1/removal-requests public
Ask us to remove a business
Request body application/json, required
| Field | Type | Description |
|---|---|---|
business_namerequired | string | |
domain | string | |
address | string | |
requester_emailrequired | string (email) | |
relationship | "owner" | "employee" | "other" |
Responses
| Code | Description | Body |
|---|---|---|
201 | Ticket | RemovalRequest |
curl -X POST \
-H "Content-Type: application/json" -d '{"business_name":"Alder Street Dental","domain":"example.com","address":"","requester_email":"owner@example.com"}' \
"https://opsblu.com/v1/removal-requests" GET /v1/samples/{token} public
Outreach sample page payload (noindex)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
tokenrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | |
410 | Expired |
curl \
"https://opsblu.com/v1/samples/demo" Account
Credits, billing, keys, territories.
GET /v1/me
Current account and balance
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Account |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/me" POST /v1/me/connect-bonus
Claim the +750 connect bonus (MCP/API/TagFrog)
Request body application/json
| Field | Type | Description |
|---|---|---|
source | "mcp" | "api" | "tagfrog" |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Account |
409 | Already granted |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"source":"mcp"}' \
"https://opsblu.com/v1/me/connect-bonus" POST /v1/billing/topup
Start a Stripe Checkout top-up
Request body application/json, required
| Field | Type | Description |
|---|---|---|
amountrequired | number |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"amount":20}' \
"https://opsblu.com/v1/billing/topup" PUT /v1/billing/spend-cap
Set the monthly spend cap
Request body application/json, required
| Field | Type | Description |
|---|---|---|
monthly_usd | number |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | Account |
curl -X PUT -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"monthly_usd":20}' \
"https://opsblu.com/v1/billing/spend-cap" GET /v1/billing/ledger
Credit ledger
Responses
| Code | Description | Body |
|---|---|---|
200 | OK | object |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/billing/ledger" GET /v1/keys
List keys
Responses
| Code | Description | Body |
|---|---|---|
200 | OK |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/keys" POST /v1/keys
Create an API key (secret shown once)
Request body application/json
| Field | Type | Description |
|---|---|---|
label | string | |
spend_cap | number |
Responses
| Code | Description | Body |
|---|---|---|
201 | Created | APIKey |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"label":"claude-desktop","spend_cap":20}' \
"https://opsblu.com/v1/keys" DELETE /v1/keys/{id}
Revoke a key
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
204 | Revoked |
curl -X DELETE -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/keys/ob_por_den_0007" GET /v1/territories
List territories
Responses
| Code | Description | Body |
|---|---|---|
200 | OK |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/territories" POST /v1/territories
Save a territory
Request body application/json, required
| Field | Type | Description |
|---|---|---|
id | string | |
name | string | |
kind | "city" | "zip" | "polygon" | |
state | string | |
city | string | |
zip | string | |
geojson | object | |
alerts | object |
Responses
| Code | Description | Body |
|---|---|---|
201 | Created |
curl -X POST -H "Authorization: Bearer ob_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"id":"","name":"Portland metro","kind":"city","state":"oregon"}' \
"https://opsblu.com/v1/territories" GET /v1/territories/{id}
Get a territory
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK |
curl -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/territories/ob_por_den_0007" PUT /v1/territories/{id}
Update a territory / alerts
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
200 | OK |
curl -X PUT -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/territories/ob_por_den_0007" DELETE /v1/territories/{id}
Delete a territory
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string |
Responses
| Code | Description | Body |
|---|---|---|
204 | Deleted |
curl -X DELETE -H "Authorization: Bearer ob_live_YOUR_KEY" \
"https://opsblu.com/v1/territories/ob_por_den_0007" Schemas
Shapes referenced above. Count is either an integer or the string "<5": small-count suppression protects individual businesses in aggregate views.
Count
Counts under 5 are suppressed as "<5".
One of: integer or "<5".
Error
| Property | Type | Description |
|---|---|---|
error | string | |
message | string | |
retry_after | integer |
Coverage
| Property | Type | Description |
|---|---|---|
businesses_total | integer | |
added_today | integer | |
cities_covered | integer | |
new_this_week | integer | |
updated_at | string (date-time) | |
cities | object[] |
Depth
| Property | Type | Description |
|---|---|---|
phones | Count | |
websites | Count | |
emails | Count | |
ads | Count | |
cwv_fail | Count | |
no_website | Count | |
new_90d | Count | |
enriched | Count |
CategoryCount
| Property | Type | Description |
|---|---|---|
slug | string | |
name | string | |
count | Count |
Stats
| Property | Type | Description |
|---|---|---|
scope | string | |
state | string | |
city | string | |
category | string | |
total | Count | |
depth | Depth | |
categories | CategoryCount[] | |
platforms | object<Count> | |
tracking | object<Count> | |
new_by_month | object[] | |
hexes | object[] | |
suppressed | boolean | |
coverage_complete | boolean | |
updated_at | string (date-time) |
Signals
| Property | Type | Description |
|---|---|---|
hot | boolean | |
new_in_days | integer | |
changed | object[] | |
no_website | boolean | |
cwv_fail | boolean | |
ads_no_tracking | boolean |
BusinessSummary
| Property | Type | Description |
|---|---|---|
id | string | |
name | string | |
category | string | |
category_slug | string | |
city | string | |
state | string | |
signals | Signals | |
has_phone | boolean | |
has_email | boolean | |
has_website | boolean | |
platform | string | |
revealed | boolean |
CostEstimate
| Property | Type | Description |
|---|---|---|
records | integer | |
hot_records | integer | |
already_revealed | integer | |
credits | integer | |
usd | number |
BusinessPage
| Property | Type | Description |
|---|---|---|
items | BusinessSummary[] | |
total | integer | |
next_cursor | string | |
estimate | CostEstimate |
CWV
| Property | Type | Description |
|---|---|---|
lcp | number | |
cls | number | |
inp | number | |
pass | boolean |
SEO
| Property | Type | Description |
|---|---|---|
title | boolean | |
meta | boolean | |
h1 | boolean | |
schema | boolean | |
sitemap | boolean | |
robots | boolean |
Marketing
| Property | Type | Description |
|---|---|---|
analytics | string[] | |
ads_platforms | string[] | |
pixels | string[] | |
server_side_tracking | boolean | |
consent_tool | string |
Business
Full paid record. No reviews, ratings or photos.
| Property | Type | Description |
|---|---|---|
id | string | |
name | string | |
category | string | |
subcategories | string[] | |
address | string | |
city | string | |
state | string | |
zip | string | |
lat | number | |
lng | number | |
phone | string | |
emails | string[] | |
website | string | |
domain | string | |
socials | object<string> | |
first_seen_at | string (date-time) | |
last_verified_at | string (date-time) | |
status | "active" | "closed" | "unknown" | |
web | object | object with has_website, platform, cwv, seo, ssl_issued_at |
marketing | Marketing | |
signals | Signals |
Filters
| Property | Type | Description |
|---|---|---|
state | string | |
city | string | |
zip | string | |
category | string | |
q | string | |
no_website | boolean | |
cwv_fail | boolean | |
platform | string | |
has_ads | boolean | |
has_analytics | boolean | |
server_side | boolean | |
new_days | integer | |
changed_days | integer | |
has_phone | boolean | |
has_email | boolean | |
hot | boolean | |
territory_id | string |
ChangeEvent
| Property | Type | Description |
|---|---|---|
id | string | |
type | string | |
at | string (date-time) | |
business | BusinessSummary |
Export
| Property | Type | Description |
|---|---|---|
export_id | string | |
status | "pending_confirm" | "ready" | "expired" | |
filters | Filters | |
ids | string[] | |
estimate | CostEstimate | |
estimated_credits | integer | |
credits_used | integer | |
rows | integer | |
format | string | |
download_url | string | |
created_at | string (date-time) | |
confirmed_at | string (date-time) |
ReportCard
| Property | Type | Description |
|---|---|---|
id | string | |
url | string | |
domain | string | |
status | string | |
cwv_mobile | CWV | |
cwv_desktop | CWV | |
seo | SEO | |
platform | string | |
tech_stack | string[] | |
marketing | Marketing | |
tracking_health | object | object with score, issues |
comparison | object | object with category, zip, peer_count, peer_cwv_pass_rate, peer_ads_rate, peer_server_side_rate |
RemovalRequest
| Property | Type | Description |
|---|---|---|
ticket_id | string | |
business_name | string | |
domain | string | |
requester_email | string | |
relationship | string | |
status | string | |
created_at | string (date-time) | |
applies_by | string (date-time) |
Account
| Property | Type | Description |
|---|---|---|
id | string | |
email | string | |
domain | string | |
business_email | boolean | |
credits_balance | integer | 1 credit = $0.01 |
free_credits | integer | |
free_credits_expire_at | string (date-time) | |
spend_cap | number | USD per month, 0 = none |
spent_this_month_usd | number | |
connect_bonus_granted | boolean | |
created_at | string (date-time) |
LedgerEntry
| Property | Type | Description |
|---|---|---|
id | string | |
at | string (date-time) | |
type | "grant" | "topup" | "reveal" | "export" | "expiry" | "adjustment" | |
credits | integer | |
balance_after | integer | |
note | string | |
ref_id | string |
APIKey
| Property | Type | Description |
|---|---|---|
id | string | |
label | string | |
prefix | string | |
secret | string | Only returned on create |
spend_cap | number | |
created_at | string (date-time) | |
last_used_at | string (date-time) | |
revoked | boolean |
Territory
| Property | Type | Description |
|---|---|---|
id | string | |
name | string | |
kind | "city" | "zip" | "polygon" | |
state | string | |
city | string | |
zip | string | |
geojson | object | |
alerts | object | object with email, slack_webhook, webhook, types, cadence |