Pay cycle closes → a payslip per employee

One document per employee, all at once, every month, and none of them can be wrong.

Trigger → artefact

The payroll run closesa payslip PDF per employee, each with its own numbers

Payroll is the burst case. Nothing happens for a month and then a thousand documents are owed inside an hour, each different, each with an employee who will read it carefully.

We measured it rather than estimated it: 1,000 payslips submitted in 45.6 seconds, the last one finished at 423.6 seconds, no failures — about 142 documents a minute. When we are at capacity we refuse with a Retry-After header instead of timing out.

Built with the HTML API. Need a designed image or certificate instead? Use the Canvas API — same data, same free year.

A payslip PDF rendered by PixlDocs, showing earnings, deductions and net pay
Payslip — rendered by the template this page calls.

What ships in this category

The documents, and the part of each that is hard.

A payslip PDF rendered by PixlDocs, showing earnings, deductions and net pay

Payslip

Employer and employee identity, earnings and deductions tables, net pay in words, and year-to-date columns.

Hard part Earnings and deductions totals, net pay, and the amount in words.

template_id 9f7b8380-710e-433a-a4c9-4ecafc986d35

A salary certificate PDF rendered by PixlDocs, certifying employment, role and monthly pay

Salary certificate

The letter an employee asks HR for when a bank, a landlord or a visa office wants proof: role, joining date, pay components, gross and net.

Hard part Gross and net per period from the components, and the yearly gross when the caller does not send one.

template_id 1c0ace95-a57a-40ce-a3a2-2b035b07d5ee

The whole integration

One call. Named fields. No SDK to install.

the call your product makesbash
# once per employee, when the run closes
curl https://api.pixldocs.com/v1/render \
  -H "Authorization: Bearer $PIXLDOCS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "9f7b8380-710e-433a-a4c9-4ecafc986d35",
    "format": "pdf",
    "data": {
      "employee_name": "A. Fischer",
      "pay_period": "July 2026",
      "pay_date": "2026-07-31",
      "earnings": [
        { "component": "Basic", "current": 60000, "ytd": 240000 }
      ]
    }
  }' --output payslip.pdf

Run against the API before publishing: 200, application/pdf. For pay day, submit up to 100 at a time to /v1/renders/batch and poll the ids.

The contract

Exactly what you send, exactly what you get.

These templates take named keys — no positional arguments, no layout to describe. Send a row array and the pagination follows it.

Payslip

9f7b8380-710e-433a-a4c9-4ecafc986d35

Fields · 28

company_namecompany_addresscompany_emailcompany_tax_idpayslip_nopay_periodperiod_startperiod_endpay_dateemployee_nameemployee_codeemployee_titleemployee_departmentemployee_locationdate_joinedemployee_tax_idemployee_pf_nobank_namebank_account_maskedpayment_modecurrency_codecurrency_localedays_in_period #days_paid #days_unpaid #leave_balancenotesfooter_note

Rows · earnings[]

component · current · ytd

Send as many as you have. Pagination, running totals and the carried-forward header are the renderer's problem, not yours.

Rows · deductions[]

component · current · ytd

Send as many as you have. Pagination, running totals and the carried-forward header are the renderer's problem, not yours.

Rows · employer_contributions[]

component · current · ytd

Send as many as you have. Pagination, running totals and the carried-forward header are the renderer's problem, not yours.

Salary certificate

1c0ace95-a57a-40ce-a3a2-2b035b07d5ee

Fields · 27

employer_nameemployer_addressemployer_registrationemployer_emailemployer_phonecertificate_noissue_dateissue_placeemployee_nameemployee_idemployee_designationemployee_departmentemployment_typedate_of_joiningemployment_statuswork_locationcurrency_codecurrency_localepay_period_labelperiods_per_year #purposedeclarationnotessignatory_namesignatory_rolesignatory_emailconfidential_note

Rows · components[]

description · kind · amount

Send as many as you have. Pagination, running totals and the carried-forward header are the renderer's problem, not yours.

What you would ask next.

What happens to the other 999 when one row is bad?

Nothing. Each render is independent and carries its own id. A row missing a required field returns 400 with the field named, and no credit is used for it.

Ten thousand employees, not one thousand?

At the throughput we measured that is roughly 75 minutes — but we have measured 1,000, not 10,000, so treat it as arithmetic rather than a promise.

Your system already knows when the document is owed.

Free to start · No credit card required