developer.ledrapay.com › docs › api

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.

Base URLhttps://api.ledrapay.com/v1
Sandboxhttps://sandbox.ledrapay.com/v1
AuthPartner-scoped bearer keys
FormatJSON · UTF-8 · ISO 8601 / 4217
StatusBeta — schemas stable, additive changes only

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):

viewerreads only
preparercreate & edit pay runs, employees — cannot approve/finalise/submit/void
approverapprove · finalise · submit (STP) · void — cannot prepare (four-eyes)
admineverything 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
rolestringnoviewer | preparer | approver | admin · default "viewer"—
scopeobjectno{ cost_centres, sites }data scope; omitted keys default to "*" (partner keys) / must stay within the delegating admin's own scope
componentsarray of stringno—component allowlist, or ["*"] (default)
employee_idstringno—pin the token to one employee
originstringno^[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_idstringno——
user_namestringno——

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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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

ObjectWhat it is
PartnerYou — the platform embedding Ledra Pay. Holds API keys and a portfolio of tenants.
TenantAn employer (your customer). Owns employees, pay runs, and attached regulation packs. Fully isolated.
Regulation packA certified, versioned country ruleset — tax scales, social contributions, statutory outputs. Attached to a tenant, pinned to a version.
Pay runThe lifecycle object: draft → previewed → approved → finalised → lodged (lodged via the AU STP rail — see statutory lodgement), plus forecast / retro ROADMAP variants.
Shadow run ROADMAPA pay run calculated for comparison only — returns a line-level variance report against incumbent results you supply.
LodgementA statutory submission generated from a finalised pay run. Live for AU STP Phase 2 (POST …/pay-runs/{prId}/submit); RTI and payday filing ROADMAP.
EvidenceAn 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

FieldTypeReqConstraintsDescription
namestringyesmin 1—
jurisdictionstringnoAU | DE | NZ | UK | US · default "AU"jurisdiction
enginestringnodefault "mock"engine to bind: payroll_engine_au (AU), payroll_engine (DE), myaccountant, mock (dev only)
pack_versionstringno—pin a specific regulation-pack version (defaults to the pack current_version)
connection_idstringno—cin_* governance-cell connection (required for myaccountant bindings)
connector_packstringno—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_refstringno—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)
ParameterInTypeReqDescription
limitqueryinteger | stringnopage size, 1–200 (default 50)
cursorquerystringnoopaque 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
packCodepathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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" } }]
}
PackCalculationLodgement rail
AUBetaSTP Phase 2 — submit + status polling live on the sandbox rail (lodgement)
NZBetaPayday filing — planned next, with design partner
UKBetaRTI FPS/EPS — planned next, with design partner
DEBetaELSTER + GKV/ITSG — design-partner project
FR · NL · BE · ES · PT · AT · CH · LU · USBetaStatutory 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"
    }
  }]
}
CapabilityAU packDE packWhat it gates
effective_datingunsupportednative?as_of= reads · effective_from writes
stateless_calculateunsupportednativePOST /calculate with no pay run
statutory_lodgementnativeunsupportedSTP / lodgement rails
payment_file · payslip_pdf · retirement_batchnativeunsupportedABA / payslip PDF / SuperStream
Read capabilities before you branch. The two engines are mirror images — AU lodges statutorily but has no effective-dated history; DE has native effective-dating + stateless calc but its lodgement rails are a design-partner build. Gate your UI on the matrix (the SDK exposes 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
account_namestringno——
bsbstringno——
account_numberstringno——
bank_namestringno——
fi_codestringno——
apca_user_idstringno——
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | booleanno—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)
ParameterInTypeReqDescription
include_archivedquerystringnoinclude archived cost centres — true | false
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
codestringyes^[A-Z0-9][A-Z0-9-]*$cost-centre code (UPPERCASE alphanumeric + dashes)
namestringyesmin 1—
parent_codestring | nullno——
gl_accountstring | nullno——
sitestring | nullno——
statusstringnoactive | archived—
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
codepathstringyescost-centre code (UPPERCASE alphanumeric + dashes) — ^[A-Z0-9][A-Z0-9-]*$

Request body (optional)

