territory-api-and-assignment

Territory2 API in Apex: programmatic territory member management (UserTerritory2Association, ObjectTerritory2Association), bulk assignment DML, and SOAP-based rule evaluation. NOT for ETM admin setup, territory model design, declarative assignment rules, or Opportunity territory configuration (use admin/enterprise-territory-management).

Best use case

territory-api-and-assignment is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Territory2 API in Apex: programmatic territory member management (UserTerritory2Association, ObjectTerritory2Association), bulk assignment DML, and SOAP-based rule evaluation. NOT for ETM admin setup, territory model design, declarative assignment rules, or Opportunity territory configuration (use admin/enterprise-territory-management).

Teams using territory-api-and-assignment 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/territory-api-and-assignment/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/territory-api-and-assignment/SKILL.md"

Manual Installation

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

How territory-api-and-assignment Compares

Feature / Agentterritory-api-and-assignmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Territory2 API in Apex: programmatic territory member management (UserTerritory2Association, ObjectTerritory2Association), bulk assignment DML, and SOAP-based rule evaluation. NOT for ETM admin setup, territory model design, declarative assignment rules, or Opportunity territory configuration (use admin/enterprise-territory-management).

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

# Territory API and Assignment

This skill activates when a practitioner needs to manage Enterprise Territory Management (ETM) memberships or account territory associations programmatically in Apex — by inserting or deleting `UserTerritory2Association` or `ObjectTerritory2Association` records — or when bulk territory assignment rule evaluation must be triggered from code via a SOAP API callout. Use it for automated onboarding/offboarding of territory reps, data-migration-driven bulk account assignments, and any scenario where territory membership must be driven by code rather than manual setup.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm Enterprise Territory Management (ETM) is enabled in the org and that at least one Territory2Model is in Active state. ETM-related objects are not queryable until the feature is on and a model is active.
- Identify the `Territory2` record IDs you need to work with. You cannot use Territory2 names directly in DML; you must resolve IDs first.
- Determine whether you need to evaluate assignment rules after inserting `ObjectTerritory2Association` rows. Assignment rules do **not** run automatically on DML to `ObjectTerritory2Association` — rule evaluation requires a separate SOAP API callout.
- Know the session ID constraints: `UserInfo.getSessionId()` returns `null` in asynchronous contexts (Batch Apex, Queueable, Scheduled Apex, future methods). Any SOAP callout for rule evaluation must be made in a synchronous user context or from a Platform Event subscriber running in a synchronous context.
- Know the 200-record-per-SOAP-call limit for bulk assignment rule evaluation requests.

---

## Core Concepts

### UserTerritory2Association

`UserTerritory2Association` is the junction object that places a user into a `Territory2`. It supports standard Apex DML (`insert`, `delete`) and can be queried with SOQL. Key fields:

| Field | Type | Description |
|---|---|---|
| `Territory2Id` | ID | The territory the user belongs to |
| `UserId` | ID | The user being assigned |
| `RoleInTerritory2` | String | Picklist value (e.g., `Account Executive`, empty string for no role) |
| `IsActive` | Boolean | Read-only; controlled by the territory model state |

Inserting a `UserTerritory2Association` record is the programmatic equivalent of clicking "Add Users" in Setup > Territories. The insert fires standard field-level security checks but **does not fire Apex triggers** — there is no trigger support on `UserTerritory2Association`.

Duplicate inserts (same `Territory2Id` + `UserId` combination) produce a `DUPLICATE_VALUE` DML error. Use `Database.insert(list, false)` and inspect `SaveResult` errors to handle gracefully.

### ObjectTerritory2Association

`ObjectTerritory2Association` places an Account (the only supported object type) into a `Territory2`. Key fields:

| Field | Type | Description |
|---|---|---|
| `ObjectId` | ID | The Account ID being assigned |
| `Territory2Id` | ID | The territory the account belongs to |
| `AssociationCause` | String | `Territory` (manual/API) or `Territory2RuleAssociation` (rule-based; platform-controlled) |

