Independence Day offer · ₹52/employee/moClaim offer
INDPayroll
API reference

Payroll runs

The engine itself - preview, calculate, hold, lock, mark paid, reverse.

Base URLhttps://api.indpayroll.com/v116 endpoints
POST /payroll-runs/preview

Calculate a run without persisting it

A dry run. The engine values every employee for the period and returns the slips; nothing is written, no payslip ids are issued and no webhook fires.

This is the whole integration for calculate-only partners: send the period, the employees and their inputs, render the response in your own UI. Anything passed inline in employees[] overrides what is stored, so you never have to push attendance first.

Preview is metered against the payroll-run rate limit rather than the general one - it does the same work as a real run.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

organization_id required
integer
The entity to calculate for.
cycle
enum: monthly, weekly, bi-weekly
Pay frequency. Must match the cycle the employees are assigned to.
period_start required
string (date)
First day of the period being paid, inclusive.
period_end required
string (date)
Last day of the period being paid, inclusive.
employee_ids
string[]
Restrict the run to these employees. Omit for every payable employee in the organization.
department_ids
integer[]
Restrict the run to these departments. Combined with employee_ids as a union.
options
PayrollRunOptions
The same five switches the product shows above the Generate button. They decide what the engine reads, not what it computes - statutory rules are never optional.
employees
PreviewEmployeeInput[]
Inline period inputs. Anything sent here wins over what is stored, which is what makes a stateless, calculate-only integration possible.

Responses

  • 200 Calculated slips. Nothing was persisted. PayrollRunPreview
  • 400 The request was malformed or failed validation. ErrorResponse
  • 401 The key is missing, unknown, or the HMAC signature did not verify. ErrorResponse
  • 403 The key is valid but lacks the scope, or cannot reach this organization. ErrorResponse
  • 422 The request was well-formed but the engine could not compute it - a missing salary structure, no PT state on an employee, or a period with no inputs. ErrorResponse
  • 429 Rate limit exceeded. Retry after the number of seconds in Retry-After. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/preview \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "organization_id": 42,
  "cycle": "monthly",
  "period_start": "2026-09-01",
  "period_end": "2026-09-30",
  "employee_ids": [
    "ext-1001",
    "ext-1002"
  ],
  "options": {
    "use_attendance": true,
    "mark_leaves_paid": true,
    "mark_absent_unpaid": true,
    "include_expense_claims": true,
    "add_timelogs": false
  }
}'
Response · 200
{
  "organization_id": 42,
  "period_start": "2026-09-01",
  "period_end": "2026-09-30",
  "cycle": "monthly",
  "options": {
    "use_attendance": true,
    "mark_leaves_paid": true,
    "mark_absent_unpaid": true,
    "include_expense_claims": true,
    "add_timelogs": false
  },
  "totals": {
    "employee_count": 2,
    "gross": 144733.33,
    "total_deductions": 17516,
    "net_payable": 127505.33,
    "pf_employee": 3600,
    "pf_employer": 3600,
    "esi_employee": 0,
    "esi_employer": 0,
    "professional_tax": 400,
    "tds": 13516,
    "lwf": 0,
    "reimbursements": 288,
    "employer_cost": 148333.33
  },
  "employees": [
    {
      "employee_id": "1187",
      "external_id": "ext-1001",
      "name": "Aditi Sharma",
      "employee_code": "ACM-0142",
      "department": "Engineering",
      "period_start": "2026-09-01",
      "period_end": "2026-09-30",
      "payable_days": 28,
      "total_days": 30,
      "lop_days": 2,
      "earnings": [
        {
          "code": "BASIC",
          "name": "Basic",
          "amount": 39666.67,
          "taxable": true,
          "pro_rated": true
        },
        {
          "code": "HRA",
          "name": "House Rent Allowance",
          "amount": 15866.67,
          "taxable": true,
          "pro_rated": true
        },
        {
          "code": "SPL",
          "name": "Special Allowance",
          "amount": 20253.33,
          "taxable": true,
          "pro_rated": true
        },
        {
          "code": "INCENTIVE",
          "name": "Q2 sales incentive",
          "amount": 18000,
          "taxable": true,
          "pro_rated": false
        }
      ],
      "deductions": [
        {
          "code": "PF",
          "name": "Provident Fund",
          "amount": 1800
        },
        {
          "code": "PT",
          "name": "Professional Tax",
          "amount": 200
        },
        {
          "code": "TDS",
          "name": "Income Tax",
          "amount": 9420
        }
      ],
      "employer_contributions": [
        {
          "code": "PF_EMPLOYER",
          "name": "Employer PF",
          "amount": 1800
        },
        {
          "code": "EPS",
          "name": "Employees Pension Scheme",
          "amount": 1250
        }
      ],
      "reimbursements": [
        {
          "code": "EXP",
          "name": "Client visit - Cuttack",
          "amount": 288
        }
      ],
      "basic": 39666.67,
      "hra": 15866.67,
      "other_earnings": 38253.33,
      "overtime_amount": 0,
      "overtime_hours": 0,
      "gross": 93786.67,
      "pf_employee": 1800,
      "pf_employer": 1800,
      "eps_employer": 1250,
      "esi_employee": 0,
      "esi_employer": 0,
      "professional_tax": 200,
      "tds": 9420,
      "lwf_employee": 0,
      "lwf_employer": 0,
      "lop_amount": 6333.33,
      "advance_recovery": 0,
      "total_deductions": 11420,
      "total_reimbursements": 288,
      "net_payable": 82654.67,
      "employer_cost": 96836.67,
      "currency": "INR",
      "on_hold": false,
      "warnings": []
    }
  ],
  "warnings": [],
  "calculated_at": "2026-09-30T10:02:18Z"
}
GET /payroll-runs

