Dylan AndersenDylan Andersen's Docs
Cursor + SalesforceExpert Cursor

Connected Apps & Integrations

Named Credentials, External Services, and the Connected App dance for "I need to call an external API from my demo"

At some point in every POC a customer asks "can it call our API?" and the honest answer is yes, but with a small stack of Salesforce primitives between you and the network. This page is the short version of that stack.

Connected Apps and Integrations hero

The shortcut

For most SE use cases, you want a Named Credential pointing at the external API and an Apex class (or Flow) doing the callout. Everything else on this page is either scaffolding for that or a cleaner version of it.

The three pieces

Salesforce splits "talk to the outside world" into three parts:

  1. Connected App. How external systems authenticate to Salesforce. OAuth consumer key, callback URL, scopes.
  2. Named Credential. How Salesforce authenticates to an external system. URL, auth scheme, credentials.
  3. External Service / Apex / Flow. The code or declarative action that actually makes the call.

SEs usually build Named Credential + Apex or Named Credential + Flow. You almost never need to build Connected Apps for a POC unless the external system is calling Salesforce first.

Named Credentials: the short version

Named Credentials let you store a URL and credentials once, and call them from Apex, Flow, or External Services without handling tokens yourself.

A modern Named Credential is three pieces of metadata:

force-app/main/default/
├── externalCredentials/
│   └── ExternalWeather.externalCredential-meta.xml
├── namedCredentials/
│   └── ExternalWeather.namedCredential-meta.xml
└── permissionsets/
    └── ExternalWeatherAccess.permissionset-meta.xml

The External Credential holds the auth scheme. The Named Credential holds the URL and points at the External Credential. The Permission Set grants access so your user (not just admins) can use the credential.

The fast path with Cursor

The sf-integration skill handles this whole setup. Install it, then:

Activate the sf-integration skill. I need a Named Credential called
ExternalWeather that calls https://api.example-weather.com with an
API-key auth scheme. Set up the External Credential, Named Credential,
Permission Set, and show me a sample Apex class that does a GET against
/current?city=SF using the credential.

The skill produces all the metadata files, an Apex class that uses the credential, and a one-line reminder to assign the permission set to your demo user.

Example: a Flow calling the same API

For SEs who live in Flow more than Apex, you can use External Services to expose a Named Credential as a callable Flow action.

  1. Create the Named Credential (above).
  2. In Setup > External Services, click "Add External Service", pick the Named Credential, and point at an OpenAPI schema URL or paste in a schema.
  3. Salesforce generates invocable actions, one per operation in the schema.
  4. Drag the action into your Flow. Fill in parameters. Done.

The sf-integration skill can generate the OpenAPI schema for simple APIs if the vendor doesn't ship one.

Connected Apps: when you actually need one

You need a Connected App when:

  • An external system is calling into Salesforce (common for a Next.js or Python companion app).
  • You want a Web Server OAuth flow, JWT Bearer flow, or a user-password flow (for integration users).
  • You're setting up server-to-server access with a stored private key.

For POCs, the two most common patterns are:

Best when: you have a customer-facing app that lets a user log in and act on their behalf.

<?xml version="1.0" encoding="UTF-8"?>
<ConnectedApp xmlns="http://soap.sforce.com/2006/04/metadata">
    <contactEmail>sales-eng@example.com</contactEmail>
    <label>POC Companion App</label>
    <oauthConfig>
        <callbackUrl>https://mycompanion.vercel.app/auth/callback</callbackUrl>
        <isAdminApproved>false</isAdminApproved>
        <scopes>Api</scopes>
        <scopes>RefreshToken</scopes>
        <scopes>OpenID</scopes>
    </oauthConfig>
</ConnectedApp>

The user lands on login.salesforce.com, approves the app, and Salesforce calls your callback URL with a code. Trade the code for an access token using the consumer key and secret.

Best when: a server-side process needs to call Salesforce without a user in the loop. The classic "nightly sync job" scenario.

<?xml version="1.0" encoding="UTF-8"?>
<ConnectedApp xmlns="http://soap.sforce.com/2006/04/metadata">
    <contactEmail>sales-eng@example.com</contactEmail>
    <label>POC Integration User</label>
    <oauthConfig>
        <callbackUrl>https://login.salesforce.com/services/oauth2/success</callbackUrl>
        <certificate>LS0tLS1CRUdJTiBDRVJU... (base64 public cert)</certificate>
        <isAdminApproved>true</isAdminApproved>
        <scopes>Api</scopes>
        <scopes>RefreshToken</scopes>
    </oauthConfig>
</ConnectedApp>

You generate a key pair. The public cert goes in the Connected App. The private key signs a JWT on the server side. Salesforce validates and hands back an access token.

The sf-connected-apps skill scaffolds both of these, including the private key generation and the trusted IP ranges. It also writes the Apex pattern for parsing the callback on the Salesforce side if you need it.

The minimum viable external call

If you want the simplest possible "show me it works" path:

# 1. Create a Named Credential
sf project generate --name-credential ExternalWeather \
  --url https://api.example-weather.com \
  --auth-scheme AwsSv4

# (or have the sf-integration skill build it. It's cleaner.)

# 2. Deploy
sf project deploy start --source-dir force-app/main/default

# 3. Run a throwaway callout
cat > scripts/apex/test-callout.apex <<'APEX'
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ExternalWeather/current?city=SF');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
System.debug(res.getStatusCode() + ' ' + res.getBody());
APEX

sf apex run --file scripts/apex/test-callout.apex --target-org my-scratch

If the debug log shows the real response body, you're good. If it shows Remote Site not allowed, you skipped the permission set assignment on the Named Credential.

Common traps

Unauthorized endpoint despite having a Named Credential

You're using the raw endpoint URL in your Apex instead of callout:NamedCredentialName/.... Use the callout syntax.

INVALID_SESSION_ID from a JWT Bearer flow

Usually one of four things: the integration user isn't activated, the Connected App isn't isAdminApproved for the user's profile, the JWT audience is wrong (https://login.salesforce.com vs test), or the certificate in the Connected App doesn't match the private key signing the JWT.

REQUIRED_FIELD_MISSING on an External Service action

The OpenAPI schema is marking fields as required that you're not passing in the Flow. Either mark them optional in the schema or fill them in the Flow action.

Customer security team won't let you open a Named Credential

Ask if they'll allow a Private Connect or a MuleSoft integration instead. For POC purposes, you can usually mock the external system with a static resource or a simple Vercel endpoint.

Verification

After deploying a Named Credential, run this in the Developer Console or via sf apex run:

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:ExternalWeather/ping');
req.setMethod('GET');
System.debug(new Http().send(req).getStatusCode());

A status code in the 200 to 499 range means Salesforce is reaching the endpoint. A callout exception means a config problem, almost always the permission set.

Next moves

  • Install sf-connected-apps and sf-integration if you haven't.
  • Project Anatomy for where these files live.
  • Permission Sets & Access for assigning the Named Credential permission set to your demo user.

On this page