Overview

A Pay Event represents one pay run — a container for the individual employee pay items in that run. The STP lifecycle is:

draft → ready → submitted → accepted (or rejected / error)
  1. Create a pay event in draft status
  2. Add items (one per employee)
  3. Mark ready
  4. Submit to the ATO
  5. Poll or wait for webhook to know when it's accepted

The Pay Event object

Common fields

Field Type Notes
id integer
employer_id integer
employer_name string Computed
pay_period_start string (ISO 8601 date)
pay_period_end string (ISO 8601 date)
period_label string Human-readable range
payment_date string (ISO 8601 date) The actual pay date
pay_frequency string weekly, fortnightly, monthly, quarterly
event_type string pay_event, update_event, full_file_replacement
status string draft, ready, submitted, accepted, rejected, error
employee_count integer Number of items on this pay event
total_gross number Computed. Sum of item gross amounts
total_tax number Computed. Sum of item PAYG withholding
total_super number Computed. Sum of item super guarantee
is_finalisation boolean True if this is an EOFY finalisation event
financial_year string e.g. 2026-27, set automatically from payment_date if blank
created_at string (ISO 8601)
updated_at string (ISO 8601)

Detail fields (on GET /:id)

Field Type Notes
notes string Free-text notes
submitted_at string (ISO 8601)
responded_at string (ISO 8601) When ATO replied
message_id string ATO message ID once submitted
items array Pay event items (see below)

Endpoints

List pay events

GET /api/v1/pay_events

Scopes: pay_events:read

Query parameters:

Param Default Notes
employer_id Filter by employer
status Filter by status
page / per_page 1 / 25

Response: 200 OK with an array of compact Pay Event objects.

Get a pay event (with items)

GET /api/v1/pay_events/:id

Scopes: pay_events:read

Response: 200 OK with full detail including items array.

Create a pay event

POST /api/v1/pay_events

Scopes: pay_events:write
Idempotent: yes

Body:

{
  "pay_event": {
    "employer_id": 12,
    "pay_period_start": "2026-04-01",
    "pay_period_end": "2026-04-14",
    "payment_date": "2026-04-14",
    "pay_frequency": "fortnightly",
    "event_type": "pay_event",
    "is_finalisation": false,
    "notes": "Fortnight 1 for FY 26-27"
  }
}

Required: employer_id, pay_period_start, pay_period_end, payment_date

Response: 201 Created with the new Pay Event (empty items).

Update a pay event

PATCH /api/v1/pay_events/:id

Scopes: pay_events:write
Idempotent: yes

Only allowed while the pay event is in draft status. Once it's ready or later, updates return 403 Forbidden.

Pay event items

Each item represents one employee's pay data for this pay event. STP Phase 2 requires disaggregated reporting — you send separate amounts for ordinary time earnings, overtime, allowances, etc.

Item fields

All monetary fields are decimal AUD amounts, defaulting to 0.00. Required for every item: employee_id.

Field Type Notes
id integer
employee_id integer Required
employee_name string Computed
employee_payroll_id string Computed
hours_paid number Total hours paid in this period
gross_payment number Total gross
ordinary_time_earnings number OTE component
overtime number
bonuses_commissions number
directors_fees number
salary_sacrifice_super number Salary sacrificed into super
salary_sacrifice_other number Other salary sacrifice
task_allowances number
lump_sum_a number Long service leave, annual leave on termination
lump_sum_b number Unused long service leave from pre-16 Aug 1978
lump_sum_d number Tax-free component of genuine redundancy
lump_sum_e number Back payment accruing ≥12 months
lump_sum_w number Return to work payments
payg_withholding number Tax withheld
foreign_tax_paid number
super_guarantee number SG contribution
super_salary_sacrifice number
employer_additional_super number Above-SG employer contributions
reportable_super number Reportable employer super contributions (RESC)
qualifying_earnings number Payday Super (from 1 Jul 2026). SG contribution base per QE rules. Leave as 0 for pre-Payday-Super pay events.
annual_leave number Annual leave paid
personal_leave number Personal/sick leave paid
long_service_leave number
community_service_leave number
paid_leave number Write-only convenience alias. If your source system doesn't yet disaggregate leave by type, send one flat paid_leave figure and we'll book it as annual_leave on ingestion. Ignored if any of the four STP Phase 2 sub-types — annual_leave, personal_leave, long_service_leave, community_service_leave — is also supplied. Not returned on reads — the stored value is in annual_leave. See Leave disaggregation below.
etp_taxable number Employment termination payment — taxable
etp_tax_free number ETP — tax-free
etp_tax_withheld number ETP — tax withheld
etp_code string ETP reason code
rfba_exempt number Reportable fringe benefits — exempt
rfba_non_exempt number Reportable fringe benefits — non-exempt
total_super number Computed. Sum of all super components
total_leave number Computed. Sum of all leave components

Add an item

POST /api/v1/pay_events/:id/items

Scopes: pay_events:write
Idempotent: yes

Body:

{
  "item": {
    "employee_id": 88,
    "hours_paid": 76.0,
    "gross_payment": 4820.00,
    "ordinary_time_earnings": 4820.00,
    "payg_withholding": 1145.00,
    "super_guarantee": 554.30
  }
}

Response: 201 Created with the new Item.