Critical constraints:
- Standard DML (`insert`, `delete`) is supported.
- **Apex triggers do not fire on `ObjectTerritory2Association`** — not before-insert, not after-insert, nothing. You cannot use triggers to react to territory-account associations.
- Inserting with `AssociationCause = 'Territory'` creates a manual/API-driven association. Associations created by assignment rules use `Territory2RuleAssociation` (read-only from DML; set by the platform after rule evaluation).
- Only Account records can be the `ObjectId`. Associating Contacts, Opportunities, or other objects directly is not supported.

### Assignment Rule Evaluation

ETM assignment rules evaluate which territories an Account belongs to based on account field values (e.g., BillingState, Industry). Rule evaluation is **not triggered automatically** when you insert `ObjectTerritory2Association` rows or update Account fields — you must explicitly request evaluation.

From Apex, rule evaluation requires a SOAP API callout to the Territory2 evaluation service endpoint. The pattern is:

1. Obtain a valid session ID via `UserInfo.getSessionId()` — available only in synchronous user-context Apex.
2. Build an HTTP callout to `/services/Soap/s/<api-version>/` with the correct SOAP envelope.
3. Pass up to 200 Account IDs per request.
4. Parse the SOAP response to determine success or fault.

Because `UserInfo.getSessionId()` returns `null` in async Apex, bulk rule evaluation for large datasets is typically orchestrated via a Platform Event published from a batch `finish` method, consumed by a synchronous trigger or flow that performs the callout.

---

## Common Patterns

### Pattern 1: Bulk User Territory Assignment from Apex

**When to use:** A territory assignment change (e.g., sales rep territory realignment, new hire onboarding) must be applied to many users at once from a migration script, process automation, or an admin tool.

**How it works:**

1. Collect `(Territory2Id, UserId)` pairs to add.
2. Query `UserTerritory2Association` to find which pairs already exist (avoid `DUPLICATE_VALUE`).
3. Build `UserTerritory2Association` sObjects for net-new pairs.
4. Insert with `Database.insert(newAssociations, false)` and inspect `SaveResult` for any errors beyond `DUPLICATE_VALUE`.
5. For removals, query by `Territory2Id IN :ids AND UserId IN :userIds`, then delete.

```apex
public static void assignUsersToTerritory(Id territory2Id, List<Id> userIds) {
    Set<Id> existingUserIds = new Set<Id>();
    for (UserTerritory2Association uta :
             [SELECT UserId FROM UserTerritory2Association
              WHERE Territory2Id = :territory2Id AND UserId IN :userIds]) {
        existingUserIds.add(uta.UserId);
    }

    List<UserTerritory2Association> toInsert = new List<UserTerritory2Association>();
    for (Id uid : userIds) {
        if (!existingUserIds.contains(uid)) {
            toInsert.add(new UserTerritory2Association(
                Territory2Id = territory2Id,
                UserId = uid,
                RoleInTerritory2 = ''
            ));
        }
    }

    if (!toInsert.isEmpty()) {
        List<Database.SaveResult> results = Database.insert(toInsert, false);
        for (Database.SaveResult sr : results) {
            if (!sr.isSuccess()) {
                for (Database.Error err : sr.getErrors()) {
                    if (err.getStatusCode() != StatusCode.DUPLICATE_VALUE) {
                        throw new DmlException('Territory user assignment failed: ' + err.getMessage());
                    }
                }
            }
        }
    }
}
```

**Why not the alternative:** Using `insert` without `Database.insert(list, false)` rolls back the entire transaction on the first duplicate, which is common in realignment scenarios where some users are already assigned.

### Pattern 2: Manual Account-Territory Association via ObjectTerritory2Association

**When to use:** Accounts must be pinned to specific territories independent of assignment rules — for example, a data migration that preserves legacy territory assignments, or a key account that must always appear in an executive territory regardless of field values.

**How it works:**

1. Resolve the `Territory2Id` values for the target territories.
2. For each account, build an `ObjectTerritory2Association` with `AssociationCause = 'Territory'`.
3. Insert with `Database.insert(list, false)` to handle duplicates.
4. Query `ObjectTerritory2Association` to verify the associations are in place.

