API Reference · v1 · Beta
The payroll layer, documented.
One REST API for the full embedded payroll lifecycle: tenants, certified country regulation packs, employees, pay runs, statutory lodgement, payslips, and an evidence chain on every write. Jurisdiction is a parameter — never a separate integration.
OpenAPI: openapi.json — import into Postman/Insomnia; the spec is generated from the same schemas that validate every request.
Getting started
Overview
Ledra Pay is provider-shaped: the API models what your platform ships — employees, pay runs, payslips, filings — not the internals of a calculation engine. Country logic lives in certified regulation packs attached per tenant; the request and response shapes are identical for every country.
All tenant-scoped routes are prefixed /v1/tenants/{tenantId}. Partner-scoped routes (tenant provisioning and lookup) are prefixed /v1/partners.
# Your first calculation — tenant-scoped, against a PE-native (DE) binding curl https://sandbox.ledrapay.com/v1/tenants/ten_8f3c…/calculate \ -H "Authorization: Bearer lp_sandbox_..." \ -H "Content-Type: application/json" \ -d '{ "frequency": "monthly", "employee": { "residency": "resident" }, "earnings": [ { "type": "ordinary", "amount": 3500.00 } ] }' # calculate is capability-gated (stateless_calculate): native on PE-native # (DE) bindings, 501 CAPABILITY_NOT_SUPPORTED otherwise. The unscoped # POST /v1/calculate is a 501 stub — always call the tenant-scoped route.
Getting started
Authentication
Every request carries a partner-scoped bearer key. Keys are issued per environment (lp_sandbox_*, lp_live_*; legacy ps_* keys remain valid) and grant access only to tenants registered to your partner account — strict partner-to-tenant isolation is enforced at the gateway, not in your code.
Embedded UI components authenticate with short-lived embed tokens minted server-side via POST /v1/tenants/{tenantId}/embed/token. Partner keys never touch the browser, and embed tokens cannot mint further tokens — a leaked token dies at its 15-minute TTL. Tokens are pinned to one tenant; reads within that tenant are allowed, while writes are gated by the component allowlist (components in the mint body — e.g. a payslip-viewer token cannot approve a pay run). Two optional mint fields narrow scope further: employee_id pins the token to a single employee for employee-facing surfaces, and origin binds it to one browser origin (requests presenting a different Origin header are rejected).
Components can call https://api.ledrapay.com/v1 directly from your pages — CORS is enabled, bearer-only (cookies are never accepted cross-origin). Direct cross-origin calls require an origin-bound token: mint with { "origin": "https://app.yourdomain.com" } and set api-url="https://api.ledrapay.com/v1" on the components. Alternatively, mount a same-origin reverse proxy on your own domain and skip CORS entirely — both integrations are first-class. The vendor embedding guide covers both topologies, token scoping, and the launch checklist.
Access control
User tokens & role-based access
For a full app where many users of one organisation each get restricted access (the enterprise model), mint a user token — an embed token plus a role and a data scope. Mint server-side with a partner key (or a tenant admin user token) via POST /v1/tenants/{tenantId}/users/token:
{ "role": "preparer",
"scope": { "cost_centres": ["OPS-ACS"], "sites": "*" },
"user_id": "usr_…", "user_name": "Priya P." }
Roles enforce segregation of duties (not a linear ladder). The API gate is the boundary; the SDK mirrors it by hiding/disabling buttons (query GET /v1/tenants/{id}/me for the caller's role + scope):
| viewer | reads only |
| preparer | create & edit pay runs, employees — cannot approve/finalise/submit/void |
| approver | approve · finalise · submit (STP) · void — cannot prepare (four-eyes) |
| admin | everything within the tenant, incl. offboard & config; may mint lower user tokens (never above its own role or wider than its own scope) |
Data scope restricts which rows a user sees by cost-centre/site. It fails closed: a scoped user querying a data source that can't attach the cost-centre dimension receives an empty result with scope_not_enforceable: true — never unfiltered data. (Cost-centres are a governed overlay, not engine-native; enforcement is full where the overlay is present and honest-empty where it isn't yet.) Unscoped users ("*") are unaffected.
POST /tenants/{tenantId}/users/tokenMint a user-scoped token (role + data scope) — partner key or admin delegation
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| role | string | no | viewer | preparer | approver | admin · default "viewer" | — |
| scope | object | no | { cost_centres, sites } | data scope; omitted keys default to "*" (partner keys) / must stay within the delegating admin's own scope |
| components | array of string | no | — | component allowlist, or ["*"] (default) |
| employee_id | string | no | — | pin the token to one employee |
| origin | string | no | ^[a-zA-Z][a-zA-Z0-9+.-]*://[^/]+$ | browser origin the token is bound to, e.g. https://app.example.com (scheme://host[:port], no path) |
| user_id | string | no | — | — |
| user_name | string | no | — | — |
201 → data: { token, expires_in, role, scope }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/meCaller context (mode, role, data scope, components, pinned employee)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: { mode, user, role, scope, components, employee_id } · caller context decoded from the token (no DB read) — backs the SDK scope-aware UX
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Bureau
Portfolio API
For a payroll bureau / MSP running payroll across many client companies (the bureau model), the portfolio endpoints aggregate across every tenant on the partner key. Partner key only — tenant-scoped embed/user tokens are refused (they can't span a portfolio). First-party use behind an operator session; the partner key is never exposed to the browser.
GET /partners/portfolio/summaryAggregate KPI stats across the partner's client tenants
200 → data: { stats, tenant_count, generated_at, errored_tenants }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /partners/portfolio/pipelinePay-runs across the portfolio grouped by lifecycle (kanban)
200 → data: { draft, approved, blocked, finalised }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /partners/portfolio/clientsPer-client status rows across the portfolio
200 → data: array of object (open) · per-client status row: id, name, country, site_count, workers, gross_mtd, currency, risk, health, in_flight, next_pay_date, days_until_pay
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
The bureau SDK components (ledrapay-portfolio-dashboard, ledrapay-client-list, ledrapay-pipeline-board) render these. Because they carry portfolio-wide access they authenticate via a same-origin session only and must never be embedded in an untrusted page — see the SDK docs.
Getting started
Conventions
Idempotency
All POST endpoints accept an Idempotency-Key header. Retries with the same key return the original response for 24 hours. Use it on everything that creates pay runs or submits to an authority.
Pagination
List endpoints return data, has_more, and next_cursor. Pass ?cursor= and ?limit= (max 200).
Money and dates
Amounts are decimal strings with an explicit currency (ISO 4217). Dates are YYYY-MM-DD; timestamps are UTC ISO 8601. Identifiers are prefixed: ten_, emp_, pr_, ps_, ldg_, and cev_ for evidence records — every governed write returns an evidence_id with this prefix (cev_mock_ in sandbox mock-governance mode).
Versioning
The API is versioned in the path (/v1) and changes additively. Regulation packs are versioned independently (AU-2026.1) — statutory updates ship as new pack releases, never as silent behaviour changes under a pinned version.
Getting started
Core concepts
| Object | What it is |
|---|---|
| Partner | You — the platform embedding Ledra Pay. Holds API keys and a portfolio of tenants. |
| Tenant | An employer (your customer). Owns employees, pay runs, and attached regulation packs. Fully isolated. |
| Regulation pack | A certified, versioned country ruleset — tax scales, social contributions, statutory outputs. Attached to a tenant, pinned to a version. |
| Pay run | The lifecycle object: draft → previewed → approved → finalised → lodged (lodged via the AU STP rail — see statutory lodgement), plus forecast / retro ROADMAP variants. |
| Shadow run ROADMAP | A pay run calculated for comparison only — returns a line-level variance report against incumbent results you supply. |
| Lodgement | A statutory submission generated from a finalised pay run. Live for AU STP Phase 2 (POST …/pay-runs/{prId}/submit); RTI and payday filing ROADMAP. |
| Evidence | An immutable, hash-chained governance record written on every state-changing call. |
Platform
Partners & tenants
Provision a tenant per employer. A tenant is created with its engine binding (pack + version) attached at provisioning time — the pack is pinned on the binding, not patched in later.
POST /partners/tenantsProvision a tenant (employer) + engine binding under the calling partner
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| name | string | yes | min 1 | — |
| jurisdiction | string | no | AU | DE | NZ | UK | US · default "AU" | jurisdiction |
| engine | string | no | default "mock" | engine to bind: payroll_engine_au (AU), payroll_engine (DE), myaccountant, mock (dev only) |
| pack_version | string | no | — | pin a specific regulation-pack version (defaults to the pack current_version) |
| connection_id | string | no | — | cin_* governance-cell connection (required for myaccountant bindings) |
| connector_pack | string | no | — | connector pack backing the connection. Defaults to myaccountant_company — the per-customer pack, where the company is pinned on the connection and book-level operations are absent. Pass myaccountant_partner only for a legacy shared partner connection. |
| engine_tenant_ref | string | no | — | existing engine-side tenant reference to bind to |
201 → data: { tenant, binding }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /partners/tenantsList the calling partner's tenants (paginated)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| limit | query | integer | string | no | page size, 1–200 (default 50) |
| cursor | query | string | no | opaque cursor from a previous response |
200 → data: array of { id, slug, name, jurisdiction, group } · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /partners/tenants/{tenantId}Fetch one tenant with its engine bindings
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: { tenant, bindings }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Batch operations across tenants and tenant update/unregister are on the roadmap.
Platform
Regulation packs
Packs are how Ledra Pay stays one integration across countries. Attach a pack to a tenant and pin a version; the pay run API never changes shape. Certification status, maintainer, and changelog are queryable — compliance is an API object, not a PDF.
GET /packsList the regulation-pack registry
200 → data: array of { code, jurisdiction, name, current_version }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /packs/{packCode}Fetch one regulation pack by code
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| packCode | path | string | yes | min 1 |
200 → data: { code, jurisdiction, name, current_version }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/packsList the tenant's active bindings with their pinned pack + capability matrix
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of { binding_id, engine, role, pinned_version, pack, capabilities, capability_basis, capability_grant_state } · active engine binding + the regulation pack it pins
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · CAPABILITY_NOT_SUPPORTED 501 · plus 401/403/404 isolation — see Errors.
{
"code": "AU",
"current_version": "AU-2026.1",
"status": "beta", // draft | beta | certified
"maintainer": "Ledra Pay Compliance (AU)",
"scope": ["paye_tax", "medicare_levy", "stsl", "super_guarantee", "awards", "payroll_tax_state"],
"lodgement_rails": [{ "type": "STP2", "channel": "ATO-SBR", "status": "in_build" }],
"versions": [{ "version": "AU-2026.1", "effective": { "from": "2026-07-01", "to": "2027-06-30" } }]
}
| Pack | Calculation | Lodgement rail |
|---|---|---|
| AU | Beta | STP Phase 2 — submit + status polling live on the sandbox rail (lodgement) |
| NZ | Beta | Payday filing — planned next, with design partner |
| UK | Beta | RTI FPS/EPS — planned next, with design partner |
| DE | Beta | ELSTER + GKV/ITSG — design-partner project |
| FR · NL · BE · ES · PT · AT · CH · LU · US | Beta | Statutory outputs only |
Capability matrix
GET /tenants/{tenantId}/packs returns the attached pack(s) with a capability matrix per binding. Capabilities are how the one API stays honest across engines that genuinely differ: a feature is either native or unsupported, never silently faked. An unsupported call returns 501 CAPABILITY_NOT_SUPPORTED with the alternatives, not a wrong answer.
{
"data": [{
"binding_id": "bnd_…", "engine": "…", "pinned_version": "DE-2026.1",
"capabilities": {
"effective_dating": "native", // as_of reads / effective_from writes
"stateless_calculate": "native", // forecast /calculate with no pay run
"statutory_lodgement": "unsupported",
"payment_file": "unsupported",
"payslip_pdf": "unsupported"
}
}]
}
| Capability | AU pack | DE pack | What it gates |
|---|---|---|---|
| effective_dating | unsupported | native | ?as_of= reads · effective_from writes |
| stateless_calculate | unsupported | native | POST /calculate with no pay run |
| statutory_lodgement | native | unsupported | STP / lodgement rails |
| payment_file · payslip_pdf · retirement_batch | native | unsupported | ABA / payslip PDF / SuperStream |
getCapabilities() / supportsEffectiveDating()) so an unsupported feature is hidden, not a runtime 501.Platform
Organisation
Company details are read from the tenant's bound engine; the company bank account (the debit side of the payment file) is readable and updatable. Cost centres are a Ledra Pay governed overlay — they drive the data-scope enforcement in role-based access and cost-centre grouping on entries and payslips.
GET /tenants/{tenantId}/companyCompany / organisation details
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · company/organisation details (engine-shaped) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/company/bankEmployer disbursement bank account
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · employer disbursement account (engine-shaped: account name, BSB, account number…) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/company/bankSet the employer disbursement bank account (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| account_name | string | no | — | — |
| bsb | string | no | — | — |
| account_number | string | no | — | — |
| bank_name | string | no | — | — |
| fi_code | string | no | — | — |
| apca_user_id | string | no | — | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: object (open) · engine write result · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Cost centres
Full CRUD, per-employee allocation, and engine-dimension mappings. All writes require a governance_reason and are evidence-chained like every other write.
GET /tenants/{tenantId}/cost-centresList cost centres (active by default)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| include_archived | query | string | no | include archived cost centres — true | false |
| tenantId | path | string | yes | min 1 |
200 → data: array of { code, name, parent_code, gl_account, site, status }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/cost-centresCreate a cost centre (governed — reason required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| code | string | yes | ^[A-Z0-9][A-Z0-9-]*$ | cost-centre code (UPPERCASE alphanumeric + dashes) |
| name | string | yes | min 1 | — |
| parent_code | string | null | no | — | — |
| gl_account | string | null | no | — | — |
| site | string | null | no | — | — |
| status | string | no | active | archived | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: { code, name, parent_code, gl_account, site, status } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/cost-centres/{code}Update a cost centre (declared fields only; governed — reason required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| code | path | string | yes | cost-centre code (UPPERCASE alphanumeric + dashes) — ^[A-Z0-9][A-Z0-9-]*$ |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| name | string | no | min 1 | — |
| parent_code | string | null | no | — | — |
| gl_account | string | null | no | — | — |
| site | string | null | no | — | — |
| status | string | no | active | archived | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { code, name, parent_code, gl_account, site, status } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/cost-centres/{code}Archive a cost centre (governed — reason via x-governance-reason header)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| code | path | string | yes | cost-centre code (UPPERCASE alphanumeric + dashes) — ^[A-Z0-9][A-Z0-9-]*$ |
200 → data: { code, status } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PUT /tenants/{tenantId}/employees/{employeeId}/cost-centreAssign an employee's primary cost centre (governed — reason required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | employee id (emp_<16 hex>) — ^emp_[A-Za-z0-9_-]+$ |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| cost_centre_code | string | yes | ^[A-Z0-9][A-Z0-9-]*$ | cost-centre code (UPPERCASE alphanumeric + dashes) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { employee_id, cost_centre_code } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/employees/{employeeId}/cost-centreClear an employee's cost-centre allocation (governed — reason via header)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | employee id (emp_<16 hex>) — ^emp_[A-Za-z0-9_-]+$ |
200 → data: { employee_id, cost_centre_code } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/cost-centre-mappingsList attribute → cost-centre mapping rules
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| source | query | string | no | employee | pay_item |
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, source, attribute, match_value, cost_centre_code, priority }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/cost-centre-mappingsCreate a mapping rule (governed — reason required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| source | string | yes | employee | pay_item | — |
| attribute | string | yes | min 1 | dot-path on the entity (e.g. 'employment.type'); matched against match_value |
| match_value | string | number | yes | — | value to match; '*' is the catch-all |
| cost_centre_code | string | yes | ^[A-Z0-9][A-Z0-9-]*$ | cost-centre code (UPPERCASE alphanumeric + dashes) |
| priority | integer | no | ≥ 0 · default 100 | lower wins; rules evaluated priority-ordered |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: { id, source, attribute, match_value, cost_centre_code, priority } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/cost-centre-mappings/{id}Delete a mapping rule (governed — reason via x-governance-reason header)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| id | path | string | yes | mapping id (ccm_…) — min 1 |
200 → data: { id, deleted } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
People
Employees
One employee model for every country. Country-specific fields (TFN declarations, UK tax codes, NI numbers) live under tax_profile, validated by the tenant's attached pack — your integration code stays identical.
GET /tenants/{tenantId}/employeesList employees (cursor-paginated; scoped users see only in-scope rows)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| limit | query | integer | string | no | page size (default 50, max 200) — ^[0-9]+$ |
| cursor | query | string | no | opaque cursor from a previous response |
| page | query | string | no | legacy page number — ignored; use cursor — ^[0-9]+$ |
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls) · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/employeesCreate an employee (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| first_name | string | yes | — | — |
| last_name | string | yes | — | — |
| employment | object | no | open object (passthrough) | employment terms (type, annual_salary, pay_frequency, start/end dates) |
| status | string | no | active | terminated | — |
| effective_from | string | no | YYYY-MM-DD | effective-dated write (engines with native effective_dating only) |
| jurisdiction_data | object | no | open object (passthrough) | per-country extension fields captured by the tax/bank/pension forms |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | object | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
201 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}Get one employee (statutory fields attested, never raw PII)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/employees/{employeeId}Update an employee (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| first_name | string | no | — | — |
| last_name | string | no | — | — |
| employment | object | no | open object (passthrough) | employment terms (type, annual_salary, pay_frequency, start/end dates) |
| status | string | no | active | terminated | — |
| effective_from | string | no | YYYY-MM-DD | effective-dated write (engines with native effective_dating only) |
| jurisdiction_data | object | no | open object (passthrough) | per-country extension fields captured by the tax/bank/pension forms |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | object | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/employees/{employeeId}Terminate an employee (hard soft-delete, irreversible via the API)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/employees/{employeeId}/end-employmentEnd employment by date (reversible offboard)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| effective_date | string | no | YYYY-MM-DD | end date (defaults to today; a future date schedules a leaver) |
| reason_code | string | no | — | optional STP cessation reason code |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/employees/{employeeId}/reactivateReactivate an end-dated employee
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}/tax-settingsRead an employee's tax settings section
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: object (open) · tax section as the engine holds it (attested flags, never raw PII) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/employees/{employeeId}/tax-settingsSave an employee's tax settings (flat form fields; governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| tfn | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| country | string | no | — | jurisdiction code for the write (defaults 'au'); lowercased server-side |
| employee_type | string | no | — | engine employee type (tax metadata; defaults 'Employee') |
| effective_from | string | no | YYYY-MM-DD | effective-dated write (engines with native effective_dating only) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}/bank-accountsRead an employee's bank details section
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: object (open) · bank section as the engine holds it (attested flags, never raw account numbers) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/employees/{employeeId}/bank-accountsSave an employee's bank details (flat form fields; governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| bank_bsb | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| bank_account_number | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| bank_account_name | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| country | string | no | — | jurisdiction code for the write (defaults 'au'); lowercased server-side |
| employee_type | string | no | — | engine employee type (tax metadata; defaults 'Employee') |
| effective_from | string | no | YYYY-MM-DD | effective-dated write (engines with native effective_dating only) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}/pensionRead an employee's super/pension section
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: object (open) · super/pension section as the engine holds it · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/employees/{employeeId}/pensionSave an employee's super/pension details (flat form fields; governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| as_of | query | string | no | ISO date YYYY-MM-DD |
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| super_fund_name | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| super_usi | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| super_member_number | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| smsf_account_name | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| smsf_bsb | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| smsf_account_number | string | no | — | PII — transmitted as an inline attestation, never stored raw |
| country | string | no | — | jurisdiction code for the write (defaults 'au'); lowercased server-side |
| employee_type | string | no | — | engine employee type (tax metadata; defaults 'Employee') |
| effective_from | string | no | YYYY-MM-DD | effective-dated write (engines with native effective_dating only) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { id, first_name, last_name, status, employment, statutory, jurisdiction_data } · Canonical cross-country employee. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}/readinessPer-tab payroll readiness (verdicts only, never field values)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: { jurisdiction, tabs, blocks_finalise, unverified_requirements } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/employees/{employeeId}/completenessPer-tab payroll readiness (legacy alias of /readiness)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| employeeId | path | string | yes | — |
200 → data: { jurisdiction, tabs, blocks_finalise, unverified_requirements } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Effective dating
Employee facts are effective-dated on engines that support it (capability effective_dating: native): a write carries an optional effective_from that stamps when the change takes effect, and a read carries an optional as_of that resolves the value in force on that date (default: today). A future-dated raise is the canonical case — it does not change "now", but it is already on file.
// DE / PE-native packs only — AU (myaccountant) returns 501 for a non-today date // future-dated raise — takes effect 2026-09-01, not today PATCH /tenants/{t}/employees/emp_8f3c… { "employment": { "annual_salary": 90000 }, "effective_from": "2026-09-01" } GET …/employees/emp_8f3c…?as_of=2026-08-01 → annual_salary 60000 // before GET …/employees/emp_8f3c…?as_of=2026-10-01 → annual_salary 90000 // after GET …/employees/emp_8f3c… → annual_salary 60000 // today (raise still future)
as_of/effective_from returns 501 CAPABILITY_NOT_SUPPORTED rather than silently ignoring the parameter (today/omitted always works). Check the capability matrix first. Effective-dating is scoped to the employee record: a finalised pay run already froze the values in force at its period-end, so its entries are point-in-time by construction.People
Leave
Leave is split across two planes on purpose. Leave requests (pending / approved / rejected / cancelled) are a Ledra Pay control-plane object — the payroll engines never see them. The engine only ever sees leave taken: the pay-affecting absence booked on approval.
governance_reason) and returns the cev_ evidence id on the request. Reject and cancel never touch the engine.Request lifecycle
POST /tenants/{tenantId}/leave/requestsApply for leave (creates a pending control-plane request; no engine write)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| employee_id | string | yes | min 1 | — |
| leave_type | string | yes | min 1 | — |
| employee_name | string | no | — | — |
| start_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| end_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| quantity | number | no | — | — |
| unit | string | no | hours | days | — |
| reason | string | no | max 2000 | — |
201 → data: { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/leave/requestsList leave requests (status / employee filters, cursor-paginated)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| status | query | string | no | pending | approved | rejected | cancelled |
| employee_id | query | string | no | min 1 |
| limit | query | integer | string | no | page size, 1–200 (default 50) |
| cursor | query | string | no | opaque cursor from a previous response |
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/leave/requests/{id}Get one leave request
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| id | path | string | yes | leave-request id (req_…) — min 1 |
200 → data: { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/requests/{id}/approveApprove a pending request (governed engine booking — reason required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| id | path | string | yes | leave-request id (req_…) — min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| note | string | no | max 500 | decision note stored on the request |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/requests/{id}/rejectReject a pending request (no engine write)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| id | path | string | yes | leave-request id (req_…) — min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| note | string | no | max 500 | decision note stored on the request |
200 → data: { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/requests/{id}/cancelCancel (withdraw) a pending request (no engine write)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| id | path | string | yes | leave-request id (req_…) — min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| note | string | no | max 500 | withdrawal note stored on the request |
200 → data: { id, employee_id, employee_name, leave_type, start_date, end_date, quantity, unit, reason, … } · Control-plane leave request (never touches the engine until approved). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Engine leave — taken, balances, configuration
The engine-side surface for leave that affects pay. Setup and booking calls are capability-gated per pack: AU is entitlement-centric (leave types are assignable, taken leave rides the pay run); on DE leave types are fixed by the regulation (configure/create return 501) and absences are effective-dated cases.
GET /tenants/{tenantId}/reference/leave-typesLeave-type catalogue (engine-sourced + statutory)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of { code, name, category, paid, unit, source } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/reference/leave-typesCreate/assign a company leave type (governed write; AU only)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| leave_type | string | no | — | master leave type hint (typeCode or name, e.g. "Annual Leave") |
| name | string | no | — | alias hint for leave_type |
| master_id | string | number | no | — | exact master pay-item type id (skips the hint lookup) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: object (open) · created/assigned company leave type (id, name, master_id) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/leave-balancesLeave balances (accrued/taken/balance per employee per type)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of { employee_id, employee_name, leave_type, accrued, taken, balance, unit } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/configureConfigure leave entitlements/accrual (governed write; AU only)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| vendorId | string | number | no | — | engine employee ref the entitlement applies to |
| accrual_method | string | no | — | accrual method (default 'Monthly') |
| annual_entitlement_hours | number | no | — | — |
| personal_entitlement_hours | number | no | — | — |
| start_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | string | number | boolean | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
201 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/bookBook leave taken / a pay-affecting absence (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| employee_id | string | no | ^emp_[A-Za-z0-9_-]+$ | employee id (emp_<16 hex>) |
| vendorId | string | number | no | — | legacy/explicit engine employee ref (prefer employee_id) |
| payRunEmployeeId | string | number | no | — | legacy alias for vendorId |
| leave_type | string | no | annual | personal | sick | compassionate | long_service | family_domestic_violence | community | parental | maternity | short_time | special | unpaid | cash_out | other | canonical leave category |
| category | string | no | — | legacy loose label alias for leave_type (engine names/synonyms tolerated) |
| leave_type_code | string | number | no | — | specific engine type code — disambiguates same-category types |
| quantity | number | no | ≥ 0 | hours (AU) | days (DE) |
| hours | number | no | ≥ 0 | legacy alias for quantity (unit=hours) |
| days | number | no | ≥ 0 | legacy alias for quantity (unit=days) |
| unit | string | no | hours | days | — |
| start_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| end_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| pay_run_id | string | no | — | AU: pin the target draft run (else discovered) |
| amount | number | no | — | AU: explicit line amount (defaults 0 — engine computes) |
| effective_from | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: { booked, employee_id, leave_type, unit, quantity, start_date, end_date, evidence_id } · The canonical booking receipt — identical across engines (AU pay-item line, DE absence case). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/leaveBooked-leave history (leave taken)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| employee_id | query | string | no | employee id (emp_<16 hex>) — ^emp_[A-Za-z0-9_-]+$ |
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, employee_id, employee_name, leave_type, unit, quantity, start_date, end_date, status } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/{recordId}/cancelCancel a booked leave record (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| recordId | path | string | yes | engine handle from the leave-taken list — min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| pay_run_id | string | no | — | AU: pin the target draft run (else discovered) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · cancellation result (id, status) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/leave/{recordId}/amendAmend a booked leave record (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| recordId | path | string | yes | engine handle from the leave-taken list — min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| quantity | number | no | ≥ 0 | — |
| amount | number | no | — | AU: explicit line amount |
| start_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| end_date | string | null | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| pay_run_id | string | no | — | AU: pin the target draft run (else discovered) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { booked, employee_id, leave_type, unit, quantity, start_date, end_date, evidence_id } · The canonical booking receipt — identical across engines (AU pay-item line, DE absence case). · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Three SDK components cover the flow end to end — ledrapay-leave-request (employee), ledrapay-leave-approvals (manager), and ledrapay-leave-management (balances + history) — try them wired together at playground.ledrapay.com/#leave-flow.
People
Timesheets
Roadmap — not yet available
| POST | /tenants/{tenantId}/timesheets | ROADMAP Create timesheet with daily entries |
| POST | /tenants/{tenantId}/timesheets/bulk | ROADMAP Bulk create for multiple employees |
| GET | /tenants/{tenantId}/timesheets | ROADMAP List with filters (employee, status, period) |
| POST | /tenants/{tenantId}/timesheets/{id}/approve | ROADMAP Approve for pay-run import |
Payroll
Pay runs
The pay run is a state machine: draft → previewed → approved → finalised, and on the AU rail → lodged after a successful STP submit. Preview never advances state — call it as often as you like; approve is a control-plane transition (an evidence envelope, no engine command); finalise commits to the engine. forecast and retro variants are roadmap.
POST /tenants/{tenantId}/pay-runsCreate a draft pay run
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| kind | string | no | regular | adhoc | reversal | — |
| period_start | string | yes | YYYY-MM-DD | ISO date YYYY-MM-DD |
| period_end | string | yes | YYYY-MM-DD | ISO date YYYY-MM-DD |
| payment_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runsList pay runs (engine-primary merge of engine runs + Ledra-governed run_index)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| limit | query | string | no | page size (1–200, default 50) — ^\d+$ |
| cursor | query | string | no | opaque cursor from a previous response |
| status | query | string | no | draft | previewed | approved | finalised | lodged | voided | deleted |
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}Get one pay run (Ledra-governed or engine-native pr_eng_ id)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/pay-runs/{payRunId}Delete a draft/previewed run (engine delete; run_index row marked deleted)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/exceptionsGoverned R2P exceptions for a run (block/warn/info findings + verdict + freshness)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: { findings, summary, source, source_freshness, freshness }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/entries/{entryId}/adjustGoverned pay-item adjustment for one entry (preview verdict or committed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
| entryId | path | string | yes | — |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| mode | string | no | preview | commit | preview = zero-write verdict; commit = governed write |
| adjustments | array of object | yes | min 1 item · items { pay_item_type_id, code, type, amount, quantity, rate, … } | — |
| ack | boolean | no | — | — |
| rule_ref | string | no | — | the R2P control this adjustment resolves (e.g. R2P-5) |
| control_id | string | no | — | alias for rule_ref |
| before_gross | string | number | no | — | — |
| expected_gross | string | number | no | — | — |
| thresholds | object | no | open object (passthrough) | R2P threshold scaffold inputs (profile auto-resolve is a server-side TODO) |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · governed-adjustment result — platform verdict (pass / needs_ack / block), per-item outcomes, evidence refs
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/adjust/bulkGoverned bulk adjustment (selector → action, expanded and gated server-side)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| mode | string | no | preview | commit | preview = zero-write verdict; commit = governed write |
| selector | object | yes | { employee_ids, pay_item_type_id, code, classification } | match spec — expanded server-side against the run's authoritative pay_items |
| action | object | yes | { op, type, pay_item_type_id, amount, quantity } | what to apply to each matched item |
| ack | boolean | no | — | — |
| rule_ref | string | no | — | — |
| control_id | string | no | — | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · governed-adjustment result — platform verdict (pass / needs_ack / block), per-item outcomes, evidence refs
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/entriesList the entries (one per employee) on a run
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: array of { id, employee_id, employee_name, gross, tax, net, statutory, pay_items } · One employee line on a run. · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/entriesAdd an employee entry to a draft run
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| employee_id | string | yes | ^emp_[A-Za-z0-9_-]+$ | employee id (emp_<16 hex>) |
| pay_items | array of object | no | — | — |
| lines | array of object | no | — | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | any | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
201 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/pay-runs/{payRunId}/entries/{entryId}Edit an entry's pay items on a draft/previewed run
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
| entryId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| pay_items | array of object | no | min 1 item | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| additional fields | any | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
DEL /tenants/{tenantId}/pay-runs/{payRunId}/entries/{entryId}Remove an entry from a draft run
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
| entryId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/previewPreview a run (compute entries + totals; draft/previewed → previewed)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { pay_run, entries, validation } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/approveApprove a previewed run (control-plane transition; evidence envelope only)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/finaliseFinalise an approved run (engine commit; freezes the cost-centre allocation)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/voidVoid a run (terminal control-plane status; row stays, engine untouched)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
| reason | string | no | min 3 · max 500 | alias for governance_reason (SDK void dialog sends { reason }) |
200 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/cloneClone a run into a new draft for a new period
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| period_start | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| period_end | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| payment_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| as_reversal | boolean | no | — | — |
| start_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| end_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| pay_date | string | no | YYYY-MM-DD | ISO date YYYY-MM-DD |
| reversal | boolean | no | — | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
201 → data: { id, kind, status, period_start, period_end, payment_date, source, engine_status, totals } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/pay-runs/{payRunId}/entries/{entryId}/set-payitemSet one earnings line (hours + amount) on a draft-run employee
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
| entryId | path | string | yes | — |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| pay_item_type_id | string | yes | min 1 | — |
| quantity | string | number | null | no | — | hours for the line |
| amount | string | number | null | no | — | rate/amount for the line |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: { ok } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
# Preview response (abridged) — identical shape for every country { "pay_run": "pr_8f2c", "status": "previewed", "pack": "AU-2026.1", "entries": [{ "employee": "emp_31ad", "gross": { "amount": "3269.23", "currency": "AUD" }, "lines": [ { "type": "tax.paye", "amount": "-612.00" }, { "type": "tax.medicare_levy", "amount": "-65.38" }, { "type": "employer.super_guarantee", "amount": "392.31", "employer_cost": true } ], "net": { "amount": "2591.85", "currency": "AUD" } }], "evidence": "cev_a90b" // every calculation is receipted }
Entries — the cross-country shape
GET …/pay-runs/{prId}/entries returns one row per employee with a jurisdiction-stable core and pack-variant extras. The core is guaranteed identical in every country; extras are additive per pack and never replace core fields.
{
"data": [{
// core — every pack, always (amounts are 2dp decimal strings)
"id": "…", "employee_id": "emp_0e4c888c5987c068",
"employee_name": "Alex Chen",
"gross": "1140.00", "tax": "299.00", "net": "841.00",
// pack-variant extras (additive):
"super": "136.80", // AU/NZ: superannuation / KiwiSaver
"social": "1057.50", // DE: KV+RV+AV+PV employee share
"wage_types": [ "…" ] // PE-native packs: full statutory line breakdown
}],
"evidence_id": "cev_…"
}
Rows with all-zero amounts are engine truth, not errors: employees active in the period but with no pay items yet. Currency lives on the run's totals, never per entry.
Payroll
Shadow runs
Roadmap — not yet available
Prove the numbers before anything goes live. Post the inputs from a real payrun together with your incumbent provider's results; Ledra Pay calculates independently and returns a line-level variance report. Shadow runs never lodge, never pay, and never touch production state.
| POST | /tenants/{tenantId}/shadow-runs | ROADMAP Create shadow run with inputs and incumbent results |
| GET | /tenants/{tenantId}/shadow-runs/{id} | ROADMAP Variance report: matched, divergent, and unexplained lines |
{
"shadow_run": "sh_c41e",
"summary": { "entries": 640, "matched": 634, "divergent": 6, "max_variance": "4.85" },
"divergences": [{
"employee": "emp_99c2", "line": "tax.stsl",
"incumbent": "128.00", "ledrapay": "123.15",
"explanation": "Incumbent applied 2024-25 STSL thresholds after 1 July"
}]
}
Payroll
Calculation engine
Stateless gross-to-net for embedding calculations in your own UX — quote a net salary in onboarding — without creating payroll objects. Tenant-scoped and capability-gated: stateless_calculate is native on PE-native (DE) bindings and unsupported elsewhere (501). The unscoped POST /v1/calculate is a 501 stub that returns the capability matrix — always call the tenant-scoped route.
POST /tenants/{tenantId}/calculateStateless gross-to-net calculation via the bound engine (capability-gated)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| additional fields | any | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
200 → data: { pack, gross, net, provenance } · stateless calculation result
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · CAPABILITY_NOT_SUPPORTED 501 · plus 401/403/404 isolation — see Errors.
POST /calculateUnbound stateless calculation — always 501 with the capability matrix (use the tenant-bound route)
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| additional fields | any | no | — | jurisdiction passthrough — extra fields are accepted and forwarded to the pack |
Errors (501 / 4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · CAPABILITY_NOT_SUPPORTED 501 · plus 401/403/404 isolation — see Errors.
Payroll
Statutory lodgement
POST …/pay-runs/{prId}/submit lodges a finalised run's pay event via the AU engine's STP rail and moves the run to lodged. Gated by the statutory_lodgement capability (native on AU, unsupported on DE — the DE rails, ELSTER + GKV/ITSG, are a design-partner build advertised on the pack as design_partner_next). The generic multi-rail /lodgements surface (RTI, payday filing) is roadmap.POST /tenants/{tenantId}/pay-runs/{payRunId}/submitLodge STP to the ATO for a finalised run (signatory required)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| event_type | string | no | pay | update | — |
| submission_type | string | no | pay | update | alias for event_type |
| is_final | boolean | no | — | marks the EOFY finalisation declaration |
| signature_user | string | no | min 1 | ATO declaration signatory — full name |
| signatory | string | no | min 1 | alias for signature_user |
| signature_name | string | no | min 1 | alias for signature_user |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · statutory lodgement record (submission id, status, authority response) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/submissionsList STP submissions lodged for a run
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: array of object (open) · statutory lodgement record (submission id, status, authority response) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/submissions/{submissionId}Poll ATO lodgement status for one STP submission
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
| submissionId | path | string | yes | — |
200 → data: object (open) · statutory lodgement record (submission id, status, authority response) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/stpSTP lodgement history for a pay run
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | min 1 |
200 → data: array of object (open) · statutory lodgement record (submission id, status, authority response) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Payroll
Payslips & payments
GET /tenants/{tenantId}/pay-runs/{payRunId}/payslipsList payslips for a run (post-preview/finalise; pinned tokens see only their own)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: array of { id, employee_id, employee_name, gross, net, cost_centre, lines, pdf } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-runs/{payRunId}/payment-fileEngine payment file (ABA) for a finalised run
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
200 → data: object (open) · vendor payment-file payload — ABA file text or a structured object; null when the engine has no payment-file capability · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/embed/tokenMint a short-lived embed token for SDK components (partner-key only)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| components | array of string | no | — | component allowlist, or ["*"] (default) |
| employee_id | string | no | — | pin the token to one employee (employee-facing surfaces) |
| origin | string | no | ^[a-zA-Z][a-zA-Z0-9+.-]*://[^/]+$ | browser origin the token is bound to, e.g. https://app.example.com (scheme://host[:port], no path) |
201 → data: { token, expires_in }
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Superannuation
The payday-super leg: SG contributions are per-payday, with fund receipt due within 7 business days of the qualifying-earnings day. Batches are created from a finalised run (you pay super on what you finalised); the engine remits via SuperStream — these routes initiate and record, they never move money.
POST /tenants/{tenantId}/pay-runs/{payRunId}/superCreate a super batch from a finalised run (SuperStream remittance is engine-side)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| payRunId | path | string | yes | — |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| description | string | no | — | — |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · super batch record (engine shape) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/superannuation/overviewSuperannuation obligations overview for a financial year
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| financialYear | query | string | no | AU financial year END year (2026 = FY2025-26) — ^\d{4}$ |
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · super obligations overview (header, summary, per-employee rows) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/superannuationList superannuation (SuperStream) batches
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of object (open) · super batch (engine-shaped: id, description, period, totalAmount, status) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/superannuation/{superId}/historyStatus history for a super batch
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| superId | path | string | yes | min 1 |
200 → data: array of object (open) · status-history entry (engine-shaped) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
PATCH /tenants/{tenantId}/superannuation/{superId}/statusTransition a super batch's status (governed write)
Governed write. Requires governance_reason (body) or the x-governance-reason header — bound into the evidence chain; the response carries evidence_id (cev_).
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| superId | path | string | yes | min 1 |
Request body
| Field | Type | Req | Constraints | Description |
|---|---|---|---|---|
| status | string | yes | min 1 | engine-native batch status (e.g. 'Lodged', 'Paid') |
| governance_reason | string | no | min 3 · max 500 | why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header. |
200 → data: object (open) · the transitioned batch (engine-shaped) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — governance_reason_required 400 · bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
finalise commits the payrun and persists entries but payment-file returns an honest empty payload.Trust
Governance
Every state-changing call writes an immutable evidence record into a hash chain: actor, reason, inputs digest, pack version, result digest. When a regulator, auditor, or customer asks "why was this payslip this number", the answer is one API call.
GET /tenants/{tenantId}/governance/evidence/{evidenceId}Fetch one governed-evidence record
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
| evidenceId | path | string | yes | min 1 |
200 → data: object (open) · governed-evidence envelope (cev_ id, actor, operation, hash chain)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/governance/audit-trailGovernance-plane audit events (paginated) + this tenant's run-level evidence refs
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| limit | query | integer | string | no | page size, 1–200 (default 50) |
| cursor | query | string | no | opaque cursor from a previous response |
| tenantId | path | string | yes | min 1 |
200 → data: array of object (open) · governance-plane audit event · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
POST /tenants/{tenantId}/governance/verifyVerify the tenant's evidence hash chain with the governance provider
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
Request body (optional)
| Field | Type | Req | Constraints | Description |
|---|
200 → data: object (open) · provider chain-verification result: valid (boolean), length, broken_at?
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Trust
Reference data
The static catalogues, per-regulation case metadata, and reporting reads. Engine leave types live with Leave.
GET /reference/awardsModern Award rate scales (kernel-sourced; read-only consumer view)
200 → data: array of object (open) · award rate scale (code, name, country, industry, levels[] with base/penalty rates)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reports/summaryPayroll activity summary (KPIs + per-cost-centre breakdown)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · activity summary (gross/net/tax/super totals, by_cost_centre[] breakdown) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reports/activityPayroll activity time-series (trend charts)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| limit | query | integer | string | no | page size, 1–200 (default 50) |
| cursor | query | string | no | opaque cursor from a previous response |
| tenantId | path | string | yes | min 1 |
200 → data: array of object (open) · activity period point (period_start/end, payment_date, status, gross/net/tax/super, currency, employee_count) · evidence_id (cev_ receipt on governed calls) · paginated (has_more, next_cursor)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reference/regulation-catalogueFull engine regulation case catalogue (pack authoring)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of object (open) · regulation case definition (name, fields, type-codes) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reference/case-fieldsPer-case field definitions (pack authoring)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| cases | query | string | no | comma-separated case names (e.g. DE.MinijobGleitzone,DE.Einmalzahlungen) |
| case | query | string | no | alias for cases |
| cluster | query | string | no | engine cluster set name |
| clusterSetName | query | string | no | alias for cluster |
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · case name → field definitions map · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reference/case-detailSingle-case detail read (carries caseFields[])
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| cases | query | string | no | comma-separated case names |
| case | query | string | no | alias for cases |
| op | query | string | no | override the executor op name |
| operation | query | string | no | alias for op |
| tenantId | path | string | yes | min 1 |
200 → data: object (open) · case name → case detail map · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/reference/case-values-scanLearn case field names from live employee case values
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| cases | query | string | no | comma-separated case names |
| case | query | string | no | alias for cases |
| tenantId | path | string | yes | min 1 |
200 → data: { matches, all_case_fields, employees_scanned } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
Pay items
The tenant's pay-item vocabulary, read from the bound engine — the type ids you pass when adding entry lines.
GET /tenants/{tenantId}/pay-item-typesL1 master pay-item type templates
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of object (open) · L1 master pay-item type template (engine-shaped) · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /tenants/{tenantId}/pay-itemsCompany pay items (L2 canonical)
| Parameter | In | Type | Req | Description |
|---|---|---|---|---|
| tenantId | path | string | yes | min 1 |
200 → data: array of { id, name, taxable, accrues_super, active, pay_item_type_id } · evidence_id (cev_ receipt on governed calls)
Errors (4XX / 5XX): { error: { code, message, status, errors[] } } — bad_request 400 · state_conflict 409 · pack_validation_failed 422 · plus 401/403/404 isolation — see Errors.
GET /v1/packs and GET /v1/packs/{packCode} return statutory scope, versions and lodgement-rail status. The remaining granular reference endpoints above are roadmap.Integrate
Roadmap
Hand-curated and honest: everything below is not yet callable. The endpoint blocks above are generated from openapi.json and only ever show real routes. Whole-surface roadmap areas keep their own sections: Timesheets, Shadow runs, Webhooks, MCP.
Partners & tenants
| PATCH | /partners/tenants/{tenantId} | ROADMAP Update tenant registration |
| DELETE | /partners/tenants/{tenantId} | ROADMAP Unregister — revokes access, data retained per policy |
| POST | /partners/batch/pay-runs | ROADMAP Create pay runs across many tenants in one call |
| POST | /partners/batch/lodge | ROADMAP Batch statutory lodgement across tenants |
| GET | /partners/dashboard | ROADMAP Portfolio summary: upcoming runs, alerts, lodgement states |
Regulation packs
| POST | /tenants/{tenantId}/packs | ROADMAP Attach a pack post-hoc. Today the pack is pinned at tenant provisioning (POST /partners/tenants) |
| PATCH | /tenants/{tenantId}/packs/{packCode} | ROADMAP Move the pinned version (e.g. annual statutory update) |
Organisation
| PATCH | /tenants/{tenantId}/organisation | ROADMAP Update company details |
| GET | /tenants/{tenantId}/sites | ROADMAP List work sites / locations (drives state payroll tax, regional rules) |
| POST | /tenants/{tenantId}/sites | ROADMAP Create work site |
| POST | /tenants/{tenantId}/validate | ROADMAP Tenant-level payroll-readiness check (per-employee readiness is live — see Employees) |
Employees
| POST | /tenants/{tenantId}/employees/bulk | ROADMAP Bulk import up to 500 employees |
| GET | /tenants/{tenantId}/employees/{empId}/documents | ROADMAP Statutory documents (payment summaries, P60, P45) |
Pay runs & lodgement
| POST | /tenants/{tenantId}/pay-runs/{prId}/import-lines | ROADMAP Import approved timesheet lines |
| POST | /tenants/{tenantId}/pay-runs/eoy | ROADMAP End-of-year finalisation for the attached pack's year-end |
| GET | /tenants/{tenantId}/lodgements | ROADMAP Rail-agnostic lodgement list with authority responses |
| GET | /tenants/{tenantId}/lodgements/{ldgId} | ROADMAP Lodgement detail, receipt, and error remediation hints |
| GET | /tenants/{tenantId}/pay-runs/{prId}/statutory-outputs | ROADMAP Statutory output files for packs without a rail |
| GET | /tenants/{tenantId}/payslips/{psId}/pdf | ROADMAP Download payslip PDF (white-label theming) — payslip_pdf is unsupported on PE today |
| GET | /embed/components | ROADMAP List embeddable components — the SDK ships the catalogue today |
Calculation engine
| POST | /calculate/award-interpret | ROADMAP AU: interpret award rates, penalties, loadings for a shift |
| POST | /calculate/leave | ROADMAP Leave entitlements and accruals |
| POST | /calculate/termination | ROADMAP Final pay including unused leave and termination payments |
Reference data
| GET | /reference/{packCode}/tax-tables | ROADMAP Current tax scales and thresholds for a pack version |
| GET | /reference/AU/awards/{awardCode} | ROADMAP Award rates, penalties, loadings |
| GET | /reference/AU/super-funds | ROADMAP Super fund directory (USI lookup) |
| GET | /reference/{packCode}/income-types | ROADMAP Statutory income type codes (e.g. STP income types) |
Integrate
Webhooks
Roadmap — not yet available
Subscribe to events; deliveries are signed (Ledra Pay-Signature, HMAC-SHA256) and retried with backoff for 72 hours.
| POST | /webhooks | ROADMAP Create subscription with event filters |
| GET | /webhooks | ROADMAP List subscriptions |
| DELETE | /webhooks/{id} | ROADMAP Remove subscription |
| Event | Fires when |
|---|---|
| pay_run.previewed | Preview calculation completes |
| pay_run.finalised | Run committed to the engine; entries persisted (bank/super files AU-only) |
| lodgement.accepted / lodgement.rejected | Authority responds to a submission |
| shadow_run.completed | Variance report ready |
| employee.created / employee.terminated | Employee lifecycle changes |
| pack.version_released | A statutory update ships for a pack you use |
Integrate
MCP & agents
The REST write path is already governance-first: write operations require a governance_reason, supplied either as a body field or the x-governance-reason header — it lands in the evidence chain on every state-changing call. Omitting it returns 400 governance_reason_required.
Roadmap — MCP server not yet available
Ledra Pay will ship an MCP server so AI agents can operate payroll safely. Every tool carries a risk tier; all write tools require a governance_reason; high and critical tools support human-approval gates configured per partner. The tool catalogue below is the planned surface, not yet callable.
| Tool ROADMAP | Risk | Description |
|---|---|---|
| list_employees · get_employee · list_pay_runs · get_pay_run | LOW | Read operations with governance hashes |
| calculate_wage · interpret_award · employee_pay_summary | LOW | Stateless calculations |
| create_timesheet · approve_timesheet · preview_pay_run | MED | Reversible writes, no money movement |
| create_employee · update_employee · create_pay_run · update_pay_entry | HIGH | Writes requiring governance_reason |
| finalise_pay_run · submit_to_authority · terminate_employee · run_full_payroll | CRIT | Irreversible or outward-facing; approval-gated by default |
| get_audit_trail · verify_chain · payroll_readiness_check · run_shadow_run | LOW | Trust and verification surface |
Integrate
Errors
Errors are JSON with a stable code, human message, and — for pack validation failures — the statutory rule that rejected the input. code values are snake_case (pack_validation_failed, state_conflict, governance_reason_required), except the load-bearing CAPABILITY_NOT_SUPPORTED.
{
"error": {
"code": "pack_validation_failed",
"message": "tax_profile.tfn failed checksum validation",
"rule": "AU-2026.1/identity/tfn_algorithm",
"status": 422
}
}
| Status | Meaning |
|---|---|
| 400 / 422 | Malformed request (bad_request, governance_reason_required) / pack validation failure (pack_validation_failed) |
| 401 / 403 | Invalid key / tenant not in your partner portfolio |
| 404 | Object not found within your isolation boundary |
| 409 | state_conflict (e.g. finalising an unapproved run, adding entries outside draft); idempotency replay mismatch |
| 429 | Rate limited — honour Retry-After |
| 501 | CAPABILITY_NOT_SUPPORTED — the bound pack cannot do this (e.g. stateless_calculate on AU, statutory lodgement on DE, a non-today as_of/effective_from on a non-native engine). Body carries capability, pack, and alternatives — never a wrong answer |