IAccountManagementGET Apex Interface

The IAccountManagementGET Apex interface provides extensibility for GET operations through Apex pre- and post-hooks and GraphQL query customization. Implementations can validate incoming requests, modify query structures, and refine the response to support business-specific retrieval rules while maintaining TMF-compliant behavior.

This interface supports the following hooks.

Account Retrieve Operation Use Cases 

Use CaseHook(s)DescriptionExample ScenariosBenefit
Role-Based Data FilteringapplyCustomValidations, handlePostOperationEnforces access rules before execution and filters sensitive fields or records after retrieval. Access can be evaluated using the caller’s role, region, business unit, channel, or request context.- Restrict access to strategic or high-value Accounts
- Hide financial, risk, or compliance fields from frontline users
- Enforce regional or business-unit access boundaries
- Remove sensitive fields from partner-facing responses
Enforces data governance and security through both pre-query authorization and post-query response filtering.
Custom Field EnrichmenthandlePostOperationAdds calculated, derived, or externally sourced fields to the response without changing the base TMF632 schema.- Add an Account health score
- Flag preferred, strategic, or high-risk Accounts
- Include aggregated revenue or usage insights
- Attach hierarchy or entitlement information
Delivers richer and more contextual Account data without requiring changes to the shared schema.
Dynamic Query CustomizationcustomiseGraphQLQueryModifies the GraphQL query before execution by adding fields, relationships, filters, or server-side constraints according to the client or request context.- Return a minimal field set to mobile clients
- Include hierarchy or entitlement fields for portals
- Add organization-specific Account fields
- Apply tenant, region, status, or channel filters automatically
Supports multiple client requirements through a single API and reduces the need for client-specific endpoints or API versions.
Business-Rule ValidationapplyCustomValidationsEvaluates organization-specific business, eligibility, and access rules before the GraphQL query executes. Invalid requests are rejected early with a clear error response.- Require an active Account before returning related services
- Reject requests from unauthorized or unverified channels
- Enforce lifecycle-state restrictions
- Validate tenant or business-unit ownership
Rejects invalid requests early, maintains business consistency, and avoids unnecessary downstream processing.
External-ID ResolutiontransformRequestResolves a business key, legacy identifier, or external-system reference to the canonical Salesforce Account ID and adds it to the shared request context as { id }.- Retrieve an Account by customer number
- Resolve an ERP or billing-system identifier
- Map a partner-system key to a Salesforce Account ID
- Translate a migrated legacy identifier
Allows clients to fetch Accounts by meaningful business keys instead of requiring a Salesforce ID.

transformRequest 

This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.

Hook Method 

Map<String, Object> transformRequest(Map<String, Object> context)

Sample Apex Implementation 

Resolve an external Account Number to a Billing Account ID.

1global class AccountMgmtTransformRequestExt implements comms_apex_ext.IAccountManagementGET {
2    global static Map<String, Object> transformRequest(Map<String, Object> context) {
3        if (context == null) {
4            return context;
5        }
6
7
8        Object idObj = context.get('id');
9        if (idObj == null || !(idObj instanceof String)) {
10            return context;
11        }
12        String idValue = (String) idObj;
13
14
15        // Already a Salesforce id (15 or 18 chars) — leave it alone.
16        if (idValue.length() == 15 || idValue.length() == 18) {
17            return context;
18        }
19
20
21        // Otherwise treat it as an external billing-account number and resolve it.
22        List<BillingAccount> accounts = [
23            SELECT Id
24            FROM BillingAccount
25            WHERE ExtlBillAccountNumber__c = :idValue
26            LIMIT 1
27        ];
28        if (!accounts.isEmpty()) {
29            context.put('id', accounts[0].Id);
30        }
31
32
33        return context;
34    }
35
36    // ... other hooks return null / empty map ...
37}

applyCustomValidations 

This hook validates custom business logic before retrieving customer record. If validation fails, it rejects the request and returns an error response.

The handler processes return values as follows.

Success:

  • Return a map with validationStatus set to “pass” (case-insensitive).
  • The API request continues normally.
  • Other fields in the map are logged but not used.

