← Blog/Salesforce

🚀 Integrating F1 API Data into Salesforce with Apex & Flow: A Technical Deep Dive 🏎️

Ricardo Simões
Ricardo SimõesSoftware Engineer
|
🚀 Integrating F1 API Data into Salesforce with Apex & Flow: A Technical Deep Dive 🏎️

Introduction

Building Salesforce integrations with external APIs can be complex. Recently, I created a solution to import Formula 1 season data into Salesforce using Apex and Flow. This post breaks down the architecture, pagination, data modeling, and best practices learned! ⚙️


Architecture Overview

  • Secure API Access: Using Named Credentials for authentication. 🔐
  • Apex Wrappers: Strongly typed classes to deserialize JSON data. 📦
  • Pagination Logic: Looping through API pages, concatenating JSON before deserialization. 🔄
  • Flow Integration: An Invocable Apex method triggers the whole process. ⚡

Pagination & JSON Assembly

The API delivers data paginated. To handle this, I built a method that loops over pages, collects JSON snippets, then assembles a full JSON for deserialization.

public static F1APIWrappers.ResultAPIWrapper createPagedResponse(String season, String raceType) { Integer limit = 30; Integer offset = 0; Integer total = 0; String accumulatedBody = '['; Boolean firstPage = true; do { // Perform HTTP callout with offset and limit HttpResponse response = callAPI(season, raceType, limit, offset); String body = response.getBody(); // Strip array brackets and append String trimmedBody = trimArrayWrapper(body); if (!firstPage) { accumulatedBody += ','; } accumulatedBody += trimmedBody; // Parse headers for pagination info total = parseTotal(response); offset += limit; firstPage = false; } while (offset < total); accumulatedBody += ']'; // Wrap in full JSON structure expected by wrappers String fullJson = '{"MRData": {"RaceTable": {"Races": ' + accumulatedBody + '}}}'; return (F1APIWrappers.ResultAPIWrapper) JSON.deserialize(fullJson, F1APIWrappers.ResultAPIWrapper.class); }

Apex Wrapper Classes Example

To map JSON into Apex, here’s an excerpt of the wrapper classes:

public class F1APIWrappers { public class ResultAPIWrapper { public MRData MRData; } public class MRData { public RaceTable RaceTable; } public class RaceTable { public List<Race> Races; } public class Race { public Integer round; public String raceName; public Circuit Circuit; public List<Result> Results; } public class Circuit { public String circuitId; public String circuitName; } public class Result { public Integer position; public Integer points; public Driver Driver; public Constructor Constructor; } public class Driver { public String driverId; public String givenName; public String familyName; } public class Constructor { public String constructorId; public String name; } }

This structure mirrors the JSON, enabling JSON.deserialize() to populate all nested objects automatically.


Data Upsert with External IDs

To maintain data integrity and avoid duplicates, I used External ID fields in Salesforce. For example:

Race__c raceRecord = new Race__c( Name = race.raceName, Round__c = race.round, External_Id__c = season + '-' + race.round + '-Race' ); upsert raceRecord External_Id__c;

Similarly, race results have composite keys, like:

Race_Result__c resultRecord = new Race_Result__c( Name = season + '-' + raceRecord.Name + '-' + result.position, Race_Result_External_Id__c = raceRecord.External_Id__c + '-' + result.position ); upsert resultRecord Race_Result_External_Id__c;

This logic ensures Salesforce either inserts or updates records correctly without duplication.


Invocable Method for Flow Automation

The process is exposed to Salesforce Flow through an Invocable Apex method, allowing automation or admin-triggered imports:

@InvocableMethod(label='Get Season Data' description='Fetch race results for the season') public static List<Results> execute(List<Requests> requests) { List<Results> outputs = new List<Results>(); for (Requests req : requests) { List<F1APIWrappers.Race> races = F1APIService.getResultsBySeason(req.season, 'results'); ProcessedData data = insertOrUpdateRaceRecords(races, String.valueOf(req.season)); insertOrUpdateAllRecords(data); Results res = new Results(); res.races = data.races; outputs.add(res); } return outputs; }

The Flow passes the season year, triggers the import, and stores the processed data — though the data isn’t directly shown in the Flow UI yet.


Error Handling & Logs

Throughout the code, try-catch blocks and debug logs help detect and surface issues:

try { insert currentSeason; } catch (DmlException e) { System.debug('Error creating season: ' + e.getMessage()); throw new AuraHandledException('Season creation failed: ' + e.getMessage()); }

🔧 Summary & Next Steps

This integration is still an MVP and will continue evolving:

  • ✅ Improve JSON validation for optional/missing fields
  • ✅ Add monitoring & logging visibility
  • ✅ Refactor and modularize for maintainability
  • ✅ Incremental updates instead of full re-syncs If you're working on Salesforce integrations with paginated APIs or external data syncs, I’d love to connect! 🙌

📌 Note: The GitHub repository will be shared soon — once the code is cleaned up and properly structured for public release. Stay tuned! 💻🧼

Tags

0

Comments

Loading animation