REST & SOAP APIs
📖 Concept
Salesforce provides extensive API capabilities for both consuming (callouts from Salesforce) and exposing (custom APIs) data and functionality.
Inbound APIs (external systems calling Salesforce):
- REST API — Standard CRUD on Salesforce records via HTTP (JSON/XML)
- SOAP API — Enterprise-grade XML-based API
- Bulk API 2.0 — For large data operations (millions of records)
- Composite API — Multiple operations in a single request
- Tooling API — Metadata operations (deployments, describe)
- Streaming API — Push notifications (PushTopic, CDC, Platform Events)
- GraphQL API — Query exactly the fields you need (newer)
Outbound (Salesforce calling external systems):
- HTTP Callouts — Apex HttpRequest/HttpResponse
- Named Credentials — Secure credential management
- External Services — Low-code API integration
- Outbound Messaging — Legacy SOAP-based notifications
Custom REST APIs in Apex:
You can expose your own REST endpoints using @RestResource:
- Custom URL:
/services/apexrest/your-endpoint - Supports GET, POST, PUT, PATCH, DELETE
- Full control over request/response format
- Respects sharing and security settings
Governor limits for APIs:
- 100 callouts per transaction
- 120-second cumulative callout timeout
- 6MB request/response size per callout
- API calls count against daily API limits (based on edition)
💻 Code Example
1// REST & SOAP Integration23// 1. Making HTTP Callouts from Apex4public class ExternalApiService {56 // GET request7 public static String getExternalData(String endpoint) {8 HttpRequest req = new HttpRequest();9 req.setEndpoint(endpoint);10 req.setMethod('GET');11 req.setHeader('Content-Type', 'application/json');12 req.setHeader('Accept', 'application/json');13 req.setTimeout(30000); // 30 seconds1415 HttpResponse res = new Http().send(req);1617 if (res.getStatusCode() == 200) {18 return res.getBody();19 } else {20 throw new CalloutException(21 'API Error: ' + res.getStatusCode() + ' — ' + res.getBody()22 );23 }24 }2526 // POST request with JSON body27 public static Map<String, Object> createExternalRecord(Map<String, Object> data) {28 HttpRequest req = new HttpRequest();29 req.setEndpoint('callout:External_CRM/api/v1/customers');30 req.setMethod('POST');31 req.setHeader('Content-Type', 'application/json');32 req.setBody(JSON.serialize(data));33 req.setTimeout(60000);3435 HttpResponse res = new Http().send(req);3637 if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {38 return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());39 } else {40 throw new CalloutException('Create failed: ' + res.getBody());41 }42 }4344 // Using Named Credentials (best practice for auth)45 public static String callWithNamedCredential() {46 HttpRequest req = new HttpRequest();47 // Named Credential handles authentication automatically48 req.setEndpoint('callout:My_External_System/api/data');49 req.setMethod('GET');5051 HttpResponse res = new Http().send(req);52 return res.getBody();53 }54}5556// 2. Custom REST API (exposing Apex as API)57@RestResource(urlMapping='/api/accounts/*')58global with sharing class AccountRestApi {5960 @HttpGet61 global static List<Account> getAccounts() {62 RestRequest req = RestContext.request;63 String searchTerm = req.params.get('search');6465 if (String.isNotBlank(searchTerm)) {66 String search = '%' + searchTerm + '%';67 return [68 SELECT Id, Name, Industry, AnnualRevenue69 FROM Account WHERE Name LIKE :search LIMIT 5070 ];71 }72 return [SELECT Id, Name, Industry FROM Account LIMIT 100];73 }7475 @HttpPost76 global static Account createAccount() {77 RestRequest req = RestContext.request;78 Map<String, Object> body = (Map<String, Object>)79 JSON.deserializeUntyped(req.requestBody.toString());8081 Account acc = new Account(82 Name = (String) body.get('name'),83 Industry = (String) body.get('industry')84 );85 insert acc;8687 RestContext.response.statusCode = 201;88 return acc;89 }9091 @HttpPut92 global static Account updateAccount() {93 RestRequest req = RestContext.request;94 String accountId = req.requestURI.substringAfterLast('/');9596 Map<String, Object> body = (Map<String, Object>)97 JSON.deserializeUntyped(req.requestBody.toString());9899 Account acc = new Account(Id = accountId);100 if (body.containsKey('name')) acc.Name = (String) body.get('name');101 if (body.containsKey('industry')) acc.Industry = (String) body.get('industry');102 update acc;103104 return [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];105 }106107 @HttpDelete108 global static void deleteAccount() {109 RestRequest req = RestContext.request;110 String accountId = req.requestURI.substringAfterLast('/');111 delete [SELECT Id FROM Account WHERE Id = :accountId];112 RestContext.response.statusCode = 204;113 }114}115116// 3. Callout from Trigger (must use @future)117public class TriggerCalloutService {118119 @future(callout=true)120 public static void syncToErp(Set<Id> accountIds) {121 List<Account> accounts = [122 SELECT Id, Name, Industry, BillingCity123 FROM Account WHERE Id IN :accountIds124 ];125126 for (Account acc : accounts) {127 HttpRequest req = new HttpRequest();128 req.setEndpoint('callout:ERP_System/api/customers');129 req.setMethod('POST');130 req.setHeader('Content-Type', 'application/json');131 req.setBody(JSON.serialize(new Map<String, Object>{132 'sfId' => acc.Id,133 'name' => acc.Name,134 'industry' => acc.Industry135 }));136137 try {138 HttpResponse res = new Http().send(req);139 if (res.getStatusCode() != 200) {140 ErrorLogger.log('ERP sync failed for ' + acc.Id +141 ': ' + res.getBody(), 'TriggerCalloutService', 'ERROR');142 }143 } catch (Exception e) {144 ErrorLogger.log(e, 'TriggerCalloutService.syncToErp');145 }146 }147 ErrorLogger.flush();148 }149}
🏋️ Practice Exercise
Integration Practice:
- Create a custom REST API that supports GET, POST, PUT, DELETE for a custom object
- Write an Apex class that makes a GET callout to a public API and parses the JSON response
- Set up a Named Credential for an external system and use it in a callout
- Implement a @future(callout=true) method called from a trigger to sync data externally
- Build a Bulk API integration that imports 50,000 records from a CSV
- Create a Composite API request that creates an Account and its Contacts in one API call
- Write a test class with HttpCalloutMock for all your callout methods
- Design an error handling and retry strategy for failed API callouts
- Implement rate limiting to stay within daily API call limits
- Build a real-time sync using Change Data Capture + External API callouts
⚠️ Common Mistakes
Making callouts from triggers without @future — synchronous callouts in triggers are not allowed. Use @future(callout=true) or Queueable with Database.AllowsCallouts
Not using Named Credentials — hard-coding credentials in Apex is a security vulnerability and fails security reviews
Ignoring callout governor limits — 100 callouts per transaction, 120-second total timeout. Plan for limits in bulk scenarios
Not writing HttpCalloutMock tests — callouts cannot be made in test context. Without mocks, your code has 0% test coverage for callout logic
Not handling HTTP error responses — checking only for 200 misses 201, 204, and other valid success codes. Check for ranges (200-299)
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for REST & SOAP APIs. Login to unlock this feature.