The Salesforce Integration Framework is a comprehensive, production-ready solution that simplifies HTTP API integration in Salesforce.
It provides a unified, fluent API. It uses the Builder pattern to handle all types of external system integration. This approach ensures minimal code complexity.
For Developers:
For Organizations:
For Enterprise:
Before the Framework:
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/users');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setHeader('Authorization', 'Bearer ' + token);
req.setBody(JSON.serialize(userData));
req.setTimeout(30000);
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
// Process result
} else {
// Manual error handling
}
With the Framework:
apex// ✅ Simple, clean, readable
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.POST)
.endpoint('/api/users')
.withNamedCredential('My_API')
.jsonBody(userData)
.execute();
if (response.isSuccess()) {
Map<String, Object> result = response.getBodyAsMap();
// Process result
}
Install the framework in your Salesforce org using the URLs below. Below is the list of unmanaged package URLs
If you want to test that the framework is working, use this:
// Test in Developer Console - Execute Anonymous
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('https://httpbin.org/get')
.withApiKey('test', 'X-Test')
.execute();
System.debug('Status: ' + response.getStatusCode());
System.debug('Body: ' + response.getBody());
If you want the quickest way to make API calls, use these convenience methods:
// Quick GET - when you just need to fetch data
IntegrationResponse users = IntegrationFramework.get('My_API', '/api/users');
// Quick POST - when you need to send JSON data
Map<String, Object> data = new Map<String, Object>{'name' => 'John'};
IntegrationResponse created = IntegrationFramework.post('My_API', '/api/users', data, true); // pass a boolean parameter to use Named Credentials
If you want to retrieve data from an API, use GET requests:
// Simple GET - when you just need basic data retrieval
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users');
// GET with parameters - when you need to filter or paginate results
Map<String, String> params = new Map<String, String>{
'page' => '1',
'limit' => '50'
};
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users', params);
// Advanced GET - when you need custom headers or specific configuration
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('/api/users')
.withNamedCredential('My_API')
.parameter('status', 'active')
.header('Accept', 'application/json')
.execute();
If you want to create new records or send data to an API, use POST requests:
// JSON POST - when you need to send structured data (most common)
Map<String, Object> userData = new Map<String, Object>{
'firstName' => 'John',
'lastName' => 'Doe',
'email' => '[email protected]'
};
IntegrationResponse response = IntegrationFramework.post('My_API', '/api/users', userData, true);
// Form POST - when the API expects form-encoded data (like login forms)
Map<String, String> formData = new Map<String, String>{
'username' => 'john',
'password' => 'secret'
};
IntegrationResponse response = IntegrationFramework.postForm('My_API', '/api/login', formData, true);
// Advanced POST - when you need custom headers or specific content types
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.POST)
.endpoint('/api/users')
.withNamedCredential('My_API')
.contentType(IntegrationContentType.JSON)
.jsonBody(userData)
.header('X-Source', 'Salesforce')
.execute();
If you want to update existing records, use PUT for full updates or PATCH for partial updates:
// PUT (full update) - when you're replacing the entire record
Map<String, Object> updatedData = new Map<String, Object>{
'firstName' => 'Jane',
'lastName' => 'Smith',
'email' => '[email protected]'
};
IntegrationResponse response = IntegrationFramework.put('My_API', '/api/users/123', updatedData, true);
// PATCH (partial update) - when you're only changing specific fields
Map<String, Object> partialUpdate = new Map<String, Object>{'status' => 'active'};
IntegrationResponse response = IntegrationFramework.patch('My_API', '/api/users/123', partialUpdate, true);
If you want to remove records from the API, use DELETE requests:
// Simple DELETE - when you just need to delete a record by ID
IntegrationResponse response = IntegrationFramework.delete('My_API', '/api/users/123');
// DELETE with headers - when you need to provide additional context or authorization
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.DELETE)
.endpoint('/api/users/123')
.withNamedCredential('My_API')
.header('X-Reason', 'User requested')
.execute();
If you want the most secure way to authenticate (recommended for production), use Named Credentials:
// Setup: Create Named Credential in Setup → Named Credentials
// Then use it like this for any API that requires authentication
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('/api/data') // Relative to Named Credential URL
.withNamedCredential('My_API')
.execute();
If your API uses JWT tokens or OAuth access tokens, use Bearer Token authentication:
String token = 'your_bearer_token_here';
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('https://api.example.com/data')
.withBearerToken(token)
.execute();
// Quick method - when you just need a simple GET with token
IntegrationResponse response = IntegrationFramework.get('https://api.example.com/data', token);
If your API uses username/password authentication, use Basic Auth:
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('https://api.example.com/secure')
.withBasicAuth('username', 'password')
.execute();
If your API uses API keys in headers, use API Key authentication:
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.GET)
.endpoint('https://api.example.com/data')
.withApiKey('your_api_key', 'X-API-Key') // Custom header name
.execute();
If you want to handle different response scenarios properly, always check the status first:
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users');
if (response.isSuccess()) {
// 2xx responses - when everything went well
System.debug('Success!');
} else if (response.isClientError()) {
// 4xx responses - when there's an issue with your request
System.debug('Client error: ' + response.getStatusCode());
} else if (response.isServerError()) {
// 5xx responses - when there's an issue with the server
System.debug('Server error: ' + response.getStatusCode());
}
If you want to extract data from the API response, use these parsing methods:
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users', true);
// As JSON Map - when the API returns JSON (most common)
Map<String, Object> data = response.getBodyAsMap();
List<Object> users = (List<Object>) data.get('users');
// As specific type - when you have a custom Apex class for the response
MyResponseClass result = (MyResponseClass) response.getBodyAs(MyResponseClass.class);
// As XML - when the API returns XML
Dom.Document xmlDoc = response.getBodyAsXml();
// Raw string - when you need the response exactly as received
String rawBody = response.getBody();
If you need to access response headers (for rate limiting, pagination, etc.), use these methods:
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users', true);
String contentType = response.getHeader('Content-Type');
String rateLimit = response.getHeader('X-RateLimit-Remaining');
String[] allHeaders = response.getHeaderKeys();
If you want to handle errors gracefully and provide meaningful feedback, use this pattern:
try {
IntegrationResponse response = IntegrationFramework.post('My_API', '/api/users', userData, true);
if (response.isSuccess()) {
// Handle success - when API call worked
Map<String, Object> result = response.getBodyAsMap();
} else {
// Handle API errors - when API returned an error status
switch on response.getStatusCode() {
when 400 {
System.debug('Bad request - check data');
}
when 401 {
System.debug('Unauthorized - check credentials');
}
when 404 {
System.debug('Not found');
}
when else {
System.debug('Error: ' + response.getStatusCode());
}
}
}
} catch (IntegrationException e) {
// Handle framework errors - when something went wrong with the request itself
System.debug('Integration failed: ' + e.getMessage());
}
If you want to test your integration code without making real API calls, use mocks like this:
@isTest
public class MyIntegrationTest {
@isTest
static void testSuccessfulCall() {
// 1. Create mock - this replaces the real HTTP call
IntegrationMockLibrary.MockHttpCallout mock = new IntegrationMockLibrary.MockHttpCallout();
// 2. Configure response - tell the mock what to return
Map<String, Object> mockData = new Map<String, Object>{'id' => '123', 'name' => 'Test'};
mock.setResponse('callout:My_API/api/users', IntegrationMockLibrary.jsonSuccessResponse(mockData));
// 3. Set mock - activate it for this test
Test.setMock(HttpCalloutMock.class, mock);
// 4. Execute test - now this will use your mock instead of real API
Test.startTest();
IntegrationResponse response = IntegrationFramework.get('My_API', '/api/users', true);
Test.stopTest();
// 5. Verify - check that your code handled the response correctly
System.assert(response.isSuccess());
Map<String, Object> data = response.getBodyAsMap();
System.assertEquals('123', data.get('id'));
}
}
If you want to simulate different API responses, use these pre-built response types:
// Success responses - when you want to test happy path scenarios
IntegrationMockLibrary.successResponse('{"status": "ok"}')
IntegrationMockLibrary.jsonSuccessResponse(myObject)
// Error responses - when you want to test error handling
IntegrationMockLibrary.errorResponse(400, 'Bad Request')
IntegrationMockLibrary.notFoundResponse()
IntegrationMockLibrary.unauthorizedResponse()
IntegrationMockLibrary.serverErrorResponse()
// Custom response - when you need specific status codes or headers
IntegrationMockLibrary.response()
.statusCode(201)
.status('Created')
.jsonBody(createdObject)
.header('Location', '/api/users/123')
If you want to verify that your code made the correct API call, check the captured request:
@isTest
static void testRequestDetails() {
IntegrationMockLibrary.MockHttpCallout mock = new IntegrationMockLibrary.MockHttpCallout();
mock.setDefaultResponse(IntegrationMockLibrary.successResponse('OK'));
Test.setMock(HttpCalloutMock.class, mock);
Map<String, Object> testData = new Map<String, Object>{'name' => 'Test'};
Test.startTest();
IntegrationResponse response = IntegrationFramework.post('My_API', '/api/users', testData, true);
Test.stopTest();
// Verify request was made correctly - check method, headers, body
HttpRequest capturedRequest = mock.getLastRequest();
System.assertEquals('POST', capturedRequest.getMethod());
System.assertEquals('application/json', capturedRequest.getHeader('Content-Type'));
// Verify request body contains what you expected
Map<String, Object> requestBody = (Map<String, Object>) JSON.deserializeUntyped(capturedRequest.getBody());
System.assertEquals('Test', requestBody.get('name'));
}
public class UserService {
public String createUser(Map<String, Object> userData) {
try {
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.POST)
.endpoint('/api/users')
.withNamedCredential('User_API')
.contentType(IntegrationContentType.JSON)
.jsonBody(userData)
.execute();
if (response.isSuccess()) {
Map<String, Object> result = response.getBodyAsMap();
return (String) result.get('id');
} else {
throw new IntegrationException('Failed to create user: ' + response.getBody());
}
} catch (Exception e) {
System.debug('User creation failed: ' + e.getMessage());
return null;
}
}
public Map<String, Object> getUser(String userId) {
IntegrationResponse response = IntegrationFramework.get('User_API', '/api/users/' + userId);
if (response.isSuccess()) {
return response.getBodyAsMap();
} else if (response.getStatusCode() == 404) {
return null; // User not found
} else {
throw new IntegrationException('Failed to get user: ' + response.getBody());
}
}
}
public class AccountTriggerHandler {
public static void syncAccounts(List<Account> accounts) {
for (Account acc : accounts) {
syncAccountAsync(acc.Id);
}
}
@future(callout=true)
public static void syncAccountAsync(Id accountId) {
Account acc = [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];
Map<String, Object> accountData = new Map<String, Object>{
'name' => acc.Name,
'industry' => acc.Industry,
'salesforce_id' => acc.Id
};
IntegrationResponse response = IntegrationFramework.post('ERP_API', '/api/accounts', accountData);
if (response.isSuccess()) {
Map<String, Object> result = response.getBodyAsMap();
acc.External_ID__c = (String) result.get('id');
update acc;
}
}
}
public class DataSyncBatch implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator('SELECT Id, Name FROM Account WHERE LastModifiedDate = TODAY');
}
public void execute(Database.BatchableContext bc, List<Account> accounts) {
List<Map<String, Object>> batchData = new List<Map<String, Object>>();
for (Account acc : accounts) {
batchData.add(new Map<String, Object>{
'id' => acc.Id,
'name' => acc.Name
});
}
IntegrationResponse response = IntegrationFramework.newRequest()
.method(HttpMethod.POST)
.endpoint('/api/batch-sync')
.withNamedCredential('Batch_API')
.contentType(IntegrationContentType.JSON)
.jsonBody(new Map<String, Object>{'accounts' => batchData})
.timeout(120000) // 2 minutes
.execute();
if (!response.isSuccess()) {
System.debug('Batch sync failed: ' + response.getBody());
}
}
public void finish(Database.BatchableContext bc) {
System.debug('Batch sync completed');
}
}
public class DataRetriever {
public List<Object> getAllRecords() {
List<Object> allRecords = new List<Object>();
Integer page = 1;
Boolean hasMore = true;
while (hasMore) {
Map<String, String> params = new Map<String, String>{
'page' => String.valueOf(page),
'limit' => '100'
};
IntegrationResponse response = IntegrationFramework.get('Data_API', '/api/records', params, true);
if (response.isSuccess()) {
Map<String, Object> data = response.getBodyAsMap();
List<Object> pageRecords = (List<Object>) data.get('records');
allRecords.addAll(pageRecords);
hasMore = pageRecords.size() == 100;
page++;
} else {
hasMore = false;
}
}
return allRecords;
}
}
.compressed(true))You’re ready to build robust integration! 🚀