List payroll runs

Query parameters

organization_id
integer
Organization to act on. Required when the API key can reach more than one organization; optional - and defaulted - when it can reach exactly one.
status
enum: draft, generated, locked, paid, reversed
month
integer
year
integer
page
integer
1-based page number.
per_page
integer
Items per page. The engine caps this at 200.

Responses

  • 200 Runs, newest period first. object
  • 401 The key is missing, unknown, or the HMAC signature did not verify. ErrorResponse
POST /payroll-runs

Create a run

Opens a draft for a period and an employee set. Nothing is calculated yet - call POST /payroll-runs/{id}/calculate when the inputs are in.

Omit period_start and period_end to let the organization's pay calendar derive them from month and year.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

organization_id required
integer
The entity the run belongs to.
cycle
enum: monthly, weekly, bi-weekly
Which cycle this run pays.
month
integer
Calendar month of the period, 1-12.
year
integer
Four-digit year of the period.
period_start
string (date)
Defaults to the period the organization's calendar derives from month and year.
period_end
string (date)
employee_ids
string[]
Restrict the run to these employees. Omit for every payable employee.
department_ids
integer[]
Restrict the run to these departments.
options
PayrollRunOptions
Defaults for the calculation. They can be overridden when you calculate.

Responses

  • 201 Draft created. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 403 The key is valid but lacks the scope, or cannot reach this organization. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "organization_id": 42,
  "cycle": "monthly",
  "month": 9,
  "year": 2026
}'
GET /payroll-runs/{id}

Retrieve a run

The header and the totals - gross, deductions, net, employer PF and ESI.

Path parameters

id required
string
Payroll run id.

Responses

  • 200 The run. PayrollRun
  • 401 The key is missing, unknown, or the HMAC signature did not verify. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
Request
curl -X GET https://api.indpayroll.com/v1/payroll-runs/{id} \
  -H "Authorization: Bearer $INDP_API_KEY"
Response · 200
{
  "id": "run_2026_09_0042",
  "organization_id": 42,
  "cycle": "monthly",
  "month": 9,
  "year": 2026,
  "period_start": "2026-09-01",
  "period_end": "2026-09-30",
  "status": "generated",
  "options": {
    "use_attendance": true,
    "mark_leaves_paid": true,
    "mark_absent_unpaid": true,
    "include_expense_claims": true,
    "add_timelogs": false
  },
  "totals": {
    "employee_count": 86,
    "gross": 4820000,
    "total_deductions": 612400,
    "net_payable": 4207600,
    "pf_employee": 154800,
    "pf_employer": 154800,
    "esi_employee": 8240,
    "esi_employer": 35620,
    "professional_tax": 17200,
    "tds": 432160,
    "lwf": 0,
    "reimbursements": 24800,
    "employer_cost": 5010420
  },
  "currency": "INR",
  "employee_count": 86,
  "held_count": 1,
  "calculated_at": "2026-09-30T10:12:44Z",
  "locked_at": null,
  "paid_on": null
}
DELETE /payroll-runs/{id}

