Processing Payroll via the Friday API

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:

FieldWhat it means
start / endThe earning period date range
checkDateThe payday (when employees get paid)
pay_frequencyweekly, biweekly, semimonthly, or monthly
pay_schedule_group_idWhich group of employees this period covers
employee_countTotal employees assigned to this group
eligible_payroll_employee_countEmployees eligible to be paid in this period
isDrafttrue if a draft already exists for this period (you can resume it)
actions.initializeA 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:

FilterExampleDescription
statuspending, paid, failedPayroll processing status
year2026Filter by tax year
typeregular, off_cyclePayroll type
pay_frequencybiweeklyPay schedule frequency

Download Artifacts

After a payroll has been processed:

EndpointWhat it returns
GET /payrolls/:id/paystubsEmployee pay stubs (PDF download URL)
GET /payrolls/:id/checksPaper check images for manual-payment employees
GET /payrolls/:id/checks/fullDetailed per-employee and per-contractor check data

Run Payroll Reports

Seven report endpoints are available under /payroll-reports:

EndpointDescription
GET /payroll-reports/company-payroll-summaryHigh-level payroll totals by period
GET /payroll-reports/cash-requirementCash needed for upcoming payrolls
GET /payroll-reports/tax-liabilityTax obligations breakdown
GET /payroll-reports/tax-depositsTax deposit history
GET /payroll-reports/w4-exemption-statusEmployee W-4 filing statuses
GET /payroll-reports/w2-previewYear-end W-2 data preview
GET /payroll-reports/contractor-payments1099-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:

EventWhen it fires
payroll.createdA payroll draft is created
payroll.updatedA draft is modified
payroll.deletedA draft is discarded
payroll.approvedA payroll is approved for processing
payroll.paidA payroll has been successfully paid
payroll.failedA 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.