Tutoriel · Salesforce · Intégration API
Bulk API 2.0 : charger et extraire des millions d’enregistrements
Job d’ingestion en upsert, envoi du CSV, suivi d’état, reprise des lignes en échec et extraction paginée : le cycle complet en cURL, et quand préférer l’API REST.
Quand utiliser la Bulk API
Salesforce propose plusieurs API pour écrire des données. Le bon choix dépend surtout du volume et du besoin de réponse immédiate :
| API | Volume par appel | Usage |
|---|---|---|
| REST sObject | 1 enregistrement | Action unitaire en temps réel |
| sObject Collections | Jusqu’à 200 | Petit lot synchrone, tout ou rien possible |
| Composite | Jusqu’à 25 sous-requêtes | Opérations liées (créer un compte puis ses contacts) |
| Bulk API 2.0 | Des millions | Chargements et extractions asynchrones |
Repère courant : au-delà de quelques milliers d’enregistrements, la Bulk API devient le bon outil. Elle traite en arrière-plan, découpe elle-même les données et consomme bien moins d’appels d’API.
Le cycle d’un job d’ingestion
- Créer le job : objet, opération (
insert,update,upsert,delete,hardDelete), format. - Envoyer les données en CSV.
- Fermer l’envoi (
UploadComplete) : le traitement démarre. - Suivre l’état jusqu’à
JobCompleteouFailed. - Lire les résultats : réussis, en échec, non traités.
Les exemples suivants utilisent cURL sur une org de démonstration fictive ; le jeton vient d’un utilisateur d’intégration (flux OAuth JWT Bearer, par exemple).
export SF="https://acme-demo.my.salesforce.com/services/data/v62.0"
export AUTH="Authorization: Bearer $SF_ACCESS_TOKEN"Créer le job et envoyer le CSV
Un upsert sur un champ External ID rend le chargement rejouable : relancé après un incident, il met à jour au lieu de créer des doublons.
curl -s -X POST "$SF/jobs/ingest" -H "$AUTH" -H "Content-Type: application/json" -d '{
"object": "Contact",
"operation": "upsert",
"externalIdFieldName": "Ref_Externe__c",
"contentType": "CSV",
"lineEnding": "LF"
}'
# → { "id": "750...", "state": "Open", ... } Le CSV utilise les noms d’API des champs. Une colonne Account.Ref_Externe__c rattache chaque contact à son compte par l’identifiant externe du parent, sans connaître les Id Salesforce :
Ref_Externe__c,FirstName,LastName,Email,Account.Ref_Externe__c
CLI-000001,Camille,Martin,camille.martin@example.com,AG-0042
CLI-000002,Yanis,Bernard,yanis.bernard@example.com,AG-0042
CLI-000003,Inès,Petit,ines.petit@example.com,AG-0107 JOB=750...
curl -s -X PUT "$SF/jobs/ingest/$JOB/batches" -H "$AUTH" -H "Content-Type: text/csv" \
--data-binary @contacts.csv
# Fermer l'envoi : Salesforce commence le traitement.
curl -s -X PATCH "$SF/jobs/ingest/$JOB" -H "$AUTH" -H "Content-Type: application/json" \
-d '{ "state": "UploadComplete" }'Suivre le job et lire les résultats
On interroge l’état à intervalle raisonnable (toutes les 10 à 30 secondes), puis on récupère les lignes en échec :
curl -s "$SF/jobs/ingest/$JOB" -H "$AUTH"
# → { "state": "JobComplete", "numberRecordsProcessed": 120000, "numberRecordsFailed": 37, ... }
curl -s "$SF/jobs/ingest/$JOB/failedResults/" -H "$AUTH" -o echecs.csv
curl -s "$SF/jobs/ingest/$JOB/unprocessedrecords/" -H "$AUTH" -o non-traites.csv Le fichier d’échecs reprend chaque ligne d’origine, précédée de l’erreur :
"sf__Id","sf__Error",Ref_Externe__c,FirstName,LastName,Email,Account.Ref_Externe__c
"","INVALID_EMAIL_ADDRESS:Email: invalid email address: yanis.bernard@:Email --",CLI-000002,...
"","UNABLE_TO_LOCK_ROW:unable to obtain exclusive access to this record:--",CLI-004518,... Un job JobComplete peut contenir des lignes en échec : toujours lire numberRecordsFailed. Les erreurs de données (e-mail invalide, champ obligatoire) se corrigent à la source ; les erreurs de verrouillage se rejouent telles quelles dans un nouveau job.
Extraire des données : les jobs de requête
Pour exporter des millions de lignes, un job de requête exécute la SOQL en arrière-plan et sert le résultat en CSV, page par page :
curl -s -X POST "$SF/jobs/query" -H "$AUTH" -H "Content-Type: application/json" -d '{
"operation": "query",
"query": "SELECT Id, Ref_Externe__c, Email FROM Contact WHERE LastModifiedDate = LAST_N_DAYS:7"
}'
# Une fois le job en JobComplete : lecture par pages, chaînées par l'en-tête Sforce-Locator.
curl -s -D entetes.txt "$SF/jobs/query/$JOB/results?maxRecords=50000" -H "$AUTH" -o page-1.csv
grep -i "Sforce-Locator" entetes.txt
# → Sforce-Locator: MTAwMDAw (la valeur "null" signale la dernière page)
curl -s "$SF/jobs/query/$JOB/results?locator=MTAwMDAw&maxRecords=50000" -H "$AUTH" -o page-2.csv L’opération queryAll inclut aussi les enregistrements supprimés et archivés. Une extraction incrémentale filtre sur SystemModstamp ou LastModifiedDate, et doit rester sélective sur les gros objets.
Bonnes pratiques
- Taille des envois : un envoi de CSV est limité (de l’ordre de 100 Mo de données). Au-delà, répartir sur plusieurs jobs.
- Verrous : trier le CSV par enregistrement parent (le compte d’un contact) limite les erreurs
UNABLE_TO_LOCK_ROW, car Salesforce traite les lots en parallèle. - Automatisations : triggers, flows et règles de validation s’exécutent pour chaque ligne. Les tester en volume en sandbox ; pour une reprise de données ponctuelle, prévoir un moyen documenté de les contourner.
- Limites : les jobs consomment un quota quotidien d’enregistrements et de jobs. Le suivre dans Setup → Bulk Data Load Jobs et via la ressource
/limits. - Idempotence : préférer
upsertsur External ID, conserver les fichiers d’échecs et journaliser les identifiants de job. - Outils : la Salesforce CLI (
sf data import bulk,sf data export bulk) et Data Loader s’appuient sur cette API ; l’appel direct sert surtout aux intégrations automatisées.