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 :

APIVolume par appelUsage
REST sObject1 enregistrementAction unitaire en temps réel
sObject CollectionsJusqu’à 200Petit lot synchrone, tout ou rien possible
CompositeJusqu’à 25 sous-requêtesOpérations liées (créer un compte puis ses contacts)
Bulk API 2.0Des millionsChargements 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

  1. Créer le job : objet, opération (insert, update, upsert, delete, hardDelete), format.
  2. Envoyer les données en CSV.
  3. Fermer l’envoi (UploadComplete) : le traitement démarre.
  4. Suivre l’état jusqu’à JobComplete ou Failed.
  5. 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 upsert sur 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.
🍪 Gestion des cookies
Ce site utilise des cookies pour mesurer l'audience (Google Analytics) et lire des vidéos (YouTube). Vous pouvez personnaliser votre choix.
Mentions légales
Réglages des cookies
Essentiels Nécessaires au fonctionnement du site — ne peuvent pas être désactivés.
Google Analytics Analyse d'audience (pages vues, sources de trafic). Aucune donnée personnelle identifiante.
YouTube Permet de lire les vidéos YouTube intégrées sur cette page.