LearnFrenzy
Sign in Take a free test

REST APIs in Salesforce · Lesson 29 of 30

Apex REST and the Salesforce REST API

5 min read Updated Nov 8, 2026 Saurabh Samir

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"}'
  • urlMapping sets the path under /services/apexrest; the class and methods must be global.
  • @HttpGet, @HttpPost, @HttpPut, @HttpPatch, @HttpDelete map HTTP methods to static methods.
  • Set RestContext.response.statusCode and headers yourself to return proper 201/400/404/422 responses, as in the rest of this course.
  • with sharing, WITH USER_MODE and as user DML keep the caller's permissions enforced.
  • Test with RestContext.request/response set 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 maintainyou need a stable contract that hides your data model
Composite can bundle the operationsyou 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

  1. 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/sobjects to 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.

Next lesson

Search LearnFrenzy

Esc

Quick links