Delete a draft run

Drafts only. A run that has been calculated must be reversed, not deleted.

Path parameters

id required
string
Payroll run id.

Responses

  • 204 Deleted. No body.
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
POST /payroll-runs/{id}/calculate

Calculate and persist the slips

Runs the engine and writes the payslips. The options are the same five switches the product shows above the Generate button, and they default to whatever the run was created with.

The run moves to generated. payroll.run.calculated fires, and payslip.ready fires per employee as PDFs render. Calling it twice on a run that already has slips is refused - use recalculate.

Large runs are calculated asynchronously: a 202 means the work is queued, and the run's status tells you when it lands.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · optional

options
PayrollRunOptions
The same five switches the product shows above the Generate button. They decide what the engine reads, not what it computes - statutory rules are never optional.
employee_ids
string[]
Calculate only these employees. Omit for the whole run.

Responses

  • 200 Calculated. The run header carries the totals. PayrollRun
  • 202 Accepted. The work runs asynchronously; poll the returned resource. AsyncAck
  • 400 The request was malformed or failed validation. ErrorResponse
  • 403 The key is valid but lacks the scope, or cannot reach this organization. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
  • 422 The request was well-formed but the engine could not compute it - a missing salary structure, no PT state on an employee, or a period with no inputs. ErrorResponse
  • 429 Rate limit exceeded. Retry after the number of seconds in Retry-After. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/calculate \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "options": {
    "use_attendance": true,
    "mark_leaves_paid": true,
    "mark_absent_unpaid": true,
    "include_expense_claims": true,
    "add_timelogs": false
  }
}'
POST /payroll-runs/{id}/recalculate

Recalculate selected employees

Replaces the slips of the employees you name, leaving the rest alone. Allowed while the run is draft or generated; refused once it is locked or paid.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

employee_ids required
string[]
Employees to recalculate. Their earlier slips are replaced.
options
PayrollRunOptions
The same five switches the product shows above the Generate button. They decide what the engine reads, not what it computes - statutory rules are never optional.
reason
string

Responses

  • 200 Recalculated. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
  • 422 The request was well-formed but the engine could not compute it - a missing salary structure, no PT state on an employee, or a period with no inputs. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/recalculate \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "employee_ids": [
    "ext:emp-1044"
  ],
  "reason": "Attendance corrected after the cut-off."
}'
GET /payroll-runs/{id}/employees

List per-employee lines

One calculated slip per employee, paginated. The same shape preview returns.

Path parameters

id required
string
Payroll run id.

Query parameters

status
enum: payable, on_hold
Filter to held or payable employees.
page
integer
1-based page number.
per_page
integer
Items per page. The engine caps this at 200.

Responses

  • 200 Employee lines. object
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
GET /payroll-runs/{id}/employees/{empId}

Retrieve one slip breakdown

Earnings, deductions, employer contributions and reimbursements for a single employee in this run.

Path parameters

id required
string
Payroll run id.
empId required
string
Employee id, or ext: + your own identifier.

Responses

  • 200 The slip. PayslipCalculation
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
PATCH /payroll-runs/{id}/employees/{empId}

Override one slip

A manual adjustment - hold the pay, add a deduction, add an earning, override the payable days, print a remark. Every override is kept in the run's audit trail and is refused once the run is locked.

Path parameters

id required
string
Payroll run id.
empId required
string
Employee id, or ext: + your own identifier.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

on_hold
boolean
Hold this employee's pay without removing them from the run.
extra_deduction
number (double)
An amount in the organization's payroll currency, to two decimal places.
extra_deduction_label
string
extra_earning
number (double)
An amount in the organization's payroll currency, to two decimal places.
extra_earning_label
string
paid_days
number
Override the payable days the engine derived.
remark
string
Printed on the payslip.

