View Categories

Partner API Processing Payroll

9 min read

Partner API Processing Payroll #

This guide walks you through running payroll end-to-end using the Friday API. By the end you’ll understand how to fetch available pay periods, build a payroll draft, review the cost preview, and approve the payroll for processing.


Before You Start #

Plan requirement — All payroll endpoints require a payroll plan or higher on the API key’s company. If the company is on a timetracking-only plan, requests will return 403.

Company setup — The company must be fully onboarded for payroll (bank account linked, tax details configured). If payroll hasn’t been set up yet, you’ll receive a 404 with a message indicating the company is not configured for payroll.

Base URL — All endpoints below are relative to /partner/v1.


The Happy Path #

┌──────────────────────────────────────┐
│ 1. GET /payrolls/period-options │ ← What periods are available?
└──────────────────┬───────────────────┘

┌──────────────────────────────────────┐
│ 2. POST /payrolls/initialize │ ← Pre-populate employee earnings
└──────────────────┬───────────────────┘

(review & edit data)

┌──────────────────────────────────────┐
│ 3. POST /payrolls │ ← Create draft & get cost preview
└──────────────────┬───────────────────┘

(review preview totals)

┌──────────────────────────────────────┐
│ 4. POST /payrolls/:id/approve │ ← Submit for processing
└──────────────────────────────────────┘

Each step feeds into the next. The API is designed so you can pass response data from one step directly into the request for the next.


Step 1: Discover Available Pay Periods #

GET /payrolls/period-options

This returns the pay periods that are ready to be run, grouped by pay schedule group (e.g., “Biweekly Salaried”, “Weekly Hourly”). Each period option tells you:

| Field | What it means |
|——-|—————|
| start / end | The earning period date range |
| checkDate | The payday (when employees get paid) |
| pay_frequency | weekly, biweekly, semimonthly, or monthly |
| pay_schedule_group_id | Which group of employees this period covers |
| employee_count | Total employees assigned to this group |
| eligible_payroll_employee_count | Employees eligible to be paid in this period |
| isDraft | true if a draft already exists for this period (you can resume it) |
| actions.initialize | A ready-made request body — pass it directly to Step 2 |

Tip: The actions.initialize object is designed to be used as-is. You can grab it from the response and POST it straight to the initialize endpoint without modification.


Step 2: Initialize Payroll Data #

POST /payrolls/initialize

This takes a period (from Step 1) and returns pre-populated earnings data for every eligible employee and contractor. Think of it as the “starting spreadsheet” — hours, rates, PTO, bonuses, and other compensation fields are filled in with defaults from the system.

Request body — use the actions.initialize object from Step 1, or build your own:

{
“type”: “regular”,
“period_start”: “2026-03-01”,
“period_end”: “2026-03-14”,
“payday”: “2026-03-18”,
“pay_frequency”: “biweekly”,
“pay_schedule_group_id”: 3,
“employee_ids”: [40, 159]
}

The employee_ids field is optional. Omit it to initialize data for all eligible employees; include it to scope to specific people.

Understanding the Response #

Each employee entry comes back with:

  • payrollData — the editable earnings: hours, rates, salary, PTO, bonuses, reimbursements, custom earnings, etc.
  • time_totals — read-only time tracking totals (what the time clock computed)
  • gross_pay — the server-calculated gross based on payrollData
  • payment_method — how the employee gets paid (direct_deposit or manual)

Here’s a simplified example:

{
“employee_id”: 123,
“display_name”: “Jane Smith”,
“payment_method”: “direct_deposit”,
“gross_pay”: 2537.50,
“payrollData”: {
“regular_hours”: 80,
“regular_payment_amount”: 25.00,
“overtime_hours”: 5,
“overtime_payment_amount”: 37.50,
“salary_amount”: 0,
“pto_hours”: { “vacation”: 8, “sick”: 0, “holiday”: 0, “approved”: 8 },
“pto_payment_amount”: 25.00,
“bonus_amount”: 0,
“commission_amount”: 0,
“reimbursements_amount”: 0
},
“time_totals”: {
“regular_hours”: 80,
“overtime_hours”: 5,
“pto_hours”: 8,
“paid_time_hours”: 93
}
}

At this point, you can modify any values in payrollData — adjust hours, add a bonus, change reimbursement amounts, etc. The time_totals are informational only and don’t need to be sent back.

What About Splits? #

Some employees will have splits instead of a flat payrollData. This happens when an employee’s pay rate changed mid-period (e.g., they got a raise on March 8th in a March 1–14 pay period).

A split employee looks like this:

{
“employee_id”: 456,
“display_name”: “Bob Johnson”,
“payment_method”: “direct_deposit”,
“gross_pay”: 3200.00,
“splits”: [
{
“earning_period_start”: “2026-03-01”,
“earning_period_end”: “2026-03-07”,
“payrollData”: { “regular_hours”: 40, “regular_payment_amount”: 20.00 }
},
{
“earning_period_start”: “2026-03-08”,
“earning_period_end”: “2026-03-14”,
“payrollData”: { “regular_hours”: 40, “regular_payment_amount”: 22.00 }
}
]
}

Key rule: An employee has either payrollData or splits, never both. When an employee has splits, edit each split’s payrollData individually.


Step 3: Create the Draft #

POST /payrolls

Send the (optionally modified) payroll data back to create a draft. The server calculates taxes, benefits, deductions, and net pay, then returns a full cost preview.

Request body:

{
“type”: “regular”,
“period_start”: “2026-03-01”,
“period_end”: “2026-03-14”,
“payday”: “2026-03-18”,
“pay_frequency”: “biweekly”,
“pay_schedule_group_id”: 3,
“employees”: [
{
“employee_id”: 123,
“payment_method”: “direct_deposit”,
“note”: “Extra shift bonus”,
“payrollData”: {
“regular_hours”: 80,
“regular_payment_amount”: 25.00,
“overtime_hours”: 5,
“overtime_payment_amount”: 37.50,
“bonus_amount”: 200,
“salary_amount”: 0,
“reimbursements_amount”: 0
}
}
],
“contractors”: [
{
“employee_id”: 789,
“payment_method”: “direct_deposit”,
“payrollData”: {
“salary_amount”: 1500
}
}
]
}

Validation rules to keep in mind:

  • type, period_start, period_end, payday are always required.
  • pay_frequency and pay_schedule_group_id are required for type: "regular".
  • Each employee must have employee_id plus either payrollData or splits (not both).
  • Contractors always use payrollData (splits are not allowed for contractors).
  • payment_method defaults to direct_deposit. It’s automatically set to manual for employees without a bank account on file.

Reading the Preview Response #

The response (HTTP 201) includes a full cost breakdown:

{
“data”: {
“id”: “pay_abc123def456”,
“status”: “draft”,
“period_start”: “2026-03-01”,
“period_end”: “2026-03-14”,
“payday”: “2026-03-18”,
“type”: “regular”,
“totals”: {
“employee_gross”: “12500.00”,
“employee_taxes”: “2875.00”,
“employee_net”: “8975.00”,
“contractor_gross”: “3000.00”,
“liability”: “14137.50”,
“cash_requirement”: “14137.50”
},
“items”: [
{
“id”: “pit_abc123”,
“employee”: “emp_abc123”,
“net_pay”: “1850.00”,
“earnings”: [],
“taxes”: [],
“benefits”: [],
“post_tax_deductions”: []
}
],
“contractor_payments”: [
{
“id”: “cpy_abc123”,
“contractor”: “cnt_abc123”,
“amount”: “1500.00”,
“net_pay”: “1500.00”
}
],
“actions”: {
“approve”: {
“method”: “POST”,
“path”: “/partner/v1/payrolls/pay_abc123def456/approve”
}
}
}
}

The key fields to review before approving:

  • totals.cash_requirement — total amount that will be debited from the company’s bank account
  • totals.employee_net — total take-home pay across all employees
  • items — per-employee detail with earnings, taxes, benefits, and deductions
  • contractor_payments — per-contractor payment amounts

Step 4: Approve the Payroll #

Once you’re satisfied with the preview, approve the draft:

POST /payrolls/:payroll_id/approve

No request body is needed. On approval the system:

  1. Submits the payroll to the payment processor
  2. Assigns check numbers for any manual-payment employees
  3. Activates pending pay schedules
  4. Creates PTO accruals for hourly employees
  5. Sends confirmation emails and push notifications to company admins

The payroll transitions from draftpending and will be processed on the payday date.


Optional Steps (Between Draft and Approve) #

You don’t have to go straight from draft to approval. The API supports a review-and-revise workflow:

Re-fetch the preview #

GET /payrolls/:payroll_id/preview