Failure:

  • Return a map with validationStatus set to “fail” (case-insensitive).
  • The API request is terminated immediately.
  • Raise a ValidationException containing
    • message: value from validationMessage key (or “Validation failed” by default)
    • details: value from validationDetails key (optional)
  • The client receives an HTTP 400 error response.

Hook Method 

Map<String, Object> applyCustomValidations(Map<String, Object> context)

Sample Apex Implementation 

Restrict billing account visibility for customer service representatives.

1global class AccountMgmtAccessValidationExt implements comms_apex_ext.IAccountManagementGET {
2
3    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
4        String billingAccountId = (String) context.get('id');
5        String userId = UserInfo.getUserId();
6        Map<String, Object> validationResult = new Map<String, Object>();
7        try {
8            User currentUser = [SELECT Id, Profile.Name FROM User WHERE Id = :userId LIMIT 1];
9
10
11            if ('Customer Service Rep'.equals(currentUser.Profile.Name) && billingAccountId != null) {
12                BillingAccount ba = [
13                    SELECT Id, PrimaryBillAddrCountry
14                    FROM BillingAccount
15                    WHERE Id = :billingAccountId
16                    LIMIT 1
17                ];
18
19                if ('US'.equals(ba.PrimaryBillAddrCountry)) {
20                    validationResult.put('validationStatus', 'PASS');
21                    validationResult.put('validationMessage', 'User has access to this billing account');
22                } else {
23                    validationResult.put('validationStatus', 'FAIL');
24                    validationResult.put('validationMessage', 'User does not have access to billing accounts in this region');
25                    validationResult.put('validationDetails', new Map<String, Object>{
26                        'denialReason'  => 'REGION_RESTRICTION',
27                        'accountRegion' => ba.PrimaryBillAddrCountry
28                    });
29                }
30            } else {
31                validationResult.put('validationStatus', 'PASS');
32                validationResult.put('validationMessage', 'User profile has unrestricted access');
33            }
34        } catch (Exception e) {
35            validationResult.put('validationStatus', 'FAIL');
36            validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
37            validationResult.put('validationDetails', new Map<String, Object>{
38                'errorType' => e.getTypeName()
39            });
40        }
41
42
43        return validationResult;
44    }
45    // ... other hooks return null / empty map ...
46}

customiseGraphQLQuery 

This hook modifies the GraphQL query before execution to add fields, filters, or transformations.

Hook Method 

Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)

Sample Apex Implementation 

Add custom fields, filter and ordering.

1global class AccountMgmtQueryCustomizeExt implements comms_apex_ext.IAccountManagementGET {
2
3    global static Map<String, Object> customiseGraphQLQuery(
4        Map<String, Object> querySpec,
5        Map<String, Object> context
6    ) {
7        return new Map<String, Object>{
8            'nodes' => new List<Object>{
9                new Map<String, Object>{
10                    'path'      => 'billingAccount',
11                    'addFields' => new List<String>{ 'CustomField__c', 'ExtlBillFrequency' },
12                    'addFilters' => new Map<String, Object>{
13                        'clearExistingFilters' => false,
14                        'filterGraphQlSnippet' => '{ Status: { eq: \"Active\" } }'
15                    },
16                    'orderBy' => new Map<String, Object>{
17                        'clearExistingOrderBy' => true,
18                        'orderGraphQlSnippet'  => '{ Name: { order: ASC } }'
19                    }
20                }
21            }
22        };
23    }
24    // ... other hooks return null / empty map ...
25}

handlePostOperation 

This hook post-processes and transforms the API response after data retrieval.

Hook Method 

Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)

Sample Apex Implementation 

Enrich responses with calculated fields (single and list-aware).