FieldTypeReqConstraintsDescription
namestringnomin 1—
parent_codestring | nullno——
gl_accountstring | nullno——
sitestring | nullno——
statusstringnoactive | archived—
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
codepathstringyescost-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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyesemployee id (emp_<16 hex>) — ^emp_[A-Za-z0-9_-]+$

Request body

FieldTypeReqConstraintsDescription
cost_centre_codestringyes^[A-Z0-9][A-Z0-9-]*$cost-centre code (UPPERCASE alphanumeric + dashes)
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyesemployee 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
ParameterInTypeReqDescription
sourcequerystringnoemployee | pay_item
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
sourcestringyesemployee | pay_item—
attributestringyesmin 1dot-path on the entity (e.g. 'employment.type'); matched against match_value
match_valuestring | numberyes—value to match; '*' is the catch-all
cost_centre_codestringyes^[A-Z0-9][A-Z0-9-]*$cost-centre code (UPPERCASE alphanumeric + dashes)
priorityintegerno≥ 0 · default 100lower wins; rules evaluated priority-ordered
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
idpathstringyesmapping 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)
ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
limitqueryinteger | stringnopage size (default 50, max 200) — ^[0-9]+$
cursorquerystringnoopaque cursor from a previous response
pagequerystringnolegacy page number — ignored; use cursor — ^[0-9]+$
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
first_namestringyes——
last_namestringyes——
employmentobjectnoopen object (passthrough)employment terms (type, annual_salary, pay_frequency, start/end dates)
statusstringnoactive | terminated—
effective_fromstringnoYYYY-MM-DDeffective-dated write (engines with native effective_dating only)
jurisdiction_dataobjectnoopen object (passthrough)per-country extension fields captured by the tax/bank/pension forms
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | boolean | objectno—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)
ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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_).

ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
first_namestringno——
last_namestringno——
employmentobjectnoopen object (passthrough)employment terms (type, annual_salary, pay_frequency, start/end dates)
statusstringnoactive | terminated—
effective_fromstringnoYYYY-MM-DDeffective-dated write (engines with native effective_dating only)
jurisdiction_dataobjectnoopen object (passthrough)per-country extension fields captured by the tax/bank/pension forms
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | boolean | objectno—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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
effective_datestringnoYYYY-MM-DDend date (defaults to today; a future date schedules a leaver)
reason_codestringno—optional STP cessation reason code
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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_).

ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
tfnstringno—PII — transmitted as an inline attestation, never stored raw
countrystringno—jurisdiction code for the write (defaults 'au'); lowercased server-side
employee_typestringno—engine employee type (tax metadata; defaults 'Employee')
effective_fromstringnoYYYY-MM-DDeffective-dated write (engines with native effective_dating only)
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | booleanno—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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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_).

ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
bank_bsbstringno—PII — transmitted as an inline attestation, never stored raw
bank_account_numberstringno—PII — transmitted as an inline attestation, never stored raw
bank_account_namestringno—PII — transmitted as an inline attestation, never stored raw
countrystringno—jurisdiction code for the write (defaults 'au'); lowercased server-side
employee_typestringno—engine employee type (tax metadata; defaults 'Employee')
effective_fromstringnoYYYY-MM-DDeffective-dated write (engines with native effective_dating only)
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | booleanno—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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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_).

ParameterInTypeReqDescription
as_ofquerystringnoISO date YYYY-MM-DD
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
super_fund_namestringno—PII — transmitted as an inline attestation, never stored raw
super_usistringno—PII — transmitted as an inline attestation, never stored raw
super_member_numberstringno—PII — transmitted as an inline attestation, never stored raw
smsf_account_namestringno—PII — transmitted as an inline attestation, never stored raw
smsf_bsbstringno—PII — transmitted as an inline attestation, never stored raw
smsf_account_numberstringno—PII — transmitted as an inline attestation, never stored raw
countrystringno—jurisdiction code for the write (defaults 'au'); lowercased server-side
employee_typestringno—engine employee type (tax metadata; defaults 'Employee')
effective_fromstringnoYYYY-MM-DDeffective-dated write (engines with native effective_dating only)
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | booleanno—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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
employeeIdpathstringyes—

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)
Capability-gated. Against an engine without effective-dating, a non-today 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.

