sf-integration
Skill15 starsChanged 7 months agoMITView on GitHub
Use when building Salesforce Apex integrations — REST/SOAP callouts, Named Credentials, inbound API, External Services. Do NOT use for Platform Events or Flow-only automation.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- VGBRO/claude-skillstheir own version, first seen Oct 11, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- VGBRO/claude-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository58 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- asideSkill
- checkpointSkill
- configure-sccSkill
- continuous-agent-loopSkill
- mcp-server-patternsSkill
- model-routeSkill
- prompt-optimizerSkill
- refactor-cleanSkill
- resume-sessionSkill
- save-sessionSkill
- search-firstSkill
- security-scanSkill
- sessionsSkill
- sf-2gp-security-reviewSkill
- sf-agentforce-developmentSkill
- sf-agentforce-mcp-actionsSkill
- sf-apex-async-patternsSkill
- sf-apex-best-practicesSkill
- sf-apex-constraintsSkill
- sf-apex-cursorSkill
- sf-apex-enterprise-patternsSkill
- sf-apex-testingSkill
- sf-api-designSkill
- sf-approval-processesSkill
- sf-aura-developmentSkill
- sf-build-fixSkill
- sf-cli-referenceSkill
- sf-data-modelingSkill
- sf-debuggingSkill
- sf-deployment-constraintsSkill
- sf-deploymentSkill
- sf-devops-ci-cdSkill
- sf-docs-lookupSkill
- sf-e2e-testingSkill
- sf-experience-cloudSkill
- sf-flow-developmentSkill
- sf-governor-limitsSkill
- sf-harness-auditSkill
- sf-helpSkill
- sf-lwc-constraintsSkill
- sf-lwc-developmentSkill
- sf-lwc-testingSkill
- sf-metadata-managementSkill
- sf-platform-events-cdcSkill
- sf-quickstartSkill
- sf-security-constraintsSkill
- sf-securitySkill
- sf-soql-constraintsSkill
- sf-soql-optimizationSkill
- sf-tdd-workflowSkill
- sf-testing-constraintsSkill
- sf-trigger-constraintsSkill
- sf-trigger-frameworksSkill
- sf-visualforce-developmentSkill
- strategic-compactSkill
- update-docsSkill
- update-platform-docsSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 426
Create External Client App in Setup > External Client Apps
- Distinctive: line 429
Test OAuth flow in sandbox before production deployment
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Salesforce Integration Patterns
Procedures for building integrations between Salesforce and external systems. Limits, auth protocols, and pattern decision matrices live in the reference file.
@../_reference/INTEGRATION_PATTERNS.md
When to Use
- Designing a new integration between Salesforce and an external system
- Choosing between REST callout, SOAP callout, Bulk API, or Composite API
- Implementing an inbound REST API endpoint in Salesforce
- Configuring Named Credentials and External Credentials for authentication
- Adding retry logic to callout classes for resilience
- Migrating from Connected Apps to External Client Apps (Spring '26+)
Outbound REST Callout — Complete Pattern
public with sharing class OrderManagementIntegration {
private static final String NAMED_CREDENTIAL = 'OrderManagementAPI';
private static final Integer TIMEOUT_MS = 10000;
private static final Integer MAX_RETRIES = 2;
public static OrderResponse createExternalOrder(OrderRequest orderData) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:' + NAMED_CREDENTIAL + '/api/v2/orders');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setHeader('Accept', 'application/json');
req.setTimeout(TIMEOUT_MS);
req.setBody(JSON.serialize(orderData));
return executeWithRetry(req, MAX_RETRIES);
}
public static OrderResponse getOrderStatus(String externalOrderId) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:' + NAMED_CREDENTIAL +
'/api/v2/orders/' + EncodingUtil.urlEncode(externalOrderId, 'UTF-8'));
req.setMethod('GET');
req.setHeader('Accept', 'application/json');
req.setTimeout(TIMEOUT_MS);
return executeWithRetry(req, MAX_RETRIES);
}
/**
* In-transaction retry for transient network glitches.
* For true backoff, use Queueable chaining with
* AsyncOptions.minimumQueueableDelayInMinutes between attempts.
*/
private static OrderResponse executeWithRetry(HttpRequest req, Integer retries) {
Http http = new Http();
HttpResponse res;
Exception lastException;
for (Integer attempt = 0; attempt <= retries; attempt++) {
try {
res = http.send(req);
if (res.getStatusCode() == 200 || res.getStatusCode() == 201) {
return (OrderResponse) JSON.deserialize(
res.getBody(), OrderResponse.class);
}
if (res.getStatusCode() == 429) {
if (attempt == retries) {
throw new IntegrationException(
'Rate limited (429) after ' + (retries + 1) + ' attempts.');
}
continue;
}
if (res.getStatusCode() >= 500 && attempt < retries) continue;
throw new IntegrationException(
'HTTP ' + res.getStatusCode() + ': ' + res.getBody());
} catch (System.CalloutException e) {
lastException = e;
if (attempt == retries) {
throw new IntegrationException(
'Callout failed after ' + (retries + 1) +
' attempts: ' + e.getMessage(), e);
}
}
}
throw new IntegrationException('Unexpected retry loop exit');
}
public class OrderRequest {
public String externalAccountId;
public String productCode;
public Integer quantity;
public Decimal unitPrice;
public String currency_x;
}
public class OrderResponse {
public String orderId;
public String status;
public String message;
public String createdAt;
}
public class IntegrationException extends Exception {}
}Named Credentials Setup
Modern Model: External Credentials + Named Credentials (API 54.0+)
Step 1 -- Create External Credential (Setup > Security > Named Credentials > External Credentials)
- Principal type:
Named Principal(shared credential) orPer User(each user authenticates separately) - Authentication Protocol: OAuth 2.0, JWT Token, Basic, Custom Header, etc.
Step 2 -- Create Named Credential referencing the External Credential
- Name:
OrderManagementAPI - URL:
https://erp.example.com - External Credential: select from Step 1
Step 3 -- Grant permission via Permission Set on the External Credential Principal
// Usage in Apex — identical to legacy Named Credentials
req.setEndpoint('callout:OrderManagementAPI/api/v2/orders');
// Auth headers injected automaticallyLegacy Named Credentials
Still valid for simpler use cases. Combine URL + auth in one config.
req.setEndpoint('callout:LegacyNamedCred/endpoint');SOAP Callout via WSDL2Apex
# Generate Apex stub from WSDL
sf generate apex from-wsdl \
--file path/to/service.wsdl \
--output-dir force-app/main/default/classesManual SOAP Callout (when WSDL2Apex is impractical)
public with sharing class SoapIntegration {
private static final String SOAP_ENDPOINT = 'callout:LegacySoapService/service';
public static String invokeMethod(String accountNumber) {
String safeAccountNumber = escapeXml(accountNumber);
String soapBody =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"' +
' xmlns:leg="http://legacy.example.com/service">' +
' <soapenv:Header/>' +
' <soapenv:Body>' +
' <leg:GetAccountDetails>' +
' <leg:AccountNumber>' + safeAccountNumber + '</leg:AccountNumber>' +
' </leg:GetAccountDetails>' +
' </soapenv:Body>' +
'</soapenv:Envelope>';
HttpRequest req = new HttpRequest();
req.setEndpoint(SOAP_ENDPOINT);
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
req.setHeader('SOAPAction', '"GetAccountDetails"');
req.setBody(soapBody);
req.setTimeout(15000);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException('SOAP error: ' + res.getStatus());
}
Dom.Document doc = res.getBodyDocument();
Dom.XmlNode root = doc.getRootElement();
Dom.XmlNode body = root.getChildElement(
'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
Dom.XmlNode responseEl = body.getChildElements()[0];
Dom.XmlNode result = responseEl.getChildElement(
'Result', 'http://legacy.example.com/service');
return result != null ? result.getText() : null;
}
private static String escapeXml(String input) {
if (String.isBlank(input)) return input;
return input
.replace('&', '&')
.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace('\'', ''');
}
}Inbound REST API
@RestResource(urlMapping='/v1/accounts/*')
global with sharing class AccountRestService {
@HttpGet
global static AccountDTO getAccount() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
String accountId = req.requestURI.substring(
req.requestURI.lastIndexOf('/') + 1);
if (String.isBlank(accountId)) {
res.statusCode = 400;
return new AccountDTO(null, null, 'Invalid Account ID');
}
try {
Id.valueOf(accountId);
} catch (StringException e) {
res.statusCode = 400;
return new AccountDTO(null, null, 'Invalid Account ID');
}
List<Account> accounts = [
SELECT Id, Name, Phone, BillingCity, BillingCountry
FROM Account WHERE Id = :accountId
WITH USER_MODE LIMIT 1
];
if (accounts.isEmpty()) {
res.statusCode = 404;
return new AccountDTO(null, null, 'Account not found');
}
res.statusCode = 200;
return new AccountDTO(accounts[0].Id, accounts[0].Name, null);
}
@HttpPost
global static AccountDTO createAccount() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
try {
Map<String, Object> body =
(Map<String, Object>) JSON.deserializeUntyped(
req.requestBody.toString());
Account acc = new Account();
acc.Name = (String) body.get('name');
acc.Phone = (String) body.get('phone');
acc.BillingCity = (String) body.get('billingCity');
if (String.isBlank(acc.Name)) {
res.statusCode = 400;
return new AccountDTO(null, null, 'Account name is required');
}
Database.insert(acc, AccessLevel.USER_MODE);
res.statusCode = 201;
return new AccountDTO(acc.Id, acc.Name, 'Account created');
} catch (DmlException e) {
res.statusCode = 400;
return new AccountDTO(null, null, 'DML error: ' + e.getDmlMessage(0));
} catch (JSONException e) {
res.statusCode = 400;
return new AccountDTO(null, null, 'Invalid JSON: ' + e.getMessage());
} catch (System.SecurityException e) {
res.statusCode = 403;
return new AccountDTO(null, null, 'Insufficient access: ' + e.getMessage());
}
}
global class AccountDTO {
global String id;
global String name;
global String message;
global AccountDTO(String id, String name, String message) {
this.id = id;
this.name = name;
this.message = message;
}
}
}HttpCalloutMock -- Testing Callouts
Single Endpoint Mock
@IsTest
public class OrderIntegrationMock implements HttpCalloutMock {
private Integer statusCode;
private String body;
public OrderIntegrationMock(Integer statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(statusCode);
res.setHeader('Content-Type', 'application/json');
res.setBody(body);
return res;
}
}Multi-Endpoint Mock
@IsTest
public class MultiRequestMock implements HttpCalloutMock {
private Map<String, HttpCalloutMock> mocks = new Map<String, HttpCalloutMock>();
public void addMock(String endpoint, HttpCalloutMock mock) {
mocks.put(endpoint, mock);
}
public HttpResponse respond(HttpRequest req) {
String endpoint = req.getEndpoint();
for (String key : mocks.keySet()) {
if (endpoint.contains(key)) {
return mocks.get(key).respond(req);
}
}
throw new IllegalArgumentException('Unexpected callout to: ' + endpoint);
}
}Using Mocks in Tests
@IsTest
static void testCreateOrder_success_returnsOrderId() {
String mockResponse =
'{"orderId":"ERP-001","status":"PENDING","message":"Order created"}';
Test.setMock(HttpCalloutMock.class,
new OrderIntegrationMock(201, mockResponse));
Test.startTest();
OrderManagementIntegration.OrderRequest req =
new OrderManagementIntegration.OrderRequest();
req.productCode = 'PROD-001';
req.quantity = 5;
req.unitPrice = 99.99;
OrderManagementIntegration.OrderResponse res =
OrderManagementIntegration.createExternalOrder(req);
Test.stopTest();
System.assertEquals('ERP-001', res.orderId);
System.assertEquals('PENDING', res.status);
}External Client Apps (Spring '26 GA)
External Client Apps replace Connected Apps for new OAuth-based integrations.
| Feature | Connected App | External Client App |
| Status | Existing -- maintain as-is | New standard for Spring '26+ |
| Location | Setup > App Manager | Setup > External Client App Manager |
| Metadata type | ConnectedApp | ExternalClientApplication |
| Recommendation | Keep existing; do not migrate | Use for all new integrations |
Creating an External Client App (Metadata)
<?xml version="1.0" encoding="UTF-8"?>
<ExternalClientApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<contactEmail>integrations@example.com</contactEmail>
<description>OAuth integration with Warehouse Management System</description>
<distributionState>Global</distributionState>
<label>Warehouse Integration</label>
<name>WarehouseIntegration</name>
</ExternalClientApplication>sf project deploy start \
--metadata ExternalClientApplication:WarehouseIntegration \
--target-org ProductionKey Steps for New Integrations
- Create External Client App in Setup > External Client Apps
- Configure OAuth scopes and callback URLs
- Reference the External Client App in your Named Credential's External Credential
- Test OAuth flow in sandbox before production deployment
Retry Pattern with Queueable
For callouts with transient failures, implement a retry Queueable:
public class RetryCalloutQueueable implements Queueable, Database.AllowsCallouts {
private Id recordId;
private Integer attemptNumber;
private static final Integer MAX_ATTEMPTS = 3;
public RetryCalloutQueueable(Id recordId, Integer attemptNumber) {
this.recordId = recordId;
this.attemptNumber = attemptNumber;
}
public void execute(QueueableContext ctx) {
try {
ExternalIntegration.sync(recordId);
update new MyRecord__c(
Id = recordId,
Sync_Status__c = 'Success',
Last_Sync__c = Datetime.now()
);
} catch (Exception e) {
if (attemptNumber < MAX_ATTEMPTS) {
System.enqueueJob(
new RetryCalloutQueueable(recordId, attemptNumber + 1));
} else {
update new MyRecord__c(
Id = recordId,
Sync_Status__c = 'Failed',
Sync_Error__c = e.getMessage()
);
}
}
}
}Related
- Agent:
sf-integration-agent-- for interactive, in-depth guidance - Constraints: sf-apex-constraints, sf-security-constraints
- Reference: @../_reference/INTEGRATION_PATTERNS.md
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

