# URLs
Versioned under /v1. Backwards-incompatible changes will get a /v2.
# Quickstart
Paste your key in the box at the top right — all curl samples on this page auto-fill with it. The simplest authenticated call:
curl -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/me
Successful response (HTTP 200):
"data": { "key_id": 1, "key_prefix": "cck_NFcP9qQ2", "label": "Smoke test (Eli)", "tier": "pro", "rate_per_min": 300, "rate_per_day": 50000 }
# Authentication
Every /v1/* endpoint except /v1/health requires a key. Three ways to send it, in priority order:
Authorization: Bearer cck_...preferredX-API-Key: cck_...?api_key=cck_...discouraged — leaks into logs/Referer
Three identical calls
curl -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/me
curl -H "X-API-Key: YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/me
curl "https://compcurve-license-api.eli-ded.workers.dev/v1/me?api_key=YOUR_KEY"
Keys are stored server-side as SHA-256 hashes. If you lose one, request a new one — the original cannot be re-displayed. Revoke unused keys promptly.
# Response envelope
Every successful response has the same top-level shape:
{
"data": …, // the payload (array, object, or single record)
"pagination": { … }, // only on list endpoints
"meta": { … } // what the server actually applied (snapshot, sort, filters)
}
meta.filters_applied is especially useful when debugging — it shows which filter params the server recognized.
# Errors
Errors return non-2xx HTTP status and this shape:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded (60 per minute). Retry at 2026-05-10T17:45:00.000Z."
}
}
Common codes
| HTTP | error.code | When |
|---|---|---|
| 400 | invalid_sort | Tried to sort on a column not in the whitelist. |
| 400 | invalid_field | Asked for a field in ?fields= that doesn't exist. |
| 400 | invalid_date | Date param isn't YYYY-MM-DD. |
| 400 | invalid_order | order wasn't asc or desc. |
| 400 | offset_too_deep | page × limit exceeded MAX_OFFSET (50,000). Filter harder. |
| 400 | no_snapshot | Asked for a snapshot that hasn't been loaded. |
| 401 | unauthorized | Missing, malformed, invalid, or revoked key. |
| 404 | not_found | License ID doesn't exist, or unknown route. |
| 405 | method_not_allowed | Only GET is supported. |
| 429 | rate_limited | You've exceeded your per-minute or per-day cap. |
| 503 | service_unavailable | Backend store is temporarily down (e.g. mid-import). |
# Rate limits
Default tier: 60/min, 1,000/day. Pro tier: 300/min, 50,000/day. Every authenticated response includes:
| Header | Meaning |
|---|---|
| RateLimit-Limit | Current window's cap. |
| RateLimit-Remaining | Requests left in this window. |
| RateLimit-Reset | Seconds until the window rolls over. |
| RateLimit-Policy | E.g. 300;w=60 — 300 requests per 60s window. |
| X-RateLimit-Window | minute or day — which limit is currently tightest. |
| Retry-After | Only on 429 responses. Seconds to wait. |
curl -i -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/me
# Pagination
Offset-based via page + limit. Defaults: page=1, limit=100. Max limit=1000. page × limit is capped at 50,000 — past that, filter harder.
Response includes:
"pagination": { "page": 3, "limit": 100, "total": 1703, // matches across the entire result set "total_pages": 18, "has_next": true, "has_prev": true }
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA&last_name=smith&page=3&limit=100"
# Sort
?sort=col1,col2&order=asc|desc. Single direction applies to all columns. id is always appended as a tie-breaker for stable pagination.
Whitelisted sort columns:
id · state · member_last_name · member_first_name · member_full_name · member_state_license_number · member_license_status · member_license_issued · member_license_expires · member_license_first_issued · office_name · last_refreshed
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA&license_status=Licensed&sort=member_license_expires&order=asc&limit=5"
# Sparse fieldsets
By default /v1/licenses returns all 30 columns per row. Pass ?fields=col1,col2,... to slim the payload — ideal for dashboards and large pages.
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA&last_name=smith&fields=member_full_name,member_state_license_number&limit=5"
{
"data": [
{ "id": 406322, "member_full_name": "Aaron Joseph Smith II", "member_state_license_number": "02145091" },
{ "id": 426253, "member_full_name": "Aaron Matthew Smith", "member_state_license_number": "02189531" }
],
…
}
id is always included even if omitted from fields — you need it for follow-up /v1/licenses/:id calls.
# Filter reference
All optional, AND-combined. Empty / missing = no constraint on that field.
| Parameter | Behavior | Example |
|---|---|---|
| state | Exact match; comma-separable. | state=CA or state=CA,TX,FL |
| country | Exact (US or CA). | country=US |
| last_name | Case-insensitive prefix. | last_name=smith |
| office_name | Case-insensitive substring. | office_name=keller |
| license_number | Exact. | license_number=00007012 |
| license_status | Exact (taxonomies vary by state — see caveats). | license_status=Active |
| member_type | Exact. | member_type=Broker |
| city | Exact. | city=Orinda |
| postal_code | Exact (alias: zip). | postal_code=94563 |
| issued_after / issued_before | ISO date range (inclusive). | issued_after=2024-01-01 |
| expires_after / expires_before | ISO date range (inclusive). | expires_before=2026-12-31 |
| first_issued_after / first_issued_before | ISO date range. | first_issued_after=2010-01-01 |
| snapshot | Pick a specific month. Defaults to the latest loaded. | snapshot=2026-05 |
# GET /v1/health no auth
Lightweight liveness check. Returns DB reachability and snapshot count. No rate limit.
curl https://compcurve-license-api.eli-ded.workers.dev/v1/health
{
"status": "ok",
"db": true,
"snapshots_loaded": 1,
"time": "2026-05-10T17:37:27.924Z",
"version": "v1"
}
status is "degraded" if the DB query failed (rare; usually means a mid-import lock).
# GET /v1/me auth required
Echoes your key's metadata and configured rate limits. Useful in scripts to confirm the key is valid and to learn its quotas.
curl -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/me
{
"data": {
"key_id": 1,
"key_prefix": "cck_NFcP9qQ2",
"label": "Smoke test (Eli)",
"tier": "pro",
"rate_per_min": 300,
"rate_per_day": 50000
}
}
# GET /v1/snapshots auth required
List of loaded monthly snapshots with row counts. Sorted newest first.
curl -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/snapshots
{
"data": [
{
"snapshot": "2026-05",
"loaded_at": "2026-05-10 17:36:47",
"us_row_count": 2339569,
"ca_row_count": 177272,
"total_row_count": 2516841,
"notes": null
}
]
}
# GET /v1/states auth required
Row counts per state / province for a snapshot. Useful for navigation UIs and sanity-checks. Filter by country if you only need one side.
Query params
| snapshot | Defaults to latest loaded. |
| country | US or CA. Omit for both. |
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/states?country=CA"
{
"data": [
{ "country": "CA", "state": "AB", "row_count": 18072 },
{ "country": "CA", "state": "BC", "row_count": 29003 },
{ "country": "CA", "state": "ON", "row_count": 103199 },
{ "country": "CA", "state": "QC", "row_count": 16196 }
// + 7 more provinces/territories
],
"meta": { "snapshot": "2026-05", "country": "CA" }
}
# GET /v1/licenses auth required
The main query endpoint. Combines filters + sort + pagination + sparse fieldsets. See the filter reference for all parameters.
Example 1 — all Brokers in Florida with a Smith last name
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=FL&last_name=smith&member_type=Broker&limit=3"
Example 2 — active CA licensees expiring in 2026, sorted by date
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA&license_status=Licensed&expires_after=2026-05-10&expires_before=2026-12-31&sort=member_license_expires&order=asc&limit=10"
Example 3 — multi-state, sparse fieldsets, page 2
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA,TX,FL&license_status=Licensed&fields=member_full_name,state,member_state_license_number,member_license_expires&page=2&limit=100"
Sample response (truncated)
{
"data": [
{
"id": 406322,
"snapshot": "2026-05",
"country": "US",
"state": "CA",
"member_full_name": "Aaron Joseph Smith II",
"member_first_name": "Aaron Joseph",
"member_last_name": "Smith II",
"member_type": "Salesperson",
"member_city": "NEWPORT BEACH",
"member_state_or_province": "CA",
"member_postal_code": "92660",
"office_name": "COLDWELL BANKER REALTY",
"member_state_license_number": "02145091",
"member_license_status": "Licensed",
"member_license_issued": "2023-10-23",
"member_license_expires": "2029-10-22",
"member_license_first_issued": "2023-10-23",
"last_refreshed": "2026-05-01"
}
],
"pagination": { "page": 1, "limit": 3, "total": 1703, "total_pages": 568, "has_next": true, "has_prev": false },
"meta": { "snapshot": "2026-05", "sort": ["id"], "order": "asc", "fields": null, "filters_applied": ["state", "last_name"] }
}
# GET /v1/licenses/:id auth required
Fetch one record by its internal numeric id. The id comes from any /v1/licenses response. Returns 404 if no row matches.
curl -H "Authorization: Bearer YOUR_KEY" \
https://compcurve-license-api.eli-ded.workers.dev/v1/licenses/406322
Returns the full record (all 30 columns), unwrapped from any list/pagination metadata: { "data": { ...record... } }.
# Recipe — page through all results
Walk the entire match set with a bash loop. Note the has_next check.
KEY=YOUR_KEY
URL=https://compcurve-license-api.eli-ded.workers.dev
PAGE=1
while : ; do
RES=$(curl -s -H "Authorization: Bearer $KEY" \
"$URL/v1/licenses?state=CA&last_name=smith&fields=member_full_name,member_state_license_number&limit=500&page=$PAGE")
echo "$RES" | jq -r '.data[] | [.member_state_license_number, .member_full_name] | @tsv'
HAS_NEXT=$(echo "$RES" | jq -r '.pagination.has_next')
[ "$HAS_NEXT" != "true" ] && break
PAGE=$((PAGE + 1))
done
For result sets larger than 50,000 rows you'll hit offset_too_deep — split by state, by status, or by date range to stay under the cap.
# Recipe — convert response to CSV
curl -s -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=NY&license_status=Active&fields=member_full_name,member_state_license_number,member_license_expires&limit=1000" \
| jq -r '
.data[0] as $first | $first | keys_unsorted | @csv,
(.. | objects | select(has(($first|keys_unsorted)[0])) | [.[($first|keys_unsorted)[]]] | @csv)
' 2>/dev/null \
| head
For more than 1,000 rows, combine with the paging recipe above.
# Recipe — find licensees expiring in the next 60 days
TODAY=$(date -u +%Y-%m-%d)
SOON=$(date -u -v +60d +%Y-%m-%d 2>/dev/null || date -u -d "+60 days" +%Y-%m-%d)
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=CA&license_status=Licensed&expires_after=$TODAY&expires_before=$SOON&sort=member_license_expires&order=asc&fields=member_full_name,member_state_license_number,member_license_expires,office_name&limit=50"
Returns CA licensees whose status is Licensed and who expire within the next 60 days, sorted by soonest first.
# Recipe — bulk lookup by license #
v1 has no batch endpoint yet, so today you fire one request per license number. Up to 300/min on the pro tier:
KEY=YOUR_KEY
URL=https://compcurve-license-api.eli-ded.workers.dev
for L in 02145091 02189531 02194195 02197976; do
curl -s -H "Authorization: Bearer $KEY" \
"$URL/v1/licenses?state=CA&license_number=$L&fields=member_full_name,office_name,member_license_status,member_license_expires" \
| jq '.data[0]'
done
A POST batch endpoint is on the roadmap — see the future enhancements in the README.
# Recipe — discover statuses for a state
Until /v1/license-statuses exists, you can probe a state by sampling rows. Most states cluster around a small number of distinct values.
curl -H "Authorization: Bearer YOUR_KEY" \
"https://compcurve-license-api.eli-ded.workers.dev/v1/licenses?state=ON&fields=member_license_status&limit=200" \
| jq '[.data[].member_license_status] | group_by(.) | map({status: .[0], n: length}) | sort_by(.n) | reverse'
For Ontario you'd see REGISTERED as the dominant value. See caveats for the cheat sheet of per-state statuses.
# Field reference
Every license record has these columns (also queryable via ?fields=):
| Field | Type | Description |
|---|---|---|
| id | integer | Internal stable ID across a single snapshot. Use for follow-up /v1/licenses/:id. |
| snapshot | string | YYYY-MM. The monthly snapshot this row belongs to. |
| country | string | US or CA. |
| state | string | 2-letter state / province code (3-letter for PEI). |
| member_full_name | string | Full name as supplied by the source jurisdiction. |
| member_first_name, _middle_name, _last_name, _name_prefix, _name_suffix, _nickname | string | Parsed name components. |
| member_type | string | E.g. Broker, Salesperson. Varies by jurisdiction. |
| member_address_1, _address_2, _city, _state_or_province, _postal_code | string | Licensee's address of record. |
| office_broker_name, office_name, office_key_numeric | string | The licensee's office of record. |
| member_state_license_number | string | The official license number issued by the state. Format varies. |
| member_license_status | string | The current status. Per-state taxonomy — see caveats. |
| member_secondary_license_status | string | Optional secondary status (e.g. Active). |
| member_license_issued | date | ISO YYYY-MM-DD. Current term's issue date. |
| member_license_expires | date | ISO YYYY-MM-DD. Current term's expiration. |
| member_license_first_issued | date | ISO YYYY-MM-DD. The earliest known issue date for this licensee. |
| member_disciplined | string | Disciplinary indicator (when supplied by the source). |
| member_preferred_phone, member_email | string | Contact info, when supplied. |
| last_refreshed | date | When this jurisdiction's data was last refreshed in the source pipeline. |
# Caveats & gotchas
License-status taxonomies vary by jurisdiction
There is no universal "active" value. Examples:
| State / Province | Typical "active" value(s) |
|---|---|
| California (CA) | Licensed, Licensed NBA |
| Ontario (ON) | REGISTERED |
| Alaska (AK) | Active |
| Florida (FL) | Active |
| New York (NY) | Active |
Use the status-discovery recipe to confirm before relying on a value.
Dates are normalized to ISO YYYY-MM-DD
The upstream raw CSVs use MM/DD/YYYY; the API converts everything to ISO so dates sort correctly and date-range filters work.
50,000-row deep-paging cap
Past page × limit = 50,000 the API returns 400 offset_too_deep. Narrow your filter or split the query.
Monthly snapshots are point-in-time
A row's contents reflect the source's data as of the snapshot. Status changes mid-month aren't visible until the next snapshot loads.