Responses

  • 200 The recalculated slip. PayslipCalculation
  • 400 The request was malformed or failed validation. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X PATCH https://api.indpayroll.com/v1/payroll-runs/{id}/employees/{empId} \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "extra_deduction": 4500,
  "extra_deduction_label": "Asset recovery - laptop",
  "remark": "Recovered per exit checklist."
}'
POST /payroll-runs/{id}/hold

Hold or release employees

Held employees keep their slip but are excluded from the bank file and from the net payable the run disburses. Send hold: false to release.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

employee_ids required
string[]
hold
boolean
False releases a hold.
reason
string

Responses

  • 200 Updated run header. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/hold \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "employee_ids": [
    "ext:emp-1044"
  ],
  "hold": true,
  "reason": "Exit formalities pending."
}'
POST /payroll-runs/{id}/lock

Freeze a run

No more edits: no recalculation, no overrides, no holds. Lock before you generate statutory files, so the ECR and the 24Q cannot drift from the slips you filed against.

Send confirm_totals to have the lock refused unless the net payable is still the figure you approved.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · optional

confirm_totals
number (double) or null
Optional guard. When sent, the lock is refused unless the run's net payable still matches this amount.

Responses

  • 200 Locked. PayrollRun
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/lock \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "confirm_totals": 4207600
}'
POST /payroll-runs/{id}/unlock

Unlock a run

Requires the payroll:admin scope and a reason, both of which are written to the audit trail. A paid run cannot be unlocked - reverse it instead.

Path parameters

id required
string
Payroll run id.

Body application/json · required

reason required
string
Recorded against the unlock in the audit trail. Required.

Responses

  • 200 Unlocked. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 403 The key is valid but lacks the scope, or cannot reach this organization. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/unlock \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Reopened to correct September attendance for two employees."
}'
POST /payroll-runs/{id}/mark-paid

Mark a run paid

Records the disbursement date, the payment method and your bank reference, and moves the run to paid. Held employees are skipped. Fires payroll.run.paid.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

paid_on required
string (date)
payment_method_id
integer
reference
string
Your bank reference or UTR batch number.
employee_ids
string[]
Mark only part of the run paid. Omit for all of it.

Responses

  • 200 Marked paid. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/mark-paid \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "paid_on": "2026-10-05",
  "payment_method_id": 2,
  "reference": "HDFC-BULK-20261005-0007"
}'
POST /payroll-runs/{id}/email

Email payslip PDFs

Optional - most partners send payslips themselves from GET /payslips/{id}.pdf. Mail is queued and sent from the INDPayroll SMTP; employees without an email address are skipped and reported.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · optional

employee_ids
string[]
Omit to email every employee in the run who has an email address.
template
integer
Payslip template to render. Defaults to the organization's.
reply_to
string (email)
message
string
Prepended to the body of the mail.

Responses

  • 202 Accepted. The work runs asynchronously; poll the returned resource. AsyncAck
  • 400 The request was malformed or failed validation. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/email \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "template": 2,
  "reply_to": "payroll@acme.in"
}'
POST /payroll-runs/{id}/reverse

Reverse a run

Voids the slips of a generated or paid run and moves it to reversed. The run and its slips stay readable for audit; nothing is deleted. Statutory files already generated from it are marked stale on the filing dashboard. Fires payroll.run.reversed.

Path parameters

id required
string
Payroll run id.

Headers

Idempotency-Key
string
A unique key, at most 255 characters, that makes this request safe to retry. Replaying a key within 24 hours returns the original response and does not act again.

Body application/json · required

reason required
string
void_payslips
boolean
Mark the slips void. They stay readable for audit either way.

Responses

  • 200 Reversed. PayrollRun
  • 400 The request was malformed or failed validation. ErrorResponse
  • 403 The key is valid but lacks the scope, or cannot reach this organization. ErrorResponse
  • 404 No such resource, or it belongs to an organization this key cannot reach. ErrorResponse
  • 409 The resource is in a state that forbids the change - a locked or paid run, a duplicate external_id, or a settlement already approved. ErrorResponse
Request
curl -X POST https://api.indpayroll.com/v1/payroll-runs/{id}/reverse \
  -H "Authorization: Bearer $INDP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Paid against the wrong bank account - reissuing.",
  "void_payslips": true
}'