Step 3 - Create Apex Class (Document AI API Wrapper)
Step 4 - Create Bank Statement Extraction Flow (Apex Wrapper)
Create a custom Apex class to enable the Document AI flow functionality. This Apex class:
de_BankStatementExtractor and click OK.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.
Note
1public class de_BankStatementExtractor {
2
3 @InvocableMethod(label='Run Bank statement analysis using DataCloud Document AI')
4 public static List<Result> runImageProcessing(List<Request> requests) {
5 List<Result> results = new List<Result>();
6
7 for (Request req : requests) {
8 Result result = new Result();
9 try {
10 // 1. Get access token
11 String token = getAccessToken(req.Org_Domain, req.Client_Id, req.Client_Secret);
12 System.debug('Access Token >> ' + token);
13
14 // Intialize variables
15 String orgDomain = req.Org_Domain;
16 String llmModel = req.LLM_Model;
17 String idpConfigId = req.IDPConfigId;
18
19 // 2. Get latest version ID as fileId
20 ContentVersion cv = [
21 SELECT Id
22 FROM ContentVersion
23 WHERE ContentDocumentId = :req.ContentDocumentId
24 ORDER BY VersionNumber DESC
25 LIMIT 1
26 ];
27 String fileId = cv.Id;
28 System.debug('Using fileId >> ' + fileId);
29
30 // 3. Call schema generation or extract directly
31 String schemaJson;
32 String finalResponse;
33
34 if (String.isBlank(idpConfigId)) {
35 schemaJson = getSchemaJson(token, fileId, orgDomain, llmModel);
36 System.debug('Decoded Schema JSON >> ' + schemaJson);
37
38 finalResponse = extractData(token, fileId, schemaJson, orgDomain, llmModel, null);
39 } else {
40 finalResponse = extractData(token, fileId, null, orgDomain, llmModel, idpConfigId);
41 }
42
43 result.Status = 'Success';
44 result.ExtractedDataJson = finalResponse;
45
46 } catch (Exception e) {
47 result.Status = 'Failed';
48 result.ErrorMessage = e.getMessage();
49 System.debug('Error >> ' + e.getMessage());
50 }
51 results.add(result);
52 }
53 return results;
54 }
55
56 public class Request {
57 @InvocableVariable(label='Org Domain' required=true) public String Org_Domain;
58 @InvocableVariable(label='Client Id' required=true) public String Client_Id;
59 @InvocableVariable(label='Client Secret' required=true) public String Client_Secret;
60 @InvocableVariable(label='Document Type' required=true) public String Document_Type;
61 @InvocableVariable(label='LLM Model') public String LLM_Model;
62 @InvocableVariable(label='Content Document Id' required=true) public Id ContentDocumentId;
63 @InvocableVariable(label='IDPConfigId') public String IDPConfigId;
64 }
65 public class Result {
66 @InvocableVariable(label='Status') public String Status;
67 @InvocableVariable(label='Extracted Data (JSON)') public String ExtractedDataJson;
68 @InvocableVariable(label='Error Message (if any)') public String ErrorMessage;
69 }
70
71 // === 1. Get access token ===
72 private static String getAccessToken(String orgDomain, String clientId, String clientSecret) {
73 HttpRequest req = new HttpRequest();
74 req.setEndpoint(orgDomain + '/services/oauth2/token');
75 req.setMethod('POST');
76 req.setHeader('Content-Type','application/x-www-form-urlencoded');
77 req.setBody('grant_type=client_credentials&client_id=' + clientId + '&client_secret=' + clientSecret);
78
79 HttpResponse res = new Http().send(req);
80 if (res.getStatusCode() == 200) {
81 Map<String,Object> resp = (Map<String,Object>)JSON.deserializeUntyped(res.getBody());
82 return (String)resp.get('access_token');
83 }
84 throw new CalloutException('Access Token Error: '+res.getBody());
85 }
86
87 // === 2. Generate schema ===
88 private static String getSchemaJson(String accessToken, String fileId, String orgDomain, String llmModel) {
89 HttpRequest req = new HttpRequest();
90 req.setEndpoint(orgDomain + '/services/data/v63.0/ssot/document-processing/actions/generate-schema');
91 req.setMethod('POST');
92 req.setHeader('Authorization','Bearer '+accessToken);
93 req.setHeader('Content-Type','application/json');
94 req.setHeader('Accept','application/json');
95 req.setHeader('Sforce-Call-Options','client=SchemaTest');
96 req.setTimeout(120000);
97
98 // Force hardcoded model for testing
99 llmModel = 'llmgateway__OpenAIGPT4Omni_08_06';
100
101 Map<String,Object> payload = new Map<String,Object>{
102 'mlModel' => llmModel,
103 'files' => new List<Object>{ new Map<String,Object>{ 'fileId' => fileId } }
104 };
105
106 String body = JSON.serialize(payload);
107 System.debug('Sending Schema Payload: ' + body);
108
109 req.setBody(body);
110
111 HttpResponse res = new Http().send(req);
112 System.debug('Schema Status Code: ' + res.getStatusCode());
113 System.debug('Schema Response: ' + res.getBody());
114
115 if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
116 Map<String,Object> bodyMap = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
117 String schemaRaw = (String) bodyMap.get('schema');
118 if (schemaRaw == null) {
119 throw new CalloutException('Schema Error: No "schema" field in response');
120 }
121 // Decode HTML escapes
122 String schemaDecoded = schemaRaw.replaceAll('"', '"').replaceAll('&', '&');
123 return schemaDecoded;
124 } else {
125 throw new CalloutException('Schema Error(' + res.getStatusCode() + '): ' + res.getBody());
126 }
127 }
128
129 // === 3. Extract data ===
130 private static String extractData(String accessToken, String fileId, String schemaJson,
131 String orgDomain, String llmModel, String idpConfigId) {
132 HttpRequest req = new HttpRequest();
133 req.setEndpoint(orgDomain + '/services/data/v63.0/ssot/document-processing/actions/extract-data');
134 req.setMethod('POST');
135 req.setHeader('Authorization','Bearer '+accessToken);
136 req.setHeader('Content-Type','application/json');
137 req.setHeader('Accept','application/json');
138 req.setTimeout(120000);
139
140 Map<String,Object> payload;
141 if (!String.isBlank(schemaJson)) {
142 payload = new Map<String,Object>{
143 'mlModel'=>llmModel,
144 'files'=>new List<Object>{ new Map<String,Object>{ 'fileId'=>fileId } },
145 'schemaConfig'=>schemaJson
146 };
147 } else {
148 payload = new Map<String,Object>{
149 'idpConfigurationIdOrName'=>idpConfigId,
150 'files'=>new List<Object>{ new Map<String,Object>{ 'fileId'=>fileId } }
151 };
152 }
153
154 System.debug('Extract Payload >> '+JSON.serializePretty(payload));
155 req.setBody(JSON.serialize(payload));
156
157 HttpResponse res = new Http().send(req);
158 System.debug('Extract Status >> '+res.getStatusCode());
159 System.debug('Extract Response >> '+res.getBody());
160
161 if (res.getStatusCode()>=200 && res.getStatusCode()<300) {
162 Map<String,Object> bodyMap = (Map<String,Object>)JSON.deserializeUntyped(res.getBody());
163 if (bodyMap.containsKey('data')) {
164 return JSON.serialize(bodyMap.get('data'));
165 }
166 return 'No data field in response';
167 }
168 throw new CalloutException('Extract Error('+res.getStatusCode()+'): '+res.getBody());
169 }
170}