Tenant API v1 — Payroll
Base path: /api/tenant/v1
Middleware: auth:tenant-api, tenant.user, not.suspended, verified, module:payroll, plus can:payroll.*.
Catalog version: 1.4.1.
My pay slips (self-service)
| Method | Path | Permission |
|---|---|---|
| GET | /my-pay-slips | payroll.view_own or payroll.view |
| GET | /my-pay-slips/{payRunLine}/pdf | payroll.view_own or payroll.view |
| GET | /pay-runs/{payRun}/lines/{payRunLine}/pdf | payroll.view |
GET /my-pay-slips returns paid lines for the caller’s linked active employee only. PDF endpoints return application/pdf rendered via MyPaySlipService with the same branded document chrome as invoices/quotations (logo, primary color, company address/contact from Settings → Branding / General). Layout A: employer header + PAY SLIP meta (slip # zero-padded to four digits), Employee | Period details parties strip (no daily rate on the PDF), Gross/Adjustments table, NET PAY bar.
Payroll profiles
| Method | Path | Permission |
|---|---|---|
| GET | /payroll-profiles | payroll.view |
| POST | /payroll-profiles | payroll.create |
| GET | /payroll-profiles/{payrollProfile} | payroll.view |
| PUT | /payroll-profiles/{payrollProfile} | payroll.update |
| DELETE | /payroll-profiles/{payrollProfile} | payroll.delete |
| POST | /payroll-profiles/{payrollProfile}/restore | payroll.restore |
| DELETE | /payroll-profiles/{payrollProfile}/force | payroll.force.delete |
Create body: employee_id (required, unique per tenant), base_salary (required), optional currency (3 chars, defaults to workspace currency from Settings → General), pay_frequency (monthly|biweekly|weekly), effective_from, notes.
Pay-run lines include breakdown fields working_days, unpaid_leave_days, absent_days, days_present, late_count, late_deduction_days (late ladder from Settings → Attendance).
Pay runs
| Method | Path | Permission |
|---|---|---|
| GET | /pay-runs | payroll.view |
| POST | /pay-runs | payroll.create |
| GET | /pay-runs/{payRun} | payroll.view |
| PUT | /pay-runs/{payRun} | payroll.update |
| POST | /pay-runs/{payRun}/approve | payroll.approve |
| POST | /pay-runs/{payRun}/pay | payroll.pay |
| POST | /pay-runs/{payRun}/post | payroll.post |
| DELETE | /pay-runs/{payRun} | payroll.delete |
| POST | /pay-runs/{payRun}/restore | payroll.restore |
| DELETE | /pay-runs/{payRun}/force | payroll.force.delete |
POST /pay-runs
Body: period_start, period_end (required), optional notes.
Creates a draft pay run and one line per active employee with a payroll profile. Gross starts as base_salary; adjustments deduct unpaid leave and unexcused absences when Leave Management / Attendance are installed. Line payload includes working_days, unpaid_leave_days, absent_days, days_present, gross, adjustments, net.
PUT /pay-runs/{payRun}
Draft only. Optional period_start, period_end, notes, and lines[] with id plus optional gross, adjustments, notes. Net is recalculated as gross + adjustments.
POST /pay-runs/{payRun}/approve
Requires at least one line. draft → approved.
POST /pay-runs/{payRun}/pay
approved → paid. Sets paid_at.
Body optional unless Accounting is entitled:
| Field | Notes |
|---|---|
paid_from_account_id | Required when Accounting is installed; active cash/bank asset |
expense_account_id | Optional expense override for auto-created accrual |
liability_account_id | Optional liability override for accrual + payment debit |
With Accounting: ensures a posted accrual (Dr Salary Expense / Cr Salaries Payable; posts an existing draft or creates+posts), then creates+posts a payment journal (Dr liability / Cr paid-from) and stores payment_journal_entry_id + account FKs. Without Accounting: status-only (empty body).
POST /pay-runs/{payRun}/post
Soft Accounting integration. Body optional: debit_account_id, credit_account_id.
Requires Accounting entitled; pay run status approved or paid; net total > 0; not already posted. Creates a draft accrual journal (expense debit / liability credit) and stores journal_entry_id. Defaults to system Salary Expense 6400 and Salaries Payable 2200 when ids are omitted. Persist expense_account_id / liability_account_id on the pay run.