Skip to content

REST First

Module 2 of 7 in the chapter Salesforce as an API Provider, chapter 8 of the Integration Architect course. Each module stands on its own. Previous: All the Locked Doors · Next: Many in One Call

The question this module answers: How does a program outside Salesforce read and write records through the REST API, and what does an architect decide along the way?

On the map: Records, one at a time On the map: Records, one at a time. One row from the map of every door into Salesforce, in module 1: the set records, one at a time, with its doors: REST: query, sObject rows, upsert by external ID, describe, and updated and deleted. {"author": "Steve Simpson", "blueprint": "1.0.0", "builder": "standard 1.57.1", "built": "2026-10-31", "id": "DGM-INT-SP-09", "rights": "\u00a9 2026 Twinshark LLC. All rights reserved.", "template": "cells 0.5.0", "theme": "stevetecharc 1.4.0"} THE SET: WHAT THE CALLER IS DOING THE DOORS Records, one at a time REST: the everyday call Query SOQL, page by page · ch 8.2 sObject rows get, create, update, delete · ch 8.2 Upsert by external ID the caller's own key · ch 8.2 Describe the shape of an object · ch 8.2 Updated and deleted what changed since · ch 8.2 One set from the map of every door into Salesforce (module 1). HOW TO READ IT taught in this chapter © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

Where this module sits on the map.

You’re an outside process. You need to reach in and get data, push data, look at the metadata. Of all the doors from module 1, REST is where most programs start. It’s the everyday call.

Over 100 doors, and this module starts with one. Here’s why.

It’s where the industry went. In my class I say it this way: REST is where it’s at. SOAP is the past, and you use it when you need it. Chapter 3 told how REST won: it travels on the same port as every web page, so it gets through firewalls that block almost everything else, and its answers are small enough for a phone. Whatever language the calling program is written in, it already knows how to make a REST call. It’s about six lines of code: the address, the verb, the format, send it, check for a 200. SOAP is still there, and a few things still need it; chapter 17 (coming soon) covers it.

The other doors are built on it. Look at the addresses. The query, the describe and the records in this module, Composite in module 3, GraphQL and the UI API in module 4, and the Bulk API 2.0 jobs in chapter 10 all start the same way: /services/data/v68.0/, then the resource. The same token goes in the header, the same version is pinned, the same verbs and status codes come back. Each of those doors is REST with one thing added: many records in one call, only the fields you ask for, the screen the user sees, a job that runs in the background. Learn how one REST call to Salesforce works, and you’ve learned most of how the next ones work.

It’s the right size for most calls. One record, or a page of records, right now, with an answer the program can act on straight away. That’s what most integrations do most of the day: a partner checks one delivery, a portal changes one address, a script reads today’s list. When the count grows, the Number of Zeros tells you when to move: to Composite when you have many records for one call, to Bulk when you’re past about 2,000.

So REST is the starting point, and the rest of this chapter is about when to leave it, and for what.

Chapter 3 took a REST request apart: the verb, the address, the headers, the body. Now we point it at Salesforce. Every call in this module goes to Steve Trucking’s org, with the token from module 1 in the Authorization: Bearer header, and every address starts the same way:

https://steve-trucking.my.salesforce.com/services/data/v68.0/

That v68.0 is a choice. Salesforce also accepts latest in its place, and routes the call to the newest version the org supports. Salesforce’s own advice on when to use which is clear: “Use it for scripts and internal tools. Keep a pinned version for production integrations that depend on a specific contract” (The Salesforce Developer’s Guide to the Winter ‘27 Release (opens in a new tab), checked 2026-10-31).

That fits everything in module 1. A pinned version keeps its promise, and you know which number to look for when Salesforce announces a retirement. latest changes under you three times a year. For Redrock’s system, pin it.

The query is the call you’ll make most. It’s a GET, so the question goes in the address. You pass a SOQL statement in a parameter called q, with the spaces encoded:

