fsl-apex-extensions

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

Best use case

fsl-apex-extensions is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

Teams using fsl-apex-extensions should expect a more consistent output, faster repeated execution, less prompt rewriting, better workflow continuity with your supporting tools.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.
  • You already have the supporting tools or dependencies needed by this skill.

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/fsl-apex-extensions/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/fsl-apex-extensions/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/fsl-apex-extensions/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How fsl-apex-extensions Compares

Feature / Agentfsl-apex-extensionsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.

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

# FSL Apex Extensions

This skill activates when a practitioner needs to write Apex that directly calls the `FSL` managed-package namespace to book, schedule, grade, or optimize Field Service Lightning service appointments. It covers the governor constraint that prevents mixing uncommitted DML with FSL scheduling calls, the transaction-splitting patterns required to work around it, and the additional requirements for the OAAS optimization API.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm the FSL managed package is installed and the `FSL` namespace is resolvable in the target org. The classes live in the managed package, not the Salesforce core platform.
- Identify whether the ServiceAppointment has a `ServiceTerritory` assigned — `ScheduleService.schedule()` will throw a runtime exception without it.
- Confirm which scheduling policy record will be passed; it must exist as a `FSL__Scheduling_Policy__c` record in the org.
- Determine whether any DML has been executed earlier in the same transaction that will call `GetSlots()` or `schedule()`. If so, a transaction boundary is mandatory.
- For OAAS calls, confirm the org has the Enhanced Scheduling and Optimization add-on license — OAAS is not available on the base FSL license.

---

## Core Concepts

### FSL Apex Namespace Classes

The Field Service managed package exposes four primary Apex classes in the `FSL` namespace:

- **`FSL.AppointmentBookingService`** — provides `GetSlots(serviceAppointmentId, schedulingPolicyId, operatingHoursId)`, which returns `List<FSL.AppointmentBookingSlot>`. Each slot contains a `Interval_Start__c` and `Interval_End__c` datetime and a grade score.
- **`FSL.ScheduleService`** — provides `schedule(serviceAppointmentId, schedulingPolicyId, serviceResourceId, startDateTime, endDateTime)` to commit a specific time slot and resource assignment to the service appointment.
- **`FSL.GradeSlotsService`** — grades a provided list of time slots against the scheduling policy without returning new available windows. Used when the caller already has candidate slots and wants to rank them.
- **`FSL.OAAS`** (Optimization As A Service) — triggers asynchronous optimization runs: global, in-day, resource schedule, or reshuffle. Returns a job ID. Requires the Enhanced Scheduling and Optimization add-on.

### The Callout-DML Transaction Constraint

Both `FSL.AppointmentBookingService.GetSlots()` and `FSL.ScheduleService.schedule()` internally perform HTTP callouts to the FSL routing engine to calculate travel time (SLR — Simple Linear Routing — or P2P routing depending on configuration). Salesforce enforces a platform rule: **a callout cannot be made after uncommitted DML in the same transaction**. This produces the error:

> `System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out`

This is the most common production failure when integrating FSL scheduling into Apex flows. The trigger, flow-invoked action, or REST handler that inserts or updates records before calling GetSlots/schedule will hit this wall immediately.

### Transaction-Splitting Patterns

Because the callout constraint cannot be bypassed, the solution is always to **separate the DML transaction from the scheduling API transaction**:

1. **Queueable Apex** — insert/update the ServiceAppointment in the synchronous transaction, then enqueue a `Queueable` class that calls `GetSlots()` or `schedule()` in its own fresh transaction. The Queueable receives the record IDs as constructor parameters.
2. **`Database.Batchable` with `batchSize=1`** — a batch job with batch size set to 1 ensures each `execute()` call processes one ServiceAppointment in isolation, with no prior uncommitted DML. The `start()` method queries the appointments to schedule.
3. **`@future` method** — a static `@future(callout=true)` method can be called from a trigger or synchronous context and runs in a fresh transaction. Drawback: `@future` cannot be called from batch context and has tighter limits.

The Queueable pattern is the most flexible for interactive use; the Batch pattern is preferred for bulk scheduling runs.

### OAAS and License Requirements

`FSL.OAAS` methods (`FSL.OAAS.optimizeBySchedulingPolicy()`, `FSL.OAAS.inDay()`, etc.) trigger the FSL optimization engine asynchronously. The return value is a String job ID that can be polled. OAAS requires:

- The **Enhanced Scheduling and Optimization** add-on (a separate SKU from base FSL).
- A valid `FSL__Scheduling_Policy__c` record with optimization settings configured.
- ServiceTerritory in scope for the optimization run.

Calling OAAS without the add-on throws a managed-package exception at runtime — it does not fail gracefully.

---

## Common Patterns

### Pattern 1: Queueable Appointment Booking (GetSlots then Schedule)

**When to use:** A user action (screen flow, LWC, trigger) creates or updates a ServiceAppointment and immediately needs to book it into the first available slot.

