Skip to main content

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.

StepWhat you doEndpoint
0Get a user tokenPOST /api/admin/authentication/token
1Create the business and confirm its contactsPOST /api/businesses, POST .../verify
2Set ATO details and request an SSIDPOST .../ato-settings, POST .../stp/stp-settings/register
3Add the default bank accountPOST .../business-bank-accounts
4Add the default super fundPOST .../super-funds
5Register the business with BeamPOST .../beam-participant/register
6Create an employeePOST .../employee-bulk-data/import
7Create a pay runPOST .../pay-runs
8Add an earnings linePOST .../pay-runs/{payRunId}/employees/{employeeId}
9Finalise the pay runPOST .../pay-runs/{payRunId}/finalise
10Lodge STP and submit superPOST .../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:

statusNameMeaning
1CreatedPay run created, job queued.
2ProcessingCalculation in progress.
3CompletedCalculated. Safe to review, edit or finalise.
4FinalisedLocked.
5ErrorThe job failed.
6CancelledCancelled.
7PendingWaiting to process.
8ChangedEdits are being recalculated. Wait for 3.
9FinalisingFinalise 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>"
}'
FieldValueMeaning
eventTypes2PayRunFinalised
19EmployeeImportCompleted
32SuperContributionStatusChanged
40PayEventUpdated
trigger1Immediate. 0 is Delayed.
secretSigns 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.

warning

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"
}'
FieldValueMeaning
state1NSW

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 with GET .../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" }'
FieldValueMeaning
type1Mobile (code sent by SMS)
2Email

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"
}'
FieldValueMeaning
reportingType0AsBusiness

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:

accessStatusMeaningNext action
1 RequestSSIDSSID requested.Wait. Paysense generates the SSID.
2 PendingSSIDSSID generation in progress.Wait.
3 RequiresAccessGrantssid 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 AccessGrantedReady 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.

note

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).

  1. Show the Beam Landing Page in your product.

  2. When it sends the beam-register message, 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.

  3. Redirect the user to hostedUIUrl. They accept Beam's terms and set up their direct debit authority, then Beam returns them to your redirectUrl.

  4. On return, call POST .../beam-participant/refresh-status. The response shows registered and termsAcceptedUtc.

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.

FieldValueMeaning
employmentType1FullTime
rateUnit1Hourly
primaryPayCycleId120The Weekly pay cycle, from GET .../pay-cycles/search
primaryBusinessPayTypeId784The Permanent Hourly pay type, from GET .../pay-types/search
standardBusinessHourstrueUse the business's standard hours per day.
taxScaleCategoryCode1Regular
residencyStatusType1Australian
medicareLevySurchargeType1None
medicareExemptionType1None
medicareLevyReductionType1None
superFundType1RegulatedSuperFund
bankAccountType1Electronic
paymentAllocationType1EntireBalance
OptionMeaning
overwritetrue replaces an existing employee's data with the imported values.
strictApply strict validation.
enablePartialImportWhen 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.

tip

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.

SectionEndpoint
Personal detailsPUT .../employees/{employeeId}/details
Pay run settings and ratesPUT .../employees/{employeeId}/pay-run-settings
Tax detailsPUT .../employees/{employeeId}/tax-details
Super fundsPUT .../employees/{employeeId}/super-funds
Bank accountsPUT .../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
}'
FieldValueMeaning
payRunType1PayCycle. Required.
employeeInclusionType0AllEligible: 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 @-
FieldValueMeaning
id0New record. Existing records keep the id returned by the GET.
businessPayTypeId784The Permanent Hourly pay type, from GET .../pay-types/search
earningType1Standard

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
}'
FieldMeaning
dispatchOptionPayslips: 0 DontSend, 1 SendNow, 2 SendLater (set scheduledDateTime).
createPayEventLodgementCreate 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).
lodgementTypeSTP lodgement type when createPayEventLodgement is true. 1 is Regular.
createSuperContributionCreate the super contribution for this pay run. It is created as a draft.
superBankAccountIdRequired with createSuperContribution. The bank account id from step 3.
superBankPaymentTypeRequired 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.

  1. Validate the pay event:

    curl -X POST "$BIZ/stp/pay-event-lodgements/{id}/validate" \
    -H "Authorization: Bearer $TOKEN"

    Returns 200 with the results:

    {
    "summary": { "numberOfEmployees": 1, "payDate": "2026-09-29T00:00:00Z", "totalGross": 1615, "totalPayGW": 335 },
    "status": "SUCCESS",
    "items": []
    }

    items lists any errors and warnings. Errors must be fixed before you continue. Warnings do not block lodgement. Read the latest results again at any time with GET .../stp/pay-event-lodgements/{id}/validation.

  2. 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 event status moves to 6 (Approved).

  3. 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 and lodgementStatus moves to 6 (Submitted). The ATO then processes it asynchronously. Each change sends a PayEventUpdated webhook; fetch GET .../stp/pay-event-lodgements/{id} and read lodgementStatus:

    lodgementStatusNameMeaning
    6SubmittedSent. Waiting for the ATO.
    9ReceivedByAtoThe ATO has the submission and is processing it.
    8LodgedAccepted.
    10LodgedWithWarningsAccepted. Review the warnings.
    11LodgedWithErrorsNot accepted. Fix the errors and lodge a new pay event.
    7ErrorSubmission failed.

    The full ATO response is at GET .../stp/pay-event-lodgements/{id}/result.

CallPermission
validate, approvestp:write
lodgestp:submit

10c. Submit the super contribution​

The business must be registered with Beam (step 5).

  1. 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 isValid is true. Warnings do not block submission. ERR126 (terms not accepted) and ERR127 (no matching direct debit authority) mean the employer has not finished Beam registration. Read the latest results again with GET .../super-contributions/{id}/validation.

  2. Submit it to Beam:

    curl -X POST "$BIZ/super-contributions/{id}/submit" \
    -H "Authorization: Bearer $TOKEN"
    warning

    Submitting 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 contribution status moves to 4 (Submitted) and clearingHouseReference holds Beam's reference.

  3. Beam then processes the payment and sends it to the funds. Each change sends a SuperContributionStatusChanged webhook; fetch GET .../super-contributions/{id} and read status: 5 (Completed) or 6 (Failed). GET .../super-contributions/{id}/status returns Beam's detailed processing response once Beam has sent one, and 404 until then.

CallPermission
validatesupercontribution:validate
submitsupercontribution:submit