Create a custom Apex class to enable the Document AI flow functionality. This Apex class:
- Allows the Salesforce flow to invoke the Data 360 Document AI functionality
- Passes in necessary credentials and document identities
- Receives a clean JSON output of the extracted bank statement data
- Go to the Setup Menu.
- From the dropdown menu, select Developer Console.
- In the Developer Console, click File > New > Apex Class.
- Enter the class name:
de_BankStatementExtractorand click OK. - When the code editor opens, paste this code into the editor and save the file.
note: This is an out-of-the-box class that includes a method invoked by the flow you create on the next step. If you choose to use a different Apex class, make sure it includes such an invocable method. This class also contains a hardcoded override for the LLM model. If you use a different model, update the llmModel string.
public class de_BankStatementExtractor {
@InvocableMethod(label='Run Bank statement analysis using DataCloud Document AI')
public static List<Result> runImageProcessing(List<Request> requests) {
List<Result> results = new List<Result>();
for (Request req : requests) {
Result result = new Result();
try {
// 1. Get access token
String token = getAccessToken(req.Org_Domain, req.Client_Id, req.Client_Secret);
System.debug('Access Token >> ' + token);
// Intialize variables
String orgDomain = req.Org_Domain;
String llmModel = req.LLM_Model;
String idpConfigId = req.IDPConfigId;
// 2. Get latest version ID as fileId
ContentVersion cv = [
SELECT Id
FROM ContentVersion
WHERE ContentDocumentId = :req.ContentDocumentId
ORDER BY VersionNumber DESC
LIMIT 1
];
String fileId = cv.Id;
System.debug('Using fileId >> ' + fileId);
// 3. Call schema generation or extract directly
String schemaJson;
String finalResponse;
if (String.isBlank(idpConfigId)) {
schemaJson = getSchemaJson(token, fileId, orgDomain, llmModel);
System.debug('Decoded Schema JSON >> ' + schemaJson);
finalResponse = extractData(token, fileId, schemaJson, orgDomain, llmModel, null);
} else {
finalResponse = extractData(token, fileId, null, orgDomain, llmModel, idpConfigId);
}
result.Status = 'Success';
result.ExtractedDataJson = finalResponse;
} catch (Exception e) {
result.Status = 'Failed';
result.ErrorMessage = e.getMessage();
System.debug('Error >> ' + e.getMessage());
}
results.add(result);
}
return results;
}
public class Request {
@InvocableVariable(label='Org Domain' required=true) public String Org_Domain;
@InvocableVariable(label='Client Id' required=true) public String Client_Id;
@InvocableVariable(label='Client Secret' required=true) public String Client_Secret;
@InvocableVariable(label='Document Type' required=true) public String Document_Type;
@InvocableVariable(label='LLM Model') public String LLM_Model;
@InvocableVariable(label='Content Document Id' required=true) public Id ContentDocumentId;
@InvocableVariable(label='IDPConfigId') public String IDPConfigId;
}
public class Result {
@InvocableVariable(label='Status') public String Status;
@InvocableVariable(label='Extracted Data (JSON)') public String ExtractedDataJson;
@InvocableVariable(label='Error Message (if any)') public String ErrorMessage;
}
// === 1. Get access token ===
private static String getAccessToken(String orgDomain, String clientId, String clientSecret) {
HttpRequest req = new HttpRequest();
req.setEndpoint(orgDomain + '/services/oauth2/token');
req.setMethod('POST');
req.setHeader('Content-Type','application/x-www-form-urlencoded');
req.setBody('grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + clientSecret);
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
Map<String,Object> resp = (Map<String,Object>)JSON.deserializeUntyped(res.getBody());
return (String)resp.get('access_token');
}
throw new CalloutException('Access Token Error: '+res.getBody());
}
// === 2. Generate schema ===
private static String getSchemaJson(String accessToken, String fileId, String orgDomain, String llmModel) {
HttpRequest req = new HttpRequest();
req.setEndpoint(orgDomain + '/services/data/v63.0/ssot/document-processing/actions/generate-schema');
req.setMethod('POST');
req.setHeader('Authorization','Bearer '+accessToken);
req.setHeader('Content-Type','application/json');
req.setHeader('Accept','application/json');
req.setHeader('Sforce-Call-Options','client=SchemaTest');
req.setTimeout(120000);
// Force hardcoded model for testing
llmModel = 'llmgateway__OpenAIGPT4Omni_08_06';
Map<String,Object> payload = new Map<String,Object>{
'mlModel' => llmModel,
'files' => new List<Object>{ new Map<String,Object>{ 'fileId' => fileId } }
};
String body = JSON.serialize(payload);
System.debug('Sending Schema Payload: ' + body);
req.setBody(body);
HttpResponse res = new Http().send(req);
System.debug('Schema Status Code: ' + res.getStatusCode());
System.debug('Schema Response: ' + res.getBody());
if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
Map<String,Object> bodyMap = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
String schemaRaw = (String) bodyMap.get('schema');
if (schemaRaw == null) {
throw new CalloutException('Schema Error: No "schema" field in response');
}
// Decode HTML escapes
String schemaDecoded = schemaRaw.replaceAll('"', '"').replaceAll('&', '&');
return schemaDecoded;
} else {
throw new CalloutException('Schema Error(' + res.getStatusCode() + '): ' + res.getBody());
}
}
// === 3. Extract data ===
private static String extractData(String accessToken, String fileId, String schemaJson,
String orgDomain, String llmModel, String idpConfigId) {
HttpRequest req = new HttpRequest();
req.setEndpoint(orgDomain + '/services/data/v63.0/ssot/document-processing/actions/extract-data');
req.setMethod('POST');
req.setHeader('Authorization','Bearer '+accessToken);
req.setHeader('Content-Type','application/json');
req.setHeader('Accept','application/json');
req.setTimeout(120000);
Map<String,Object> payload;
if (!String.isBlank(schemaJson)) {
payload = new Map<String,Object>{
'mlModel'=>llmModel,
'files'=>new List<Object>{ new Map<String,Object>{ 'fileId'=>fileId } },
'schemaConfig'=>schemaJson
};
} else {
payload = new Map<String,Object>{
'idpConfigurationIdOrName'=>idpConfigId,
'files'=>new List<Object>{ new Map<String,Object>{ 'fileId'=>fileId } }
};
}
System.debug('Extract Payload >> '+JSON.serializePretty(payload));
req.setBody(JSON.serialize(payload));
HttpResponse res = new Http().send(req);
System.debug('Extract Status >> '+res.getStatusCode());
System.debug('Extract Response >> '+res.getBody());
if (res.getStatusCode()>=200 && res.getStatusCode()<300) {
Map<String,Object> bodyMap = (Map<String,Object>)JSON.deserializeUntyped(res.getBody());
if (bodyMap.containsKey('data')) {
return JSON.serialize(bodyMap.get('data'));
}
return 'No data field in response';
}
throw new CalloutException('Extract Error('+res.getStatusCode()+'): '+res.getBody());
}
}
Next Step
- Step 4 - Create Bank Statement Extraction Flow (Apex Wrapper): Create a screen flow to extract data from the customer's bank statement.