1global class AccountMgmtResponseEnrichExt implements comms_apex_ext.IAccountManagementGET {
2    global static Map<String, Object> handlePostOperation(
3        Map<String, Object> graphQLResult,
4        Map<String, Object> tmfResponse,
5        Map<String, Object> context
6    ) {
7        if (tmfResponse == null) {
8            return null;
9        }
10
11        if (tmfResponse.containsKey('recordList')) {
12            // LIST response
13            List<Object> recordList = (List<Object>) tmfResponse.get('recordList');
14            if (recordList == null || recordList.isEmpty()) {
15                return null;
16            }
17            List<Object> updatedRecordList = new List<Object>();
18            for (Object recordObj : recordList) {
19                Map<String, Object> record = new Map<String, Object>((Map<String, Object>) recordObj);
20                enrich(record);
21                updatedRecordList.add(record);
22            }
23            Map<String, Object> updatedPayload = new Map<String, Object>(tmfResponse);
24            updatedPayload.put('recordList', updatedRecordList);
25            return new Map<String, Object>{
26                'payloadOverwritten' => true,
27                'updatedPayload'     => updatedPayload
28            };
29        } else {
30            // SINGLE response
31            Map<String, Object> updatedPayload = new Map<String, Object>(tmfResponse);
32            enrich(updatedPayload);
33            return new Map<String, Object>{
34                'payloadOverwritten' => true,
35                'updatedPayload'     => updatedPayload
36            };
37        }
38    }
39    private static void enrich(Map<String, Object> record) {
40        record.put('paymentStatus', 'Paid');
41        record.put('retrievedBy', UserInfo.getName());
42        record.put('lastRetrievedAt',
43            DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
44    }
45    // ... other hooks return null / empty map ...
46}

Full Implementation Example 

Here’s a complete sample Apex implementation using all supported hooks.

1/**
2 * Complete TMF666 Account Management GET API Extension
3 * Demonstrates transformRequest, applyCustomValidations, customiseGraphQLQuery, and handlePostOperation.
4 */
5global class AccountManagementGETExtension implements comms_apex_ext.IAccountManagementGET {
6
7
8    /** HOOK 1: Resolve external id → Salesforce id (pass-through here) */
9    global static Map<String, Object> transformRequest(Map<String, Object> context) {
10        return context;
11    }
12
13
14    /** HOOK 2: Not applicable for Account Management */
15    global static Map<String, Boolean> configureDefaultValidations(
16        Map<String, Boolean> defaultValidationConfiguration,
17        Map<String, Object> context
18    ) {
19        return new Map<String, Boolean>();
20    }
21
22
23    /** HOOK 3: Role-based access control */
24    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
25        String billingAccountId = (String) context.get('id');
26        String userId = UserInfo.getUserId();
27        Map<String, Object> result = new Map<String, Object>();
28
29
30        try {
31            User currentUser = [SELECT Id, Profile.Name FROM User WHERE Id = :userId LIMIT 1];
32            if ('Customer Service Rep'.equals(currentUser.Profile.Name) && billingAccountId != null) {
33                BillingAccount ba = [
34                    SELECT Id, PrimaryBillAddrCountry FROM BillingAccount
35                    WHERE Id = :billingAccountId LIMIT 1
36                ];
37                if ('US'.equals(ba.PrimaryBillAddrCountry)) {
38                    result.put('validationStatus', 'PASS');
39                } else {
40                    result.put('validationStatus', 'FAIL');
41                    result.put('validationMessage', 'Access denied - region not allowed');
42                    result.put('validationDetails', new Map<String, Object>{ 'denialReason' => 'REGION_RESTRICTION' });
43                }
44            } else {
45                result.put('validationStatus', 'PASS');
46            }
47        } catch (Exception e) {
48            result.put('validationStatus', 'FAIL');
49            result.put('validationMessage', 'Validation error: ' + e.getMessage());
50        }
51        return result;
52    }
53
54
55    /** HOOK 4: Add fields + filter */
56    global static Map<String, Object> customiseGraphQLQuery(
57        Map<String, Object> querySpec,
58        Map<String, Object> context
59    ) {
60        return new Map<String, Object>{
61            'nodes' => new List<Object>{
62                new Map<String, Object>{
63                    'path'      => 'billingAccount',
64                    'addFields' => new List<String>{ 'CustomField__c' },
65                    'addFilters' => new Map<String, Object>{
66                        'clearExistingFilters' => false,
67                        'filterGraphQlSnippet' => '{ Status: { eq: \"Active\" } }'
68                    }
69                }
70            }
71        };
72    }
73
74
75    /** HOOK 5: Enrich the response */
76    global static Map<String, Object> handlePostOperation(
77        Map<String, Object> graphQLResult,
78        Map<String, Object> tmfResponse,
79        Map<String, Object> context
80    ) {
81        if (tmfResponse == null) {
82            return null;
83        }
84        if (tmfResponse.containsKey('recordList')) {
85            List<Object> recordList = (List<Object>) tmfResponse.get('recordList');
86            if (recordList == null || recordList.isEmpty()) {
87                return null;
88            }
89            List<Object> updated = new List<Object>();
90            for (Object recordObj : recordList) {
91                Map<String, Object> record = new Map<String, Object>((Map<String, Object>) recordObj);
92                record.put('source', 'apex_extensibility');
93                updated.add(record);
94            }
95            Map<String, Object> updatedPayload = new Map<String, Object>(tmfResponse);
96            updatedPayload.put('recordList', updated);
97            return new Map<String, Object>{ 'payloadOverwritten' => true, 'updatedPayload' => updatedPayload };
98        } else {
99            tmfResponse.put('source', 'apex_extensibility');
100            return new Map<String, Object>{ 'payloadOverwritten' => true, 'updatedPayload' => tmfResponse };
101        }
102    }
103}

