callout-limits-and-async-patterns

Use when designing or troubleshooting Apex callouts that approach governor limits: choosing between synchronous callouts, @future, Queueable, Continuation, or async chaining strategies. NOT for HTTP request construction or Named Credential setup (use named-credentials-setup).

Best use case

callout-limits-and-async-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when designing or troubleshooting Apex callouts that approach governor limits: choosing between synchronous callouts, @future, Queueable, Continuation, or async chaining strategies. NOT for HTTP request construction or Named Credential setup (use named-credentials-setup).

Teams using callout-limits-and-async-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/callout-limits-and-async-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/integration/callout-limits-and-async-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/callout-limits-and-async-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How callout-limits-and-async-patterns Compares

Feature / Agentcallout-limits-and-async-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when designing or troubleshooting Apex callouts that approach governor limits: choosing between synchronous callouts, @future, Queueable, Continuation, or async chaining strategies. NOT for HTTP request construction or Named Credential setup (use named-credentials-setup).

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Callout Limits And Async Patterns

This skill activates when a practitioner needs to design or debug Apex callouts that are approaching governor limits or require an asynchronous execution model. It covers the decision between synchronous, `@future`, Queueable, Continuation, and chained async callout patterns, including the DML-before-callout restriction.

---

## Before Starting

Gather this context before working on anything in this domain:

- Identify the execution context: synchronous trigger, batch Apex, LWC/Aura controller, REST endpoint, or scheduled Apex — this determines which async callout options are available.
- Count the expected number of callouts per transaction and whether they are to a single endpoint or multiple distinct endpoints.
- Confirm whether DML occurs before the callout in the transaction — if yes, the callout will throw `System.CalloutException` and must be moved to an async context.

---

## Core Concepts

### Synchronous Callout Governor Limits

Per Apex transaction limits for callouts (from Apex Governor Limits documentation):
- **100 callouts** per transaction (synchronous or asynchronous)
- **120 seconds** maximum timeout per individual callout (configurable via `HttpRequest.setTimeout()`)
- **10 MB** maximum response body size per callout

These limits apply equally to synchronous and asynchronous transactions. An async context (Queueable, Batch, @future) does NOT increase the 100-callout limit — it only changes the execution context.

### DML-Before-Callout Rule

If DML operations (insert, update, delete, upsert) are executed before a callout in the same transaction, the callout throws `System.CalloutException: You have uncommitted work pending`. This is a hard platform restriction.

**The fix:** Move the callout to an async context by enqueuing a Queueable (with `callout=true`) after the DML completes. The Queueable runs in a new transaction where no uncommitted DML exists.

```apex
// Trigger: DML first, then enqueue callout
trigger AccountTrigger on Account (after insert) {
    List<Id> newAccountIds = new List<Id>();
    for (Account a : Trigger.new) newAccountIds.add(a.Id);
    System.enqueueJob(new AccountCalloutQueueable(newAccountIds));
}
```

### @future vs Queueable for Callouts

`@future(callout=true)` is the simplest async callout mechanism but has significant restrictions:
- Cannot pass sObject parameters (only primitives and collections of primitives)
- Cannot chain — no way to enqueue another @future from within a @future
- Cannot be called from Batch Apex

`Queueable with callout=true` is the preferred approach for all new code:
- Accepts any type as a parameter (sObjects, custom objects, Maps)
- Can chain: call `System.enqueueJob()` from within `execute()` to create a callout chain
- Works from Batch Apex `execute()` method (one enqueue per execute call)

### Continuation Class for Long-Running LWC/Aura Callouts

The `Continuation` class enables asynchronous callouts from LWC, Aura, and Visualforce controllers when the callout may take longer than the synchronous response window. Key behavior:
- Maximum timeout per Continuation: 120 seconds
- Maximum 3 callout requests per Continuation instance
- Maximum 3 Continuations chained (3 × 3 requests = 9 total, with new Apex transaction limits resetting on each callback)
- NOT available from triggers, batch Apex, or Queueable — LWC/Aura/VF controllers only

Each callback in a Continuation chain runs in a fresh Apex transaction with its own governor limits.

---

## Common Patterns

### Queueable Callout After DML

**When to use:** A trigger or synchronous Apex class needs to make a callout after performing DML.

