SfSkills Domains Docs GitHub

Home / data / bulk-api-and-large-data-loads

bulk-api-and-large-data-loads

Use when choosing between Bulk API 2.0 and REST/SOAP API for large-volume data operations, sizing batch jobs, monitoring bulk job status, and handling failed records in bulk pipelines. Triggers: bulk api, large data load, batch insert, bulk job, serial vs parallel mode. NOT for Data Loader UI steps (use data-import-and-management) or real-time single-record integrations (use apex/callouts-and-http-integrations).

Open the skill folder on GitHub


Bulk API and Large Data Loads

This skill activates when a practitioner needs to move more than a few thousand records into or out of Salesforce programmatically, choose between Bulk API 2.0 and synchronous REST/SOAP, configure serial vs. parallel processing, or recover from failed bulk jobs. It does not cover Data Loader UI workflows or real-time single-record callouts.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

Bulk API 2.0 vs. REST/SOAP

Any data operation involving more than 2,000 records is a good candidate for Bulk API 2.0. Operations under 2,000 records should use bulkified synchronous calls (REST Composite or SOAP). Bulk API 2.0 is the recommended choice over legacy Bulk API (v1) because it has a simpler workflow, automatic batching, a single results endpoint, and automatic PK chunking for query jobs.

Bulk API 2.0 supports two job types:

Bulk API 2.0 uses standard OAuth 2.0 bearer token authentication, the same as all other Salesforce REST APIs.

Job Lifecycle (Ingest)

Every Bulk API 2.0 ingest job moves through these states:

  1. Open — Job created, accepting data uploads.
  2. UploadComplete — Caller signals upload is done; Salesforce begins processing. This PATCH is mandatory — omitting it means the job never starts.
  3. InProgress — Salesforce auto-batches records (one batch per 10,000 records) and processes them.
  4. JobComplete — All batches processed. Individual records may still have failed.
  5. Failed — The guide's wording is "Some records in the job failed. Job data that was successfully processed isn't rolled back." A job also fails when a batch exhausts 20 retries or the daily record limit is exceeded mid-job; the remaining data is not processed.
  6. Aborted — Job canceled by the creator or a user with Manage Data Integrations permission.

Once a job reaches JobComplete or Failed, the caller must retrieve results from three endpoints:

Serial vs. Parallel Mode

Bulk API 2.0 processes jobs in parallel mode by default, creating internal batches that run concurrently. This is faster but can cause record lock contention when:

Bulk API 2.0 has no serial mode. Its concurrencyMode response field is "For future use... Currently only parallel mode is supported," and a future mode "will be chosen automatically by the API and will not be user configurable." Correction (2026-10-03): an earlier version said Bulk API 2.0 had a serial equivalent of uploading one chunk at a time; no such setting exists. Serial processing is available only in Bulk API 1.0, set with concurrencyMode Serial on the JobInfo resource. For objects prone to locking, sort child records by parent ID so they share a batch, and move only the lock-prone load to a Bulk API 1.0 serial job if lock errors persist.

The official guidance: avoid serial mode unless parallel mode results in lock timeouts and you cannot reorganize batches to avoid the locks.

Failed Record Handling

Bulk API 2.0 does not roll back successfully processed records when other records in the same job fail. Each record is processed independently. After a job completes:

  1. Retrieve failedResults — each row contains sf__Error describing the failure reason.
  2. Retrieve unprocessedRecords — records that were never attempted (batch-level failure, not record-level).
  3. Fix the root cause (validation rule violation, missing required field, duplicate, etc.).
  4. Submit a new job with only the failed and unprocessed records.

Do not delete your local source CSV until all three result endpoints confirm complete coverage.


Common Patterns

Pattern 1: Standard Bulk Ingest Pipeline

When to use: Any insert, update, upsert, or delete of >2,000 records from an external system or migration pipeline.

How it works:

  1. Authenticate with OAuth 2.0 to get a bearer token.
  2. POST /services/data/vXX.X/jobs/ingest/ with {"object": "Account", "operation": "insert", "contentType": "CSV"} — note the id in the response.
  3. PUT /services/data/vXX.X/jobs/ingest/{jobId}/batches with the CSV body (at most 100 MB raw, which becomes about 150 MB after base64). For more data than that, create more jobs.
  4. PATCH /services/data/vXX.X/jobs/ingest/{jobId} with {"state": "UploadComplete"} — this starts processing.
  5. Poll GET /services/data/vXX.X/jobs/ingest/{jobId} until state is JobComplete or Failed.
  6. Retrieve successfulResults, failedResults, and unprocessedRecords.
  7. Resubmit a new job for any records in failed or unprocessed.

Why not synchronous REST: Synchronous REST calls are subject to governor limits per transaction and cannot handle millions of records in a single call. Bulk API 2.0 processes records asynchronously in the background without tying up a transaction thread.

Pattern 2: Bulk Query for Large Extracts

When to use: Extracting more than 1 million records, or any query that would time out via synchronous SOQL.

How it works:

  1. POST /services/data/vXX.X/jobs/query with {"operation": "query", "query": "SELECT Id, Name FROM Account"}.
  2. Poll the job status until JobComplete.
  3. GET .../results with pagination using the Sforce-Locator response header to iterate through result pages.

Bulk API 2.0 query jobs automatically apply PK chunking to split large queries into manageable segments.