GET /services/data/v68.0/query?q=SELECT+Id,Status+FROM+WorkOrder+WHERE+Status='Scheduled'

What comes back is a page of records, not always all of them. Salesforce returns “up to 2,000 records… at a time”, and sometimes fewer, depending on how big the records are (Query (opens in a new tab), REST API Developer Guide, Winter ‘27, checked 2026-10-31). So the answer tells you where you are:

{
"totalSize": 3300,
"done": false,
"nextRecordsUrl": "/services/data/v68.0/query/01gRO0000016PIAYA2-2000",
"records": [ ... ]
}

done is false, so there’s more. Call nextRecordsUrl, and you get the next page, and the next, until done is true. REST is stateless, so this is about the only place where one call depends on the one before. The server holds your place for you.

Three things architects get wrong with the query:

  • Reading only the first page. A program that never checks done works perfectly in a sandbox with 200 deliveries, and quietly misses most of them in production. A day of Steve Trucking’s deliveries, 3,300 of them, is already two pages.
  • Asking for too much. You can ask for a smaller page with a header, Sforce-Query-Options: batchSize=500, between 200 and 2,000. Salesforce says there’s “no guarantee” you’ll get exactly that size (Query Options Header (opens in a new tab), checked 2026-10-31), so the program still follows nextRecordsUrl.
  • Forgetting the query has to be fast. An outside query is still a query on Salesforce’s database. Add explain instead of q, and Salesforce tells you how it would run it, the same feedback the Query Plan tool gives inside the org. In my video on the query, I asked it about a query with no filter on 74,000 records, and it answered with a table scan: every row read. Add a selective filter and ask again, and the plan and its cost change. You can test the shape of a query from outside before anyone runs it for real. Data Architect’s How Salesforce Finds Data explains what to look for.

One more variation: queryAll instead of query. It “returns records that are soft deleted due to a merge or delete”, while they’re still in the recycle bin, and archived tasks and events (QueryAll (opens in a new tab), checked 2026-10-31). A copy that has to know what was deleted asks with queryAll.

And if you’re paging through tens of thousands of rows, the query is telling you something. If you really need large amounts of data, you should be looking at the bulk API (chapter 10).

And the address has a size limit. Chapter 3 showed why a long question in a GET can outgrow it. When a query gets that big, that’s usually a sign to ask a better question, or to use a different door.

Sometimes the program needs to know the shape of the data, not the data. You can be outside Salesforce and still read the metadata:

GET /services/data/v68.0/sobjects/WorkOrder/describe/

That returns everything about the object: every field, its type, its picklist values, its relationships. A program that builds its own screens, or checks that a field still exists before it writes to it, starts here.

And it’s live. In my video on the describe, I added a third value to a picklist in Setup, described the object again straight away, and the new value was there. The describe also hands you links to follow: the object’s layouts, its list views, its compact layouts. And leave the object off, GET /services/data/v68.0/sobjects/, and you get the global describe: the list of objects. But only the objects the signed-in user can see. The integration user’s describe is the integration user’s world, not the whole org.

It’s a big answer, so don’t ask for it on every call. Send an If-Modified-Since header with the time you last asked. If nothing has changed, Salesforce answers 304 Not Modified with no body (sObject Describe (opens in a new tab), checked 2026-10-31).

When you have a record’s ID, you can get the record:

GET /services/data/v68.0/sobjects/WorkOrder/0WOxx0000004C92GAE

All I had to send was the one ID, and it gives me everything. I didn’t write a SOQL statement listing fields; I got every field the integration user can see. That’s handy when you’re exploring. In production, it’s usually too much. Add ?fields=Status,Partner_Delivery_Key__c and you get only what you asked for (Get Records Using sObject Rows (opens in a new tab), checked 2026-10-31). Less data on the wire, and less to change when someone adds a field.

Writing follows the verbs from chapter 3. POST is new, PATCH is update.

  • Create: POST /sobjects/WorkOrder/ with the fields in the body. The answer is 201 Created and the new record’s ID.
  • Update: PATCH /sobjects/WorkOrder/0WOxx0000004C92GAE with only the fields that change. The answer is 204 No Content: it worked, and there’s nothing to send back.
  • Delete: DELETE on the same address.