Returns the same cost breakdown as the create-draft response. Useful if the user navigates away and comes back, or if you want to re-display the preview.

Update the draft #

PATCH /payrolls/:payroll_id

Replaces the entire draft with a new payload. The request body follows the same shape as POST /payrolls. Use this when you need to change employee hours, add a bonus, or adjust any earnings after seeing the preview.

Delete the draft #

DELETE /payrolls/:payroll_id

Discards the draft entirely. The period becomes available again for a new payroll run.

All three of these endpoints only work while the payroll is in draft status. Once approved, the draft is locked.


After Approval #

Once a payroll is approved and processed, you can access additional data:

View Payroll Details #

GET /payrolls/:payroll_id

Returns the full payroll record with updated status, per-employee items, and contractor payments.

List Past Payrolls #

GET /payrolls

Paginated list of all payrolls for the company. Supports filters:

| Filter | Example | Description |
|——–|———|————-|
| status | pending, paid, failed | Payroll processing status |
| year | 2026 | Filter by tax year |
| type | regular, off_cycle | Payroll type |
| pay_frequency | biweekly | Pay schedule frequency |

Download Artifacts #

After a payroll has been processed:

| Endpoint | What it returns |
|———-|—————-|
| GET /payrolls/:id/paystubs | Employee pay stubs (PDF download URL) |
| GET /payrolls/:id/checks | Paper check images for manual-payment employees |
| GET /payrolls/:id/checks/full | Detailed per-employee and per-contractor check data |

Run Payroll Reports #

Seven report endpoints are available under /payroll-reports:

| Endpoint | Description |
|———-|————-|
| GET /payroll-reports/company-payroll-summary | High-level payroll totals by period |
| GET /payroll-reports/cash-requirement | Cash needed for upcoming payrolls |
| GET /payroll-reports/tax-liability | Tax obligations breakdown |
| GET /payroll-reports/tax-deposits | Tax deposit history |
| GET /payroll-reports/w4-exemption-status | Employee W-4 filing statuses |
| GET /payroll-reports/w2-preview | Year-end W-2 data preview |
| GET /payroll-reports/contractor-payments | 1099-eligible contractor payment summary |


Staying in Sync with Webhooks #

If you’ve registered a webhook endpoint, Friday will notify you of payroll lifecycle events automatically:

| Event | When it fires |
|——-|—————|
| payroll.created | A payroll draft is created |
| payroll.updated | A draft is modified |
| payroll.deleted | A draft is discarded |
| payroll.approved | A payroll is approved for processing |
| payroll.paid | A payroll has been successfully paid |
| payroll.failed | A payroll encountered a processing error |

See the Webhook Integration Guide in the developer portal for setup instructions, payload format, and signature verification.


Common Pitfalls #

Sending both payrollData and splits for the same employee — The API enforces mutual exclusivity. If the initialize response gave you splits, send back splits. If it gave you payrollData, send back payrollData. Mixing them returns a validation error.

Forgetting pay_frequency or pay_schedule_group_id — These are required for regular type payrolls. They’re included in the actions.initialize body from Step 1 automatically.

Trying to approve a non-draft payroll — Only payrolls with status: "draft" can be approved, updated, previewed, or deleted. Once approved, the payroll is locked.

Editing time_totals or gross_pay — These are read-only / server-computed fields. The server ignores them on create and update. Edit payrollData to change compensation values; gross_pay will be recalculated from those fields.

Contractors with splits — Contractors never have splits. Always use a flat payrollData for contractor entries.


Quick Reference #

| Action | Method | Endpoint | Plan |
|——–|——–|———-|——|
| List pay periods | GET | /payrolls/period-options | payroll |
| Initialize earnings | POST | /payrolls/initialize | payroll |
| Create draft | POST | /payrolls | payroll |
| Preview draft | GET | /payrolls/:id/preview | payroll |
| Update draft | PATCH | /payrolls/:id | payroll |
| Delete draft | DELETE | /payrolls/:id | payroll |
| Approve payroll | POST | /payrolls/:id/approve | payroll |
| List payrolls | GET | /payrolls | payroll |
| Get payroll detail | GET | /payrolls/:id | payroll |
| Download paystubs | GET | /payrolls/:id/paystubs | payroll |
| Download checks | GET | /payrolls/:id/checks | payroll |
| Full check detail | GET | /payrolls/:id/checks/full | payroll |