Decision Guidance

SituationRecommended ApproachReason
<2,000 recordsBulkified REST Composite or SOAPSynchronous; simpler error handling; avoids async overhead
2,000–10M records, standard objectBulk API 2.0, parallel modeAutomatic batching; faster throughput
Object with heavy triggers or sharing recalcBulk API 2.0, consider legacy Bulk API serial modeLock contention risk; serial reduces concurrent batch collisions
Nightly delta feed, known low failure rateBulk API 2.0 parallel, retry loop for failuresBalance throughput with automated error recovery
Large extract (>1M records)Bulk API 2.0 query jobAuto PK chunking; paginated results from single endpoint
Permanent deletion bypassing Recycle BinBulk API 2.0 with hardDelete operationdelete soft-deletes; hardDelete removes from Recycle Bin immediately
Initial data migration with sharing rulesDefer sharing calculation, then Bulk API 2.0Reduces sharing recalculation overhead during load

Questions to Ask Before Configuring

Each question traces to a gotcha in references/gotchas.md.

QuestionWhy it mattersWhat a good answer addsWhat proper configuration adds over just doing it
"How many records, and how large is the raw CSV?"Under 2,000 records belongs in REST Composite; over 100 MB raw needs more than one job (Gotcha 6)A job plan: number of jobs, rows per job, raw size per jobNo upload is rejected for size, and no tiny load pays async overhead
"Which objects have triggers, flows, or roll-ups that touch a shared parent?"Parallel batches lock the same parent; Bulk API 2.0 cannot run serially (Gotcha 1)Sort keys per object and a list of loads that need Bulk API 1.0 serial modeLock failures are designed out instead of retried away
"What produced the CSV, and what line ending and delimiter does it use?"A CRLF file loaded with the default LF setting can fail or carry a carriage return into the last column (Gotcha 5)lineEnding and columnDelimiter values for the jobThe first run loads clean data instead of a cleanup project
"How will the pipeline prove every row landed?"JobComplete is not success, and unprocessed rows are not in failedResults (Gotchas 2, 3)A reconciliation rule: successful + failed + unprocessed = uploadedSilent data loss becomes a failed pipeline step
"How many other integrations submit Bulk API batches each day?"15,000 batches per rolling 24 hours are shared with Bulk API 1.0 and other jobs (Gotcha 7)A daily batch budget per integrationA migration weekend does not starve the nightly feeds
"Is any operation a hardDelete, and who holds Bulk API Hard Delete?"hardDelete skips the Recycle Bin and needs a permission that is off by default (Gotcha 8)A named integration user with the permission and an approval stepIrreversible deletes are deliberate and auditable
  1. Size the load and pick the API with the Questions table: REST Composite under 2,000 records, Bulk API 2.0 above, Bulk API 1.0 serial only for proven lock contention.
  2. Prepare each CSV: UTF-8, API field names in the header, rows sorted by parent ID, at most 100 MB raw per job, and the real line ending recorded.
  3. Create the job with explicit lineEnding, columnDelimiter, and (for upsert) externalIdFieldName, using the request bodies in references/rest-examples.md; use a multipart create for payloads of 100,000 characters or less.
  4. Upload, send {"state":"UploadComplete"}, and poll the job with backoff until JobComplete, Failed, or Aborted.
  5. Pull successfulResults, failedResults, and unprocessedrecords, reconcile the counts against the upload, and resubmit failures in a new job after fixing the cause.
  6. Run python3 scripts/check_bulk_api_and_large_data_loads.py --manifest-dir <integration source> to catch pipelines that skip result retrieval or UploadComplete.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Parallel mode lock contention on trigger-heavy objects — Parallel batches running concurrently can simultaneously try to update the same parent record or trigger shared resource updates, causing UNABLE_TO_LOCK_ROW errors. This surfaces in failedResults and silently leaves records unprocessed. Use serial mode (Bulk API v1) or group records by parent ID to minimize collisions.
  2. Unprocessed records are not in failedResults — Practitioners check failedResults and assume any record not there was successful. Unprocessed records from a batch-level failure appear only in unprocessedRecords. If you do not call that endpoint, you silently lose records. Always call all three result endpoints after every job.
  3. UploadComplete is mandatory — omitting it means nothing happens — A job created with state: Open does not start processing until the caller sends PATCH {"state": "UploadComplete"}. No timeout starts it for you. The Limits Quick Reference caps the time an ingest job can remain open at 24 hours, and non-terminal jobs older than seven days are periodically cleaned up. Correction (2026-10-03): an earlier version said an open job waits indefinitely. Forgetting this PATCH is a common silent failure in new implementations.
  4. Job state machine gotcha: JobComplete does not mean all records succeeded — JobComplete means Salesforce finished processing attempts. Records inside the job may still have failed with validation errors, duplicate errors, or permission errors. Treating JobComplete as "everything worked" without checking failedResults is a data integrity risk.

Output Artifacts

ArtifactDescription
API selection recommendationBulk API 2.0 vs. REST Composite, with record count and object type rationale
Concurrency mode decisionParallel vs. serial with documented justification based on trigger complexity
Job monitoring checklistStep-by-step poll pattern with all three result endpoint retrievals
Failed record retry strategyPattern for extracting, remediating, and resubmitting failed and unprocessed records

Package files