afv-library/skills/integration-connectivity-generate/references/external-services-guide.md

8.2 KiB

External Services Guide

Overview

External Services in Salesforce automatically generate Apex classes from OpenAPI (Swagger) specifications, enabling type-safe REST API integrations without writing HTTP code.

How It Works

┌─────────────────────────────────────────────────────────────────┐
│  EXTERNAL SERVICE FLOW                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. OpenAPI Spec (JSON/YAML)                                    │
│     ↓                                                           │
│  2. External Service Registration (Metadata)                    │
│     ↓                                                           │
│  3. Auto-generated Apex Classes                                 │
│     ↓                                                           │
│  4. Type-safe API calls from Apex                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Prerequisites

  1. Named Credential configured for authentication
  2. OpenAPI Specification (2.0 or 3.0) for the API
  3. API Version 48.0+ (Winter '20)

Creating External Service

Via Setup UI

  1. Go to Setup → External Services
  2. Click New External Service
  3. Provide name and description
  4. Select Named Credential
  5. Upload or paste OpenAPI spec
  6. Review operations
  7. Save

Via Metadata API

<?xml version="1.0" encoding="UTF-8"?>
<ExternalServiceRegistration xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>Stripe API</label>
    <description>Stripe payment processing API</description>
    <namedCredential>Stripe_API</namedCredential>
    <schemaType>OpenApi3</schemaType>
    <schema>
{
  "openapi": "3.0.0",
  "info": { "title": "Stripe API", "version": "1.0" },
  "paths": { ... }
}
    </schema>
    <status>Complete</status>
</ExternalServiceRegistration>

Generated Classes

For an External Service named "StripeAPI":

Class Purpose
ExternalService.StripeAPI Main service class with operation methods
ExternalService.StripeAPI_createCustomer_Request Request wrapper
ExternalService.StripeAPI_createCustomer_Response Response wrapper
ExternalService.StripeAPI_Customer DTO matching schema

Usage Patterns

Basic GET Request

// Instantiate service
ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();

// Call GET operation
ExternalService.StripeAPI_getCustomer_Response response =
    stripe.getCustomer('cus_ABC123');

// Access response data
String email = response.email;

POST with Request Body

// Create request object
ExternalService.StripeAPI_createCustomer_Request request =
    new ExternalService.StripeAPI_createCustomer_Request();
request.email = 'customer@example.com';
request.name = 'John Doe';

// Make call
ExternalService.StripeAPI_createCustomer_Response response =
    stripe.createCustomer(request);

// Get created resource ID
String customerId = response.id;

Handling Nested Objects

// Access nested data
ExternalService.StripeAPI_Address address = response.address;
String city = address.city;
String postalCode = address.postalCode;

Error Handling

try {
    ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
    response = stripe.getCustomer('invalid_id');

} catch (ExternalService.ExternalServiceException e) {
    // API returned error response
    System.debug('Status Code: ' + e.getStatusCode());
    System.debug('Error Body: ' + e.getBody());
    System.debug('Error Message: ' + e.getMessage());

} catch (CalloutException e) {
    // Network/connection error
    System.debug('Connection failed: ' + e.getMessage());
}

Async Calls (Queueable)

Use Queueable for calls from triggers:

public class StripeCustomerSync implements Queueable, Database.AllowsCallouts {

    private Account account;

    public StripeCustomerSync(Account account) {
        this.account = account;
    }

    public void execute(QueueableContext context) {
        ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();

        ExternalService.StripeAPI_createCustomer_Request req =
            new ExternalService.StripeAPI_createCustomer_Request();
        req.email = account.Email__c;
        req.name = account.Name;

        try {
            ExternalService.StripeAPI_createCustomer_Response resp =
                stripe.createCustomer(req);

            account.Stripe_Customer_Id__c = resp.id;
            update account;
        } catch (Exception e) {
            System.debug('Sync failed: ' + e.getMessage());
        }
    }
}

OpenAPI Schema Tips

Supported Features

  • GET, POST, PUT, PATCH, DELETE methods
  • Path and query parameters
  • Request and response bodies
  • JSON schema types (string, number, boolean, object, array)
  • References ($ref)
  • Basic authentication headers

Limitations

  • No file uploads (multipart/form-data limited)
  • No WebSockets (HTTP only)
  • No streaming responses
  • Some complex schemas may not parse
  • Maximum schema size limited

Schema Best Practices

{
  "openapi": "3.0.0",
  "info": {
    "title": "My API",
    "version": "1.0.0"
  },
  "paths": {
    "/customers/{id}": {
      "get": {
        "operationId": "getCustomer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Customer" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Customer": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "email": { "type": "string" }
        }
      }
    }
  }
}

Updating External Service

When API changes:

  1. Get updated OpenAPI spec
  2. Go to Setup → External Services
  3. Edit service
  4. Upload new schema
  5. Review changes to operations
  6. Save and validate
  7. Update calling code if signatures changed

Testing

@isTest
private class StripeIntegrationTest {

    @isTest
    static void testCreateCustomer() {
        // Set mock
        Test.setMock(HttpCalloutMock.class, new StripeMock());

        Test.startTest();

        ExternalService.StripeAPI stripe = new ExternalService.StripeAPI();
        ExternalService.StripeAPI_createCustomer_Request req =
            new ExternalService.StripeAPI_createCustomer_Request();
        req.email = 'test@example.com';

        ExternalService.StripeAPI_createCustomer_Response resp =
            stripe.createCustomer(req);

        Test.stopTest();

        System.assertEquals('cus_test123', resp.id);
    }

    private class StripeMock implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest request) {
            HttpResponse response = new HttpResponse();
            response.setStatusCode(201);
            response.setBody('{"id": "cus_test123", "email": "test@example.com"}');
            return response;
        }
    }
}

Use with Agentforce

External Services are ideal for Agent Actions:

  1. Create External Service from API spec
  2. Create Flow that calls External Service
  3. Reference Flow in Agent Script action
actions:
    lookup_customer:
        description: "Looks up customer in payment system"
        inputs:
            customer_email: string
        outputs:
            customer_id: string
        target: "flow://Lookup_Stripe_Customer"