```apex
public static void pinAccountsToTerritory(Id territory2Id, List<Id> accountIds) {
    Set<Id> existingAccounts = new Set<Id>();
    for (ObjectTerritory2Association ota :
             [SELECT ObjectId FROM ObjectTerritory2Association
              WHERE Territory2Id = :territory2Id AND ObjectId IN :accountIds
              AND AssociationCause = 'Territory']) {
        existingAccounts.add(ota.ObjectId);
    }

    List<ObjectTerritory2Association> toInsert = new List<ObjectTerritory2Association>();
    for (Id acctId : accountIds) {
        if (!existingAccounts.contains(acctId)) {
            toInsert.add(new ObjectTerritory2Association(
                ObjectId = acctId,
                Territory2Id = territory2Id,
                AssociationCause = 'Territory'
            ));
        }
    }

    if (!toInsert.isEmpty()) {
        Database.insert(toInsert, false);
    }
}
```

**Why not the alternative:** Using assignment rules to pin accounts requires crafting a rule that matches a specific field value, which is fragile. Manual API associations via `AssociationCause = 'Territory'` are explicit and survive rule re-evaluation without being overwritten.

### Pattern 3: SOAP-Based Assignment Rule Evaluation

**When to use:** After updating Account fields that affect territory rule criteria, you need to re-evaluate which territories those accounts belong to — from a synchronous user context where a session ID is available.

**How it works:**

1. Collect Account IDs in chunks of at most 200.
2. Build the SOAP envelope with the session ID from `UserInfo.getSessionId()`.
3. Make an HTTP callout to the Territory2 evaluation endpoint.
4. Parse the SOAP response for success or fault elements.

```apex
public static void evaluateRulesForAccounts(List<Id> allAccountIds) {
    String sessionId = UserInfo.getSessionId();
    if (sessionId == null) {
        throw new IllegalStateException(
            'evaluateRules requires a synchronous user context; '
            + 'UserInfo.getSessionId() returned null.'
        );
    }

    String baseEndpoint = URL.getOrgDomainUrl().toExternalForm()
        + '/services/Soap/s/' + System.requestVersion().major + '.0';

    Integer batchSize = 200;
    for (Integer i = 0; i < allAccountIds.size(); i += batchSize) {
        List<Id> chunk = allAccountIds.subList(i, Math.min(i + batchSize, allAccountIds.size()));
        String body = buildSoapEnvelope(chunk);

        HttpRequest req = new HttpRequest();
        req.setEndpoint(baseEndpoint);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
        req.setHeader('SOAPAction', '""');
        req.setHeader('Authorization', 'Bearer ' + sessionId);
        req.setBody(body);

        HttpResponse resp = new Http().send(req);
        if (resp.getStatusCode() != 200) {
            throw new CalloutException('SOAP rule eval failed: ' + resp.getBody());
        }
    }
}
```

**Why not the alternative:** There is no native Apex method to trigger ETM assignment rule evaluation. The SOAP callout is the only supported mechanism from Apex code.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Add a user to a territory programmatically | Insert `UserTerritory2Association` with `Database.insert(list, false)` | Standard DML; pre-query existing assignments to avoid `DUPLICATE_VALUE` |
| Remove a user from a territory | Query `UserTerritory2Association` by `Territory2Id + UserId`, then delete | Standard DML delete; no recalculation hook needed |
| Pin an account to a territory regardless of rules | Insert `ObjectTerritory2Association` with `AssociationCause = 'Territory'` | Manual associations survive rule re-evaluation |
| Re-evaluate which territories an account belongs to after field changes | SOAP API callout in synchronous context, chunked to ≤200 IDs per call | No native Apex method exists; rule evaluation is not automatic |
| React to a new territory-account association in code | Cannot use triggers on `ObjectTerritory2Association`; dispatch Platform Events from the service layer | Apex triggers never fire on `ObjectTerritory2Association` DML |
| Evaluate rules in async Apex (Batch, Queueable) | Publish a Platform Event in `finish`; consume in synchronous subscriber | Session ID unavailable in async context; SOAP callout must be synchronous |
| Bulk assignment for >200 accounts needing rule evaluation | Chunk into batches of 200, call SOAP sequentially in a loop | 200-record limit per SOAP call; 100 callout limit per transaction |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner activating this skill:

