aba-rank Agent API - Quickstart Guide
This guide complements the interactive reference at /docs/api. Read this first, then jump to the reference for endpoint-by-endpoint details and try-it-out.
Authentication
Anonymous reads work out of the box (rate-limited per IP). For higher limits and write access, create an API key at /account/api-keys and pass it as a Bearer token:
curl https://www.abarank.com/api/v1/vendors \
-H "Authorization: Bearer abr_live_XXXXXXXX..."Keys come in two flavors: abr_live_* (production) and abr_test_* (preview / dev). Scope is locked at key creation -read by default; check write:leads to also callPOST /api/v1/leads.
Rate limits
Every response includes both X-RateLimit-* and IETF RateLimit-* headers:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 60
RateLimit-Limit: 300
RateLimit-Remaining: 287
RateLimit-Reset: 60Reset values are delta seconds in both header families - the number of seconds until the current window resets, not an epoch timestamp. (We emit the same delta value on X-RateLimit-Reset as the IETF RateLimit-Reset for consistency.)
When exhausted you get an HTTP 429 with Retry-After header and matching retry_after body field.
| Tier | Reads | Writes |
|---|---|---|
| Anonymous (no key) | 60 req / minute / IP, 1000 / hour / IP | Not permitted |
| Keyed (any tier) | 300 req / minute / key + 10,000 / hour / key | 30 req / minute / key + 500 / hour / key (if scope granted) |
Retry guidance: on 429, sleep for the duration in Retry-After seconds (or RateLimit-Reset) before retrying. Exponential backoff is unnecessary - our window is fixed.
Error codes
Every non-2xx response uses the same envelope: { error: { code, message, details?, retry_after? }, meta: { as_of } }.
| Code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | Missing, malformed, or revoked Bearer token |
invalid_scope | 403 | Key lacks the scope required by the endpoint |
rate_limited | 429 | Per-IP or per-key rate limit exceeded |
attestation_required | 400 | Required attestation field (e.g. agent_disclosure) missing or false |
invalid_pagination | 400 | per_page out of [1..100] or cursor malformed |
not_found | 404 | Resource (vendor, clinic, article, category) does not exist or is not public |
validation_failed | 400 | Request body failed schema validation; details holds field-level issues |
conflict | 409 | Request collides with current resource state (e.g. buyer-open-cap exceeded). Distinct from validation_failed: do not retry without changing context (e.g. different buyer email). |
internal_error | 500 | Unhandled server error; please retry with backoff and report if persistent |
Example payloads
invalid_api_key
{ "error": { "code": "invalid_api_key", "message": "Missing or invalid API key" } }invalid_scope
{ "error": { "code": "invalid_scope", "message": "This endpoint requires scope: write:leads" } }rate_limited
{ "error": { "code": "rate_limited", "message": "Too many requests", "retry_after": 30 } }attestation_required
{ "error": { "code": "attestation_required", "message": "Required attestation field missing or invalid", "details": { "issues": [{ "path": "agent_disclosure", "message": "Invalid literal value" }] } } }invalid_pagination
{ "error": { "code": "invalid_pagination", "message": "per_page must be an integer between 1 and 100" } }not_found
{ "error": { "code": "not_found", "message": "Vendor 'foo' not found" } }validation_failed
{ "error": { "code": "validation_failed", "message": "Lead submission validation failed", "details": { "issues": [...] } } }conflict
{ "error": { "code": "conflict", "message": "Buyer has too many open leads", "details": { "code": "lead.buyer_open_cap_exceeded" } } }internal_error
{ "error": { "code": "internal_error", "message": "Something went wrong" } }MCP quickstart (Claude Desktop / Claude Code)
Paste this into your MCP config to expose aba-rank tools (read tools work without auth; submit_lead appears only when an API key is provided):
{
"mcpServers": {
"aba-rank": {
"url": "https://www.abarank.com/api/mcp",
"transport": "http",
"headers": {
"Authorization": "Bearer abr_live_XXXXXXXX..."
}
}
}
}Available tools: search_vendors, get_vendor, search_clinics, get_clinic, list_categories, list_subcategories, get_articles, search_all, get_methodology, submit_lead (write:leads scope).
Ranking methodology
Every vendor + clinic response includes rank_score, a normalized 0–1 value (3 decimal places) — multiply by 100 for the 100-point Index published at /how-we-rank — plus rank_position (1-indexed within category) and the methodology version (currently 3.0). The weights are the exact coefficients in the engine, served as JSON at /api/v1/methodology:
- Qualified reviews (50%) - published-review quality, volume, recency, and verified-interview presence, behind a 3-review / 3.0-star eligibility floor.
- Profile completeness (30%) - coverage of the profile fields published for that vendor type.
- Verification recency (15%) - exponential decay on last_verified_at, exp(-days/180) — the term falls to ~1/e after 180 days.
- Primary-category fit (5%) - whether this is the vendor’s primary category rather than a secondary one.
The weights sum to 1.0 and every term is operational: as of methodology 3.0 there is no purchasable input to rank_score and no multiplier applied after the sum.
Sponsor cap: Sponsored placements are capped (no more than 25 of 100 results in any ranked list), rendered in labeled slots above the ranked list, and never change rank_score. As of methodology v3.0 no paid product of any kind contributes Index points: subscription tier is not an input to the ranking formula.
Versioning + changelog policy
URL prefix /api/v1/ is the contract surface. What v1 guarantees:
- Field removal or type change in a documented response is a breaking change → bumps to
/api/v2/with a 6-month overlap. - Field addition is non-breaking and ships in
v1without notice. - Bumping
methodology_versionis a contract event (the value appears in every response); we announce it in/llms.txtand the changelog. - Error-code additions to the locked union also bump
v1.x- we never remove or rename existing codes.
Copy-paste examples
Search vendors (curl)
curl "https://www.abarank.com/api/v1/vendors?type=software&per_page=5" \
-H "Authorization: Bearer abr_live_XXXXXXXX..."Search vendors (JavaScript fetch)
const res = await fetch('https://www.abarank.com/api/v1/vendors?type=software&per_page=5', {
headers: { Authorization: 'Bearer abr_live_XXXXXXXX...' },
});
const { data, meta } = await res.json();
console.log(data, meta.cursor);Get a vendor (curl)
curl https://www.abarank.com/api/v1/vendors/centralreachGet a vendor (fetch)
const { data } = await (await fetch('https://www.abarank.com/api/v1/vendors/centralreach')).json();
console.log(data.aggregate_rating); // present only when published reviews >= 3Geo-search clinics (curl)
curl "https://www.abarank.com/api/v1/clinics?lat=34.05&lng=-118.24&radius_km=25"Unified search (curl)
curl "https://www.abarank.com/api/v1/search?q=billing"Submit a lead (curl)
curl https://www.abarank.com/api/v1/leads \
-X POST \
-H "Authorization: Bearer abr_live_XXXXXXXX..." \
-H "Content-Type: application/json" \
-d '{
"vendor_slug": "centralreach",
"acting_for_user": {
"name": "Sarah Chen",
"contact_email": "sarah@example.com",
"stated_need": "ABA practice management for a 50-clinician clinic"
},
"agent_disclosure": true,
"agent_id": "claude-opus-4-7"
}'Submit a lead (fetch)
await fetch('https://www.abarank.com/api/v1/leads', {
method: 'POST',
headers: {
Authorization: 'Bearer abr_live_XXXXXXXX...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
vendor_slug: 'centralreach',
acting_for_user: {
name: 'Sarah Chen',
contact_email: 'sarah@example.com',
stated_need: 'ABA practice management for a 50-clinician clinic',
},
agent_disclosure: true,
}),
});For the full interactive reference (every endpoint, every field, try-it-out), head to /docs/api.