Response Structure 

Single Billing Account Response 

1{
2  "id": "15ixx0000004C92AAE",
3  "href": "/connect/comms/accountmanagement/v5/billingaccount/15ixx0000004C92AAE",
4  "name": "UpdatedAcct001",
5  "state": "Active",
6  "accountType": "Residential",
7  "lastUpdate": "2026-04-09T05:25:10.000Z",
8  "billStructure": {
9    "format": { "name": "Detailed", "@type": "BillFormat" },
10    "presentationMedia": [
11      { "name": "Email", "@type": "BillPresentationMedia" }
12    ],
13    "cycleSpecification": { "frequency": "Monthly", "@type": "BillingCycleSpecification" },
14    "@type": "BillStructure"
15  },
16  "taxExemption": [
17    {
18      "certificateNumber": "TAX-EXEMPT-001",
19      "validFor": {
20        "startDateTime": "2025-01-01T00:00:00.000Z",
21        "endDateTime": "2026-12-31T00:00:00.000Z"
22      },
23      "reason": "Government",
24      "@type": "TaxExemption"
25    }
26  ],
27  "relatedParty": [
28    {
29      "role": "Financially Responsible Party",
30      "partyOrPartyRole": {
31        "id": "001xx000003HJdrAAG",
32        "href": "/services/data/v68.0/sobjects/Account/001xx000003HJdrAAG",
33        "name": "Acme Corp",
34        "@type": "PartyRef",
35        "@referredType": "Organization"
36      },
37      "@type": "RelatedPartyRefOrRelatedPartyRoleRef"
38    }
39  ],
40  "contact": [
41    {
42      "id": "003xx000004WpZwAAK",
43      "contactName": "John Doe",
44      "contactType": "Bill To",
45      "validFor": { "endDateTime": "2099-12-31T00:00:00.000Z" },
46      "contactMedium": [
47        {
48          "id": "15ixx0000004C92AAE-cm-1",
49          "contactType": "billing email",
50          "preferred": true,
51          "emailAddress": "billing@acme.com",
52          "@type": "EmailContactMedium"
53        },
54        {
55          "id": "15ixx0000004C92AAE-cm-2",
56          "contactType": "billing address",
57          "preferred": true,
58          "city": "San Francisco",
59          "country": "US",
60          "postCode": "94105",
61          "stateOrProvince": "CA",
62          "street1": "123 Main St",
63          "@type": "GeographicAddressContactMedium"
64        }
65      ],
66      "@type": "Contact"
67    }
68  ],
69  "@type": "BillingAccount"

List Response (GraphQL) 

Default (without pageInfo) — a bare JSON array of Individual objects:

1[
2  {
3    "@type": "BillingAccount",
4    "lastUpdate": "2026-04-09T10:06:37.000Z",
5    "name": "Home Account",
6    "id": "15ixx0000004CAeAAM",
7    "href": "/connect/comms/accountmanagement/v5/billingaccount/15ixx0000004CAeAAM",
8    "state": "Inactive",
9    "relatedParty": [
10      {
11        "role": "Financially Responsible Party",
12        "partyOrPartyRole": {
13          "id": "001xx000003HJdrAAG",
14          "href": "/services/data/v68.0/sobjects/Account/001xx000003HJdrAAG",
15          "name": "Acme Corp",
16          "@type": "PartyRef",
17          "@referredType": "Organization"
18        },
19        "@type": "RelatedPartyRefOrRelatedPartyRoleRef"
20      }
21    ]
22  },
23  {
24    "@type": "BillingAccount",
25    "lastUpdate": "2026-04-14T18:43:10.000Z",
26    "name": "Test Any Role BA",
27    "id": "15ixx0000004FjcAAE",
28    "href": "/connect/comms/accountmanagement/v5/billingaccount/15ixx0000004FjcAAE",
29    "state": "Inactive",
30    "relatedParty": [
31      {
32        "role": "Financially Responsible Party",
33        "partyOrPartyRole": {
34          "id": "001xx000003HP8BAAW",
35          "href": "/services/data/v68.0/sobjects/Account/001xx000003HP8BAAW",
36          "name": "Test Acme Corp",
37          "@type": "PartyRef",
38          "@referredType": "Organization"
39        },
40        "@type": "RelatedPartyRefOrRelatedPartyRoleRef"
41      }
42    ]
43  }
44]

GraphQL Query Transformations 

Query transformations provide the ability to enhance GraphQL queries by adding fields, applying filters, or modifying the sort order, while preserving the original query definition.

Query Enhancements: Fields and Filters 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email", "phone"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
9}
10}
11]
12}