**How it works:**
```apex
public class AccountCalloutQueueable implements Queueable, Database.AllowsCallouts {
    private List<Id> accountIds;

    public AccountCalloutQueueable(List<Id> accountIds) {
        this.accountIds = accountIds;
    }

    public void execute(QueueableContext ctx) {
        List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
        for (Account a : accounts) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:MyNamedCredential/api/sync');
            req.setMethod('POST');
            req.setBody(JSON.serialize(a));
            req.setTimeout(30000);
            new Http().send(req);
        }
    }
}
```

**Why not @future:** `@future` cannot accept sObject parameters and cannot chain. Queueable is the modern replacement with no restrictions on parameter types.

### Continuation for LWC Long-Running Callout

**When to use:** An LWC component needs to call an external API that may take 10–30 seconds to respond, and a synchronous Apex call would time out the LWC request.

**How it works:**
```apex
// Apex controller
public class ContinuationController {
    @AuraEnabled
    public static Object invokeLongCall(String endpoint) {
        Continuation c = new Continuation(40); // 40 second timeout
        c.continuationMethod = 'handleResponse';
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('GET');
        c.addHttpRequest(req);
        return c;
    }

    @AuraEnabled
    public static String handleResponse(List<String> labels, Object state) {
        HttpResponse res = Continuation.getResponse(labels[0]);
        return res.getBody();
    }
}
```

**Why not Queueable:** Queueable cannot return a result to an LWC component — it runs fully async with no callback to the UI. Continuation keeps the user session alive and returns the result to the LWC once the callout completes.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Callout needed after DML in same transaction | Queueable with `Database.AllowsCallouts` | Moves callout to new transaction where no uncommitted DML exists |
| Simple fire-and-forget callout, no chaining needed | `@future(callout=true)` | Simpler to implement; acceptable for basic integrations |
| Chained callouts (callout → process result → callout again) | Queueable chain via `System.enqueueJob()` in `execute()` | @future cannot chain; Queueable supports sequential chaining |
| LWC/Aura component needs callout result in UI | Continuation class | Returns result to UI session; supports up to 120s wait |
| Trigger needs to call out to 150+ endpoints (> 100 limit) | Batch Apex + Queueable per batch chunk | Split records into batch chunks; each chunk's Queueable gets 100 fresh callout slots |
| Callout from Batch Apex `execute()` | Queueable with callouts, enqueued once per `execute()` call | Direct callouts in Batch execute are allowed if `Database.AllowsCallouts` is implemented |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner working on this task:

1. Identify the execution context: synchronous trigger, Batch Apex, LWC controller, scheduled Apex, or REST endpoint.
2. Check whether DML occurs before the callout in the same transaction — if yes, the callout must move to a Queueable.
3. Count the required callouts per transaction — if over 100, split into Batch Apex chunks or multiple Queueable chains.
4. Select the appropriate callout pattern from the decision table above.
5. Implement the selected pattern using Named Credentials for endpoint configuration (never hardcode endpoint URLs in Apex).
6. Test governor limit behavior with `Limits.getCallouts()` assertions in test classes, using `HttpCalloutMock` to simulate responses.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] No callout occurs after uncommitted DML in the same transaction
- [ ] `@future(callout=true)` only used for simple fire-and-forget with no sObject parameters
- [ ] Queueable used instead of @future for any chaining, sObject parameters, or Batch context
- [ ] Continuation used only for LWC/Aura/Visualforce — not for triggers or Queueables
- [ ] Total callout count per transaction is under 100 (verified by design — not relying on runtime limit checks)
- [ ] Named Credentials used for all callout endpoints — no hardcoded URLs in Apex

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **Async context does NOT reset the 100-callout limit per Queueable** — Each Queueable execution gets 100 callouts, not per-chain. A chain of 5 Queueables gets 5 × 100 = 500 total callouts but 100 per Queueable execution. Attempting to make 150 callouts in a single Queueable `execute()` still fails.
2. **Continuation is not available outside LWC/Aura/Visualforce** — Calling the Continuation class from a trigger, Batch Apex, or Queueable throws a runtime exception. It is exclusively for controller classes backing UI components.
3. **DML-before-callout error is transaction-scoped, not method-scoped** — The restriction applies across the entire Apex transaction, including any helper methods called earlier in the trigger or class chain. Even if the DML was done by a completely different class called earlier in the transaction, the callout will still fail.
4. **Chained Queueables have a maximum depth of 5 per transaction** — You can call `System.enqueueJob()` from within a Queueable's `execute()` method, but the total chain depth per originating transaction is limited to avoid infinite chains. In sandboxes, only one Queueable level is executed synchronously (others are queued) — this affects testing.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Callout pattern recommendation | Selected pattern (sync, @future, Queueable, Continuation) with rationale |
| Governor limit analysis | Calculated callout count per transaction vs limit |
| Queueable callout implementation | Apex class implementing Queueable + Database.AllowsCallouts |
| Continuation implementation | Apex controller method and callback for LWC async callout |

