After a Bulk API 2.0 ingest job reaches JobComplete, inspect the job record's numberRecordsFailed field via GET /services/data/vXX.0/jobs/ingest/{jobId}
Fetch the failed-record CSV via GET /services/data/vXX.0/jobs/ingest/{jobId}/failedResults — response is CSV with sf__Error and sf__Id columns prepended
Parse sf__Error per row to categorize failures: field-validation errors, duplicate-rule blocks, required-field violations, or record-lock timeouts
For lock-timeout errors (UNABLE_TO_LOCK_ROW), extract those record IDs and queue them for a separate retry job submitted during low-activity hours
For validation or missing-field errors, route the failed CSV rows to a remediation queue (e.g., a Salesforce custom object or external database) for human review
Fetch the unprocessed-records CSV via /unprocessedResults to capture rows the job never attempted, then decide whether to resubmit or discard
Known gotchas
Failed-result and unprocessed-result endpoints return an empty 200 (no CSV body) when there are zero failures — treat an empty body as success, not an error
The sf__Error string is a plain-text concatenation of all rule violations for that row; splitting on a delimiter to parse multiple errors per row is not reliable — parse it as a free-form string
Failed-record CSVs are only retained for a limited window after job completion (check current Salesforce retention policy); download them before closing the job or they may become unavailable
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp