Run Payroll
Runs a payroll for a company. This is the last step of the core integration flow — after you've created a company and added employees — and it's a three-call sequence: start the run, advance it, then finalize it.
Requires a bearer access token (see How to Get API Access). CLIENT_ID is the company identifier returned as id when you create the company.
1. Start a payroll run
POST https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll
No request body is required.
Example request
require("dotenv").config();
const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/payroll`;
async function runPayroll() {
const response = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"api-version": "2.0",
authorization: `Bearer ${TOKEN}`,
"content-type": "application/json",
"x-api-version": "2.0"
},
});
console.log(response.status);
console.log(await response.text());
}
runPayroll();curl -s -X POST "https://api.worklio.com/wep/companies/$CLIENT_ID/payroll" \
-H "accept: application/json" \
-H "api-version: 2.0" \
-H "authorization: Bearer $API_KEY" \
-H "content-type: application/json" \
-H "x-api-version: 2.0" | jq .import os
import requests
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.environ.get("API_KEY")
CLIENT_ID = os.environ.get("CLIENT_ID")
URL = f"https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll"
def run_payroll():
response = requests.post(
URL,
headers={
"accept": "application/json",
"api-version": "2.0",
"authorization": f"Bearer {TOKEN}",
"content-type": "application/json",
"x-api-version": "2.0",
},
)
print(response.status_code)
print(response.text)
return response
if __name__ == "__main__":
run_payroll()
Example response
{
"companyId": 1031,
"runId": 1002109,
"payrollType": 0,
"startedOn": "2026-09-11",
"payDay": "2026-09-25",
"periodStart": "2026-09-19",
"periodEnd": "2026-09-25",
"daysToProcessPayments": 4,
"deadline": "2026-09-21T21:00:00Z",
"currentUIStep": 0,
"currentProcStep": 501,
"currentProcStepDesc": "Payroll Engine Activated",
"currentProcStepProgress": 0,
"currentProcStepStatus": 1,
"direction": 0,
"isPayrollBlocked": false,
"canUserOverride": false,
"errors": [],
"warnings": []
}runId identifies this payroll run — save it, you'll need it for the next and finalize calls below.
Calling this endpoint again while a run is already in progress doesn't start a second run — it returns the same run state shown above.
2. Advance the payroll run
POST https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll/{runId}/next
runId is the id returned by the start call above.
Call this endpoint to advance the run to its next processing step. Once there's nothing left to advance automatically, it responds with an error telling you to finalize instead of advance further — see the example below.
Example request
require("dotenv").config();
const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const RUN_ID = 1002109; // from the runId returned by the start call
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/payroll/${RUN_ID}/next`;
async function advancePayroll() {
const response = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"api-version": "2.0",
authorization: `Bearer ${TOKEN}`,
"content-type": "application/json",
"x-api-version": "2.0"
},
});
console.log(response.status);
console.log(await response.text());
}
advancePayroll();curl -s -X POST "https://api.worklio.com/wep/companies/$CLIENT_ID/payroll/$RUN_ID/next" \
-H "accept: application/json" \
-H "api-version: 2.0" \
-H "authorization: Bearer $API_KEY" \
-H "content-type: application/json" \
-H "x-api-version: 2.0" | jq .import os
import requests
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.environ.get("API_KEY")
CLIENT_ID = os.environ.get("CLIENT_ID")
RUN_ID = 1002109
URL = f"https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll/{RUN_ID}/next"
def run_payroll():
response = requests.post(
URL,
headers={
"accept": "application/json",
"api-version": "2.0",
"authorization": f"Bearer {TOKEN}",
"content-type": "application/json",
"x-api-version": "2.0",
},
)
print(response.status_code)
print(response.text)
return response
if __name__ == "__main__":
run_payroll()
Example response
{
"companyId": 1031,
"runId": 1002109,
"payrollType": 0,
"startedOn": "2026-09-11",
"payDay": "2026-10-02",
"periodStart": "2026-09-26",
"periodEnd": "2026-10-02",
"daysToProcessPayments": 4,
"deadline": "2026-09-28T21:00:00Z",
"currentUIStep": 0,
"currentProcStep": 701,
"skipUISteps": [],
"currentProcStepDesc": "Crunching time entry data …",
"currentProcStepProgress": 0,
"currentProcStepStatus": 1,
"direction": 0,
"isPayrollBlocked": false,
"canUserOverride": false,
"errors": [],
"warnings": []
}{
"status": 0,
"code": "500",
"errorCode": null,
"message": "9/11/2026 - 1:14:57 PM : Missing localization for key: Worklio.Business.WEP.Logic.PayrollLogic.Next step operation is not allowed.",
"stackTrace": "",
"pagination": {
"pageNo": 0,
"pageSize": 0,
"totalRecords": 0,
"totalPages": 0,
"dataToken": ""
},
"validationErrors": null
}
The message text above ("Missing localization for key: ...") is what the API actually returns — it's a broken/untranslated error string, not a copy-paste mistake in this guide. Treat the HTTP 500 plus this message as the signal that the run has no more steps to advance and is ready to finalize.
3. Finalize the payroll run
POST https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll/{runId}/finalize
Call this once next (above) tells you there are no more steps to advance. This is the last call in the sequence — it finalizes the run.
Example request
require("dotenv").config();
const TOKEN = process.env.API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const RUN_ID = 1002109; // from the runId returned by the start call
const url = `https://api.worklio.com/wep/companies/${CLIENT_ID}/payroll/${RUN_ID}/finalize`;
async function finalizePayroll() {
const response = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"api-version": "2.0",
authorization: `Bearer ${TOKEN}`,
"content-type": "application/json",
"x-api-version": "2.0"
},
});
console.log(response.status);
console.log(await response.text());
}
finalizePayroll();curl -s -X POST "https://api.worklio.com/wep/companies/$CLIENT_ID/payroll/$RUN_ID/finalize" \
-H "accept: application/json" \
-H "api-version: 2.0" \
-H "authorization: Bearer $API_KEY" \
-H "content-type: application/json" \
-H "x-api-version: 2.0" | jq .import os
import requests
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.environ.get("API_KEY")
CLIENT_ID = os.environ.get("CLIENT_ID")
RUN_ID = 1002109
URL = f"https://api.worklio.com/wep/companies/{CLIENT_ID}/payroll/{RUN_ID}/finalize"
def run_payroll():
response = requests.post(
URL,
headers={
"accept": "application/json",
"api-version": "2.0",
"authorization": f"Bearer {TOKEN}",
"content-type": "application/json",
"x-api-version": "2.0",
},
)
print(response.status_code)
print(response.text)
return response
if __name__ == "__main__":
run_payroll()
Example response
Updated 27 minutes ago
