The IIndividualPartyManagementPOST 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
This table lists common use cases and the hooks required for the POST operation.
| Use Case | Hook(s) | Description | Example Scenarios | Benefit |
|---|---|---|---|---|
| Default or Normalize Request Fields | transformRequest |
Transforms the incoming request before validation or mutation processing. It can supply default values, normalize formats, and map client-specific fields to the canonical request structure. | - Apply a default contact status<br>- Normalize phone numbers or email addresses<br>- Standardize country and region codes<br>- Map legacy fields to TMF632 fields | Ensures consistent, standardized data at the point of creation. |
| Relax or Tighten Built-In Validators | configureDefaultValidations |
Enables, disables, or adjusts built-in validation flags for create requests according to organization-specific requirements. | - Require stricter email or phone validation<br>- Disable a validator that is not applicable to an internal channel<br>- Require additional identity checks in regulated organizations<br>- Apply different validation policies by tenant or environment | Supports per-organization validation policies without modifying the shared create implementation. |
| Enforce Custom Business Rules | applyCustomValidations |
Evaluates organization-specific business and eligibility rules before the create mutation is built. Invalid payloads are rejected early with an HTTP 400 response and a clear validation message. |
- Prevent duplicate contacts based on organization rules<br>- Require consent before creating a marketing contact<br>- Validate permitted customer types or lifecycle states<br>- Require additional fields for specific regions | Rejects invalid payloads early and prevents inconsistent or noncompliant records from being created. |
| Set Organization-Specific Contact Fields | customiseMutationPayload |
Adds or modifies organization-specific Contact fields in the GraphQL mutation payload before execution. This supports fields that extend beyond the standard TMF632 schema. | - Set an internal customer segment<br>- Add a source-system identifier<br>- Populate custom consent or preference fields<br>- Store organization-specific classification data | Persists custom Contact fields beyond the TMF632 data model while preserving the shared API contract. |
| Audit or Integrate After Creation | handlePostOperation |
Runs after the create mutation completes and performs post-operation side effects using the creation result. It can write audit records, publish events, or invoke downstream integrations. | - Write a contact-creation audit record<br>- Publish a customer-created event<br>- Synchronize the contact with a downstream CRM or marketing platform<br>- Trigger onboarding or notification workflows | Enables reliable auditing and downstream processing after successful creation while keeping side effects separate from the core 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 a missing title and normalize the country code.
global class IndividualPostTransformRequestExt implements comms_apex_ext.IIndividualPartyManagementPOST {
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context == null || !(context.get('requestBody') instanceof Map<String, Object>)) {
return context;
}
Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
if (body.get('title') == null) {
body.put('title', 'Contact');
}
if (body.get('contactMedium') instanceof List<Object>) {
for (Object cmObj : (List<Object>) body.get('contactMedium')) {
if (cmObj instanceof Map<String, Object>) {
Map<String, Object> cm = (Map<String, Object>) cmObj;
if ('GeographicAddressContactMedium'.equals(cm.get('@type'))
&& cm.get('country') instanceof String) {
cm.put('country', ((String) cm.get('country')).toUpperCase());
}
}
}
}
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
Relax the contactMedium validation.
global class IndividualPostConfigValidationsExt implements comms_apex_ext.IIndividualPartyManagementPOST {
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
Map<String, Boolean> overrides = defaultValidationConfiguration != null
? defaultValidationConfiguration.clone()
: new Map<String, Boolean>();
overrides.put('contactMediumValidation', false); // allow arbitrary contactMedium shapes
return overrides;
}
// ... 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
Require a corporate email domain.
global class IndividualPostValidationExt implements comms_apex_ext.IIndividualPartyManagementPOST {
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
Map<String, Object> result = new Map<String, Object>();
if (!(context.get('requestBody') instanceof Map<String, Object>)) {
return result;
}
Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
if (body.get('contactMedium') instanceof List<Object>) {
for (Object cmObj : (List<Object>) body.get('contactMedium')) {
Map<String, Object> cm = (Map<String, Object>) cmObj;
if ('EmailContactMedium'.equals(cm.get('@type'))) {
String email = (String) cm.get('emailAddress');
if (email != null && !email.endsWithIgnoreCase('@acme.com')) {
result.put('validationStatus', 'fail');
result.put('validationMessage',
'Email domain not allowed. Individuals must use an @acme.com address.');
}
}
}
}
return result; // empty => proceed; validationStatus="fail" => HTTP 400
}
// ... 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 custom Contact fields on creation.
global class IndividualPostCustomMutationExt implements comms_apex_ext.IIndividualPartyManagementPOST {
global static Map<String, Object> customiseMutationPayload(
Map<String, Object> graphQLAsMap,
Map<String, Object> context
) {
Map<String, Object> node = new Map<String, Object>{
'path' => 'individualData',
'addFields' => new Map<String, Object>{
'LeadSource' => 'Web',
'Department' => 'Sales',
'Loyalty_Tier__c' => 'Gold'
}
};
return new Map<String, Object>{
'nodes' => new List<Object>{ node }
};
}
// ... 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
Publish a platform event after creation.
global class IndividualPostHandlePostOpExt implements comms_apex_ext.IIndividualPartyManagementPOST {
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLQueryResultAsMap,
Map<String, Object> constructedTMFResponse,
Map<String, Object> context
) {
String newId = constructedTMFResponse != null
? (String) constructedTMFResponse.get('id')
: null;
if (newId != null) {
EventBus.publish(new Individual_Created__e(Individual_Id__c = newId));
}
return null; // ignored for POST
}
// ... other hooks return null / empty map ...
}
Full Implementation Example
Here's a complete sample Apex implementation using all supported hooks.
/**
* Complete TMF632 Individual (Party Management) POST API extension,
* demonstrating all hooks with business logic.
*/
global class IndividualPartyManagementPOSTExtension implements comms_apex_ext.IIndividualPartyManagementPOST {
// Hook 1: normalize the request body.
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context != null && context.get('requestBody') instanceof Map<String, Object>) {
Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
if (body.get('title') == null) {
body.put('title', 'Contact');
}
}
return context;
}
// Hook 2: keep the default validations (all three enabled).
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
return new Map<String, Boolean>();
}
// Hook 3: enforce a corporate email domain.
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
Map<String, Object> result = new Map<String, Object>();
if (context.get('requestBody') instanceof Map<String, Object>) {
Map<String, Object> body = (Map<String, Object>) context.get('requestBody');
if (body.get('contactMedium') instanceof List<Object>) {
for (Object cmObj : (List<Object>) body.get('contactMedium')) {
Map<String, Object> cm = (Map<String, Object>) cmObj;
if ('EmailContactMedium'.equals(cm.get('@type'))) {
String email = (String) cm.get('emailAddress');
if (email != null && !email.endsWithIgnoreCase('@acme.com')) {
result.put('validationStatus', 'fail');
result.put('validationMessage',
'Email domain not allowed. Individuals must use an @acme.com address.');
}
}
}
}
}
return result;
}
// Hook 4: add custom Contact fields to the create mutation.
global static Map<String, Object> customiseMutationPayload(
Map<String, Object> graphQLAsMap,
Map<String, Object> context
) {
Map<String, Object> node = new Map<String, Object>{
'path' => 'individualData',
'addFields' => new Map<String, Object>{ 'LeadSource' => 'Web', 'Department' => 'Sales' }
};
return new Map<String, Object>{ 'nodes' => new List<Object>{ node } };
}
// Hook 5: side effect only (return ignored).
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLQueryResultAsMap,
Map<String, Object> constructedTMFResponse,
Map<String, Object> context
) {
return null;
}
}
Response Structure
Request Body
A TMF632 Individual create payload supports the following input fields:
-
Scalars:
@type(must be"Individual"),givenName,familyName,title, andbirthDate. -
contactMedium[]: Each item includes an@typediscriminator and a corresponding value field:EmailContactMedium→emailAddressPhoneContactMedium→phoneNumberFaxContactMedium→faxNumberGeographicAddressContactMedium→street1,city,stateOrProvince,postCode,country
-
relatedParty[]: Each item includes aroleand apartyOrPartyRolewith@type = "PartyRef",@referredType = "Organization", andidset to the Account ID.- The first entry sets
Contact.AccountId. - Additional entries create
AccountContactRelationrecords and require Contacts to Multiple Accounts to be enabled.
- The first entry sets
{
"@type": "Individual",
"givenName": "John",
"familyName": "Smith",
"title": "VP of Operations",
"birthDate": "1980-04-12",
"contactMedium": [
{ "@type": "EmailContactMedium", "emailAddress": "john.smith@acme.com" },
{ "@type": "PhoneContactMedium", "phoneNumber": "+1-555-0100" },
{
"@type": "GeographicAddressContactMedium",
"street1": "1 Market St",
"city": "San Francisco",
"stateOrProvince": "CA",
"postCode": "94105",
"country": "US"
}
],
"relatedParty": [
{
"role": "employer",
"partyOrPartyRole": { "@type": "PartyRef", "@referredType": "Organization", "id": "001xx000003DHP" }
}
]
}
Response (HTTP 201 Created)
The created Individual, in the same representation returned by GET (relative href, @type = "Individual", @baseType = "Party"):
{
"id": "003xx000004MZZ",
"href": "/connect/comms/partymanagement/v5/individual/003xx000004MZZ",
"@type": "Individual",
"@baseType": "Party",
"givenName": "John",
"familyName": "Smith",
"name": "John Smith",
"title": "VP of Operations",
"birthDate": "1980-04-12",
"contactMedium": [
{ "@type": "EmailContactMedium", "emailAddress": "john.smith@acme.com" },
{ "@type": "PhoneContactMedium", "phoneNumber": "+1-555-0100" },
{
"@type": "GeographicAddressContactMedium",
"street1": "1 Market St",
"city": "San Francisco",
"stateOrProvince": "CA",
"postCode": "94105",
"country": "US"
}
],
"relatedParty": [
{
"@type": "RelatedPartyRefOrPartyRoleRef",
"role": "employer",
"partyOrPartyRole": {
"id": "001xx000003DHP",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DHP",
"name": "Acme Corporation",
"@type": "PartyRef",
"@referredType": "Organization"
}
}
]
}