Batch jobs
Submit up to 1,000 mixed items in one call and read results by webhook or by polling.
#When to reach for batch
The sync endpoints - profiles/enrich, companies/enrich, posts/enrich - are fine up to a few hundred records. Past that, use POST /v1/batch: submit up to 1,000 items in one job, processed in parallel, with results delivered by webhook or by polling.
Every /v1 capability is batchable, as one of two item shapes. Three enrich shapes:
type |
Shape |
|---|---|
profile |
{ "type": "profile", "url": "..." } |
company |
{ "type": "company", "url": "..." } |
post |
{ "type": "post", "url": "..." } |
or an operation item, running any other /v1 capability as a batch item instead of a loop of sync calls:
{ "operation": "posts.engagement.comments", "body": { "urn": "7267273010393358336", "max_results": 500 } }operation is one of profiles.activity, profiles.recommendations, profiles.interests, companies.jobs_count, companies.posts, companies.headcount, jobs.enrich, posts.engagement.comments, posts.engagement.reactions, posts.engagement.reposts, search.people.sales_nav, search.companies.sales_nav, search.people.standard, search.companies.standard, search.jobs, or search.posts. body is the same shape as that operation's own sync request body, except page, per_page, and pagination_token are never accepted - batch paginates through everything up to max_results on its own. max_results defaults to 100 for every non-scalar operation, with its own cap per operation (2,500 for people search, 1,000 for company search, 500 or 1,000 for everything else); a scalar operation (companies.jobs_count, companies.headcount, jobs.enrich) rejects max_results entirely. search.people.sales_nav and search.companies.sales_nav take filters or sales_nav_url, exactly one, same as their sync endpoints. See POST /v1/batch for the full table of every operation's max_results default and cap, billing, and result row type.
#Submit a job
curl -X POST https://api.up2data.ai/v1/batch \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-chunk-014" \
-d '{
"items": [
{ "type": "profile", "url": "https://linkedin.com/in/a" },
{ "type": "profile", "url": "https://linkedin.com/in/b" },
{ "type": "company", "url": "https://linkedin.com/company/acme" }
],
"webhook_url": "https://your-server.com/hooks/up2data"
}'{
"data": { "job_id": "job_7hd92kfa", "status": "processing", "total": 3, "estimated_completion": "2026-07-07T14:03:00Z" },
"meta": { "creditsUsed": 3, "creditsRemaining": 98416, "billed": true, "reason": "job_accepted", "requestId": "req_0pl2m8xz" }
}Submission is a single request no matter how many items you send, and it's billed for the full total right then - see Credits for the per-item rates and Rate limits for the request limits.
Send an Idempotency-Key header with your submission. Retrying it with the same key returns the original job instead of creating a duplicate or charging twice - useful when a submission times out and you're not sure it landed.
#Get results
Poll the job directly:
curl https://api.up2data.ai/v1/batch/job_7hd92kfa -H "X-API-Key: YOUR_API_KEY"{
"data": {
"job_id": "job_7hd92kfa", "status": "completed", "total": 1000, "succeeded": 968, "failed": 32,
"results": [
{ "type": "profile", "operation": "enrich.profile", "url": "https://linkedin.com/in/a", "status": "ok", "input": "https://linkedin.com/in/a", "data": { "full_name": "...", "headline": "..." } },
{ "type": "company", "operation": "enrich.company", "url": "https://linkedin.com/company/acme", "status": "ok", "input": "https://linkedin.com/company/acme", "data": { "name": "Acme", "headcount": 512 } }
],
"failures": [
{ "type": "profile", "url": "https://linkedin.com/in/gone", "status": "unprocessable_target" },
{ "type": "company", "url": "https://linkedin.com/company/timed-out", "status": "upstream_timeout" }
]
},
"meta": { "creditsUsed": 1000, "creditsRemaining": 421012, "billed": true, "reason": "job_completed", "requestId": "req_v82n1kd0" }
}Every results and failures entry carries the operation that produced it, and every results entry also carries input, echoing what you submitted for it.
All 1,000 items here are profile and company enrich items, charged when the job was submitted, and none of the 32 failures refund anything: a results entry, an unprocessable_target failure, and a 500 or 504 failure all bill the same - 1 credit for a profile, company, or post, or a max_results-sized reservation for a Sales Navigator search item. The job's creditsUsed still shows the full 1,000 - see Credits. Every other operation item refunds differently: it reserves the same worst case at submission, but settles down to what it actually billed once its queue finishes, so a failed or empty item on those operations costs 0.
GET /v1/batch/{job_id} is free to poll as often as you like, whether the job is still running or done. Or set webhook_url and listen for batch.completed instead - see Webhooks.
For large jobs, GET /v1/batch/{job_id}/results returns a cursor page of result entries instead of the whole job at once, so you can stream results in while the job is still processing instead of re-fetching a growing results array on every poll:
curl "https://api.up2data.ai/v1/batch/job_7hd92kfa/results?limit=100" -H "X-API-Key: YOUR_API_KEY"Pass the next_cursor from each response back in as cursor until has_more is false.
#Credits
Submitting a job reserves the worst case for every item at once: 1 credit per profile, company, or post item, and each operation item at its own rate - see the full operations table for exact numbers. A profile, company, post, or Sales Navigator search item never refunds for a failed item - a 422 unprocessable_target item and a retryable 500 or 504 failure both bill the same as a success; only a Sales Navigator search item's unused max_results reservation refunds, once that search runs and comes back short. Every other operation settles down to what it actually billed once its queue finishes: a 422 unprocessable_target failure, a company or job that isn't found, or an empty page all cost 0 and refund in full. See Credits for the full table.