The approval is the governed step. A manager approving a request books the leave to the tenant's bound engine (one governed write, requires a 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
employee_idstringyesmin 1—
leave_typestringyesmin 1—
employee_namestringno——
start_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
end_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
quantitynumberno——
unitstringnohours | days—
reasonstringnomax 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)
ParameterInTypeReqDescription
statusquerystringnopending | approved | rejected | cancelled
employee_idquerystringnomin 1
limitqueryinteger | stringnopage size, 1–200 (default 50)
cursorquerystringnoopaque cursor from a previous response
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
idpathstringyesleave-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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
idpathstringyesleave-request id (req_…) — min 1

Request body (optional)

FieldTypeReqConstraintsDescription
notestringnomax 500decision note stored on the request
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
idpathstringyesleave-request id (req_…) — min 1

Request body (optional)

FieldTypeReqConstraintsDescription
notestringnomax 500decision 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
idpathstringyesleave-request id (req_…) — min 1

Request body (optional)

FieldTypeReqConstraintsDescription
notestringnomax 500withdrawal 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
leave_typestringno—master leave type hint (typeCode or name, e.g. "Annual Leave")
namestringno—alias hint for leave_type
master_idstring | numberno—exact master pay-item type id (skips the hint lookup)
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
vendorIdstring | numberno—engine employee ref the entitlement applies to
accrual_methodstringno—accrual method (default 'Monthly')
annual_entitlement_hoursnumberno——
personal_entitlement_hoursnumberno——
start_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsstring | number | booleanno—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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
employee_idstringno^emp_[A-Za-z0-9_-]+$employee id (emp_<16 hex>)
vendorIdstring | numberno—legacy/explicit engine employee ref (prefer employee_id)
payRunEmployeeIdstring | numberno—legacy alias for vendorId
leave_typestringnoannual | personal | sick | compassionate | long_service | family_domestic_violence | community | parental | maternity | short_time | special | unpaid | cash_out | othercanonical leave category
categorystringno—legacy loose label alias for leave_type (engine names/synonyms tolerated)
leave_type_codestring | numberno—specific engine type code — disambiguates same-category types
quantitynumberno≥ 0hours (AU) | days (DE)
hoursnumberno≥ 0legacy alias for quantity (unit=hours)
daysnumberno≥ 0legacy alias for quantity (unit=days)
unitstringnohours | days—
start_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
end_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
pay_run_idstringno—AU: pin the target draft run (else discovered)
amountnumberno—AU: explicit line amount (defaults 0 — engine computes)
effective_fromstringnoYYYY-MM-DDISO date YYYY-MM-DD
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
employee_idquerystringnoemployee id (emp_<16 hex>) — ^emp_[A-Za-z0-9_-]+$
tenantIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
recordIdpathstringyesengine handle from the leave-taken list — min 1

Request body (optional)

FieldTypeReqConstraintsDescription
pay_run_idstringno—AU: pin the target draft run (else discovered)
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
recordIdpathstringyesengine handle from the leave-taken list — min 1

Request body (optional)

FieldTypeReqConstraintsDescription
quantitynumberno≥ 0—
amountnumberno—AU: explicit line amount
start_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
end_datestring | nullnoYYYY-MM-DDISO date YYYY-MM-DD
pay_run_idstringno—AU: pin the target draft run (else discovered)
governance_reasonstringnomin 3 · max 500why 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}/timesheetsROADMAP Create timesheet with daily entries
POST/tenants/{tenantId}/timesheets/bulkROADMAP Bulk create for multiple employees
GET/tenants/{tenantId}/timesheetsROADMAP List with filters (employee, status, period)
POST/tenants/{tenantId}/timesheets/{id}/approveROADMAP 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
kindstringnoregular | adhoc | reversal—
period_startstringyesYYYY-MM-DDISO date YYYY-MM-DD
period_endstringyesYYYY-MM-DDISO date YYYY-MM-DD
payment_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
limitquerystringnopage size (1–200, default 50) — ^\d+$
cursorquerystringnoopaque cursor from a previous response
statusquerystringnodraft | previewed | approved | finalised | lodged | voided | deleted
tenantIdpathstringyesmin 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—
entryIdpathstringyes—

Request body