1. Confirm ETM is enabled and the Territory2Model is in Active state — query `Territory2Model` and verify `State = 'Active'` before attempting any DML on territory association objects.
2. Resolve Territory2 IDs — query `Territory2` by name, external ID, or hierarchy criteria to obtain the IDs you will use in association records.
3. Check for existing associations — query `UserTerritory2Association` or `ObjectTerritory2Association` before inserting to build a net-new list and avoid `DUPLICATE_VALUE` errors.
4. Perform DML with `Database.insert(list, false)` — inspect `SaveResult` errors, ignore `DUPLICATE_VALUE`, and surface all others.
5. If assignment rule evaluation is needed, confirm you are in a synchronous user context, chunk account IDs into groups of ≤200, and make the SOAP callout using `UserInfo.getSessionId()`.
6. Validate the result — query `ObjectTerritory2Association` or `UserTerritory2Association` after DML to confirm the records were created and spot partial failures.
7. Run `check_territory_api_and_assignment.py` against the Apex source to catch anti-patterns before deploying.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] ETM is enabled and at least one Territory2Model is in Active state in the target org
- [ ] All DML on `UserTerritory2Association` and `ObjectTerritory2Association` uses `Database.insert(list, false)` and inspects `SaveResult`
- [ ] No Apex triggers are relied upon to fire on `ObjectTerritory2Association` DML
- [ ] SOAP callout for rule evaluation is not called from async Apex (Batch, Queueable, Scheduled, future)
- [ ] SOAP requests are chunked to ≤200 account IDs per call
- [ ] `AssociationCause` is explicitly set to `'Territory'` for managed associations (never `'Territory2RuleAssociation'`)
- [ ] Deletion paths exist to remove stale associations when users leave territories or accounts are reassigned
- [ ] Session ID availability is verified before the SOAP callout path is invoked

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **Apex triggers do not fire on ObjectTerritory2Association** — A trigger declared on `ObjectTerritory2Association` deploys without error but its body never executes — not on DML from Apex, not from Setup, not from rule evaluation. Use Platform Events dispatched from the service layer that writes the association.

2. **UserInfo.getSessionId() returns null in all async Apex** — This is the primary cause of SOAP-based rule evaluation failures in Batch Apex `execute`, Queueable `execute`, `@future` methods, and Scheduled Apex `execute`. Any code path that depends on `getSessionId()` for a SOAP callout must run in a synchronous request context.

3. **Assignment rules do not re-evaluate automatically on Account field updates or ObjectTerritory2Association DML** — Updating `BillingState` or any other rule-relevant field on an Account does not trigger rule re-evaluation. You must explicitly call the SOAP evaluation endpoint.

4. **ObjectTerritory2Association DML with AssociationCause = 'Territory2RuleAssociation' throws a runtime error** — This value is platform-controlled and cannot be set via Apex DML. Attempting to insert with this cause throws `DmlException: FIELD_INTEGRITY_EXCEPTION`. Always set `AssociationCause = 'Territory'` explicitly.

5. **200-record-per-SOAP-call limit for rule evaluation** — The SOAP API for territory rule evaluation caps at 200 Account IDs per request. Callers that pass more than 200 IDs receive a SOAP fault. Chunk the list into batches of ≤200 and make sequential requests.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `UserTerritory2Association` DML snippet | Apex pattern for bulk user-to-territory assignment with duplicate handling |
| `ObjectTerritory2Association` DML snippet | Apex pattern for pinning accounts to territories with `AssociationCause = 'Territory'` |
| SOAP rule evaluation wrapper | Apex HTTP callout pattern for triggering ETM assignment rule evaluation (synchronous context only) |
| `check_territory_api_and_assignment.py` report | Static analysis of Apex classes for territory API anti-patterns |

---

## Related Skills

- admin/enterprise-territory-management — Use for ETM admin setup, territory model design, territory types, assignment rule configuration, and Opportunity territory assignment; this skill handles only the programmatic Apex API layer
- apex-managed-sharing — Use when territory membership drives record sharing via `__Share` objects; the two patterns are often combined
- callout-and-dml-transaction-boundaries — Use when the SOAP callout for rule evaluation must be combined with DML in the same transaction
- async-apex — Use when territory assignment operations exceed synchronous DML limits and must be moved to batch or queueable context (excluding SOAP callout steps)

Related Skills