Only one item per employee per pay event. POSTing a second item for the same employee returns 422 Unprocessable Entity.

Update an item

PATCH /api/v1/pay_events/:id/items/:item_id

Scopes: pay_events:write

Body: same shape as create. employee_id cannot be changed — if you need a different employee, delete and re-add.

Remove an item

DELETE /api/v1/pay_events/:id/items/:item_id

Scopes: pay_events:write

Response: 200 OK with { "message": "... removed from pay event" }.

Lifecycle actions

Mark ready

Transitions the pay event from draftready. No more items can be added, edited, or removed.

POST /api/v1/pay_events/:id/mark_ready

Scopes: pay_events:write
Idempotent: yes

Preconditions:
- Must be in draft status
- Must have at least one item

Response: 200 OK with updated Pay Event.

Errors:
- 400 Bad Request if the pay event is not draft, or has no items

Submit to ATO

Queues the pay event for asynchronous lodgement to the ATO gateway.

POST /api/v1/pay_events/:id/submit

Scopes: pay_events:write
Idempotent: yes

Preconditions:
- Must be in ready status
- Employer must have valid M2M credentials (for live; test uses Beeswax's shared credentials)

Response: 200 OK with updated Pay Event in submitted status.

Important: This returns immediately — the actual ATO submission happens in a background job. The pay event status will change from submitted to accepted / rejected / error when the ATO responds. Use webhooks to be notified when this happens, or poll /api/v1/submissions?employer_id=X.

Pay event status lifecycle

draft ─────────────► ready ─────────────► submitted ─────┬─► accepted
                                                          ├─► rejected
                                                          └─► error
Status Meaning
draft Created, being built up with items. Fully editable.
ready Marked ready. No more edits. Queued locally but not yet sent.
submitted Dispatched to the background lodgement job
accepted ATO confirmed receipt and validation passed
rejected ATO validation failed — check the linked submission for error details
error Something went wrong in our infrastructure (signing, network, etc.) — usually retryable

Error handling

If a submission fails, check the linked Submission for the full ATO response and error messages.

You can retry a failed pay event by:

  1. Creating a new pay event for the same period
  2. Re-adding the items
  3. Marking ready and submitting

The ATO tracks uniqueness by message_id, which Beeswax generates automatically — retries after a failure get a fresh message ID so the ATO treats them as new.

For editing a successful submission, see the STP Phase 2 update_event and full_file_replacement event types — set event_type on create to target those modes.

Common tasks

Lodge a weekly pay event for 5 employees

# 1. Create the pay event
PAY_EVENT_ID=$(curl -s https://stp.beeswaxapp.com/api/v1/pay_events \
  -X POST \
  -H "Authorization: Bearer stp_test_..." \
  -H "Idempotency-Key: payrun-acme-2026-04-14" \
  -H "Content-Type: application/json" \
  -d '{
    "pay_event": {
      "employer_id": 12,
      "pay_period_start": "2026-04-08",
      "pay_period_end": "2026-04-14",
      "payment_date": "2026-04-14",
      "pay_frequency": "weekly"
    }
  }' | jq -r '.id')

# 2. Add items for each employee
for emp in 88 89 90 91 92; do
  curl -s https://stp.beeswaxapp.com/api/v1/pay_events/$PAY_EVENT_ID/items \
    -X POST \
    -H "Authorization: Bearer stp_test_..." \
    -H "Idempotency-Key: item-$PAY_EVENT_ID-$emp" \
    -H "Content-Type: application/json" \
    -d "{ \"item\": { \"employee_id\": $emp, \"gross_payment\": 1000.00, \"payg_withholding\": 200.00, \"super_guarantee\": 115.00 } }"
done

# 3. Mark ready
curl -s https://stp.beeswaxapp.com/api/v1/pay_events/$PAY_EVENT_ID/mark_ready \
  -X POST \
  -H "Authorization: Bearer stp_test_..."

# 4. Submit
curl -s https://stp.beeswaxapp.com/api/v1/pay_events/$PAY_EVENT_ID/submit \
  -X POST \
  -H "Authorization: Bearer stp_test_..."

Leave disaggregation

STP Phase 2 requires leave to be reported in four buckets, not a single figure:

  • annual_leave
  • personal_leave (sick, carer's, compassionate)
  • long_service_leave
  • community_service_leave (jury duty, SES, RFS)

If your source system already tracks these separately, send them directly and the values flow through to the ATO unchanged.

If your source system only tracks one "paid leave" number (common for smaller payroll products and for one-off manual pay runs), send that number in paid_leave on the item payload. We map it to annual_leave on ingestion, because annual leave is the dominant category for SMB payroll — roughly 70% of all paid leave taken nationally, and LSL only kicks in at 7–10 years of continuous service, which is comparatively rare.

Rules:

  • If any of the four sub-types is explicitly present and non-blank, paid_leave is ignored — the explicit value wins. This lets you mix them: send paid_leave: 200, personal_leave: 50 and only the personal leave is recorded (200 is discarded because a sub-type was supplied).
  • paid_leave is write-only. On GET, the stored value appears as annual_leave; paid_leave is not returned.
  • If disaggregated reporting matters for your product (e.g. you surface leave balances to employees by type), use the sub-type fields — the fallback is a transitional convenience, not the recommended long-term shape.