Developer docs

Programmatic access to Local Deal Intelligence — search, listing detail, and dealer opportunity data for CRM/DMS integrations and other scripted use. This page covers the endpoints intended for that; the full interactive schema for every endpoint is at /api/docs (Swagger UI) if you need it.

Authentication

Two ways to authenticate, depending on whether a person or a script is calling:

  • Session token — Authorization: Bearer <token>, obtained from POST /auth/login or POST /auth/signup. Expires after 7 days. This is what the frontend itself uses.
  • API key — X-API-Key: <key>, issued from the dealer dashboard (bottom "API keys" card) or POST /api-keys with a session token. Meant for unattended scripts — no interactive login, no password stored anywhere. Requires a plan with API access (Dealer Pro in the default seeded plans); issuing a key on a plan without it returns 403. Only a hash of the key is stored server -side — if you lose the raw value, revoke it and issue a new one, it's never shown again.

Not every endpoint accepts both. The ones that accept an API key are called out below; everything else needs a session token.

# Issue a key (needs a session token first)
curl -X POST 'https://your-deployment/api-keys' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name": "inventory-sync-script"}'
# -> {"id": "...", "name": "inventory-sync-script", "key": "ldi_live_...", "key_prefix": "ldi_live_ab12", "created_at": "..."}
# The "key" value is shown exactly once — store it now.

# Use it
curl 'https://your-deployment/search?lat=52.52&lon=13.405&radius_km=40' \
  -H "X-API-Key: ldi_live_..."

Rate limits & plan tiers

Every request is rate-limited by IP address, independent of plan: 120 requests/minute by default. A handful of expensive endpoints (LLM-backed explanations/negotiation, the AI research agent, source discovery) have a stricter 5–10 requests/minute limit. Exceeding a limit returns 429 Too Many Requests.

Separately, GET /search is metered per plan when called with a session token or API key — a searches_per_day cap that resets daily (see the pricing page for what each tier includes). Anonymous, unauthenticated search is not metered this way, but is still subject to the per-IP rate limit above. Exceeding your daily search allowance returns 429 with a plan-specific message rather than the generic rate-limit response.

CRM/DMS export (/dealer/opportunities/export.csv, /dealer/opportunities/export-webhook) and API key issuance are gated by plan, not by a request rate — a plan that doesn't include them returns 403 regardless of how many requests you've made.

Listing detail

GET /listings/{id} — full detail for one listing, including other sources it's also listed on. No authentication required. Returns everything /search does, plus first_seen_at, last_seen_at, and duplicate_listings.

curl 'https://your-deployment/listings/b9ba0d40-cd33-43f3-8c00-dc0ea1274910'

Dealer opportunities

Dealer-only endpoints (role dealer or admin) — a ranked feed of underpriced listings near a point, plus market context. All three accept an API key.

  • GET /dealer/market-overview — price distribution and competitor activity for an area.
  • GET /dealer/opportunities — the ranked feed itself, same params as /search plus min_value_score (default filters to above-average deals).
  • GET /dealer/opportunities/export.csv — the same feed as a CSV download. Requires a plan with CRM export enabled.
  • POST /dealer/opportunities/export-webhook — pushes the same feed as JSON to a webhook URL you supply (body: {"webhook_url": "https://..."}). Also requires CRM export enabled; the URL is validated against SSRF (must be HTTPS, must not resolve to a private/internal address).
curl 'https://your-deployment/dealer/opportunities?lat=52.52&lon=13.405&radius_km=40&min_value_score=70' \
  -H "X-API-Key: ldi_live_..."

curl -X POST 'https://your-deployment/dealer/opportunities/export-webhook?lat=52.52&lon=13.405&radius_km=40' \
  -H "X-API-Key: ldi_live_..." -H 'Content-Type: application/json' \
  -d '{"webhook_url": "https://your-crm.example.com/hooks/deals"}'

Error reference

Every error is a standard FastAPI JSON body: {"detail": "..."} (a string, or a structured list for request validation errors). Status codes you'll actually see:

  • 400 — malformed request (e.g. an invalid webhook URL).
  • 401 — missing, invalid, expired, or revoked credentials (bad session token or API key).
  • 403 — authenticated, but not allowed: wrong role, or your plan doesn't include this feature.
  • 404 — the resource doesn't exist, or (for owned resources like alerts/API keys) it exists but isn't yours.
  • 422 — request parameters failed validation (out-of-range radius, bad lat/lon, etc.) — the response body lists which field.
  • 429 — rate limit or daily search quota exceeded (see above).