FieldTypeReqConstraintsDescription
modestringnopreview | commitpreview = zero-write verdict; commit = governed write
adjustmentsarray of objectyesmin 1 item · items { pay_item_type_id, code, type, amount, quantity, rate, … }—
ackbooleanno——
rule_refstringno—the R2P control this adjustment resolves (e.g. R2P-5)
control_idstringno—alias for rule_ref
before_grossstring | numberno——
expected_grossstring | numberno——
thresholdsobjectnoopen object (passthrough)R2P threshold scaffold inputs (profile auto-resolve is a server-side TODO)
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body

FieldTypeReqConstraintsDescription
modestringnopreview | commitpreview = zero-write verdict; commit = governed write
selectorobjectyes{ employee_ids, pay_item_type_id, code, classification }match spec — expanded server-side against the run's authoritative pay_items
actionobjectyes{ op, type, pay_item_type_id, amount, quantity }what to apply to each matched item
ackbooleanno——
rule_refstringno——
control_idstringno——
governance_reasonstringnomin 3 · max 500why 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body

FieldTypeReqConstraintsDescription
employee_idstringyes^emp_[A-Za-z0-9_-]+$employee id (emp_<16 hex>)
pay_itemsarray of objectno——
linesarray of objectno——
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsanyno—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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—
entryIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
pay_itemsarray of objectnomin 1 item—
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
additional fieldsanyno—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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—
entryIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
governance_reasonstringnomin 3 · max 500why this write is happening — bound into the evidence actor. Alternatively supply the x-governance-reason header.
reasonstringnomin 3 · max 500alias 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
period_startstringnoYYYY-MM-DDISO date YYYY-MM-DD
period_endstringnoYYYY-MM-DDISO date YYYY-MM-DD
payment_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
as_reversalbooleanno——
start_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
end_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
pay_datestringnoYYYY-MM-DDISO date YYYY-MM-DD
reversalbooleanno——
governance_reasonstringnomin 3 · max 500why 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—
entryIdpathstringyes—

Request body

FieldTypeReqConstraintsDescription
pay_item_type_idstringyesmin 1—
quantitystring | number | nullno—hours for the line
amountstring | number | nullno—rate/amount for the line
governance_reasonstringnomin 3 · max 500why 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-runsROADMAP 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
additional fieldsanyno—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)

FieldTypeReqConstraintsDescription
additional fieldsanyno—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

AU STP Phase 2 is live, capability-gated. 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
event_typestringnopay | update—
submission_typestringnopay | updatealias for event_type
is_finalbooleanno—marks the EOFY finalisation declaration
signature_userstringnomin 1ATO declaration signatory — full name
signatorystringnomin 1alias for signature_user
signature_namestringnomin 1alias for signature_user
governance_reasonstringnomin 3 · max 500why 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—
submissionIdpathstringyes—

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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyesmin 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription
componentsarray of stringno—component allowlist, or ["*"] (default)
employee_idstringno—pin the token to one employee (employee-facing surfaces)
originstringno^[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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
payRunIdpathstringyes—

Request body (optional)

FieldTypeReqConstraintsDescription
descriptionstringno——
governance_reasonstringnomin 3 · max 500why 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
ParameterInTypeReqDescription
financialYearquerystringnoAU financial year END year (2026 = FY2025-26) — ^\d{4}$
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
superIdpathstringyesmin 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_).

ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
superIdpathstringyesmin 1

Request body

FieldTypeReqConstraintsDescription
statusstringyesmin 1engine-native batch status (e.g. 'Lodged', 'Paid')
governance_reasonstringnomin 3 · max 500why 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.

Money movement: Ledra Pay does not hold or move funds. Bank and super files (ABA, SuperStream) are engine-generated and AU-only, capability-gated; on the DE/PE engine 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1
evidenceIdpathstringyesmin 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
ParameterInTypeReqDescription
limitqueryinteger | stringnopage size, 1–200 (default 50)
cursorquerystringnoopaque cursor from a previous response
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 1

Request body (optional)

