A Job, Call by Call
Module 2 of 8 in the chapter Moving Data at Scale, chapter 10 of the Integration Architect course. Each module stands on its own. Previous: The Bank Teller and the 18-Wheeler · Next: Two Docks: 2.0 Now, 1.0 for What Only It Does
The question this module answers: What does a bulk job look like as calls on the wire, and how does your program know when it’s done?
You pull up to the back of the bank with your truck, and they tell you it’s a five-step process:
- You hit a red buzzer, and they give you a ticket. That opens the job and gives you a job ID.
- A loading dock opens up, and you load your files, using your ticket.
- You press the big blue button on the wall. Salesforce doesn’t know you’re done until you press it. Now the doors close and Salesforce starts processing.
- “Are we there yet?” Like my kids in the back seat. You ask with your job ID: is it done? Is it done?
- You download the results. The successful records, with their new Salesforce IDs, or the failures, with their errors.
That’s the loading dock. Now let’s watch it from your program’s side, as calls. I’m using Bulk API 2.0, the one to start with (module 3 says why). Every address starts with your org’s My Domain and /services/data/v68.0.
Five Calls
Section titled “Five Calls”1. Open the Job: a POST
Section titled “1. Open the Job: a POST”POST /services/data/v68.0/jobs/ingestContent-Type: application/json
{ "object": "Account", "operation": "insert", "contentType": "CSV", "lineEnding": "LF"}You tell Salesforce the object and the operation: insert, update, upsert, delete or hard delete. It answers with the job’s id, the ticket, and a state of Open. Carry that job ID to every call after this one.
Watch lineEnding. When I demo this, I send a CSV with line feeds, because I’m on a Mac. A file made on Windows ends its lines with a carriage return and a line feed, CRLF. The job has to say which one your file uses (Step 3: Bulk Insert (opens in a new tab), checked 2026-11-01). Get it wrong, and the job doesn’t read your file the way you meant.
2. Upload: a PUT
Section titled “2. Upload: a PUT”PUT /services/data/v68.0/jobs/ingest/{jobId}/batchesContent-Type: text/csv
Name,ShippingCityNorth Hills Hardware,RivertonLakeside Supply,FairviewThe CSV goes to the job. The first row names the fields; every row after it is a record. Salesforce answers 201 Created and nothing else. It hasn’t started processing: as far as it knows, you’re still loading.
How much can you send? “A request can provide CSV data that does not in total exceed 150 MB of base64 encoded content,” and because encoding adds about half again, Salesforce says to “upload data that does not exceed 100 MB” (Upload Job Data (opens in a new tab), checked 2026-11-01). Salesforce splits it into batches for you. More data than that? Open another job.
One more line from that page: “Don’t delete your local CSV data until you’ve confirmed that all records were successfully processed by Salesforce.” Your file is your only copy of what you sent. Module 6 needs it.
3. Upload Complete: a PATCH
Section titled “3. Upload Complete: a PATCH”PATCH /services/data/v68.0/jobs/ingest/{jobId}Content-Type: application/json
{ "state": "UploadComplete" }That’s the big blue button. The state moves to UploadComplete, no more data can be added, and Salesforce queues the job. Forget this call, and your data sits on the dock. A job can stay open for 24 hours; after that, it’s too late to finish it (Bulk API and Bulk API 2.0 Limits and Allocations (opens in a new tab), checked 2026-10-31).
Changed your mind? The same PATCH with "state": "Aborted" cancels the job.
4. Are We There Yet? A GET
Section titled “4. Are We There Yet? A GET”GET /services/data/v68.0/jobs/ingest/{jobId}{ "id": "750...", "state": "JobComplete", "numberRecordsProcessed": 2, "numberRecordsFailed": 0, "retries": 0}While Salesforce works, the state is InProgress. Then it ends in one of three places: JobComplete; Failed, where in Salesforce’s words “Some records in the job failed. Job data that was successfully processed isn’t rolled back”; or Aborted, if someone cancelled it (Get Information About an Ingest Job (opens in a new tab), checked 2026-11-01). Read that Failed line twice. A failed job isn’t nothing: part of it is already in the org.
5. Get the Results: GETs
Section titled “5. Get the Results: GETs”GET /services/data/v68.0/jobs/ingest/{jobId}/successfulResultsGET /services/data/v68.0/jobs/ingest/{jobId}/failedResultsGET /services/data/v68.0/jobs/ingest/{jobId}/unprocessedrecordsEach comes back as CSV. The successful results carry the new record ID in sf__Id and whether it was created in sf__Created, beside your own columns. The failed results carry the error. The unprocessed records are the ones Salesforce never got to. You can ask once the job is JobComplete or Failed, and the results are kept for seven days. Module 6 is about what to do with them.
Notice the verbs: a POST, a PUT, a PATCH, then GETs. Five kinds of call, each a small one. Each one does count as an API call, but loading a million records only takes a handful of them.
Five calls, and the job’s state tells you which one comes next.
Are We There Yet, in 2026
Section titled “Are We There Yet, in 2026”When I taught this in 2024, I said: there is no push knowledge of the status. You query. What’s the status? What’s the status? For an ingest job, that’s still true. Your program asks.
So design the asking. Every GET is an API call against the org’s daily allowance (chapter 8 module 7), and a job of a million records won’t finish in a second. Don’t ask every second. Wait a little, ask, and wait longer each time. And give up on purpose: if the job hasn’t finished by the time your next feed is due, somebody needs to hear about it.
For query jobs, there’s now another way. In Spring ‘25, Salesforce added a platform event, BulkApi2JobEvent, published when a query job changes state or part of its results are ready. Your program subscribes, through the Pub/Sub API, instead of asking. In Salesforce’s words, “This eliminates the need for continuous polling” (Introducing Events and Partial Downloads in Bulk API 2.0 (opens in a new tab), Salesforce Developers Blog, January 2025, checked 2026-11-01). Almost two years on, it’s still a Beta, turned on in Setup, and still only for query jobs: Salesforce’s reference describes it as notifying “subscribers of changes to the status of Bulk API 2.0 query jobs” (BulkApi2JobEvent (Beta) (opens in a new tab), Platform Events Developer Guide, checked 2026-11-01). It goes to programs listening through the Pub/Sub API or the older Streaming API, not to Apex triggers or flows. A load into Salesforce still asks. Because it’s a Beta, check the release notes before you build a production feed on it. Module 5 uses it; chapter 11 teaches subscribing.
Your Lab Bench
Section titled “Your Lab Bench”Try it in your Developer Edition org, from the same API tool as chapter 8’s lab bench. Sign in, then:
- Open an insert job for Account.
- Upload a CSV of two or three made-up accounts.
- Mark it
UploadComplete. - Ask for its status until it’s
JobComplete. - Download the successful results, and find the new IDs in the org.
Then break it. Upload a row with a field that doesn’t exist, or a name that’s too long, and download the failed results. And open Setup, Bulk Data Load Jobs: your job is there, with its state and its counts. That’s where an admin will look first when your feed fails at two in the morning.
Test knowledge. Know the Bulk API 2.0 ingest job in order: create the job, upload the CSV data, set the state to
UploadComplete, check the job’s state until it’s complete, then get the successful, failed and unprocessed results. Know that nothing processes untilUploadComplete, that aFailedjob may have loaded some records, and that the client checks the status: Salesforce doesn’t call you back for an ingest job.
What an Architect Decides Here
Section titled “What an Architect Decides Here”- The file the program builds: the fields, the line ending, the size of one job, and where the copy is kept until the results are checked.
- How the program asks “are we there yet?”: how often, how much longer each time, and when it gives up and tells a person.
- What happens to a
Failedjob, knowing part of it may already be in the org. - Who can see and stop the jobs: the integration user that opens them, and the admins who watch Bulk Data Load Jobs.
Explore
Section titled “Explore”- Step 3: Bulk Insert (opens in a new tab) and Get Information About an Ingest Job (opens in a new tab): Bulk API 2.0 and Bulk API Developer Guide (checked 2026-11-01)
- Introducing Events and Partial Downloads in Bulk API 2.0 (opens in a new tab): Salesforce Developers Blog, January 2025
- Salesforce Bulk API Steps: Importing Data at the Loading Dock: my blog post from 2023, the five steps in Postman
- Loading at Volume: the Data Architect course, the five steps for a migration
- Salesforce Developers, Bulk API 2.0 and Bulk API Developer Guide, Upload Job Data (opens in a new tab), checked 2026-11-07.
- Salesforce Developers, Salesforce Developer Limits and Allocations Quick Reference, Bulk API and Bulk API 2.0 Limits and Allocations (opens in a new tab), checked 2026-11-07.
- Salesforce Developers, Platform Events Developer Guide, BulkApi2JobEvent (Beta) (opens in a new tab), checked 2026-11-07.