Look at what update needs: Salesforce’s ID. Redrock doesn’t have it. Redrock knows its own delivery number, 8472914. If Redrock has to look up our ID before every update, that’s two calls instead of one, and Redrock now has to keep a map of our IDs.

That’s what the external ID is for. Steve Trucking’s Work Orders carry the partner’s key in one field, Partner_Delivery_Key__c, as Data Architect’s keys module designs it. Then Redrock calls with its own key:

PATCH /services/data/v68.0/sobjects/WorkOrder/Partner_Delivery_Key__c/REDROCK-8472914-1

That’s an upsert. If no Work Order has that key, Salesforce creates one and answers 201 Created. If one does, it updates it and answers 200 OK. If more than one has it, Salesforce answers 300 and changes nothing (sObject Rows by External ID (opens in a new tab) and the upsert example, checked 2026-10-31). Redrock never needs our IDs, and a retry after a timeout doesn’t make a second delivery. For an integration, upsert by an external ID is usually the right first answer.

From outside, inserting a record is fast and easy. But it isn’t a back door. When Redrock’s call creates a Work Order, every trigger, every flow and every validation rule on Work Order runs, exactly as if a person had clicked Save. The integration user’s permissions and sharing apply too.

That’s good: your rules protect the data whichever door it came through. It also means a slow flow is now slow for Redrock, and a validation rule written for people (“please fill in the dispatcher’s notes”) can stop an integration cold. When you design a call-in, read what runs on the object it writes to.

One more call, for programs that keep a copy. Ask which records changed in a time window:

GET /services/data/v68.0/sobjects/WorkOrder/updated/?start=2026-10-30T00:00:00Z&end=2026-10-31T00:00:00Z

You get back the IDs, not the records, and a matching deleted call gives you what was removed. Salesforce built it for “data replication applications”, with two limits to design around: it looks back no more than 30 days, and it returns up to 600,000 IDs before it fails (sObject Get Updated (opens in a new tab), checked 2026-10-31). A copy that falls more than 30 days behind can’t catch up this way.

Everything in this module works on one record per call: one query page, one create, one update. That’s simple and fast, and for Redrock sending a few updates an hour, it’s all you need.

But every call counts against the org’s daily API allocation (module 7), and some work isn’t one record. A Work Order with its line items is several records that belong together. That’s the next module: many in one call. And when it’s thousands of records, it’s chapter 10, the bulk door.

In 2023 I made three short videos on the REST API, live in Postman: the query, the describe and retrieve, and insert and update. The notes under the videos say what’s still right and what’s changed since.

STA 1.10 · Across APIs, Salesforce REST Query · 9:56 · published 2023-11-08 · plays from YouTube when you click it · Watch on YouTube · More videos: Salesforce Integration Architecture Videos
STA 1.11 · Across the APIs: Salesforce REST SObject Describe and Retrieve · 12:10 · published 2023-11-07 · plays from YouTube when you click it · Watch on YouTube · More videos: Salesforce Integration Architecture Videos
STA 1.12 · Across the APIs: Salesforce REST SObject Insert and Update · 8:20 · published 2023-11-08 · plays from YouTube when you click it · Watch on YouTube · More videos: Salesforce Integration Architecture Videos
  • Pinned version or latest. Pin it for production integrations; write the number in the landscape inventory.
  • The key the caller uses. An external ID on the object, so callers upsert with their own key instead of keeping a map of yours.
  • How much each call asks for. Only the fields it needs, with fields or a SOQL field list; a page size the caller can handle; nextRecordsUrl followed to the end.
  • What runs when the call writes. The triggers, flows and validation rules on the object, read with the integration in mind.
  • One at a time, or more. A few records an hour: REST, one call each. Records that belong together: module 3. Thousands: chapter 10.