Guide · Australia · ~15 minutes
Run an Australian pay run, end to end.
This guide walks the complete lifecycle against a sandbox tenant: provision an employer, read employees, draft a pay run, preview the calculation, approve, and clean up — with a tamper-evident receipt on every step. Everything below is a real recorded run; the numbers are what the AU pack actually computed.
Step 0
Setup
BASE="https://api.ledrapay.com" # sandbox KEY="lp_sandbox_..." # server-side only — never in a browser AUTH="Authorization: Bearer $KEY" JSON="Content-Type: application/json"
Every POST accepts an Idempotency-Key header — retries replay the original response for 24 hours instead of duplicating work. Use it on anything that creates or submits.
Step 1
Provision a tenant
A tenant is one employer. Provisioning attaches the jurisdiction's regulation pack — for AU, that's a certified calculation engine plus statutory machinery (PAYG, Medicare, STSL, 12% super guarantee, awards) pinned to a versioned release.
curl -X POST $BASE/v1/partners/tenants \ -H "$AUTH" -H "$JSON" -H "Idempotency-Key: provision-bondi-1" \ -d '{ "name": "Zambrero Bondi Pty Ltd", "jurisdiction": "AU" }' # 201 { "tenant": { "id": "ten_053b83949e1882a8", "jurisdiction": "AU" }, ... }
curl -H "$AUTH" $BASE/v1/tenants/$TENANT/packs # pinned_version: "AU-2026.1" # capabilities: statutory_lodgement=native · payslip_pdf=native · payment_file=native
The capability matrix is the honesty contract: anything a pack can't do returns 501 CAPABILITY_NOT_SUPPORTED with alternatives — never a silent fake.
Step 2
Employees
curl -H "$AUTH" "$BASE/v1/tenants/$TENANT/employees?limit=10" { "data": [{ "id": "emp_1e7addab0cc6ed67", "first_name": "Alex", "last_name": "Chen", "employment": { "pay_frequency": "Weekly", "hours_per_week": 38 }, "tax_profile": { "treatment_code": "RNXXXX", "residency": "Resident", "tfn_attested": true }, "retirement": { "sg_rate": 12 } }, ...], "has_more": false, "evidence_id": "cev_mqau6xxr_128be067" }
tfn_attested: true and no TFN anywhere. Raw identifiers live only where they're statutorily required; the API carries attestations (existence + verification state), and even read operations are receipted — that's the evidence_id on the response.Employee IDs are deterministic hashes — stable across migrations and engine maintenance, safe to store on your side forever.
Step 3
Draft a pay run
Every write carries a governance_reason. It isn't decoration — it lands inside the evidence envelope for the operation, so the audit trail records why, not just what.
curl -X POST $BASE/v1/tenants/$TENANT/pay-runs \ -H "$AUTH" -H "$JSON" -H "Idempotency-Key: run-2026-06-w3" \ -d '{ "period_start": "2026-06-15", "period_end": "2026-06-21", "payment_date": "2026-06-23", "governance_reason": "regular weekly run W25" }' # 201 { "pay_run": { "id": "pr_03678b0e1d33626a", "status": "draft", "evidence_refs": ["cev_out_exe_mqav7imo_3muate9n"] } }
On creation the AU pack pulls in every employee active in the period and computes the draft. You can adjust entries while it's a draft (POST .../entries, timesheet imports); each adjustment is its own evidenced command.
Step 4
Preview — the calculation, before anything commits
curl -X POST $BASE/v1/tenants/$TENANT/pay-runs/$PR/preview -H "$AUTH" -H "$JSON" -d '{}' { "pay_run": { "status": "previewed", "totals": { "employee_count": 18, "gross": "6130.96", "tax": "1684.00", "super": "311.40", "net": "4446.96", "currency": "AUD" } }, "validation": { "errorCount": 27, "warningCount": 16, "warnings": [ ... ] } }
Two things matter here. First, the totals come from the certified engine — Ledra Pay never computes pay itself, so a preview is the same arithmetic that will finalise. Second, validation is the engine's own pre-finalise check: every error is something that would block a real run (a missing TFN declaration, an incomplete super fund), surfaced per employee while there's still time to fix it. Preview is repeatable and changes nothing at the engine.
Step 5
Approve, then finalise
The state machine is strict, and live: skipping approval gets you 409 state_conflict.
# Approve — a human decision, recorded as an evidence envelope curl -X POST $BASE/v1/tenants/$TENANT/pay-runs/$PR/approve \ -H "$AUTH" -H "$JSON" \ -d '{"governance_reason": "approved by payroll manager J. Smith"}' # Finalise — locks amounts; payslips, bank file, and super batches unlock curl -X POST $BASE/v1/tenants/$TENANT/pay-runs/$PR/finalise \ -H "$AUTH" -H "$JSON" \ -d '{"governance_reason": "W25 run finalised for payment"}' # Payslips curl -H "$AUTH" $BASE/v1/tenants/$TENANT/pay-runs/$PR/payslips
POST .../lodge, STP Phase 2) follows finalisation as the rail completes certification.Step 6
Clean up (sandbox etiquette)
curl -X DELETE $BASE/v1/tenants/$TENANT/pay-runs/$PR \ -H "$AUTH" -H "$JSON" \ -d '{"governance_reason": "scratch run cleanup"}' # draft/previewed runs only — finalised runs are immutable history (void instead)
Even the delete is an evidenced, reasoned command. There is no unrecorded path to changing payroll state — including removing it.
Step 7
The receipts
# Any evidence id from any response above: curl -H "$AUTH" $BASE/v1/tenants/$TENANT/governance/evidence/$EVIDENCE_ID # → request/response hashes, decision envelope incl. governance_reason, actor, chain link curl -H "$AUTH" $BASE/v1/tenants/$TENANT/governance/audit-trail curl -X POST $BASE/v1/tenants/$TENANT/governance/verify -H "$AUTH" -H "$JSON" -d '{}'
This run produced an evidence record for the tenant provision, the employee reads, the draft creation, the preview, the approval, and the deletion. When an auditor asks "why was this payslip this number," the answer is one API call — not an archaeology project.
Reference
Failure modes you should see (on purpose)
| Request | Response |
|---|---|
| Write without governance_reason | 400 governance_reason_required |
| Finalise before approve | 409 state_conflict — and nothing reached the engine |
| Replayed Idempotency-Key | Original response + idempotent-replay: true header |
| Same key, different body | 409 idempotency_replay_mismatch |
| Capability the pack lacks | 501 CAPABILITY_NOT_SUPPORTED + alternatives |
| Invalid statutory field | 422 pack_validation_failed with the rule ref (e.g. AU-2026.1/identity/tfn_format) |
| Another partner's tenant | 404 — isolation boundary, not 403 |
Full endpoint catalogue: the API reference. To run this guide against a sandbox of your own, request keys.