Onboarding a Business via the API
This guide walks through the calls an integration partner makes to take a new business from nothing to its first finalised pay run. Each step shows a request you can copy and the IDs to carry into the next step.
Full request and response schemas are in the API Reference. Before you start, read Partner Requirements. It covers the authentication and Beam rules your platform must follow.
| Step | What you do | Endpoint |
|---|---|---|
| 0 | Get a user token | POST /api/admin/authentication/token |
| 1 | Create the business and confirm its contacts | POST /api/businesses, POST .../verify |
| 2 | Set ATO details and request an SSID | POST .../ato-settings, POST .../stp/stp-settings/register |
| 3 | Add the default bank account | POST .../business-bank-accounts |
| 4 | Add the default super fund | POST .../super-funds |
| 5 | Register the business with Beam | POST .../beam-participant/register |
| 6 | Create an employee | POST .../employee-bulk-data/import |
| 7 | Create a pay run | POST .../pay-runs |
| 8 | Add an earnings line | POST .../pay-runs/{payRunId}/employees/{employeeId} |
| 9 | Finalise the pay run | POST .../pay-runs/{payRunId}/finalise |
| 10 | Lodge STP and submit super | POST .../stp/pay-event-lodgements/{id}/lodge, POST .../super-contributions/{id}/submit |
Before you start
Example conventions
The examples use two shell variables. Set API to your tenant's API address once, and set BIZ after step 1:
API=https://acme.iopayroll.com/api
BIZ=$API/businesses/39 # 39 = the business ID returned in step 1
TOKEN=<access_token> # from step 0
In tables and text, ... stands for /api/businesses/{businessId}. All example data is fictitious.
IDs in the examples, such as pay cycle 120 or bank account 827, are different for every business. Each step says which earlier response or lookup call the ID comes from.
Enum fields are sent as integers. Each step lists the values it uses, and the full lists are in the API Reference.
Background jobs and polling
Creating, editing and finalising a pay run start a long-running background job. The call returns straight away with a jobId, and the work finishes a few seconds later, or longer for large pay runs. Poll GET .../pay-runs/{payRunId} and read status:
status | Name | Meaning |
|---|---|---|
1 | Created | Pay run created, job queued. |
2 | Processing | Calculation in progress. |
3 | Completed | Calculated. Safe to review, edit or finalise. |
4 | Finalised | Locked. |
5 | Error | The job failed. |
6 | Cancelled | Cancelled. |
7 | Pending | Waiting to process. |
8 | Changed | Edits are being recalculated. Wait for 3. |
9 | Finalising | Finalise in progress. |
For finalising, you can subscribe to the PayRunFinalised webhook instead of polling.
Webhooks
STP lodgements and super contributions are processed by the ATO and Beam after your call returns. Paysense records their progress as it arrives and sends a webhook, so subscribe instead of polling. Create one subscription per business:
curl -X POST "$BIZ/webhook-subscriptions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Payroll events",
"webhookUrl": "https://app.yourcompany.com/webhooks/paysense",
"eventTypes": [2, 19, 32, 40],
"trigger": 1,
"isActive": true,
"secret": "<your signing secret>"
}'
| Field | Value | Meaning |
|---|---|---|
eventTypes | 2 | PayRunFinalised |
19 | EmployeeImportCompleted | |
32 | SuperContributionStatusChanged | |
40 | PayEventUpdated | |
trigger | 1 | Immediate. 0 is Delayed. |
secret | Signs each delivery. Always set it. |
webhookUrl must be a public address. Paysense rejects localhost. Verify every delivery as described in Webhook Signature Verification. The full event list is in the API Reference.
Step 0: Get a user token
Every call in this guide is made as a user. Exchange your partner API key for that user's access token, as described in Partner API Key:
curl -X POST "$API/admin/authentication/token" \
-H "Tenant-Key-1: <your-client-id>" \
-H "Tenant-Key-2: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "username": "user@example.com" }'
Tenant-Key-1 and Tenant-Key-2 are placeholders. Use the header names supplied with your credentials.
The response contains access_token, refresh_token and expires_in. Every later call sends Authorization: Bearer $TOKEN. Access tokens are short-lived, so refresh them as described in Partner API Key instead of requesting a new token for each call.
Creating a business needs the tenant-level business:create permission, which new users do not have. Paysense grants it manually to the user your integration uses for step 1. Use a normal user, not a tenant administrator: the partner API key cannot issue tokens for administrator accounts. The user who creates a business becomes its owner, with full permissions on it.
Step 1: Create the business
curl -X POST "$API/businesses" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Example Dental",
"legalName": "Example Dental Pty Ltd",
"registrationNumber": "<ABN>",
"contactFirstName": "Alex",
"contactSurname": "Citizen",
"contactEmail": "alex@exampledental.example",
"contactMobile": "0412345678",
"addressLine1": "1 Example Street",
"suburb": "Sydney",
"state": 1,
"postcode": "2000"
}'
| Field | Value | Meaning |
|---|---|---|
state | 1 | NSW |
Returns 200:
{ "id": 39, "name": "Example Dental", "registrationNumber": "<ABN>" }
Keep id. It is the business ID in BIZ for every later call.
Creating a business also sets up defaults you do not need to create yourself:
- Pay cycles: Weekly, Fortnightly and Monthly. List them with
GET .../pay-cycles/search. - Pay types: a standard set, including Permanent Hourly for ordinary hours. List them with
GET .../pay-types/search. - STP settings: a BMS identifier and branch code
001. Read them withGET .../stp/stp-settings.
1b. Confirm the contact details
The ATO requires a confirmed email and mobile for the business. Creating the business checks the ABN but does not confirm the mobile, and confirms the email only if it matches your user's email.
If your own onboarding already confirms the employer's email and mobile, record that instead of sending new codes:
curl -X POST "$BIZ/verify/override" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": 1 }'
Call it once for each type. It returns 204 and is recorded in the audit log.
Otherwise, send a one-time code to each contact and have the employer enter it:
curl -X POST "$BIZ/verify/request" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": 2 }'
curl -X POST "$BIZ/verify" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": 2, "code": "123456" }'
| Field | Value | Meaning |
|---|---|---|
type | 1 | Mobile (code sent by SMS) |
2 |
verify/request returns 204. The code expires in 10 minutes. A wrong code returns 400 with the number of attempts left. See Partner Requirements.
Step 2: Set ATO details and request an SSID
To lodge Single Touch Payroll (STP) reports, the business needs a software ID (SSID) and must tell the ATO that Paysense lodges on its behalf. Steps 3 onwards do not depend on the SSID, so you can continue while you wait for it.
2a. Set the ATO reporting details
curl -X POST "$BIZ/ato-settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reportingType": 0,
"reportingBusinessContactName": "Alex Citizen",
"reportingBusinessEmailAddress": "alex@exampledental.example",
"reportingBusinessPhoneNumber": "0412345678"
}'
| Field | Value | Meaning |
|---|---|---|
reportingType | 0 | AsBusiness |
Returns 204.
2b. Request an SSID
curl -X POST "$BIZ/stp/stp-settings/register" \
-H "Authorization: Bearer $TOKEN"
Returns 204. You can safely call it again while the request is pending.
2c. Track the request
Call GET .../stp/stp-settings/provider-info and read accessStatus:
accessStatus | Meaning | Next action |
|---|---|---|
1 RequestSSID | SSID requested. | Wait. Paysense generates the SSID. |
2 PendingSSID | SSID generation in progress. | Wait. |
3 RequiresAccessGrant | ssid is now populated. | The business notifies the ATO, in ATO Access Manager, of the provider and SSID shown (softwareProviderName, softwareProviderAbn, ssid). The provider is MessageXchange. |
4 AccessGranted | Ready to lodge. | None. |
2d. Confirm the ATO notification
Once the business has notified the ATO:
curl -X POST "$BIZ/stp/stp-settings/access-granted" \
-H "Authorization: Bearer $TOKEN"
Returns 204. This is only accepted from status 3.
Step 3: Add the default bank account
This is the account that pays wages and super.
curl -X POST "$BIZ/business-bank-accounts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountName": "Example Dental Operating",
"bsb": "<BSB>",
"accountNumber": "<account number>"
}'
Returns 201:
{ "id": 827, "accountName": "Example Dental Operating", "bsb": "<BSB>", "accountNumber": "<account number>", "isDefault": true }
Keep id. You send it as superBankAccountId in step 9. The business's first bank account becomes the default automatically. To change the default later, call PUT .../business-bank-accounts/{id}/default, which returns 204.
Step 4: Add the default super fund
Search for the fund, then add it to the business:
curl "$API/super-funds/search?SearchText=<fund name>" \
-H "Authorization: Bearer $TOKEN"
Each result has an id (the fund product ID) and a usi. Add the chosen fund:
curl -X POST "$BIZ/super-funds" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "superFundProductId": 1234 }'
Returns 201 with the business fund's id and isDefault. The first fund added becomes the default automatically. To change the default later, call PUT .../super-funds/{id}/default, which returns 204.
Choosing a default fund is the employer's decision. Let the employer pick from the search results. Do not pre-select or recommend a fund.
Step 5: Register the business with Beam
Each business registers with Beam Clearing House itself (see Partner Requirements).
-
Show the Beam Landing Page in your product.
-
When it sends the
beam-registermessage, your server calls:curl -X POST "$BIZ/beam-participant/register" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "redirectUrl": "https://app.yourcompany.com/beam/complete" }'The response includes
hostedUIUrl. -
Redirect the user to
hostedUIUrl. They accept Beam's terms and set up their direct debit authority, then Beam returns them to yourredirectUrl. -
On return, call
POST .../beam-participant/refresh-status. The response showsregisteredandtermsAcceptedUtc.
Step 6: Create an employee
Create the employee with every section in one call: personal details, pay run settings, tax, super and bank account. Send an array with one employee:
curl -X POST "$BIZ/employee-bulk-data/import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"employees": [
{
"correlationId": "your-employee-ref-1001",
"firstName": "Jamie",
"surname": "Example",
"email": "jamie@example.com",
"dateOfBirth": "1990-05-14",
"startDate": "2026-09-01",
"residentialAddress": {
"addressLine1": "2 Sample Road",
"suburb": "Sydney",
"state": "NSW",
"postcode": "2000"
},
"isPostalSameAsResidential": true,
"employmentDetails": {
"employmentType": 1,
"rateUnit": 1,
"baseRate": 42.50,
"hoursPerWeek": 38,
"standardBusinessHours": true,
"primaryPayCycleId": 120,
"primaryBusinessPayTypeId": 784
},
"taxDeclaration": {
"taxFileNumber": "<TFN>",
"claimTaxFreeThreshold": true,
"hasStudyAndTrainingLoan": false,
"taxScaleCategoryCode": 1,
"residencyStatusType": 1,
"medicareLevySurchargeType": 1,
"medicareExemptionType": 1,
"medicareLevyReductionType": 1
},
"superFunds": [
{
"superFundType": 1,
"paymentAllocationType": 1,
"regulatedSuperFund": { "fundUsi": "<USI>", "memberNumber": "<member number>" }
}
],
"bankAccounts": [
{
"accountName": "Jamie Example",
"bsb": "<BSB>",
"accountNumber": "<account number>",
"bankAccountType": 1,
"paymentAllocationType": 1
}
]
}
],
"options": { "overwrite": false, "strict": true, "enablePartialImport": false }
}'
Your pay rates go in employmentDetails. correlationId is your own reference for the employee and is stored as the employee's external ID.
| Field | Value | Meaning |
|---|---|---|
employmentType | 1 | FullTime |
rateUnit | 1 | Hourly |
primaryPayCycleId | 120 | The Weekly pay cycle, from GET .../pay-cycles/search |
primaryBusinessPayTypeId | 784 | The Permanent Hourly pay type, from GET .../pay-types/search |
standardBusinessHours | true | Use the business's standard hours per day. |
taxScaleCategoryCode | 1 | Regular |
residencyStatusType | 1 | Australian |
medicareLevySurchargeType | 1 | None |
medicareExemptionType | 1 | None |
medicareLevyReductionType | 1 | None |
superFundType | 1 | RegulatedSuperFund |
bankAccountType | 1 | Electronic |
paymentAllocationType | 1 | EntireBalance |
| Option | Meaning |
|---|---|
overwrite | true replaces an existing employee's data with the imported values. |
strict | Apply strict validation. |
enablePartialImport | When importing several employees, true imports the valid ones even if others fail. |
Returns 200 with a job ID (a GUID), not the employee ID. The import runs as a background job. Once it completes, find the employee with GET .../employees/search?SearchText=<name>, or subscribe to the EmployeeImportCompleted webhook. The employee is Active straight away and is included in the next pay run.
An employee is matched to an existing one by ID, TFN or correlationId. Sending the same employee again with overwrite set to true updates them.
To let the employee enter their own tax, super and bank details instead, call POST .../employees/onboard. The employee is invited to self-onboard.
Updating one section later
To change one part of an existing employee, call the endpoint for that section. Personal details returns 200; the others return 204.
| Section | Endpoint |
|---|---|
| Personal details | PUT .../employees/{employeeId}/details |
| Pay run settings and rates | PUT .../employees/{employeeId}/pay-run-settings |
| Tax details | PUT .../employees/{employeeId}/tax-details |
| Super funds | PUT .../employees/{employeeId}/super-funds |
| Bank accounts | PUT .../employees/{employeeId}/bank-accounts |
When a tax details update sets declarationAccepted to true, declarationVersion is required and must be the current ATO declaration version, ato-nat73963-2016-06.
Step 7: Create a pay run
curl -X POST "$BIZ/pay-runs" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"payCycleId": 120,
"payRunType": 1,
"fromDate": "2026-09-21",
"paymentDate": "2026-09-29",
"employeeInclusionType": 0
}'
| Field | Value | Meaning |
|---|---|---|
payRunType | 1 | PayCycle. Required. |
employeeInclusionType | 0 | AllEligible: adds every Active employee on the pay cycle. |
The end of the period is calculated from the pay cycle. Returns 200:
{ "id": 444, "jobId": "…", "status": 1, "fromDate": "2026-09-21T00:00:00Z", "toDate": "2026-09-27T00:00:00Z" }
The pay run is calculated by a background job. Poll until status is 3 (Completed).
To add an employee who became Active after the pay run was created, call POST .../pay-runs/{payRunId}/employees/{employeeId}/add.
Step 8: Add an earnings line
When the pay run is created, each employee's standard earnings are generated from their pay run settings. For example, 38 hours at the base rate. Use this step only to add or change lines, such as overtime, extra hours or an allowance.
This call replaces the employee's full set of pay run records, so every array must be present. Any earning record you leave out is removed, including the generated standard earnings line. Start from GET .../pay-runs/{payRunId}/employees/{employeeId}, change what you need, and send all the arrays back. To add a new record, set its id to 0.
This example adds 2 extra hours and keeps every existing record:
curl "$BIZ/pay-runs/{payRunId}/employees/{employeeId}" \
-H "Authorization: Bearer $TOKEN" \
| jq '{earningRecords, payGAdjustments, superAdjustments, stslAdjustments, deductions,
leaveAccrualRecords, leaveTakenRecords, lumpSumRecords, terminationPaymentRecords}
| .earningRecords += [{
"id": 0,
"businessPayTypeId": 784,
"units": 2,
"rate": 42.50,
"description": "Extra hours",
"earningType": 1
}]' \
| curl -X POST "$BIZ/pay-runs/{payRunId}/employees/{employeeId}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @-
| Field | Value | Meaning |
|---|---|---|
id | 0 | New record. Existing records keep the id returned by the GET. |
businessPayTypeId | 784 | The Permanent Hourly pay type, from GET .../pay-types/search |
earningType | 1 | Standard |
Returns 200. Tax, super and leave accruals are recalculated for you by a background job. Poll until status is 3 (Completed) again before you finalise.
Step 9: Finalise the pay run
Finalising locks the pay run. The business should review and approve it first (see Partner Requirements).
curl -X POST "$BIZ/pay-runs/{payRunId}/finalise" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dispatchOption": 1,
"createPayEventLodgement": true,
"lodgementType": 1,
"createSuperContribution": true,
"superBankAccountId": 827,
"superBankPaymentType": 1
}'
| Field | Meaning |
|---|---|
dispatchOption | Payslips: 0 DontSend, 1 SendNow, 2 SendLater (set scheduledDateTime). |
createPayEventLodgement | Create the STP pay event for this pay run. It is created as a draft. Lodging it with the ATO needs the SSID from step 2 (see step 10). |
lodgementType | STP lodgement type when createPayEventLodgement is true. 1 is Regular. |
createSuperContribution | Create the super contribution for this pay run. It is created as a draft. |
superBankAccountId | Required with createSuperContribution. The bank account id from step 3. |
superBankPaymentType | Required with createSuperContribution. How the employer pays the contribution: 1 DirectDebit, 2 DirectCredit, 3 BPay. Beam uses the direct debit authority from step 5, so use 1. |
Returns 202:
{ "jobId": "…", "payRunId": 444, "status": 9 }
Finalising runs as a background job. Poll until status is 4 (Finalised), or subscribe to the PayRunFinalised webhook.
Step 10: Lodge STP and submit super
Step 9 creates the STP pay event and the super contribution as drafts. Nothing reaches the ATO or Beam until you complete this step.
10a. Find the drafts
List each and pick the item whose filter.payRunIds contains your pay run ID:
curl "$BIZ/stp/pay-event-lodgements/search?OrderBy=Id;desc" \
-H "Authorization: Bearer $TOKEN"
curl "$BIZ/super-contributions/search?OrderBy=Id;desc" \
-H "Authorization: Bearer $TOKEN"
A new pay event has status 5 (RequiresApproval) and lodgementStatus 1 (NotLodged). A new super contribution has status 3 (Draft).
10b. Lodge the STP pay event
The business must have reached accessStatus 4 (AccessGranted) in step 2.
-
Validate the pay event:
curl -X POST "$BIZ/stp/pay-event-lodgements/{id}/validate" \
-H "Authorization: Bearer $TOKEN"Returns
200with the results:{
"summary": { "numberOfEmployees": 1, "payDate": "2026-09-29T00:00:00Z", "totalGross": 1615, "totalPayGW": 335 },
"status": "SUCCESS",
"items": []
}itemslists any errors and warnings. Errors must be fixed before you continue. Warnings do not block lodgement. Read the latest results again at any time withGET .../stp/pay-event-lodgements/{id}/validation. -
Approve the pay event as the employer's authorised user. Before you call this, show that user the pay event and the STP declaration, and record their agreement. The calling user is recorded as the signatory.
curl -X POST "$BIZ/stp/pay-event-lodgements/{id}/approve" \
-H "Authorization: Bearer $TOKEN"Returns
204. The pay eventstatusmoves to6(Approved). -
Lodge it:
curl -X POST "$BIZ/stp/pay-event-lodgements/{id}/lodge" \
-H "Authorization: Bearer $TOKEN"Returns
200. The pay event is sent to the ATO andlodgementStatusmoves to6(Submitted). The ATO then processes it asynchronously. Each change sends aPayEventUpdatedwebhook; fetchGET .../stp/pay-event-lodgements/{id}and readlodgementStatus:lodgementStatusName Meaning 6Submitted Sent. Waiting for the ATO. 9ReceivedByAto The ATO has the submission and is processing it. 8Lodged Accepted. 10LodgedWithWarnings Accepted. Review the warnings. 11LodgedWithErrors Not accepted. Fix the errors and lodge a new pay event. 7Error Submission failed. The full ATO response is at
GET .../stp/pay-event-lodgements/{id}/result.
| Call | Permission |
|---|---|
validate, approve | stp:write |
lodge | stp:submit |
10c. Submit the super contribution
The business must be registered with Beam (step 5).
-
Validate the contribution against SuperStream and Beam rules:
curl -X POST "$BIZ/super-contributions/{id}/validate" \
-H "Authorization: Bearer $TOKEN"Returns
200:{
"isValid": false,
"errors": [
{ "errorCode": "ERR126", "severity": "Error", "message": "You have not accepted the most recent terms and conditions. ...", "path": "contributionDetail.participantDetail.clientParticipantNumber" },
{ "errorCode": "WRN068", "severity": "Warning", "message": "The employee's phone number has not been provided. ...", "path": "..." }
]
}You can submit when
isValidistrue. Warnings do not block submission.ERR126(terms not accepted) andERR127(no matching direct debit authority) mean the employer has not finished Beam registration. Read the latest results again withGET .../super-contributions/{id}/validation. -
Submit it to Beam:
curl -X POST "$BIZ/super-contributions/{id}/submit" \
-H "Authorization: Bearer $TOKEN"warningSubmitting starts a real payment. Beam debits the contribution total from the employer's account, and a submitted contribution cannot easily be reversed. Have the employer review it first.
Returns
200. The contributionstatusmoves to4(Submitted) andclearingHouseReferenceholds Beam's reference. -
Beam then processes the payment and sends it to the funds. Each change sends a
SuperContributionStatusChangedwebhook; fetchGET .../super-contributions/{id}and readstatus:5(Completed) or6(Failed).GET .../super-contributions/{id}/statusreturns Beam's detailed processing response once Beam has sent one, and404until then.
| Call | Permission |
|---|---|
validate | supercontribution:validate |
submit | supercontribution:submit |