FieldTypeReqConstraintsDescription

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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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)
ParameterInTypeReqDescription
limitqueryinteger | stringnopage size, 1–200 (default 50)
cursorquerystringnoopaque cursor from a previous response
tenantIdpathstringyesmin 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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)
ParameterInTypeReqDescription
casesquerystringnocomma-separated case names (e.g. DE.MinijobGleitzone,DE.Einmalzahlungen)
casequerystringnoalias for cases
clusterquerystringnoengine cluster set name
clusterSetNamequerystringnoalias for cluster
tenantIdpathstringyesmin 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[])
ParameterInTypeReqDescription
casesquerystringnocomma-separated case names
casequerystringnoalias for cases
opquerystringnooverride the executor op name
operationquerystringnoalias for op
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
casesquerystringnocomma-separated case names
casequerystringnoalias for cases
tenantIdpathstringyesmin 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
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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)
ParameterInTypeReqDescription
tenantIdpathstringyesmin 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.

Also available today: the pack registry itself — 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-runsROADMAP Create pay runs across many tenants in one call
POST/partners/batch/lodgeROADMAP Batch statutory lodgement across tenants
GET/partners/dashboardROADMAP Portfolio summary: upcoming runs, alerts, lodgement states

Regulation packs

POST/tenants/{tenantId}/packsROADMAP 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}/organisationROADMAP Update company details
GET/tenants/{tenantId}/sitesROADMAP List work sites / locations (drives state payroll tax, regional rules)
POST/tenants/{tenantId}/sitesROADMAP Create work site
POST/tenants/{tenantId}/validateROADMAP Tenant-level payroll-readiness check (per-employee readiness is live — see Employees)

Employees

POST/tenants/{tenantId}/employees/bulkROADMAP Bulk import up to 500 employees
GET/tenants/{tenantId}/employees/{empId}/documentsROADMAP Statutory documents (payment summaries, P60, P45)

Pay runs & lodgement

POST/tenants/{tenantId}/pay-runs/{prId}/import-linesROADMAP Import approved timesheet lines
POST/tenants/{tenantId}/pay-runs/eoyROADMAP End-of-year finalisation for the attached pack's year-end
GET/tenants/{tenantId}/lodgementsROADMAP 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-outputsROADMAP Statutory output files for packs without a rail
GET/tenants/{tenantId}/payslips/{psId}/pdfROADMAP Download payslip PDF (white-label theming) — payslip_pdf is unsupported on PE today
GET/embed/componentsROADMAP List embeddable components — the SDK ships the catalogue today

Calculation engine

POST/calculate/award-interpretROADMAP AU: interpret award rates, penalties, loadings for a shift
POST/calculate/leaveROADMAP Leave entitlements and accruals
POST/calculate/terminationROADMAP Final pay including unused leave and termination payments

Reference data

GET/reference/{packCode}/tax-tablesROADMAP Current tax scales and thresholds for a pack version
GET/reference/AU/awards/{awardCode}ROADMAP Award rates, penalties, loadings
GET/reference/AU/super-fundsROADMAP Super fund directory (USI lookup)
GET/reference/{packCode}/income-typesROADMAP 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/webhooksROADMAP Create subscription with event filters
GET/webhooksROADMAP List subscriptions
DELETE/webhooks/{id}ROADMAP Remove subscription
EventFires when
pay_run.previewedPreview calculation completes
pay_run.finalisedRun committed to the engine; entries persisted (bank/super files AU-only)
lodgement.accepted / lodgement.rejectedAuthority responds to a submission
shadow_run.completedVariance report ready
employee.created / employee.terminatedEmployee lifecycle changes
pack.version_releasedA 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 ROADMAPRiskDescription
list_employees · get_employee · list_pay_runs · get_pay_runLOWRead operations with governance hashes
calculate_wage · interpret_award · employee_pay_summaryLOWStateless calculations
create_timesheet · approve_timesheet · preview_pay_runMEDReversible writes, no money movement
create_employee · update_employee · create_pay_run · update_pay_entryHIGHWrites requiring governance_reason
finalise_pay_run · submit_to_authority · terminate_employee · run_full_payrollCRITIrreversible or outward-facing; approval-gated by default
get_audit_trail · verify_chain · payroll_readiness_check · run_shadow_runLOWTrust 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
  }
}
StatusMeaning
400 / 422Malformed request (bad_request, governance_reason_required) / pack validation failure (pack_validation_failed)
401 / 403Invalid key / tenant not in your partner portfolio
404Object not found within your isolation boundary
409state_conflict (e.g. finalising an unapproved run, adding entries outside draft); idempotency replay mismatch
429Rate limited — honour Retry-After
501CAPABILITY_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