Result (GraphQL):

1query {
2myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
3id
4name
5email
6phone
7}
8}

Query Enhancements: Fields, Filters, and Ordering 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["revenue", "industry"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{
9and: [
10{ status: { eq: \"ACTIVE\" } },
11{ revenue: { gte: 1000000 } }
12]
13}"
14},
15"orderBy": {
16"clearExistingOrderBy": true,
17"orderGraphQlSnippet": "{ revenue: { order: DESC } }"
18}
19}
20]
21}

Result (GraphQL):

1query {
2myAccounts: accounts(
3where: {
4and: [
5{ status: { eq: "ACTIVE" } },
6{ revenue: { gte: 1000000 } }
7]
8},
9orderBy: { revenue: { order: DESC } }
10) {
11id
12name
13revenue
14industry
15}
16}

Query Enhancements: Multiple Query Paths with Different Transformations 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5myContacts: contacts {
6id
7name
8}
9}
10}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email"],
6"addFilters": {
7"clearExistingFilters": true,
8"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
9}
10},
11{
12"path": "myAccounts.myContacts",
13"addFields": ["email", "phone"],
14"orderBy": {
15"clearExistingOrderBy": true,
16"orderGraphQlSnippet": "{ name: { order: ASC } }"
17}
18}
19]
20}

Result (GraphQL):

1query {
2myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
3id
4name
5email
6myContacts: contacts(orderBy: { name: { order: ASC } }) {
7id
8name
9email
10phone
11}
12}
13}

Query Enhancements: Fields and Snippets 

Original Query (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5}
6}

Transformation Instructions (JSON):

1{
2"nodes": [
3{
4"path": "myAccounts",
5"addFields": ["email"]
6},
7{
8"path": "metrics",
9"insertGraphQlSnippet": "{
10metrics: systemMetrics {
11totalCount
12activeCount
13lastSyncTime
14}
15}"
16}
17]
18}

Result (GraphQL):

1query {
2myAccounts: accounts {
3id
4name
5email
6}
7metrics: systemMetrics {
8totalCount
9activeCount
10lastSyncTime
11}
12}