REST APIs in Salesforce · Lesson 29 of 30
Apex REST and the Salesforce REST API
The other direction: external systems calling into Salesforce. Salesforce already exposes every object through its standard REST API – create, read, update, delete and query records with no code. When an integration needs a business-specific operation instead ("convert this quote into an order with lines and pricing in one call"), you build a custom endpoint with Apex REST.
In this lesson
- The standard Salesforce REST API
- Authentication for integrations
- Custom endpoints with @RestResource
- Choosing between them
The standard REST API
BASE=https://yourdomain.my.salesforce.com/services/data/v65.0
# read a record (only the fields you need)
curl "$BASE/sobjects/Account/001XX000003DGb2?fields=Name,Industry" -H "Authorization: Bearer $TOKEN"
# create a record → 201 with {"id": "...", "success": true}
curl -X POST "$BASE/sobjects/Contact" -H "Authorization: Bearer $TOKEN" \
--json '{"LastName":"Verma","Email":"asha@example.com","AccountId":"001XX000003DGb2"}'
# update (PATCH) → 204 No Content
curl -X PATCH "$BASE/sobjects/Contact/003XX0000004Abc" -H "Authorization: Bearer $TOKEN" --json '{"Title":"CTO"}'
# upsert by an external id → 201 created or 200 updated
curl -X PATCH "$BASE/sobjects/Account/ERP_Id__c/ERP-1001" -H "Authorization: Bearer $TOKEN" --json '{"Name":"Acme"}'
# SOQL query (URL-encoded); follow "nextRecordsUrl" for more pages
curl -G "$BASE/query" --data-urlencode "q=SELECT Id, Name FROM Account WHERE Industry = 'Energy' LIMIT 10" \
-H "Authorization: Bearer $TOKEN"
It's textbook REST: resources under /sobjects, methods for CRUD, 201/204/404 status codes, and cursor-style pagination with nextRecordsUrl. The Composite resources (/composite, /composite/sobjects, /composite/graph) bundle up to hundreds of operations in one round trip – important because every call counts against the org's daily API limit.
Authentication for integrations
- Create an External Client App (or a Connected App in older orgs) with OAuth enabled.
- For server-to-server integrations, use the Client Credentials flow (or JWT Bearer) with a dedicated integration user whose permission sets grant only what the integration needs.
- For apps acting on behalf of users, use the Authorization Code flow with PKCE.
- Every call respects the running user's object, field and record access – one more reason for a least-privilege integration user.
A custom endpoint with Apex REST
@RestResource(urlMapping='/enrolments/*')
global with sharing class EnrolmentApi {
global class EnrolmentRequest {
global String studentEmail;
global String courseCode;
}
@HttpPost
global static void enrol() {
RestResponse res = RestContext.response;
res.addHeader('Content-Type', 'application/json');
EnrolmentRequest body;
try {
body = (EnrolmentRequest) JSON.deserialize(RestContext.request.requestBody.toString(), EnrolmentRequest.class);
} catch (JSONException e) {
res.statusCode = 400;
res.responseBody = Blob.valueOf('{"error":"Malformed JSON"}');
return;
}
if (String.isBlank(body.studentEmail) || String.isBlank(body.courseCode)) {
res.statusCode = 422;
res.responseBody = Blob.valueOf('{"error":"studentEmail and courseCode are required"}');
return;
}
List<Course__c> courses = [SELECT Id FROM Course__c WHERE Code__c = :body.courseCode WITH USER_MODE LIMIT 1];
if (courses.isEmpty()) {
res.statusCode = 404;
res.responseBody = Blob.valueOf('{"error":"Unknown course"}');
return;
}
Enrolment__c e = new Enrolment__c(Course__c = courses[0].Id, Student_Email__c = body.studentEmail);
insert as user e;
res.statusCode = 201;
res.addHeader('Location', '/services/apexrest/enrolments/' + e.Id);
res.responseBody = Blob.valueOf(JSON.serialize(new Map<String, Object>{ 'id' => e.Id, 'status' => 'active' }));
}
@HttpGet
global static Enrolment__c getEnrolment() {
String enrolmentId = RestContext.request.requestURI.substringAfterLast('/');
return [SELECT Id, Course__r.Name, Student_Email__c FROM Enrolment__c WHERE Id = :enrolmentId WITH USER_MODE];
}
}
curl -X POST https://yourdomain.my.salesforce.com/services/apexrest/enrolments \
-H "Authorization: Bearer $TOKEN" --json '{"studentEmail":"ravi@example.com","courseCode":"REST-101"}'
urlMappingsets the path under/services/apexrest; the class and methods must beglobal.@HttpGet,@HttpPost,@HttpPut,@HttpPatch,@HttpDeletemap HTTP methods to static methods.- Set
RestContext.response.statusCodeand headers yourself to return proper 201/400/404/422 responses, as in the rest of this course. with sharing,WITH USER_MODEandas userDML keep the caller's permissions enforced.- Test with
RestContext.request/responseset up in an Apex test.
Standard API or Apex REST?
| Use the standard REST API when… | Use Apex REST when… |
|---|---|
| the integration works record by record (sync contacts, read accounts) | one call must run business logic across several objects |
| you want zero code to maintain | you need a stable contract that hides your data model |
| Composite can bundle the operations | you need custom validation, transformation or idempotency rules |
Other Salesforce integration APIs
Bulk API 2.0 for millions of records, Pub/Sub API and Change Data Capture for event streams, and outbound webhooks via Flow HTTP Callouts. Pick the one that matches volume and timing – REST is best for interactive, record-level traffic.
Practice
Try it yourself
An ERP sends 50,000 product price updates every night through individual PATCH calls and hits the daily API limit. What would you change?
Show answer
Use Bulk API 2.0 (upsert by an external id such as
ERP_Id__c) for the nightly load – it processes the whole file asynchronously with very few API calls. Keep REST for small, real-time updates, using/composite/sobjectsto send up to 200 records per call.
Quick check
Where do Apex REST endpoints live?
Under /services/apexrest/ plus the urlMapping.
How do you upsert by an external id?
PATCH /sobjects/Object/ExternalIdField__c/value.
Which OAuth flow suits a nightly server-to-server sync?
Client Credentials (or JWT Bearer) with a dedicated integration user.
Summary
- The Salesforce REST API gives CRUD, upsert and SOQL over every object; Composite saves round trips.
- Authenticate integrations with OAuth and a least-privilege integration user.
- Build business-level endpoints with @RestResource, returning proper status codes and enforcing user mode.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.