1

I'm testing out using external services with the OFAC-API.com

I'm successfully using Search API v3 to search for a fella in Russia.

I'm having trouble parsing information out of the response, as its in a series of nested objects, and I can only access the properties on the top level object in the flow. I cannot find a way to get at data in the nested 'matches' object

Here is the structure of the response object. I have not run across the 'additionalProperties' property before - wondering if that is to blame?

  "SearchResponseV3": {
    "type": "object",
    "properties": {
      "error": {
        "type": "boolean"
      },
      "errorMessage": {
        "type": "string"
      },
      "sourcesUsed": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/UsedSourceV3"
        }
      },
      "matches": {
        "type": "object",
        "additionalProperties": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/SearchMatch"
          }
        }
      }
    }
  },

In setup i can see the structure of the response Apex class, and it contains a property for "matches".

OFACAPI_SearchResponseV3 Apex Class

// [SERVICE] OFACAPI
// [OBJECT] OFACAPI.SearchResponseV3
global class OFACAPI_SearchResponseV3 {
    // [PROPERTY] error
    @AuraEnabled
    global Boolean error {get; set{ this.error = value; this.error_set = true; }}
    global Boolean error_set {get; set;}

    // [PROPERTY] errorMessage
    @AuraEnabled
    global String errorMessage {get; set{ this.errorMessage = value; this.errorMessage_set = true; }}
    global Boolean errorMessage_set {get; set;}

    // [PROPERTY] sourcesUsed
    @AuraEnabled
    global List<OFACAPI_UsedSourceV3> sourcesUsed {get; set{ this.sourcesUsed = value; this.sourcesUsed_set = true; }}
    global Boolean sourcesUsed_set {get; set;}

    // [PROPERTY] matches
    @AuraEnabled
    global OFACAPI_SearchResponseV3_matches matches {get; set{ this.matches = value; this.matches_set = true; }}
    global Boolean matches_set {get; set;}
}

There is also an Apex class for the matches object. But I cannot find a way to reference this in the flow as I cannot select this class when creating an apex defined variable.

OFACAPI_SearchResponseV3_matches class
// [SERVICE] OFACAPI
// [OBJECT] OFACAPI.SearchResponseV3.matches
global class OFACAPI_SearchResponseV3_matches {
        // [PROPERTY] properties
        @AuraEnabled
        global Map<String, List<OFACAPI_SearchMatch>> properties {get; set{ this.properties = value; this.properties_set = true; }}
        global Boolean properties_set {get; set;}
    }

Any suggestions on what to try? Just when I felt like I was getting a handle on using external services, running into a wall again!

1 Answer 1

3

OpenAPI spec for OFAC APIs is at https://ofac-api.com/api-docs .

The additionalProperties is an OpenAPI keyword that defines a data structure equivalent to a Map in Apex. Values of this Map can be primitives (strings, numbers or boolean values), arrays or objects.

In case of OFAC API, when you do a search based on an entity name to see if they've been sanctioned, the result set will have this name as a key. Firing this request

{
    "apiKey": "...",
    "minScore": 95,
    "source": ["SDN"],
    "cases": [{
        "name": "Abu Abbas"
    }]
}

yields a response (edited/redacted below) that shows additionalProperties in action:

{
   "error": false,
   "sourcesUsed": [{
         "source": "SDN",
         "publishDate": "3/21/2023"
      }
   ],
   "matches": {
      "Abu Abbas": [
        {
            "source": "SDN",
            "firstName": "Abu",
            "lastName": "ABBAS",
            "title": "Director of PALESTINE LIBERATION FRONT - ABU ABBAS FACTION",
            "sdnType": "Individual",
            "programs": ["SDGT"],
 
         }, 
         {
            "source": "SDN",
            "lastName": "PALESTINE LIBERATION FRONT - ABU ABBAS FACTION",
            "sdnType": "Entity",
            "programs": ["FTO", "SDGT"]
           
         }
      ]
   }
}

matches is a JSON key, its value is a JSON object where each key in this object is the entity name from the request. Our search request had a single entity (Abu Abbas). Thus we have a single key underneath matches with a list of search results associated to this key.

