Data Enrichment API
The Data Enrichment API lets you submit a list of contacts, select which enrichment layers you want to run, and retrieve the enriched rows when the job completes. It is asynchronous: Submit returns a job_id immediately, and your client polls Job Status until ready: true, then calls Result.
| Environment | Base URL |
|---|---|
| Production | https://backend.ecoreservice.com/api/v1/data-enrichment |
| Staging | https://test.ecoreservice.com/backend/api/v1/data-enrichment |
All Data Enrichment endpoints are in the API Playground under Data Enrichment. For now the playground sends these requests to staging only (https://test.ecoreservice.com/backend/api/v1) — use a staging API key.
The legacy /api/v1/enrichment/ routes keep working for existing integrations. New integrations should use /api/v1/data-enrichment/.
Authentication
All endpoints require your DataCore API key, sent in the Authorization header. Generate and manage keys from the API Connect page in DataCore — see Authentication.
Authorization: Token sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer <key> is also accepted. Requests without a valid key return 401 Unauthorized.
Usage (credit holds, refunds, wallet transactions) is attributed to the account that owns the API key.
Response envelope
Every endpoint returns the same envelope. Success:
{ "success": true, "data": { ... } }
Error:
{ "success": false, "error": "<error_code>", "detail": "Human-readable message." }
HTTP status codes follow REST conventions (200, 201, 202, 400, 401, 402, 404, 409, 502).
Enrichment layers
Jobs are priced per selected layer, per row. See Layers for what each layer returns, which combinations are allowed, and current prices.
| Code | Label | What it returns |
|---|---|---|
L1 | Career Monitor | Current employment, title, LinkedIn URL, verified work email, job function/level, contact location. |
L2 | Company Location | Company HQ address, city/state/country, postal code, HQ phone. |
L3 | New Company | If the contact changed jobs: new company name, title, start date, website, work email. |
L4 | New Company Location | HQ address, city/state/country, phone for the new company. |
MPA | Mobile Phone Append | Mobile and direct-dial phone numbers. |
Layers must be selected in order (L1 → L2 → L3 → L4). MPA can be added to any selection but cannot run alone. Pass "full" to run every active layer in one job.
Only need to know who changed jobs? The standalone Career Monitor API is the simpler option.
Endpoints
| Method | Path | Page |
|---|---|---|
GET | /layers/ | Layers |
POST | /estimate/ | Estimate |
POST | /submit/ | Submit |
GET | /status/{job_id}/ | Job Status |
GET | /result/{job_id}/ | Result |
POST | /cancel/{job_id}/ | Cancel |
GET | /jobs/ | List Jobs |
End-to-end example
TOKEN="sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE="https://backend.ecoreservice.com/api/v1/data-enrichment"
# 1. Submit
RESP=$(curl -sS -X POST "$BASE/submit/" \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"selected_layers": ["L1", "L2"],
"title": "Q1 renewal list",
"rows": [
{"first_name":"Jane","last_name":"Doe","company_name":"Acme","email":"jane@acme.com"},
{"first_name":"John","last_name":"Smith","company_name":"Globex","email":"john@globex.com"}
]
}')
JOB_ID=$(echo "$RESP" | jq -r '.data.job_id')
# 2. Poll until ready
while : ; do
STATUS=$(curl -sS "$BASE/status/$JOB_ID/" -H "Authorization: Token $TOKEN")
READY=$(echo "$STATUS" | jq -r '.data.ready')
[ "$READY" = "true" ] && break
sleep 15
done
# 3. Fetch result as CSV
curl -sS "$BASE/result/$JOB_ID/?format=csv" \
-H "Authorization: Token $TOKEN" \
-o "enriched.csv"
import time
import requests
TOKEN = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE = "https://backend.ecoreservice.com/api/v1/data-enrichment"
H = {"Authorization": f"Token {TOKEN}"}
submit = requests.post(f"{BASE}/submit/", headers=H, json={
"selected_layers": ["L1", "L2"],
"title": "Q1 renewal list",
"rows": [
{"first_name": "Jane", "last_name": "Doe", "company_name": "Acme", "email": "jane@acme.com"},
{"first_name": "John", "last_name": "Smith", "company_name": "Globex", "email": "john@globex.com"},
],
}).json()
job_id = submit["data"]["job_id"]
while True:
status = requests.get(f"{BASE}/status/{job_id}/", headers=H).json()["data"]
if status["ready"]:
break
time.sleep(15)
result = requests.get(f"{BASE}/result/{job_id}/", headers=H).json()["data"]
for row in result["results"]:
print(row["enriched_fields"].get("Email"), row["status"])
Credits & billing
- Credits are held at submit time based on
row_count × sum(per-layer price). - When the job finishes, you're billed for successfully enriched rows only. Skipped and failed rows are refunded automatically.
- Cancelling a running job releases the entire hold.
- Prices are configurable per account — call
GET /layers/to get your current rate card.
See also Credit Costs.
Limits
- Max rows per JSON submission: 10,000. Use multipart CSV for larger lists.
- Max CSV upload size: 100 MB.
- Rate limit: 60 requests/minute per API key.
Error codes
error | HTTP | Meaning |
|---|---|---|
validation_error | 400 | Request body failed validation. |
bad_meta, bad_layers, bad_rows | 400 | Submit payload missing a required part. |
not_ready | 409 | Result requested before the job finished. |
insufficient_credits | 402 | Your DataCore wallet can't cover the hold. |
upstream_insufficient_credits | 402 | DataForge rejected the job for credit reasons. |
dataforge_error | 502 | Transient upstream failure. Safe to retry. |
| — | 401 | Missing or invalid API key. |
| — | 404 | Job id doesn't exist, or doesn't belong to the authenticated account. |
Changelog
| Date | Change |
|---|---|
| 2026-10-11 | Initial release of /api/v1/data-enrichment/. |