Bulk API
Asynchronous insert, upsert, update, delete and export jobs over CSV or JSON lines.
The Bulk API loads or extracts up to 50,000 records per job without holding a request open. You submit a job, poll it, then download per-row results and failures. Rows are written through the same services as the REST API, so validation rules, stage requirements, assignment rules, field-level security, record ownership, the audit trail, workflows and webhooks all apply to every row.
Objects: lead, contact, account, opportunity, task, and custom
objects as custom:<apiName>. Bulk jobs are part of the Import, Export &
Bulk Operations feature (Professional and above).
Create a job
| Field | |
|---|---|
object | lead, contact, account, opportunity, task or custom:<apiName> |
operation | insert, upsert, update, delete or export |
externalIdField | The key rows are matched on. Required for upsert; update/delete default to id. Any text column (email, domain, externalId…), id, or a text custom field as customFields.<apiName>. For custom objects, any field name. Matching is case-insensitive. |
file / data / records | The input: a multipart file (.csv, .jsonl or .ndjson), a data string, or a records array. |
contentType | csv or jsonl; inferred from the file name, records is JSON lines. |
fields, filter | Export only. fields picks columns (custom fields as customFields.<apiName>); filter is an object over the object's own columns, e.g. {"status":"NEW"}. |
Limits: 10 MB and 50,000 rows per job, 5 queued or running jobs per organization, 100,000 rows per export. Jobs in one organization run one at a time, in order.
Rows
- CSV: one header row; column names are field names. Empty cells are
ignored (CSV can't say "clear this field" — use JSON lines with
null). Numbers, booleans (true/false,yes/no,1/0) and lists (a;b;c) are typed from the field. - JSON lines: one JSON object per line, values typed as in the REST API.
- Custom fields:
customFields.<apiName>columns, or acustomFieldsobject in JSON. - A row's fields are validated exactly like the REST create/update body: an unknown field or a bad value fails that row, not the job.
Upsert: a key that matches one record updates it, no match creates one, and more than one match fails the row. Ownership is the REST API's — a row whose record you can't edit fails with the reason.
Poll
status is QUEUED, PROCESSING, COMPLETED, FAILED (the whole job
stopped; see error) or CANCELLED. POST /v1/bulk/jobs/{id}/cancel stops a
queued or running job; rows already written stay written. GET /v1/bulk/jobs
lists your jobs.
Results
GET /v1/bulk/jobs/{id}/results— CSV ofrow,id,action,externalId(actioniscreated,updatedordeleted). For an export, the exported records (CSV, or JSON lines whencontentTypewasjsonl).GET /v1/bulk/jobs/{id}/failures— CSV ofrow,error,externalId,input, whererowis the 1-based data row andinputis the row as received.
Files are kept for 7 days after the job finishes. Progress is saved after
every 100 rows; if the service restarts mid-job, it resumes from there and may
re-apply the last partial chunk — prefer upsert when re-running must be
safe.
Access
The job runs as the user who created it (or the API key's owner), with that user's role, Profile and field permissions, re-checked as the job runs. A user sees their own jobs; an org admin sees every job in the org.
API keys need WRITE_<resource> for writes and READ_<resource> (or
WRITE_) for exports and for reading a job — LEADS, CONTACTS,
ACCOUNTS, OPPORTUNITIES, TASKS, or CUSTOM_OBJECTS for custom objects.
Custom events
Define your own platform events with a versioned payload schema, publish them from the API, functions, flows and workflows, and subscribe with webhooks, functions, flows, the CLI and GraphQL.
Custom actions
Register your own HTTPS endpoint as a named action and call it from workflows, the assistant and record pages, with signed requests, schemas, retries and a full invocation log.