Given the OpenAPI definition of OFAC service, the REST client can be generated via External Services. Edited down and combined into one snippet for brevity, it looks like this:

global class OFAC {
    // Screens your client (case) against OFAC-API.com's combined data set using name, citizenship, DOB, passport and gender. Screen up to 1000 cases per request
    global post_Response post(post_Request input) {
        return (post_Response) System.ExternalServiceMethods.invokeOperation(
            '', 'OFAC', 'post', input); // 2nd arg is the name of Named Cred
    }

    global class post_Request {
        global OFAC_SearchRequestV3 body {get;}
        global Boolean body_set {get; set;}
    }

    global class post_Response {
        global OFAC_SearchResponseV3 Code200 {get; set;}
        global Integer responseCode {get; set;}
    }
}

global class OFAC_SearchRequestV3 {
    global String apiKey {set{ this.apiKey = value; this.apiKey_set = true; }}
    global Integer minScore {set{ this.minScore = value; this.minScore_set = true; }}
    global List<String> source {set{ this.source = value; this.source_set = true; }}
    global List<OFAC_z0Case> cases {set{ this.cases = value; this.cases_set = true; }}
}

global class OFAC_z0Case {
    global String name { set{ this.name = value; this.name_set = true; }}
}

global class OFAC_SearchResponseV3 {
    global OFAC_SearchResponseV3_matches matches {get;}
}

global class OFAC_SearchResponseV3_matches {
    global Map<String, List<OFAC_SearchMatch>> properties {get;}
}

global class OFAC_SearchMatch {
    global String fullName {get; set{ this.fullName = value; this.fullName_set = true; }}
}

It's worth noting that post_Request and post_Response are inner classes of the top-level OFAC and therefore will not visible in a Flow. They are intended to be used as convenience wrappers for developers calling this generated client from Apex.

If you were writing this in Apex, you'd create a service facade and deal with transformation of types in your facade. For a simple search request shown above, the facade could look like this:

global class SanctionsComplianceService {

    public boolean isSanctioned(String entityName) {
    
        ExternalService.OFAC_SearchRequestV3 request = new ExternalService.OFAC_SearchRequestV3();
        request.apiKey = '...';
        request.minScore = 95; // desired match quality as a minimum similarity score
        request.source = new List <String> {'SDN'}; // Specially Designated Nationals
        ExternalService.OFAC_z0Case searchCriteria = new ExternalService.OFAC_z0Case();
        searchCriteria.name = entityName;
        request.cases = new List<ExternalService.OFAC_z0Case> { searchCriteria };
    
        ExternalService.OFAC.post_Request searchRequest = new ExternalService.OFAC.post_Request();
        searchRequest.body = request;
      
        ExternalService.OFAC api = new ExternalService.OFAC();
        ExternalService.OFAC.post_Response response = api.post(searchRequest);

       /* Uncomment for debugging

        Map<String, List<ExternalService.OFAC_SearchMatch>> searchResponse = response?.Code200?.matches?.properties;
        if (searchResponse == null) return false;

        for (List<ExternalService.OFAC_SearchMatch> matches: searchResponse.values()) {
            for (ExternalService.OFAC_SearchMatch match: matches) {
                System.debug('Found a match: ' + match.fullName);
            }
        }
        */

        return response?.Code200?.matches != null;
    }
}

Doing this in a strictly no-code fashion is a little more challenging. Flows and External Services work well insofar as entities generated by External Services can be surfaced in a Flow as Apex-defined types. Unfortunately in this particular case the Flow capabilities fall short.

While the generated code for OFAC_SearchResponseV3 does show the matches property, Flow Builder doesn't see it. Forcing it to evaluate the expression by explicitly referencing the property via dot notation results in an empty string. Perhaps this is so because .matches returns OFAC_SearchResponseV3_matches type which the Flow also doesn't surface.

This appears to be a bug - please open a support case.

1
  • thanks for the details. very helpful. i'm also trying to parse via invocable apex, with some success, but will likely file a ticket to see if I can get at these results directly in flow.
    – gorav
    Mar 24, 2023 at 16:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .