SalesOS

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

# Upsert contacts from a CSV, matching on email
curl -X POST https://api.salesos.org/v1/bulk/jobs \
  -H "X-API-Key: $SALESOS_API_KEY" \
  -F object=contact -F operation=upsert -F externalIdField=email \
  -F [email protected]

# Or inline JSON
curl -X POST https://api.salesos.org/v1/bulk/jobs \
  -H "X-API-Key: $SALESOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"object":"lead","operation":"insert","records":[{"firstName":"Ann","lastName":"Lee","email":"[email protected]"}]}'
Field
objectlead, contact, account, opportunity, task or custom:<apiName>
operationinsert, upsert, update, delete or export
externalIdFieldThe 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 / recordsThe input: a multipart file (.csv, .jsonl or .ndjson), a data string, or a records array.
contentTypecsv or jsonl; inferred from the file name, records is JSON lines.
fields, filterExport 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 a customFields object 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

GET /v1/bulk/jobs/{id}
{
  "id": "cmjob123",
  "object": "contact",
  "operation": "upsert",
  "status": "PROCESSING",
  "totalRows": 1200,
  "processedRows": 400,
  "successRows": 396,
  "failedRows": 4,
  "links": {
    "results": "/v1/bulk/jobs/cmjob123/results",
    "failures": "/v1/bulk/jobs/cmjob123/failures"
  }
}

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 of row,id,action,externalId (action is created, updated or deleted). For an export, the exported records (CSV, or JSON lines when contentType was jsonl).
  • GET /v1/bulk/jobs/{id}/failures — CSV of row,error,externalId,input, where row is the 1-based data row and input is 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.

On this page