Cirra AI Article
Retry Failed Rows in a Salesforce Data Load Safely
How to retry failed rows in a Salesforce data load: build a result ledger, check upsert keys, triage errors and test in a Winter ’27 sandbox. Includes a fictional 100-row reconciliation, 2026.

Summary
- 01A failed job can leave successful writes in place. Reconcile each input row to a verified state before deciding which rows may be written again.
- 02Keep failed, unprocessed and unknown rows distinct. Missing error rows do not prove success, and unknown rows block ticket closure.
- 03Check identity before correcting data. Preserve the matching key, hold blank or repeated keys, and distinguish External ID matching from the Unique constraint.
- 04Retry only the approved subset after a sandbox sample, record read-back and automation review. A stable upsert key does not prevent repeated updates or downstream effects.
- 05Append each retry as a new attempt. Report row-attempts, unique logical rows and unique business keys separately, then close against the latest verified states.
Inside this article
- 01Executive Summary
- 02Introduction and Background
- 03A Job Failure Is Not an All-or-Nothing Rollback
- 04Separating Data Loader Files from Bulk API 2.0 Resources
- 05Building the Result Ledger
- 06Identity Before Data Quality
- 07Duplicate Keys, Parents and Update-Only Behavior
- 08Classifying Errors and Choosing a Path
- 09Correcting and Sampling the Retry File
- 10Why Upsert Is Not an Exactly-Once Guarantee
- 11Reconciling Attempts and Closing the Ticket
- 12Data Analysis and Evidence
- 13Implications and Future Directions
- 14Frequently Asked Questions (FAQs)
- 15Conclusion
Executive Summary
A partially failed Salesforce data load is not a rolled-back load. Salesforce states that processed Bulk application programming interface (API) 2.0 job data "isn’t rolled back" when a job ends in the Failed state [1], and the Data Loader guide warns that even when processing fails, "some records could have completed successfully" [2]. The safe question is not "should the file be re-run?" but "which rows are in which state, and which may be written again?" This report, anchored to the Winter ’27 release (API version 68.0) [3], answers it with a result ledger and a deterministic retry gate. Integration vendors document the same selective shape [4].
The ledger joins every input row to a job ID and a state: success, failed, unprocessed or unknown, plus unresolved for reviewed rows that stay open. Salesforce says unprocessed rows "are not the same as failed rows" [5], so an absent error row proves nothing. For one accounting scope, input rows must equal successes plus failures plus unprocessed plus unresolved, and any unknown row blocks closure.
Identity comes before data quality. An upsert inserts when the key finds no record [6], so a retry row with a blank, altered or differently cased key can create a second record. External ID is a matching handle; the Unique attribute is what "prevents duplicate values from being saved across records" [7]. Help article 000384876, published June 3, 2026, adds that duplicate record IDs in one batch fail, while duplicates in different batches are each applied and the last batch wins [8]. Other data platforms treat duplicate source rows as a hazard too: PostgreSQL’s upsert "will not be allowed to affect any single existing row more than once" [9].
Each failed row then gets one of four decisions: no action, correction and retry, lookup or matching correction, or review before any write. Only after a small sandbox sample, a read-back of actual records and a check of triggered automation does the remaining approved subset run. Upsert prevents duplicate creation through a stable key, but it can repeat updates and downstream effects, so it is not an exactly-once guarantee. AI assistance may cluster errors, propose mappings and report key differences, while a human approves the write plan [10]. A fictional 100-row example reconciles to 93 successes, 6 failures and 1 unresolved row over 123 row-attempts; it is an illustration, not a benchmark.
Introduction and Background
This situation arises when a Data Loader run, a command-line bulk job or an integration finishes with a mixed outcome: some rows landed, some were rejected, and some may never have been attempted. The tempting response is to fix the visible errors and run the whole file again. That habit creates duplicates, overwrites newer edits and re-fires automation on rows that were already correct.
The audience is Salesforce administrators, revenue operations (RevOps) data operators and consulting partners who inherit a half-finished load. This is not another insert, update and upsert tutorial, nor a duplicate-rule overview. Introductions to commands and mapping exist, such as the Salesforce Ben guide, which says upsert "updates a record if it already exists, or if not, inserts it" [6]. Trailhead positions Data Loader for loads of "50,000 to 150 million records" [11]. The gap addressed here is recovery: accounting for every row, deciding what may be written again, and proving the outcome.
The reference release is Winter ’27, which ships API version 68.0, with production dates of September 4, October 2 and October 9 by instance [12] [13]. As of October 27, 2026, the Data Loader releases page lists V67.0.0 as latest, "for use with Salesforce Summer ’26 or later release through Salesforce Force Partner API v67" [14], with the dependency upgraded "from 66.0.0 to 67.0.0" [14]. A Winter ’27 org can therefore sit at API 68.0 while the desktop tool calls API v67; the tool can fall back to the previous API version when the latest is incompatible with the org [15]. A ledger should record tool version, API version actually used, and ingest mode.
None of the behaviors below is attributed to Winter ’27 by the sources reviewed; they are long-standing documented semantics. Three limits apply. No live load was performed and no customer org was accessed, so every platform behavior comes from documentation and the worked example is fictional. No public benchmark of retry success rates was found, so none is stated. Several details (blank-key handling, desktop result ordering, the API version that introduced the Representational State Transfer (REST) updateOnly parameter) are not stated in the pages reviewed and are listed for sandbox verification.
A Job Failure Is Not an All-or-Nothing Rollback
What the documentation says
Two independent statements establish partial success. For Bulk API 2.0, the job reference says "Job data that was successfully processed isn’t rolled back" [1]. For the desktop tool, the wizard page says "some records could have completed successfully, so check the results" [2]. The retry ceilings differ between pages and are cited as written: the Data Loader page says a file is "reprocessed up to 10 times before the operation is permanently marked as failed" [2], while the Bulk API 2.0 ingest page refers to a batch that cannot be processed "after 20 retries" [16]. Which applies depends on the mode used.
Record the run before touching anything
Capture an incident header first. Each item changes which result files exist and what a retry may do.
- Org and environment: org ID, production or sandbox, sandbox name.
- Object, operation and key: the sObject, insert, update or upsert, and the matching field.
- Loader and ingest mode: tool version, API version used, and Simple Object Access Protocol (SOAP), Bulk API or Bulk API 2.0. Trailhead says Data Loader "uses the SOAP API to process records" unless configured for the Bulk API [11].
- Job identity: job ID for any Bulk job, timestamps and operator.
- Inputs and outputs: the unmodified input comma-separated values (CSV) file and every success, error and unprocessed file. One practitioner tip is to store the input "in the same folder as your success and error files" [17].
Preservation has a clock. Ingest results can be retrieved "within 7 days of job completion, unless the job has been deleted explicitly" [18]. The job ID is worth recording even for a desktop run.
Why rerunning the file is the wrong default
A full rerun treats every row as untested. Successful rows are written again, rows that failed for data reasons fail again, and never-attempted rows are mixed in and cannot be counted separately. Salesforce’s timeout guidance takes the selective route: "Create a new ingest job with only the failed and unprocessed records" [19]. The remaining sections turn that advice into an auditable procedure.
Separating Data Loader Files from Bulk API 2.0 Resources
A desktop operation does not necessarily use Bulk API 2.0, so Bulk endpoints should not be taught as if every desktop run exposes them. Table 1 below summarizes what each surface produces and the caveat that matters for reconciliation.
| Surface | What it produces | Failed rows | Unprocessed rows | Reconciliation caveat |
|---|---|---|---|---|
| Data Loader, SOAP (default) | Two CSV files per operation, one starting with "success" and one with "error" [20] | The error file holds the rejected rows with a reason column | No separate resource documented in the pages reviewed | The documentation does not name the error column; record the header you observe |
| Data Loader, Bulk API or Bulk API 2.0 | Same two local files, plus a server-side job | Error file locally; a failed-results resource on the server for Bulk API 2.0 | Server-side resource for Bulk API 2.0 jobs that failed or aborted | Capture the job ID to reach server results |
| Bulk API 2.0 direct or command-line interface (CLI) | Successful, failed and unprocessed resources | Rows carry an error code and message | Resource for failed or aborted jobs | Order is not guaranteed; results are not recorded for batches over the daily allocation |
| Salesforce CLI bulk upsert | Starts a Bulk API 2.0 job | Retrieved by job ID | Retrieved by job ID | Keep the job ID with the original CSV |
The desktop success and error files are the minimum evidence; the three Bulk API 2.0 resources are richer when a Bulk job exists. Successful results carry a flag for whether the record was created, the only direct created-versus-updated signal in these documents; for a SOAP run none is documented in the pages reviewed, so the read-back step must supply it.
Two details deserve emphasis. First, Bulk API 2.0 result files echo only matching columns: "Results only include columns that are a match" [21]. A helper column such as a row number may not come back, and whether it does must be tested before the ledger relies on it. Second, Data Loader’s Start at row setting says "If your last operation failed, you can use this setting to begin where the last successful operation finished" [22]. That is a position, not a state. It cannot distinguish failed rows from succeeded ones and should not serve as the retry gate when batches ran in parallel.
- Preserve the local success and error files as the minimum evidence for the operation.
- Record the ingest mode before assuming that a desktop operation exposes Bulk endpoints.
- Treat Start at row as a position rather than a retry gate when batches ran in parallel.
- Retrieve failedResults for rows with errors and the unprocessed resource for rows never attempted.
- Join results through a surviving key, because response order need not match the original input.
- Successful results identify whether a record was created, supporting created-versus-updated verification.
Building the Result Ledger
Columns and states
The ledger is a CSV or sheet with one line per row-attempt.
- source_row_key: a stable identifier assigned before the first load; random version 4 universally unique identifiers, which Stripe suggests for idempotency keys [23], are one option.
- business_key: the matching value, normally the external ID.
- input_row_number: position in the original file, for human lookup only.
- job_id: the Bulk job ID, or a run label for a SOAP run.
- attempt: 1 for the original load, then 2, 3 and so on.
- result_state, Salesforce_ID, exact_error: the state, the record ID returned, and the error text copied verbatim.
- correction, approver, verification: what changed, who approved the write, and what read-back confirmed.
States are strict. Success means a success result plus a read-back. Failed means an error result with a captured message. Unprocessed means the unprocessed resource, or job metadata, shows the row was never attempted. Unresolved is a documented terminal state for a row that was reviewed and could not be classified, with the reason written down. Unknown means no result has been matched yet.
Why an absent error row proves nothing
It is tempting to define unprocessed as "in the input but in neither result file". The documentation shows why that is unsafe. Unprocessed rows are a separate resource, and "Unprocessed rows are not the same as failed rows" [5]. A row can be missing from all three files because results "are not recorded for batches that exceed the daily batch allocation" [24]. The response order "is not guaranteed to match the ordering of records in the original job data" [25], so joining by position is wrong for Bulk API 2.0 and the join needs a key that survives in the result files. The pages reviewed state no ordering guarantee for the desktop files either, so the same caution applies until a sandbox test shows otherwise.
The accounting identity
For one completed accounting scope: input rows = successes + failures + unprocessed + unresolved, with unknown = 0. Unknown rows are a blocker, never a silent zero. If the sum falls short, the remainder is reviewed before any further write; it may be rows lost to the allocation caveat, rows whose helper key was not echoed, or a join error. Practitioners recommend the same intent: "Review exception reports seeing what data was not migrated" [26]. Counts keep three things apart: attempts (every line), unique rows (by source_row_key) and unique business keys (distinct matching values, with blank and repeated keys on their own lines). A file of 100 rows with one repeated key and one blank key has 100 unique rows but 98 distinct keys, and the difference is a finding.
A ticket that closes with unresolved rows is acceptable; one that closes with unknown rows is not.
Identity Before Data Quality
Most expensive retry mistakes are identity mistakes: the row matched the wrong record, no record, or two.
What an upsert match does
The Bulk API 2.0 walkthrough says it "uses the external ID field to determine whether a record is used to update an existing record or create a record" [27]. The SOAP reference states "If the external ID is matched multiple times, then an error is reported" [28], and Jitterbit’s documentation reports the same symptom when "Multiple source records share the same external ID value" [29]. Gearset ties DUPLICATE_VALUE to rows where "the same value for the external ID field" appears more than once in the source [30]. Salesforce Ben notes an update needs either the Salesforce ID or a valid external ID field [6]. Microsoft’s Dataverse upsert works the same way in principle: alternate keys are the expected route in data integration [31], and a request creates a record "if it doesn't find any records matching the keys" [31].
The consequence for retries is direct. A different, blank or re-typed key finds zero matches, and the upsert inserts. Gearset’s documentation describes the effect when a matching field differs: "it will “create” rather than “upserting” the record" [30]. A corrected file changes field values and leaves the key untouched; any proposed key change is escalated.
External ID, uniqueness and duplicate rules are different things
Salesforce Help says an external ID field can be used "to update or upsert records using the API" and, when also marked Unique, to prevent duplicates [32]. A July 16, 2026 Help article states: "Unique ID is a field-level constraint that prevents duplicate values from being saved across records" [7], whereas "You can have multiple records with the same External ID, though this it is not recommended" [7]. Without Unique, parallel processes can insert records with the same external ID [7], so Salesforce recommends using Unique with External ID [7]. Jitterbit adds that when the external ID "is not unique, Salesforce returns an error and the upsert fails for those records" [29], and Gearset notes the combination of match fields must make "a unique key for every record" [33]. Duplicate rules are a third mechanism, reported as their own status: "Duplicate records have been detected" [34]. A duplicate-rule hit does not show the external ID is wrong, and a clean external ID does not show that no duplicate-rule match exists.
Case, whitespace and blank keys
Case handling is configurable: administrators choose whether values "identical except for their case are considered unique" [32], and the SOAP reference says matching is case-insensitive only under a specific field configuration [28]. Practitioner write-ups report that different casing in a CSV "can produce a duplicate" [35] and that stray spaces make values "look identical visually but register as different entries" [36]. A spreadsheet check is case-insensitive by default, since COUNTIF criteria "aren't case sensitive" [37], so the check must follow the field’s configured behavior.
Blank keys need a sandbox test. The pages reviewed do not state the outcome of an upsert row with an empty external ID in Bulk API 2.0 or Data Loader. A blank cell in an update also does not clear a field, since empty values are ignored. Until observed in a Winter ’27 sandbox, blank-key rows are held and never retried.
Duplicate Keys, Parents and Update-Only Behavior
Duplicate keys inside the file
Duplicate keys in the CSV were documented explicitly in a June 3, 2026 Help article. Rows with the same record ID in one batch fail ("If two rows with the same record ID are included in the same processing batch"), while in different batches "The final state of the record reflects the last batch processed" [8]. The article warns of unexpected final values and advises you to "review your CSV file to identify and remove any duplicate rows" [8]. It concerns record IDs; the SOAP reference gives the parallel rule for external IDs, that such records "are marked as errors" [28]. Cross-batch behavior for repeated external IDs is not spelled out in the pages reviewed, so treat it like the record-ID case until tested.
Other platforms treat duplicate source rows as a known hazard. PostgreSQL says rows proposed for insertion "should not duplicate each other" [9]. Snowflake’s MERGE errors by default [38] because "the system is unable to determine the source value to use" [38], and with the error disabled "the row selected is not defined" [38]. Oracle reports "Two or more source rows matched the same target row" [39], and the Structured Query Language (SQL) MERGE article notes that "You cannot update a Target row multiple times with a MERGE statement" [40]. The shared lesson is to deduplicate the source on the matching key before the write, not after.
Parents and update-only behavior
Parent mapping is a check, not a project. Help notes "It may not be possible to map all reference fields to an External ID", with polymorphic fields that "do not allow mapping to external IDs" [41]. Help’s walkthrough maps the child column "to use the External ID field for matching related Object A records" [41]. A missing-parent row is fixed by loading the parent first, not by resending the child.
The REST upsert endpoint documents an updateOnly parameter that "Forces the upsert to behave like an update when" there is no match [42]. The REST page does not state the introducing API version, so verify it before relying on it. It is a REST parameter; the Data Loader settings reference reviewed lists no matching checkbox, so no plan should assume the desktop tool has an update-only switch.
Classifying Errors and Choosing a Path
Each failed row lands in one of four dispositions: no action, correction and retry, lookup or matching correction, or review before any write. The error text selects the starting point; the evidence check decides. Table 2 below covers five common classes with the next evidence check, owner and retry condition. A separate picklist reference covers restricted-picklist root causes, so that matrix is not reproduced here.
| Class | Evidence to check next | Owner | Retry condition |
|---|---|---|---|
| Validation failure | Read the exact message and field list; the code means a "custom validation formula that violates a field integrity rule" [34]. Validation runs late: "Validation rules execute after your automation runs triggers and flows" [43] | Business-rule owner or admin | Corrected value passes in a sandbox sample; a bypass is a separate approved change |
| Duplicate rule | Confirm the status is a duplicate-rule hit, not a unique-field error; view the matched record | Data steward | Steward decides merge, skip or allow. An API header can save duplicates past an alert, an exception to approve, not a default |
| Missing parent | Compare the reference value with the parent key; the status code reports that the referenced record does not exist | Data operator, then parent-data owner | Parent exists with the expected key; child key unchanged |
| Access or record type | Identify the running user and permissions; check that the record type is available to that user | Security or access admin | Permission or record-type assignment fixed and documented; same user retests a sample |
| Lock or timeout | Read the message for locked record IDs. A lock timeout is temporary; Gearset suggests "reducing the batch size, which minimizes record contention" [44] | Load operator | Resubmit only those rows, with less contention. If the same file "succeeds on a retry, parallel batch locking is your culprit" [45] |
Retry conditions differ in kind. Lock or timeout rows can often be resent unchanged once contention clears, which fits the general retry principle of not retrying permanent errors ("Don’t retry permanent errors or malformed requests in a client" (Source: sre.google)) and capping attempts ("Don’t retry a given request indefinitely" (Source: sre.google)). Azure guidance similarly says to stop when "the failure isn't transient" [46]. Validation, parent and access rows fail again unless something outside the file changes. Integration documentation lists "The records locked because another process or user edited them" among first-pass failure causes [47]. Unfamiliar error text defaults to review before any write.
Correcting and Sampling the Retry File
- 01Preserve evidence and build the ledger
Keep the original input and every result file. Match each input row to its job and state, and investigate unknown rows.
- 02Check the matching identity
Check for blank or repeated keys and preserve the original matching value. Escalate any proposed key change.
- 03Classify and correct eligible rows
Use the exact error and evidence check to choose a disposition. Include only approved retry and unprocessed rows in the retry file.
- 04Approve mapping and sample
Approve the field mapping, matching key and relationship columns, then test a small sample in a sandbox.
- 05Verify and run the approved subset
Read actual records back and inspect automation. After a backup, run the remaining approved subset with the sample's mode and key.
- 06Append attempts and reconcile
Keep earlier attempts intact. Reconcile latest verified states, attach evidence, and record a reason, owner and next step for unresolved rows.
Closure requires balanced accounting, no unknown rows, read-back evidence or a written exception, owned unresolved rows, and checked business outcomes.
Hold rows whose state, key or error is unclear, and record why before seeking human review.
Build the smallest retry file
The retry file holds only rows dispositioned as correction and retry, plus unprocessed rows approved for attempt. It keeps the original matching key byte for byte, and every changed value is logged with the approver. Keep CSV hygiene in view: RFC (Request for Comments) 4180 says fields containing "line breaks (CRLF), double quotes, and commas" must be enclosed in double quotes [48], its header should "contain the same number of fields as the records" [48], and Microsoft warns that stray spaces and mismatched quotation marks make counts return unexpected values [37].
Approve the mapping, then test a sample
The mapping is a separate approval: which column maps to which field, which is the match key, which relationship columns are used. Then test a sample in a sandbox. Help says to "Run a test with one or a small subset of records" and verify them [41]. Sandbox type limits realism: only Partial Copy and Full Copy sandboxes include production data [49], Full Copy sandboxes "can only be refreshed every 29 days" [49], and Developer sandboxes refresh once per day [49]. A sandbox without the matching records cannot show whether the key finds an existing record, so seed records that carry the production external IDs. A sandbox pass is also not a production guarantee ("Just because everything worked right in the sandbox, is no guarantee that it will in production" [26]), so production results are validated again.
Read back, then inspect automation
A sample passes only after actual records are read back: Salesforce ID, external ID, corrected fields and created-versus-updated outcome. Automation is part of the inspection. Triggers run on a record set for API or bulk record operations such as DML statements [50], DML over 200 records "are processed in batches" [51], and a bulk load "will cause a trigger to execute in chunks of 200" [52]. Flows, triggers and validation rules therefore run for retried rows as they did originally. Salesforce documents bypass patterns, such as building "a bypass in your flows for data loads and sandbox seeding" [53] with bypasses "all in one place" [53], and notes that bypassing validation rules "may be needed to correct historical data or perform data loads" [54]. Each bypass is an org change with an owner, an approval and a restore step. It is not a free convenience.
Only then does the remaining approved subset run, in the same mode and with the same key as the sample, after a backup. Help advises "Always back up your data before performing any Data Loader operation" [41].
Why Upsert Is Not an Exactly-Once Guarantee
An idempotent operation, as Amazon Web Services (AWS) puts it, can be "retried with no additional side effects" [55]; more formally it is one that "can be applied multiple times without changing the result beyond the initial application" [56]. Hypertext Transfer Protocol (HTTP) defines it by method, with "PUT, DELETE, and safe request methods" idempotent [57], though "Nothing is preventing a server in the wild from exposing a non-idempotent endpoint" [58]. Retry guidance agrees: an idempotent operation "is inherently safe to retry" [46], while non-idempotent retries "can result in the action being performed twice" [46] and can lead to conflicts [59]. Payment APIs reach the goal of the result happening "only once" [55] with caller-supplied keys, where "Retries with the same key return the saved response" [23] and the service rejects "a parameter mismatch between idempotent requests" [55].
The Salesforce sources reviewed describe key-based matching, not a stored request key with a saved response. An upsert on a stable, unique external ID avoids creating a second record. But it re-executes the update on every repeat: values are rewritten, triggers fire, flows run, and any downstream message fires again. Stable external IDs do not prove that repeated updates are harmless. Request-key systems also expire their records ("it’s impractical to retain that knowledge indefinitely" [55]), which parallels the seven-day result window. Retrying rows already marked success is therefore always "no".
Where AI assistance fits, and where it stops
The National Institute of Standards and Technology (NIST) describes human-AI configurations that "can span from fully autonomous to fully manual" [10] and calls for policies that "define and differentiate roles and responsibilities for human-AI configurations" [60]. For a retry that means explicit boundaries.
- Allowed: clustering error messages into the classes in Table 2, proposing field mappings for review, producing a key-difference report between original and retry files, and summarizing unresolved rows.
- Not allowed: inferring an external ID from a similar-looking company name, silently modifying the matching key, re-running rows marked success, or approving its own write plan.
- Human gate: an administrator approves the mapping, the retry file and the execution. NIST lists "automation bias, over-reliance" among risks of such configurations [60] and suggests collecting "the frequency and rationale with which humans overrule AI" [10]; recording overrides in the approver and verification columns serves as that trail.
Where a Salesforce admin Model Context Protocol (MCP) interface such as Cirra AI is considered, confirm that it supports the exact operation before relying on it. For load mechanics such as a Bulk API 2.0 ingest job with result resources, native tools like Data Loader or the Salesforce CLI remain the instrument.
The gate is simple to state: a row is written again only if its latest ledger state permits it, its matching key is unchanged and unique, its correction is approved, and a sample of the same shape has been verified.
Reconciling Attempts and Closing the Ticket
After the sample and the remaining subset run, the ledger gets new lines for attempt 2; attempt 1 lines are not edited. Each logical row’s latest verified state derives from its highest attempt number. Row-attempts, unique logical rows and unique business keys are reported separately, and duplicate keys get their own line instead of being netted out.
Evidence is attached per attempt: job ID, success, error and unprocessed files, the read-back extract and the approver. Because Bulk results are retrievable for only seven days, files are exported the same day. Community experience points the same way: "for your failed rows (and in real life there are always failed rows) you want to know what the errors are" [61]. That is sentiment, not specification. Closure requires four checks:
- The accounting identity holds with unknown at zero.
- Every success row has a read-back, or a written reason it does not.
- Every unresolved row has a reason, an owner and a next step.
- Expected outcomes are checked against the business request, such as an expected count in a segment, not only against the tool’s status.
A ticket that closes with unresolved rows is acceptable; one that closes with unknown rows is not.
Data Analysis and Evidence
The quantitative core is small: documented limits and a counting method. No survey, benchmark or success-rate dataset for partially failed loads was found, so none is quoted. The documented figures used are:
- 7 days of retrievable ingest results after completion, as cited above.
- 20 retries for a Bulk API 2.0 batch, versus 10 reprocessing attempts in the Data Loader text, as cited above.
- 25 custom external ID fields per object, with Unique fields counting against that limit [32].
- 200-record trigger chunks within a bulk load [52].
- API 68.0 for Winter ’27, against Data Loader V67.0.0 on Force Partner API v67 [14].
A fictional 100-row worked example (Hypothetical Example)
Table 3 below shows a fictional 100-row load, an illustration of the counting method and not a benchmark. No real org, job or customer is represented. The header values are placeholders for observed values: Data Loader 67.0.0, API v67.0 per the pairing above, ingest mode Bulk API 2.0, operation upsert on Account, match key External_Id__c.
| Step | Row-attempts | Success | Failed | Unprocessed | Unresolved | Unknown |
|---|---|---|---|---|---|---|
| Attempt 1, first read | 100 | 71 | 22 | 5 | 0 | 2 |
| After resolving the 2 unknown rows | 100 | 72 | 22 | 5 | 1 | 0 |
| Attempt 2: 23 rows retried | 23 | 21 | 2 | 0 | 0 | 0 |
| Latest verified state per logical row | 123 attempts over 100 unique rows | 93 | 6 | 0 | 1 | 0 |
The 22 attempt 1 failures split, in the fictional triage, into 9 validation, 4 missing parent, 3 lock or timeout, 2 duplicate rule, 2 access, 1 blank key and 1 repeated key. The retry set is 5 unprocessed plus 3 lock, 9 corrected validation, 4 corrected parent and 2 access rows, 23 rows in all. Four failed rows are held: 2 duplicate-rule rows awaiting a steward, the blank-key row and the repeated-key row. Attempt 2 yields 21 successes and 2 failures. The final identity is 93 + 6 + 0 + 1 = 100 with unknown at 0, and unique business keys number 98. The same 100 rows give three totals (123, 100 and 98) depending on what is counted, and a report with only one of them hides the other two.
Ledger header and formulas
A CSV ledger header for this method is:
source_row_key,business_key,input_row_number,job_id,attempt,result_state,Salesforce_ID,exact_error,correction,approver,verification
The formulas below are written for the fictional ledger, with source_row_key in column A, business_key in B, attempt in E, result_state in F, the key check in helper column L and the latest flag in helper column M, both appended after the 11 ledger columns. They have not been verified in Excel or Google Sheets, so confirm them in your own sheet. In per-row formulas, lock the ranges with dollar signs before filling down; the cells shown are for row 2 of a 123-line ledger.
- Count a state:
=COUNTIF(F2:F124,"success"). COUNTIF takes a single criterion [37], and Google Sheets documents the same limit [62]. - Reconciliation gap, attempt 1 only:
=COUNTA(A2:A101)-COUNTIF(F2:F101,"success")-COUNTIF(F2:F101,"failed")-COUNTIF(F2:F101,"unprocessed")-COUNTIF(F2:F101,"unresolved"). On the first-read ledger it should return 2, equal to the unknown rows. - Key check, per row:
=IF(TRIM(B2)="","BLANK",IF(COUNTIF(B2:B101,B2)>1,"DUPLICATE","OK")). On the fictional ledger it should flag one blank key and two lines sharing a key. - Distinct business keys:
=SUMPRODUCT((B2:B101<>"")/COUNTIF(B2:B101,B2:B101&"")), which should return 98 on the fictional ledger. - Latest attempt flag, per row:
=COUNTIFS(A2:A124,A2,E2:E124,">"&E2)=0. COUNTIFS "applies criteria to cells across multiple ranges and counts the number of times all criteria are met" [63]. - Latest state count:
=COUNTIFS(M2:M124,TRUE,F2:F124,"success"). On the fictional ledger it should return 93, with 6 failed, 0 unprocessed, 1 unresolved and 0 unknown.
Google Sheets also offers a unique count ("Counts the number of unique values in a list of specified values and ranges" [64]), but "Counting unique rows across multiple columns is not supported" [64], so a composite key needs a concatenated helper column. For review, Microsoft suggests conditional formatting to "find and highlight duplicate data" [65] and warns that Remove Duplicates means "the duplicate data is permanently deleted" [65], which is why the original input is preserved and duplicates are flagged, not removed. A case-sensitive duplicate check needs a different function, so none is given here.
Implications and Future Directions
Three implications follow. The ledger is cheap relative to an unaccounted row and can be a template attached to every load ticket. Version drift is worth logging: with Winter ’27 at API 68.0 and the desktop tool at V67.0.0, evidence should state which API version the tool called. And the duplicate-ID guidance is recent (June and July 2026 Help articles), so the Help material for the tool mode should be re-read at each release.
Because no live load was run, these items should be verified in a Winter ’27 sandbox, with minimal input and result samples attached, before the procedure becomes a standard:
- the outcome of an upsert row with a blank external ID in each mode;
- whether a helper column such as source_row_key is echoed in the result files;
- result-file column names and row ordering for the desktop tool in SOAP, Bulk API and Bulk API 2.0 modes;
- which job states are final in the selected mode (the pages reviewed name the Failed state and the 7-day retention but give no single list of terminal states for ingest jobs);
- case-sensitive versus case-insensitive matching for the specific external ID field;
- the API version in which the REST updateOnly parameter became available.
Retries also carry a system-level cost: the Google SRE book notes that "retries can destabilize a system" (Source: sre.google), a reason to cap attempts and shrink retry batches instead of resending large files. Any assistant that proposes a retry, whether a script, an administrator or an AI client, should produce a reviewable plan that a human approves, with the ledger as the audit record.
Frequently Asked Questions (FAQs)
How can operators reload only the failed records in Data Loader?
Use the error file as the source of the failed set, but build the retry file deliberately. Copy it, log the error text in the ledger, correct only the implicated fields, remove the error column before mapping, keep the matching key unchanged, and test a small sample first. No reload-from-error-file feature is described in the documentation reviewed.
What is the difference between the success file and the error file?
The success file holds "the successfully loaded records, including a column with the newly generated record IDs" [20], and the error file holds the rejected records with a reason column. Neither lists rows never attempted, which is why Bulk API 2.0 jobs also need the unprocessed resource.
Which Bulk API 2.0 resource gives failed records, and how are they retried?
Read the failedResults resource for rows with errors and the unprocessed resource for rows never attempted, then create a new job with only those rows.
How can operators avoid duplicate records when rerunning a load?
Never rerun the whole file. Keep the matching key identical, mark the key field Unique where appropriate, check the file for repeated and blank keys, and retry only rows whose ledger state allows it. Choosing insert instead of upsert makes "every row comes in as a new record" [35].
Can AI decide which rows to retry?
AI can classify errors, propose mappings and report key differences. It must not infer a key from a similar name, change the matching key, re-run successful rows or approve its own plan.
Conclusion
A partially failed load is a reconciliation problem before it is a retry problem. Processed data is not rolled back, failed and unprocessed rows are distinct, and duplicate keys in a file produce failures or last-batch-wins overwrites. A ledger that joins each row to a job and a state, an identity check ahead of any data fix, a triage that gives each error an owner and a retry condition, and a sandbox sample with read-back and automation review together make the retry deterministic. The gate is simple to state: a row is written again only if its latest ledger state permits it, its matching key is unchanged and unique, its correction is approved, and a sample of the same shape has been verified.
The method does not promise exactly-once behavior. Upsert prevents a second record only through the key, and the downstream effects of a repeated update remain the operator’s responsibility. For any row whose state, key or error is unclear, the safest default is to hold it, record why, and ask a human.
External Sources (65)
About
Cirra AI
About Cirra AI
Cirra AI is a software company dedicated to reinventing Salesforce administration through AI-powered tooling built on the Model Context Protocol (MCP). From its headquarters in Silicon Valley, the team has built the first commercial MCP server for Salesforce administration—a hosted service that lets any MCP-compatible AI tool (Claude, ChatGPT, Cursor, and others) connect to a Salesforce org and execute admin tasks through natural language. The product gives Salesforce administrators, revenue-operations teams, and consulting partners the ability to implement configuration changes in minutes instead of hours, while respecting org permissions and maintaining full auditability. Cirra AI's mission is to "let humans focus on design and strategy while software handles the clicks." To achieve that, the company develops two complementary product lines: Salesforce Admin MCP Server – A fully hosted MCP endpoint that connects any AI tool to Salesforce in minutes via OAuth. Administrators describe what they need in plain English—create custom objects and fields, configure page layouts, manage permission sets, build flows, provision users, generate documentation—and the MCP server translates those instructions into standard Salesforce Metadata and Tooling API calls, bounded by the user's existing permissions. No local infrastructure or custom code is required: sign up, authenticate, copy the MCP URL into your AI tool, and start working. Salesforce Skills Library – An open-source collection of domain-specific skills (available at skills.cirra.ai) that supercharge AI assistants with deep Salesforce expertise. Skills cover Apex development with 150-point scoring, Flow creation and validation with 110-point scoring, Lightning Web Component development with the PICKLES architecture methodology, metadata operations, permission auditing, data and SOQL operations, org-wide health audits, architecture diagramming, and Kugamon CPQ management. The skills are installable as a single plugin for Claude Cowork, Claude Code, and OpenAI Codex, or as individual skill files for Claude web, desktop, and ChatGPT. They enable AI assistants to perform complex, multi-step Salesforce tasks independently—run a comprehensive org audit, fix issues flagged in the report, generate field descriptions at scale—without prompt-by-prompt hand-holding. Together, these products address three chronic pain points in the Salesforce ecosystem: (1) the high cost of manual administration and repetitive setup-menu navigation, (2) the backlog created by scarce expert capacity, and (3) the risk of inconsistent, undocumented changes. Early adopter feedback shows time-on-task reductions of 70–90 percent for routine configuration work.
Leadership
Cirra AI was founded in 2024 by Jelle van Geuns, a Dutch-born engineer, serial entrepreneur, and veteran of the Salesforce ecosystem with over 14 years of platform experience. Before Cirra, Jelle bootstrapped Decisions on Demand, an AppExchange ISV whose rules-based lead-routing engine is used by multiple Fortune 500 companies. Under his leadership the firm reached seven-figure ARR without external funding, demonstrating a combination of deep technical innovation and pragmatic go-to-market execution. Jelle began his career at ILOG (later IBM), where he managed global solution-delivery teams and developed expertise in enterprise optimisation and AI-driven decisioning. He holds an M.Sc. in Computer Science from Delft University of Technology and speaks frequently on AI-assisted administration, MCP integration patterns, and human-in-the-loop automation at Salesforce community events and podcasts. The leadership team includes Jeff Bajayo (VP Sales), a seasoned Salesforce and SaaS professional with over a decade of experience, and Latrice Barnett (Advisor, Marketing), who brings 10+ years of partnership and ecosystem marketing expertise from the Salesforce ecosystem.
Why Cirra AI Matters
MCP-native architecture – Rather than building a proprietary agent UI, Cirra embraces the Model Context Protocol as a universal connector, letting customers use the AI tool they already prefer—Claude, ChatGPT, Cursor, or any future MCP-compatible client—while Cirra handles the Salesforce integration layer. Deep vertical focus – The Skills Library encodes thousands of Salesforce best-practice patterns, scoring rubrics, and validation scripts that generic AI assistants lack. This domain intelligence produces higher-quality, more reliable outputs for Apex, Flows, LWC, permissions, and metadata operations than general-purpose prompting alone. Enterprise-grade security – The platform uses OAuth authentication, encrypted endpoints, and inherits the connected user's Salesforce permission model. Cirra never stores Salesforce credentials, and all actions are logged for auditability—critical requirements for regulated industries adopting AI tooling. Works for admins and partners alike – Individual administrators use Cirra to eliminate setup-menu drudgery and respond faster to business requests. Consulting firms use it to scale senior-level expertise across delivery teams, enabling more projects delivered at higher quality and lower cost through improved documentation and test coverage. Accessible to non-developers – Anyone with a paid Claude or ChatGPT subscription can install the skills and connect the MCP server. No coding, no complex integrations—just sign up and start working.
Future Outlook
Cirra AI continues to expand its capabilities with the upcoming Admin Agent (launching June 2026), which will bring fully autonomous multi-step task execution to Salesforce administration. The company is also extending platform compatibility to additional AI marketplaces and broadening its skills library to cover more Salesforce clouds and use cases. By combining open standards, domain-specific intelligence, and a relentless focus on the admin experience, Cirra AI is building the de-facto AI integration layer for Salesforce administration.
Disclaimer
This document is provided for informational purposes only. No representations or warranties are made regarding the accuracy, completeness, or reliability of its contents. Any use of this information is at your own risk. Cirra AI shall not be liable for any damages arising from the use of this document. This content was generated with assistance from artificial intelligence tools, which may contain errors or inaccuracies. Readers should verify critical information independently. All product names, trademarks, and registered trademarks mentioned are property of their respective owners and are used for identification purposes only. Use of these names does not imply endorsement. This document does not constitute professional or legal advice. For specific guidance related to your needs, please consult qualified professionals.