---

## Related Skills

- named-credentials-setup — configuring Named Credentials for the callout endpoint
- retry-and-backoff-patterns — handling callout failures with retry logic
- apex-queueable-patterns — general Queueable design patterns beyond callouts

Related Skills

mfa-enforcement-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design MFA enforcement: auto-enablement, Salesforce Authenticator rollout, exceptions, service accounts, API-only users, SSO interop, and audit. Trigger keywords: MFA, multi-factor, two-factor, Salesforce Authenticator, MFA exception, MFA SSO, api-only MFA. Does NOT cover: end-user password policies, device-trust posture, or non-Salesforce IdP configuration.

encrypted-field-query-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design SOQL, filters, reporting, and indexes against Shield Platform Encryption fields. Trigger keywords: Shield Platform Encryption, encrypted field query, probabilistic vs deterministic encryption, encrypted SOQL filter, encrypted field index. Does NOT cover: Classic Encryption (deprecated), field-level security policy, or tenant secret key rotation.

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

omnistudio-testing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when testing or validating OmniStudio components — OmniScript preview, Integration Procedure step debugging, DataRaptor field-mapping validation, and end-to-end UTAM-based automation. NOT for Apex unit testing or standard Flow debugging.

omnistudio-error-handling-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing fault behavior across Integration Procedures, DataRaptors, OmniScripts, and FlexCards — error routing, user-facing messaging, retry semantics, and idempotency. Triggers: 'omnistudio error', 'integration procedure fault', 'dataraptor error handling', 'omniscript retry', 'flexcard action failure'. NOT for general Apex exception design or Flow fault paths.

omnistudio-ci-cd-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or implementing CI/CD pipelines for OmniStudio components — DataPack export/import, versioning, environment promotion, and automated deployment. NOT for standard Salesforce metadata CI/CD or Apex-only pipelines.

omnistudio-asynchronous-data-operations

8
from PranavNagrecha/AwesomeSalesforceSkills

Use Integration Procedures queues, DataRaptor Chain, and Remote Actions with async patterns for long-running OmniStudio flows. NOT for simple DataRaptor reads.

omniscript-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniScripts for guided experiences, step structure, branching, save/resume, and the boundary between OmniScript, Integration Procedures, DataRaptors, and custom LWCs. Triggers: 'omniscript design', 'too many steps in omniscript', 'save and resume omniscript', 'branching in omniscript', 'when should this be an integration procedure'. NOT for deep Integration Procedure or DataRaptor design when the guided interaction layer is not the main concern.

integration-procedure-cacheable-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers cache key design, TTL selection, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss/stale. Does NOT cover general IP authoring (see omnistudio-error-handling-patterns) or LWC client-side caching.

flexcard-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing, building, or reviewing OmniStudio FlexCards — including data source selection, card states, actions, conditional visibility, flyout configuration, and child card iteration. Triggers: 'FlexCard', 'card template', 'flyout', 'card action', 'card state', 'data source', 'child card', 'conditional visibility'. NOT for OmniScript design, standalone LWC development, or Apex controller architecture outside the FlexCard context.

dataraptor-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniStudio DataRaptors, especially Extract versus Turbo Extract versus Transform versus Load, field mapping strategy, performance tradeoffs, and when to move work into Integration Procedures or Apex. Triggers: 'DataRaptor Extract', 'Turbo Extract', 'DataRaptor Load', 'DataRaptor Transform', 'OmniStudio data mapping'. NOT for overall OmniScript journey design or Integration Procedure sequencing when the main question is not the DataRaptor shape itself.

wire-service-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.