**How it works:**

1. The synchronous transaction inserts/updates the `ServiceAppointment` and commits (transaction ends — DML is committed).
2. A Queueable is enqueued, receiving the ServiceAppointment ID, scheduling policy ID, and operating hours ID.
3. In the Queueable's `execute()` method, call `FSL.AppointmentBookingService.GetSlots()` — no prior DML in this transaction, so the internal callout succeeds.
4. Select the desired slot (e.g., highest grade or earliest start).
5. Call `FSL.ScheduleService.schedule()` with the selected slot's resource, start, and end datetimes.

**Why not the alternative:** Calling `GetSlots()` immediately after the `insert ServiceAppointment` statement in the same transaction raises `CalloutException: You have uncommitted work pending`.

### Pattern 2: Bulk Scheduling via Batch with batchSize=1

**When to use:** A nightly or on-demand job must schedule hundreds of unscheduled ServiceAppointments against the FSL engine without manual intervention.

**How it works:**

1. A `Database.Batchable<SObject>` queries all ServiceAppointments with status `None` (or a custom "Pending Scheduling" status).
2. The batch is invoked with `Database.executeBatch(new ScheduleBatch(), 1)` — batch size of 1 is mandatory.
3. Each `execute()` receives a single appointment. The execute scope has no prior uncommitted DML, so calling `FSL.ScheduleService.schedule()` directly is safe.
4. On failure, log the ServiceAppointment ID and error message; do not re-throw so the batch continues.

**Why batch size must be 1:** Each `execute()` call begins a fresh transaction, but if two appointments are in the same batch chunk, the first appointment's `schedule()` call commits travel-calculation state that conflicts with the second. Setting size to 1 guarantees a clean transaction per appointment.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Single appointment booked interactively (trigger, flow-invoked Apex) | Queueable | Clean transaction boundary; supports chaining; works from trigger and invocable contexts |
| Bulk scheduling of 50–500 appointments overnight | Batch with batchSize=1 | Governor-safe for large volumes; built-in retry on chunk failure |
| Need to show available slots to user before booking | GetSlots in Queueable, return via Platform Event or custom object | GetSlots callout cannot run synchronously in same DML transaction |
| Trigger optimization after manual schedule changes | FSL.OAAS | Purpose-built for optimization; returns job ID for monitoring |
| Single appointment, lightweight integration, no chaining needed | @future(callout=true) | Simplest pattern; acceptable when batch/Queueable overhead is undesirable and call is from synchronous non-batch context |
| Grade candidate slots already in hand | FSL.GradeSlotsService | Avoids a full GetSlots call when slot candidates are already known |

---

## Recommended Workflow

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

1. **Verify prerequisites** — confirm FSL package is installed, the ServiceAppointment has a ServiceTerritory assigned, and a valid scheduling policy record exists. For OAAS, confirm the Enhanced Scheduling and Optimization add-on is present.
2. **Identify the transaction boundary** — determine where DML occurs relative to the planned FSL API call. If any insert/update/delete/upsert runs before the FSL call in the same transaction, a transaction-splitting pattern is required.
3. **Choose the splitting pattern** — use Queueable for interactive single-record scenarios; use Batch with batchSize=1 for bulk jobs; use `@future(callout=true)` for simple non-batch scenarios without chaining needs.
4. **Implement GetSlots (if booking)** — call `FSL.AppointmentBookingService.GetSlots(saId, policyId, ohId)` in the clean transaction. Null-check the returned list before proceeding — if no slots are available, the list is empty (not null, but may be empty).
5. **Select and confirm slot** — pick the slot based on business rules (grade, earliest time, resource preference). Call `FSL.ScheduleService.schedule()` with the chosen slot's resource ID, start, and end datetimes.
6. **Handle OAAS separately** — if triggering optimization, call the appropriate `FSL.OAAS` method in its own Queueable or invocable action, capture the returned job ID, and store it for monitoring or audit.
7. **Test governor compliance** — run unit tests with `@isTest` that mock the FSL callouts using `HttpCalloutMock` or verify that the Queueable/Batch pattern is invoked rather than the scheduling method being called directly in a DML-preceded transaction.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] ServiceAppointment has ServiceTerritory assigned before any schedule() call
- [ ] No uncommitted DML precedes FSL.AppointmentBookingService.GetSlots() or FSL.ScheduleService.schedule() in the same transaction
- [ ] Queueable, Batch (batchSize=1), or @future pattern is in place if a transaction boundary is needed
- [ ] Batch size is explicitly set to 1 when using Database.Batchable for FSL scheduling
- [ ] OAAS calls are gated on Enhanced Scheduling and Optimization add-on availability (or org type check)
- [ ] Empty slot list from GetSlots() is handled gracefully — no NullPointerException on slot selection
- [ ] Unit tests mock FSL callouts and verify the asynchronous dispatch path is exercised

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **Uncommitted DML before FSL callout** — `GetSlots()` and `schedule()` both make internal HTTP callouts for travel routing. Any uncommitted DML in the same transaction triggers `System.CalloutException: You have uncommitted work pending`. This is the #1 cause of FSL scheduling Apex failures.
2. **Missing ServiceTerritory on ServiceAppointment** — `ScheduleService.schedule()` requires a ServiceTerritory on the appointment. If the territory is null, the call throws a managed-package runtime exception, not a null pointer — the error message is not always obvious about the root cause.
3. **Batch size greater than 1 for FSL scheduling** — setting batchSize > 1 in a batch class that calls FSL APIs causes the second record's callout to fail with the uncommitted work error because the first record's scheduling call leaves uncommitted state in the chunk. Always use batchSize=1.
4. **OAAS requires add-on license** — `FSL.OAAS` throws a managed-package exception if the Enhanced Scheduling and Optimization add-on is not active. This is not a compile-time error; it fails silently or with an opaque exception at runtime in orgs without the add-on.
5. **GetSlots returns empty list, not null** — when no slots are available (e.g., no resources in territory, policy blocks all windows), `GetSlots()` returns an empty `List<FSL.AppointmentBookingSlot>` rather than null. Code that assumes a non-empty list without checking will throw `ListException: List index out of bounds: 0`.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Queueable Apex class | Transaction-safe wrapper for GetSlots + schedule — passes SA ID and policy ID as constructor parameters, calls FSL API in a clean transaction |
| Batch Apex class | Bulk scheduling batch with batchSize=1, queries unscheduled SAs and calls ScheduleService per record |
| OAAS invocation snippet | Apex that calls FSL.OAAS and captures the returned job ID for monitoring |
| Review checklist | Pre-deploy verification list covering transaction boundaries, required fields, and license requirements |