territory-data-alignment

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when querying, migrating, auditing, or maintaining account-to-territory and user-to-territory data in a Salesforce Enterprise Territory Management (ETM) org. Covers ObjectTerritory2Association DML via API, UserTerritory2Association data, Territory2ModelHistory audit queries, the Track Territory Assignment History feature (GA Spring '25), coverage analysis SOQL with AssociationCause filter, and data migration of territory associations between models. Trigger keywords: ObjectTerritory2Association, UserTerritory2Association, territory assignment data, territory coverage analysis, territory association bulk load, ETM data migration, track territory assignment history. NOT for ETM configuration or territory model setup — use territory-design-requirements for that. NOT for territory rule creation or activation — those are admin concerns.

fsl-territory-data-setup

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when bulk loading Service Territory data: boundary polygons, ServiceTerritoryMember assignments, OperatingHours, TimeSlots, and territory hierarchy setup. Trigger keywords: service territory bulk load, KML polygon import FSL, ServiceTerritoryMember migration, OperatingHours data setup, PolygonUtils Apex. NOT for Enterprise Territory Management (ETM/Account Territories), admin-level territory configuration UI, or scheduling policy setup.

territory-design-requirements

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when gathering or evaluating requirements for a Salesforce Enterprise Territory Management (ETM) territory design: alignment criteria, coverage model selection, assignment rule logic, geographic considerations, hierarchy depth and breadth, and user-to-territory ratios. Trigger keywords: territory design, territory alignment, territory model requirements, sales coverage model, territory criteria, geographic territory, named account territory, overlay territory. NOT for ETM configuration or setup steps — use enterprise-territory-management for that. NOT for role hierarchy design — use sharing-and-visibility.

fsl-service-territory-setup

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill to configure Field Service Lightning service territories — including territory types, operating hours, time slots, territory hierarchy, member types, and polygon boundaries. NOT for ETM territories (Territory2 / Enterprise Territory Management in Sales Cloud), and NOT for Salesforce Scheduler territories.

enterprise-territory-management

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring or troubleshooting Salesforce Enterprise Territory Management (ETM): territory models, territory types, territory hierarchies, account assignment rules, opportunity territory assignment, and forecast by territory. Trigger keywords: territory model, territory hierarchy, ETM, assign accounts to territories, territory forecast, territory activation. NOT for role hierarchy (use sharing-and-visibility). NOT for Legacy/Original Territory Management (pre-ETM).

assignment-rules

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring or troubleshooting Lead or Case assignment rules in Salesforce: creating rule entries, setting filter criteria, assigning records to users or queues, understanding when rules run, and implementing round-robin patterns with Apex. Trigger keywords: lead assignment, case assignment, assignment rule, queue assignment, auto-assign. NOT for approval process routing (use approval-processes). NOT for Omni-Channel routing or Skills-Based Routing (those are separate routing engines). NOT for Flow-triggered field updates unrelated to ownership.

xss-and-injection-prevention

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).

transaction-security-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.

sso-saml-troubleshooting

8
from PranavNagrecha/AwesomeSalesforceSkills

Diagnosing broken SAML SSO into Salesforce — IdP-initiated vs SP-initiated flows, signing-certificate validity / expiry, NameID format mismatches, RelayState handling, audience / entityId / issuer mismatches, clock skew, the SAML Assertion Validator in Setup, the Login History debug log, and the My Domain prerequisite for SSO. Covers the standard diagnostic loop: read the SAML response, identify which check failed, fix at the IdP or SP. NOT for OAuth / OpenID Connect SSO (see security/oauth-openid-troubleshooting), NOT for setting up SSO from scratch (see security/sso-saml-setup).

shield-kms-byok-setup

8
from PranavNagrecha/AwesomeSalesforceSkills

Configure Shield Platform Encryption with customer-supplied (BYOK) or customer-held (Cache-Only Key Service) tenant secrets, rotate them, and recover. NOT for Classic Encryption or field masking.

shield-event-log-retention-strategy

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing Salesforce Shield Event Monitoring retention, SIEM routing, and storage-tier strategy — which event types to keep, for how long, where, and how to answer audit queries across hot/warm/cold tiers. Triggers: 'shield event log retention', 'route event monitoring to splunk', 'how long to keep login history', 'siem salesforce integration', 'event monitoring storage tier'. NOT for enabling Shield (see salesforce-shield-deployment).