The IAccountManagementPOST Apex interface provides extensibility for POST 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.
- transformRequest
- configureDefaultValidations
- applyCustomValidations
- customiseMutationPayload
- handlePostOperation
Create Operation Use Cases
| Use Case | Hook(s) | Description | Example Scenarios | Benefit |
|---|---|---|---|---|
| Default or Enrich Fields Before Creation | transformRequest |
Normalizes the incoming request and supplies default, derived, or contextual values before validation and mutation processing begins. | - Apply a default Account status or type <br>- Normalize names, phone numbers, and addresses <br>- Derive the region from the country code <br>- Add a source-system value from the request context | Reduces client payload complexity and ensures consistent Account data at creation. |
| Enforce Business Rules | applyCustomValidations |
Evaluates the request against organization-specific rules before the create mutation is built. Invalid requests are rejected early with a clear error response. | - Prevent duplicate Account names <br>- Restrict creation to permitted Account types <br>- Require registration details for business Accounts <br>- Validate regional or lifecycle requirements | Rejects invalid requests early and prevents inconsistent or noncompliant Accounts from being created. |
| Relax or Tighten Built-In Validators | configureDefaultValidations |
Enables, disables, or adjusts built-in validation flags according to the organization's Account creation process. | - Require additional mandatory fields <br>- Relax validations for trusted internal channels <br>- Enable stricter address or identifier checks <br>- Apply different validation policies by Account type or region | Adapts mandatory-field and validation rules to organization-specific processes without changing the shared create implementation. |
| Auto-Populate or Override Mutation Fields | customiseMutationPayload |
Adds or replaces fields in the GraphQL mutation payload before execution, including organization-specific values that are not represented in the TMF schema. | - Set internal classification or segmentation fields <br>- Add a source-system or correlation ID<br>- Populate audit and provenance fields <br>- Override a derived lifecycle status | Persists organization-specific fields beyond the standard TMF schema while preserving the shared API contract. |
| Create Related Records Atomically | customiseMutationPayload (insertGraphQlSnippet) |
Inserts an additional GraphQL operation into the create mutation so related or junction records are created together with the Account. | - Create Account-Contact junctions <br>- Add relationship roles <br>- Create related-party associations<br>- Establish Account hierarchy links | Keeps custom and related data consistent with the Account while reducing additional API calls and partial-write risks. |
| Create an Audit Trail or Trigger Downstream Processing | handlePostOperation |
Runs after the Account creation mutation succeeds and uses the operation result to perform compliance logging, publish events, or invoke downstream integrations. | - Write an Account-creation audit record <br>- Publish an Account-created event<br>- Synchronize the Account with billing or CRM systems <br>- Trigger onboarding, compliance, or notification workflows | Supports compliance and integration side effects while keeping them separate from the core creation operation. |
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
Default accountType when not provided.
global class AccountMgmtTransformRequestPostExt implements comms_apex_ext.IAccountManagementPOST {
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context == null) {
return context;
}
Object requestBodyObj = context.get('requestBody');
if (!(requestBodyObj instanceof Map<String, Object>)) {
return context;
}
Map<String, Object> requestBody = (Map<String, Object>) requestBodyObj;
// Inject a default accountType if the client did not send one.
if (!requestBody.containsKey('accountType')) {
requestBody.put('accountType', 'Retail');
}
context.put('requestBody', requestBody);
return context;
}
// ... other hooks return null / empty map ...
}
configureDefaultValidations
This hook enables, disables, or modifies the built-in validation rules before they execute.
Hook Method
Map<String, Boolean> configureDefaultValidations(Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context)
Sample Apex Implementation
Allow contacts without a contact name while preserving required validations.
global class AccountMgmtValidationConfigPostExt implements comms_apex_ext.IAccountManagementPOST {
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
// Keep mandatoryFieldsValidation and relatedPartyValidation ON (defaults);
// turn OFF only contactValidation.
return new Map<String, Boolean>{
'contactValidation' => false
};
}
// ... other hooks return null / empty map ...
}
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
Reject reserved name prefixes.
global class AccountMgmtCustomValidationPostExt implements comms_apex_ext.IAccountManagementPOST {
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
Map<String, Object> result = new Map<String, Object>();
if (requestBody != null && requestBody.containsKey('name')) {
String name = (String) requestBody.get('name');
if (name != null && name.startsWith('INVALID_')) {
result.put('validationStatus', 'fail');
result.put('validationMessage',
'Custom validation failed: name cannot start with INVALID_');
result.put('validationDetails', new Map<String, Object>{
'rejectedName' => name,
'rule' => 'RESERVED_PREFIX'
});
return result;
}
}
result.put('validationStatus', 'pass');
return result;
}
// ... other hooks return null / empty map ...
}
customiseMutationPayload
This hook modifies the mutation payload before deleting a customer record. Use this to add deletion metadata, set soft-delete flags, or apply business logic transformations.
Hook Method
Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context)
Sample Apex Implementation
Add a field to the create mutation.
global class AccountMgmtMutationPayloadPostExt implements comms_apex_ext.IAccountManagementPOST {
global static Map<String, Object> customiseMutationPayload(
Map<String, Object> request,
Map<String, Object> context
) {
Map<String, Object> addFields = new Map<String, Object>();
addFields.put('CustomerClass', 'Commercial');
Map<String, Object> node = new Map<String, Object>();
node.put('path', 'billingAccount');
node.put('addFields', addFields);
List<Object> nodes = new List<Object>{ node };
Map<String, Object> result = new Map<String, Object>();
result.put('nodes', nodes);
return result;
}
// ... other hooks return null / empty map ...
}
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
Audit-log the created Billing Account.
global class AccountMgmtPostOpAuditExt implements comms_apex_ext.IAccountManagementPOST {
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLResult,
Map<String, Object> tmfResponse,
Map<String, Object> context
) {
try {
// Extract the created BillingAccount id from the mutation result.
String createdId = null;
if (graphQLResult != null && graphQLResult.get('data') instanceof Map<String, Object>) {
Map<String, Object> data = (Map<String, Object>) graphQLResult.get('data');
Map<String, Object> uiapi = (Map<String, Object>) data.get('uiapi');
if (uiapi != null && uiapi.get('billingAccount') instanceof Map<String, Object>) {
Map<String, Object> ba = (Map<String, Object>) uiapi.get('billingAccount');
Map<String, Object> record = (Map<String, Object>) ba.get('Record');
if (record != null) {
createdId = (String) record.get('Id');
}
}
}
System.debug('BillingAccount created via TMF666 POST by '
+ UserInfo.getName() + ': ' + createdId);
} catch (Exception e) {
System.debug('Audit logging failed: ' + e.getMessage());
}
// Return value is ignored for POST.
return null;
}
// ... other hooks return null / empty map ...
}
Full Implementation Example
Here's a complete sample Apex implementation using all supported hooks.
/**
* Complete TMF666 Account Management POST API Extension
* Demonstrates transformRequest, configureDefaultValidations, applyCustomValidations,
* customiseMutationPayload, and handlePostOperation.
*/
global class AccountManagementPOSTExtension implements comms_apex_ext.IAccountManagementPOST {
/** HOOK 1: Default accountType when absent */
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context == null) { return context; }
Object bodyObj = context.get('requestBody');
if (bodyObj instanceof Map<String, Object>) {
Map<String, Object> body = (Map<String, Object>) bodyObj;
if (!body.containsKey('accountType')) {
body.put('accountType', 'Retail');
}
context.put('requestBody', body);
}
return context;
}
/** HOOK 2: Keep all default validators enabled */
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
return new Map<String, Boolean>();
}
/** HOOK 3: Reject reserved name prefixes */
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
Map<String, Object> result = new Map<String, Object>();
if (body != null && body.get('name') instanceof String
&& ((String) body.get('name')).startsWith('INVALID_')) {
result.put('validationStatus', 'fail');
result.put('validationMessage', 'name cannot start with INVALID_');
return result;
}
result.put('validationStatus', 'pass');
return result;
}
/** HOOK 4: Force CustomerClass on the created record */
global static Map<String, Object> customiseMutationPayload(
Map<String, Object> request,
Map<String, Object> context
) {
return new Map<String, Object>{
'nodes' => new List<Object>{
new Map<String, Object>{
'path' => 'billingAccount',
'addFields' => new Map<String, Object>{ 'CustomerClass' => 'Commercial' }
}
}
};
}
/** HOOK 5: Audit (return ignored for POST) */
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLResult,
Map<String, Object> tmfResponse,
Map<String, Object> context
) {
System.debug('BillingAccount created via TMF666 POST by ' + UserInfo.getName());
return null;
}
/** NOT APPLICABLE for POST — never invoked */
global static Map<String, Object> customiseGraphQLQuery(
Map<String, Object> querySpec,
Map<String, Object> context
) {
return new Map<String, Object>();
}
}
Response Structure
Request Body with All Attributes
{
"@type": "BillingAccount",
"name": "Billing Account",
"state": "Active",
"accountType": "Commercial",
"billStructure": {
"@type": "BillStructure",
"presentationMedia": [{ "@type": "BillPresentationMedia", "name": "EBill" }],
"format": { "@type": "BillFormat", "name": "Summary Bill" },
"cycleSpecification": { "@type": "BillingCycleSpecification", "frequency": "Monthly" }
},
"taxExemption": [{
"certificateNumber": "TAX-12345",
"reason": "Lower Income Group",
"startDateTime": "2025-01-01T00:00:00.000Z",
"endDateTime": "2027-12-31T00:00:00.000Z"
}],
"relatedParty": [
{
"role": "Financially Responsible Party",
"@type": "RelatedPartyRefOrRelatedPartyRoleRef",
"partyOrPartyRole": {
"@type": "PartyRefOrPartyRoleRef",
"@referredType": "Organization",
"id": "001xx000003HP8BAAW",
"name": "Test Acme Corp"
}
}
],
"contact": [
{
"contactName": "Jane Doe",
"contactType": "Bill To",
"contactMedium": [
{ "@type": "EmailContactMedium", "emailAddress": "jane.doe@acme.com" },
{ "@type": "PhoneContactMedium", "phoneNumber": "+1-555-123-4567" },
{ "@type": "FaxContactMedium", "faxNumber": "+1-555-987-6543" },
{
"@type": "GeographicAddressContactMedium",
"street1": "123 Main St", "street2": "Suite 400",
"city": "San Francisco", "stateOrProvince": "CA",
"postCode": "94105", "country": "US"
}
]
}
]
}
Response (HTTP 201 Created)
{
"id": "15ixx0000004N64AAE",
"href": "/connect/comms/accountmanagement/v5/billingaccount/15ixx0000004N64AAE",
"name": "Billing Account",
"state": "Active",
"accountType": "Commercial",
"lastUpdate": "2026-04-26T15:54:53.000Z",
"billStructure": {
"format": { "name": "Summary Bill", "@type": "BillFormat" },
"presentationMedia": [{ "name": "EBill", "@type": "BillPresentationMedia" }],
"cycleSpecification": { "frequency": "Monthly", "@type": "BillingCycleSpecification" },
"@type": "BillStructure"
},
"taxExemption": [
{
"certificateNumber": "TAX-12345",
"validFor": {
"startDateTime": "2025-01-01T00:00:00.000Z",
"endDateTime": "2027-12-31T00:00:00.000Z"
},
"reason": "Lower Income Group",
"@type": "TaxExemption"
}
],
"relatedParty": [
{
"role": "Financially Responsible Party",
"partyOrPartyRole": {
"id": "001xx000003HP8BAAW",
"href": "/services/data/v68.0/sobjects/Account/001xx000003HP8BAAW",
"name": "Test Acme Corp",
"@type": "PartyRef",
"@referredType": "Organization"
},
"@type": "RelatedPartyRefOrRelatedPartyRoleRef"
}
],
"contact": [
{
"id": "003xx000004WtVUAA0",
"contactName": "Jane Doe",
"contactType": "Bill To",
"validFor": { "endDateTime": "2099-12-31T00:00:00.000Z" },
"contactMedium": [
{ "id": "003xx000004WtVUAA0-cm-1", "preferred": true, "emailAddress": "jane.doe@acme.com", "@type": "EmailContactMedium" },
{ "id": "003xx000004WtVUAA0-cm-2", "phoneNumber": "+1-555-123-4567", "@type": "PhoneContactMedium" },
{ "id": "003xx000004WtVUAA0-cm-3", "faxNumber": "+1-555-987-6543", "@type": "FaxContactMedium" },
{
"id": "003xx000004WtVUAA0-cm-4",
"city": "San Francisco", "country": "US", "postCode": "94105",
"stateOrProvince": "CA", "street1": "123 Main St, Suite 400",
"@type": "GeographicAddressContactMedium"
}
],
"@type": "Contact"
}
],
"@type": "BillingAccount"
}