---

## Related Skills

- `admin/fsl-scheduling-policies` — scheduling policy configuration that feeds the policyId parameter into all FSL API calls
- `admin/fsl-service-territory-setup` — ServiceTerritory setup required before ScheduleService.schedule() can succeed
- `admin/fsl-service-resource-setup` — ServiceResource configuration required for the resourceId parameter

Related Skills

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.

industries-api-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when integrating with Salesforce Industries-specific API layers — Insurance Policy Business Connect API, Communications Cloud TM Forum Open APIs (TMF679, TMF680, etc.), Energy and Utilities Update Asset Status API, and Service Process Studio Connect APIs. Trigger keywords: Insurance policy issuance API, endorsement API, TMF679, Communications Cloud REST API, Update Asset Status, Service Process API, InsurancePolicy Connect API, sfiEnergy, industry-specific REST endpoint. NOT for standard Salesforce REST API, SOAP API, Bulk API, or platform event integration unrelated to an industry vertical.

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-apex-defined-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.

vscode-salesforce-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when setting up, configuring, or troubleshooting the Salesforce Extensions for VS Code: Apex Language Server activation, deploy-on-save behavior, Apex debugging, org authorization, and workspace project structure. NOT for Salesforce CLI command reference (use sf-cli-and-sfdx-essentials), scratch org definition files (use scratch-org-management), or CI/CD pipeline configuration.

scheduled-apex-failure-detection-and-monitoring

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).

platform-events-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when publishing or subscribing to Salesforce Platform Events from Apex, comparing Platform Events with Change Data Capture, or designing event-triggered error handling and monitoring. Triggers: 'EventBus.publish', 'platform event trigger', 'CDC vs Platform Events', 'replay ID', 'high-volume event'. NOT for Flow-only publish/subscribe automation.

health-cloud-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Health Cloud via Apex: implementing HealthCloudGA managed-package interfaces, automating care plan lifecycle hooks, processing referrals using Industries Common Components invocable actions, or enforcing HIPAA-compliant logging governance for clinical Apex code. Trigger keywords: CarePlanProcessorCallback, HealthCloudGA namespace, ReferralRequest, ReferralResponse, care plan invocable actions, clinical Apex extension, Health Cloud Apex API. NOT for standard Apex triggers or generic Apex development unrelated to Health Cloud managed-package extension points.

fsl-mobile-app-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building custom LWC Quick Actions, Global Actions, deep links, or offline data extensions for the Salesforce Field Service (FSL) native mobile app. Trigger keywords: FSL mobile extension, LWC action FSL, field service deep link, offline custom action, FSL mobile toolkit. NOT for LWC in standard Salesforce mobile app or Lightning Experience.

fsc-apex-extensions

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when extending Financial Services Cloud (FSC) behavior through Apex: customizing financial rollup recalculation, disabling built-in FSC triggers to write custom trigger logic, implementing Compliant Data Sharing (CDS) participant/role integrations, or building custom FSC action handlers. NOT for standard Apex unrelated to the FSC managed package, standard Salesforce sharing rules, or configuring FSC rollups through the Admin UI — use the admin/financial-account-setup skill for declarative rollup setup.