The ContactSearch v1 API exposes read-only access to your tenant's contacts, companies, and jobs. It is designed for automation platforms (n8n / Make / Zapier / custom scripts) that want to PULL data on their own schedule rather than receive WEBHOOK pushes from us.
All endpoints are tenant-scoped — every API key resolves to exactly one tenant, and cross-tenant data is never returned even if a filter would technically match another tenant's row.
Every endpoint requires an API key. Keys are minted in Settings → Integrations → API Keys and look like:
cs_live_a1b2c3d4e5f6...
Pass the key one of three ways:
# Preferred — Authorization header
curl -H "Authorization: Bearer cs_live_..." \
https://contactsearch.ai/api/v1/contacts
# Or, header without scheme
curl -H "Authorization: cs_live_..." \
https://contactsearch.ai/api/v1/contacts
# Or, query string (less secure — gets logged in access logs)
curl "https://contactsearch.ai/api/v1/contacts?api_key=cs_live_..."
Each API key gets 60 requests per minute by default (configurable per key, max 600/min). Exceeding the limit returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{"error":"Rate limit of 60 requests/min exceeded.","code":"rate_limit_exceeded","retry_after_sec":60}
Every error response is JSON with a stable code field your retry
logic can branch on:
{"error":"API key not recognized.","code":"unknown_api_key"}
Codes you'll see:
missing_api_key · malformed_api_key · unknown_api_key · revoked_api_key — auth failures (401)rate_limit_exceeded — too many requests (429)not_found — id doesn't exist in your tenant (404)missing_param — required parameter not provided (400)pdf_generation_failed — resume PDF render failed; safe to retry (500)/api/v1/contacts
company, title, city, state. Pagination: page, per_page (max 100).curl -H "Authorization: Bearer cs_live_..." \
"https://contactsearch.ai/api/v1/contacts?company=Ace&state=IL&per_page=25"
/api/v1/contacts?id=<contact_id>
/api/v1/companies
q (name LIKE). Pagination: page, per_page./api/v1/companies?id=<company_id>
/api/v1/jobs
company, since (YYYY-MM-DD). Ordered by posted_at DESC./api/v1/resume?id=<contact_id>
application/pdf.curl -H "Authorization: Bearer cs_live_..." \
-o resume.pdf \
"https://contactsearch.ai/api/v1/resume?id=12345"
/api/v1/contacts/<id>/resume.pdf.
Our router serves the same content at /api/v1/resume?id=<id> for now.
Future router upgrades may add the path-parameter form; the query-string form will keep working.
{
"contacts": [ { ...row... }, ... ],
"page": 1,
"per_page": 50,
"total": 1247,
"has_more": true
}
{
"contact": { ...full row with JSON columns decoded... }
}
Retry-After on 429s. Don't tight-loop.has_more for pagination, not total / per_page — slightly faster server-side.