Bulk API (Data Import/Export)
Migrations, opening loads, monthly interface files — when the record counts have commas, the standard Bulk API (and its friendlier face, Data Loader) is the tool. On REDA Financials objects it behaves exactly like REST with one crucial property kept intact: every trigger runs on every row, so bulk-loaded documents are real documents — numbered, validated, postable.
The load pattern that works
Load in dependency order, as drafts
Master data first (accounts, products, GLs — usually via the UI/setup, not bulk), then document headers (Draft), then lines. Keep your source reference in the Reference field for traceability and idempotency.
Post as a second pass
A bulk update flipping refin__Status__c to 'Posted' runs the full engine per record. Alternatively post from the UI with Bulk Posting — same machinery, human-visible results table. Either way, failures are per-record: the successes stand, the error file names the exact reason for each failure.
Verify like an accountant, not an engineer
Row counts matching is necessary, not sufficient: run the trial balance and tie it to the source system. The migration guide is the full checklist.
Tuning notes for accounting objects
- Posting is the heavy step — the tax engine, GL writes and validations run per document. Use moderate batch sizes on the posting pass (start around 50–100 documents per batch and observe) rather than maxing out; serial mode avoids lock contention on shared parents like entities and sequences.
- Document numbering under load is safe — sequences assign atomically; parallel inserts won't duplicate numbers. If you're preserving legacy numbers instead, write the Name explicitly and uniqueness is validated.
- Exports are trivial — Bulk query on any
refin__object; the ledger (refin__Accounting_Transaction__c) exports cleanly for warehouse feeds.
Bulk API 2.0, on the wire
The standard job lifecycle, pointed at refin__ objects:
| Step | Method & endpoint |
|---|---|
| Create an ingest job | POST /services/data/v65.0/jobs/ingest (body names the object, e.g. refin__Purchase_Invoice__c, and the operation) |
| Upload the CSV | PUT /services/data/v65.0/jobs/ingest/{jobId}/batches |
| Start processing | PATCH /services/data/v65.0/jobs/ingest/{jobId} with state UploadComplete |
| Poll status | GET /services/data/v65.0/jobs/ingest/{jobId} |
| Collect results | GET …/{jobId}/successfulResults and …/{jobId}/failedResults |
| Export at scale | POST /services/data/v65.0/jobs/query with your SOQL |
The posting pass is the simplest CSV you'll ever ship — record Ids and one field:
Id,refin__Status__c
a0Y8d000002GhIjEAK,Posted
a0Y8d000002GhIkEAK,Posted
a0Y8d000002GhIlEAK,Posted
Each row that fails lands in failedResults with the package's verbatim validation message in the error column — the same sentence a user would read, ready for your reconciliation spreadsheet.
Meridian's opening load, by the numbers
When Meridian Properties Group's UK entity went live, the cutover was three Bulk jobs run in sequence on a Saturday: 1,830 open sales invoice headers (Draft), their 2,614 lines, then the posting pass. The posting job returned 1,821 successes and 9 failures — every failure the same message about a period that hadn't been opened for the oldest legacy invoices. The team opened the period, re-ran a 9-row CSV, and tied the trial balance to the legacy system before Sunday lunch. The lesson they kept: the error file is the punch list — small, literal, and finite.
Common mistakes to avoid
| Mistake | Why it hurts | Do this instead |
|---|---|---|
| Loading lines before their headers finish | Lines reference headers that don't exist yet — a wall of lookup failures | Sequence the jobs: headers complete, then lines, then the posting pass |
| Maxing out parallelism on the posting pass | Posting is the heavy step; parallel batches contend for locks on shared parents (entities, sequences, periods) | Serial mode or modest concurrency for posting; observe, then scale |
| Expecting a "data load mode" that skips validation | There is none — all 52 packaged triggers run on every Bulk row, with no bypass flag, deliberately | Prepare data that passes the rules; the rules are the product |
| Loading historical documents into periods you've already closed | Every row fails the closure guard | Sequence the migration: load and post history first, set closure dates last |
| Treating row-count parity as success | All rows in, books still wrong — amounts, currencies or GL mappings can be consistently off | Tie the trial balance to the source system before calling it done |
Where to go next
- Opening Balances & Data Migration — the methodology.
- Bulk Posting — the UI twin of the posting pass.
- REST API — the record-at-a-time counterpart.
Frequently asked questions
Same engine underneath — Data Loader for occasional/manual loads (your implementation partner's habit), Bulk 2.0 for programmatic pipelines. The trigger behavior and the pattern above are identical.
It's cleanly split, not broken: successes are complete documents, failures didn't happen at all (per-record atomicity). Fix the error file's causes and re-run just those rows — the Reference-based idempotency keeps re-runs safe.
Yes — application objects load like any records, with all matching guards enforced per row. Load them after both sides are posted, or better: let auto-apply do the matching once documents are in.
There's no published hard limit — posting cost varies with lines per document, tax complexity and dimensions. Start moderate (50–100 documents per batch, serial), watch the job's timing and any lock errors in a sandbox dry run at production volume, and scale from evidence rather than optimism.
The same things a human doing the work would need: the packaged permission set for the documents being loaded, plus a REDA licence seat — the licence check applies to Bulk writes like any others. Nothing extra, nothing less.
Was this article helpful?