API Quickstart
Everything you need to integrate with the AddToCRM API in about 15 minutes — authentication, how credits work, rate limits, two end-to-end workflows, and the error model. For the full endpoint list see the API Reference.
Authentication
Every request must include an Authorization: Bearer header with an API key from your dashboard (Pro plan required):
Authorization: Bearer addtocrm_live_a1b2c3d4...
Keys start with addtocrm_live_so they're easy to detect in logs and GitHub. Each key carries one or more scopes (enrich, write, lists, read) — a call to an endpoint outside the key's scopes returns 403 scope_required.
Credits & caching
Read this first — it's how the API is priced. API calls draw from the same monthly credit pool as the dashboard; there is no separate API bill. Enrichment, company lookup, email verification, and people search each cost 1 credit. CRM writes, list operations, /v1/usage, and /v1/events are free.
Every enrichment and search result is cached for 30 days. A repeated lookup within that window returns cached: true and credits_deducted: 0 — so iterative and re-run workflows are far cheaper than they look. Call GET /v1/usage any time to see your remaining balance.
Rate limits
Each API key is limited to 60 requests per minute and 1,000 requests per hour. Every response includes these headers:
X-RateLimit-Limit-Minute: 60 X-RateLimit-Remaining-Minute: 47 X-RateLimit-Reset-Minute: 1716033600 X-RateLimit-Limit-Hour: 1000 X-RateLimit-Remaining-Hour: 813 X-Request-Id: 7b3e1f4a-1c2d-...
On a 429, the Retry-After header tells you how many seconds to wait; the error envelope repeats it in retry_after_seconds.
Workflow: enrich a prospect, then add them to your CRM
The most common flow — turn a LinkedIn URL into a CRM contact. First, enrich:
curl -X POST https://api.addtocrm.com/v1/people/enrich \
-H "Authorization: Bearer addtocrm_live_..." \
-H "Content-Type: application/json" \
-d '{"linkedin_url": "https://www.linkedin.com/in/some-prospect"}'Then pass the returned dataobject straight to your CRM. The call writes to whichever CRM the key's owner has connected:
curl -X POST https://api.addtocrm.com/v1/crm/contacts \
-H "Authorization: Bearer addtocrm_live_..." \
-H "Content-Type: application/json" \
-d '{"person": {"first_name": "Jane", "last_name": "Smith",
"email": "jane.smith@acme.com", "job_title": "VP Sales"}}'Workflow: verify an email before importing
Check deliverability so you only import good addresses:
curl -X POST https://api.addtocrm.com/v1/emails/verify \
-H "Authorization: Bearer addtocrm_live_..." \
-H "Content-Type: application/json" \
-d '{"email": "jane.smith@acme.com"}'If the result is deliverable, continue to POST /v1/crm/contacts; otherwise skip the row. Verification results are cached for 30 days too, so re-checking the same address is free.
Errors
Every error response uses the same envelope:
{
"error": {
"type": "insufficient_credits",
"message": "Your monthly credit balance is exhausted.",
"request_id": "7b3e1f4a-1c2d-4e5f-9a0b-c1d2e3f4a5b6"
}
}Quote request_id in any support ticket — we can grep it directly from logs.
The full set of error type values:
invalid_auth(401) — missing, malformed, or unknown keykey_revoked(401) — you revoked this key from the dashboardkey_suspended_plan(403) — the account is no longer on Proscope_required(403) — key lacks the required scopeinsufficient_credits(402) — out of creditsrate_limit_minute/rate_limit_hour(429)crm_not_connected(409) — no CRM is connectedcrm_token_expired(409) — the CRM needs re-authorisingcrm_rate_limit/crm_unavailable(503)validation_error(400),not_found(404),internal_error(500)
Active CRM
This is the most important thing to know about the CRM endpoints. Each AddToCRM user has exactly one active CRM connected. All /v1/crm/*calls write to whichever CRM is currently connected for the key's owner.
If a user switches CRMs in the dashboard, your automations silently start writing to the new CRM. Call GET /v1/crm/info at workflow-build time (Zapier/Make do this via dynamic dropdowns) to confirm the target CRM, and again if you want to defend against switches mid-run.
Pagination (events)
/v1/events uses cursor-based pagination. The first call passes ?since=<ISO-8601>; subsequent calls pass ?cursor=<next_cursor> from the previous response. Cursors encode (inserted, id)so events landing in the same second can't skip across the page boundary.
Troubleshooting
I got 409 crm_not_connected
The key's owner has not connected a CRM. Connect one at /dashboard/connection, then retry.
I got 403 scope_required
The key doesn't carry the scope that endpoint needs. Scopes are fixed at creation — generate a new key in the dashboard with the scopes you need (for example enrich + write to enrich and sync to a CRM).
I got 403 key_suspended_plan
The account dropped off the Pro plan. Keys are suspended, not deleted — they re-activate automatically when the account is back on Pro.
Why did this call cost a credit? I've looked this person up before
The cache window is 30 days. If the previous lookup was more than 30 days ago the cache entry has expired, so the call is billed as a fresh enrichment. Check cached in the response to see which path a call took.
Need more?
Building something with the API? Get in touch — we're collecting v2 endpoint requests (webhooks, OAuth, sandbox keys).