# Payroll Journal Entries with the Accounting API

Payroll providers and embedded payroll platforms typically post each pay run to the customer's general ledger as a single journal entry: gross wages, employer taxes, and deductions debited and credited against the right GL accounts. This guide shows how to map payroll categories to GL codes and post a balanced journal entry through the Apideck [Accounting API](/apis/accounting/reference) using the unified [Journal Entries](/apis/accounting/reference#tag/Journal-Entries) resource.

## Why a Unified API

Every accounting system stores journal entries differently. Account references are sometimes IDs, sometimes nominal codes; tracking dimensions are called classes in one system, departments in another, and tags in a third. Apideck normalises that surface so the same payroll-posting code targets every connector the customer is on.

- One request body shape for journal entries across QuickBooks, Xero, NetSuite, Sage Intacct, and the rest.
- A consistent way to discover GL accounts via [`GET /accounting/ledger-accounts`](/apis/accounting/reference#operation/ledgerAccountsAll) for the mapping UI.
- A single auth flow through Apideck Vault, so the customer connects once.
- Webhook events on `accounting.journal-entry.created` for confirming each posting.

## Resource mapping

The unified Journal Entries resource maps to the native GL posting object in each accounting system.

| Connector | Downstream object |
| --- | --- |
| QuickBooks | `JournalEntry` |
| Intuit Enterprise Suite | `JournalEntry` |
| Xero | `Manual Journal` |
| NetSuite | `Journal` |
| Sage Intacct | `General Ledger Journal Entry` |
| Microsoft Dynamics 365 Business Central | `Journal Line` (general journal batch) |
| Exact Online (NL/UK) | `GLTransaction` |
| Odoo | `account.move` |
| Zoho Books | `Journal` |
| Pennylane | `Manual Journal` |
| MYOB Acumatica / Acumatica | `GL Transaction` |
| Moneybird | `GeneralJournalEntry` |
| Yuki | `GLTransaction` |
| Procountor | `Journal` |
| MRI Software | `GL Journal` |
| Rillet | `JournalEntry` |
| Workday | `Journal` |
| DualEntry | `Journal Entry` |
| Campfire | `JournalEntry` |

## Walkthrough

The flow has three steps: load the customer's chart of accounts, build a mapping from payroll categories to GL accounts, then post a balanced journal entry per pay run.

### 1. Load the chart of accounts

Fetch the GL accounts the customer can post to. Filter out non-postable headers and reconciliation accounts client-side based on the `type` and `status` returned per account.

Send this to [`GET /accounting/ledger-accounts`](/apis/accounting/reference#operation/ledgerAccountsAll).

```json
{
  "data": [
    {
      "id": "led_01H8X9Y2A3B4C5D6E7F8G9H0",
      "nominal_code": "6000",
      "name": "Salaries and Wages",
      "type": "expense",
      "classification": "expense",
      "status": "active",
      "currency": "USD"
    },
    {
      "id": "led_01H8X9Y2A3B4C5D6E7F8G9H1",
      "nominal_code": "6010",
      "name": "Employer Payroll Taxes",
      "type": "expense",
      "classification": "expense",
      "status": "active",
      "currency": "USD"
    },
    {
      "id": "led_01H8X9Y2A3B4C5D6E7F8G9H2",
      "nominal_code": "2100",
      "name": "Payroll Liabilities: Federal Tax Withheld",
      "type": "liability",
      "classification": "liability",
      "status": "active",
      "currency": "USD"
    }
  ]
}
```

### 2. Persist the payroll category to GL account mapping

Show the GL accounts in a settings UI and let the customer assign each payroll category (gross wages, employer FICA, federal withholding, state withholding, 401(k) deductions, net pay clearing) to a ledger account ID. Store these mappings keyed by `consumer_id` and `service_id` so the same customer can be on multiple downstream systems.

A typical mapping for a US run looks like this:

```json
{
  "consumer_id": "csm_company_44219",
  "service_id": "quickbooks",
  "categories": {
    "gross_wages": "led_01H8X9Y2A3B4C5D6E7F8G9H0",
    "employer_payroll_taxes": "led_01H8X9Y2A3B4C5D6E7F8G9H1",
    "federal_tax_withheld": "led_01H8X9Y2A3B4C5D6E7F8G9H2",
    "state_tax_withheld": "led_01H8X9Y2A3B4C5D6E7F8G9H3",
    "employee_401k": "led_01H8X9Y2A3B4C5D6E7F8G9H4",
    "net_pay_clearing": "led_01H8X9Y2A3B4C5D6E7F8G9H5"
  }
}
```

### 3. Post the payroll journal entry

After a pay run completes, build one balanced journal entry per pay run. Debits cover gross wages and employer taxes. Credits cover the withholding and deduction liabilities and the net pay clearing account that will later be cleared by the actual ACH debit.

The journal entry must balance: total debits equal total credits. Set `posted_date` to the pay date so the expense lands in the correct period.

Send this to [`POST /accounting/journal-entries`](/apis/accounting/reference#operation/journalEntriesAdd).

```http
POST https://unify.apideck.com/accounting/journal-entries
Authorization: Bearer sk_live_d0d3a8e94e1d4b6cb81b8c6c3a2d4e91
x-apideck-app-id: dGVzdC1hcHAtaWQ
x-apideck-consumer-id: csm_company_44219
x-apideck-service-id: quickbooks
Content-Type: application/json
```

```json
{
  "title": "Payroll run 2025-PR-0042",
  "memo": "Bi-weekly payroll for pay period ending 2025-03-14",
  "posted_date": "2025-03-15",
  "currency": "USD",
  "journal_symbol": "PR",
  "line_items": [
    {
      "description": "Gross wages",
      "type": "debit",
      "total_amount": 48250.00,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H0",
        "nominal_code": "6000"
      }
    },
    {
      "description": "Employer payroll taxes (FICA, FUTA, SUTA)",
      "type": "debit",
      "total_amount": 4112.13,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H1",
        "nominal_code": "6010"
      }
    },
    {
      "description": "Federal income tax withheld",
      "type": "credit",
      "total_amount": 6890.40,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H2",
        "nominal_code": "2100"
      }
    },
    {
      "description": "State income tax withheld",
      "type": "credit",
      "total_amount": 2105.18,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H3",
        "nominal_code": "2110"
      }
    },
    {
      "description": "Employee 401(k) contributions",
      "type": "credit",
      "total_amount": 1930.00,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H4",
        "nominal_code": "2120"
      }
    },
    {
      "description": "Net pay clearing",
      "type": "credit",
      "total_amount": 41436.55,
      "ledger_account": {
        "id": "led_01H8X9Y2A3B4C5D6E7F8G9H5",
        "nominal_code": "2150"
      }
    }
  ]
}
```

Store the returned journal entry `id` against your pay run record so you can reconcile, retract, or supersede it later. To retrieve the same entry, use [`GET /accounting/journal-entries/{id}`](/apis/accounting/reference#operation/journalEntriesOne).

### 4. Optional: split per cost center, project, or department

If the customer tracks payroll by department, project, or location, attach `tracking_categories` to each line item. Discover the available dimensions through [`GET /accounting/tracking-categories`](/apis/accounting/reference#operation/trackingCategoriesAll) and let the customer map their payroll cost centers to the IDs returned. The same line-item shape works regardless of whether the downstream system calls them classes, tracking categories, or dimensions. Tracking category coverage varies per connector; verify in the coverage matrix before relying on this dimension for a given downstream system.

```json
{
  "description": "Gross wages: Engineering",
  "type": "debit",
  "total_amount": 31200.00,
  "ledger_account": {
    "id": "led_01H8X9Y2A3B4C5D6E7F8G9H0",
    "nominal_code": "6000"
  },
  "tracking_categories": [
    { "id": "trk_dept_eng", "name": "Engineering" }
  ]
}
```

## Connector-specific behavior

Coverage and quirks vary across the connector catalog. Read-only support means the connector can list and retrieve journal entries through Apideck but cannot post new ones, so payroll posting is not possible there through this resource.

| Connector | Notes |
| --- | --- |
| `access-financials` | Journal entries not in coverage. Skip this connector for payroll export. |
| `acumatica` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `banqup` | Journal entries not in coverage. |
| `campfire` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `clearbooks-uk` | Journal entries not in coverage. |
| `digits` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `dualentry` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `exact-online` | Read and write supported. Tracking categories are not in coverage for this connector, so dimensional splits must be expressed as separate lines per GL account. |
| `exact-online-nl` | Read and write supported. Same shape as Exact Online. |
| `exact-online-uk` | Read and write supported on journal entries. Bills and credit notes are read-only on this connector. |
| `freeagent` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `freshbooks` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `intuit-enterprise-suite` | Read and write supported. Tracking categories, departments, and locations are all in coverage. |
| `kashflow` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `microsoft-dynamics-365-business-central` | Read and write supported. Journal entries post to the default general journal batch unless a `pass_through` overrides the template; `journal_symbol` is a useful place to record the pay run reference. |
| `moneybird` | Read and write supported. Tracking categories are in coverage and can be applied per line. |
| `mrisoftware` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `myob` | Journal entries not in coverage. Consider posting via expenses or skipping this connector. |
| `myob-acumatica` | Read and write supported. Same shape as Acumatica. |
| `netsuite` | Read and write supported. Subsidiary is required on multi-book accounts; tracking categories are in coverage and are typically used to express departments, classes, and locations. |
| `odoo` | Read and write supported. Posts to `account.move`; the move stays in draft unless the downstream company auto-posts journal entries. |
| `pennylane` | Read and write supported. Tracking categories are in coverage. |
| `procountor-fi` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `quickbooks` | Read and write supported. `tracking_categories` map to QuickBooks Classes; departments and locations are exposed as separate resources. |
| `rillet` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |
| `sage-business-cloud-accounting` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `sage-intacct` | Read and write supported. Posts to a GL Journal; the target journal symbol can be set via `journal_symbol`. Dimensions are in coverage as tracking categories. |
| `sage-intacct-rest` | Journal entries not in coverage. |
| `stripe` | Journal entries not in coverage. Stripe is not a general ledger. |
| `visma-netvisor` | Journal entries are read-only through Apideck, so payroll posting is not supported on this connector. |
| `wave` | Journal entries not in coverage. |
| `workday` | Read and write supported on journal entries. Tracking categories are not in coverage for this connector, so worktag-style dimensions cannot be set through the unified `tracking_categories` field; verify in the coverage matrix before depending on dimensional splits here. |
| `xero` | Read and write supported. Posts as a Manual Journal. The unified `tracking_categories` field maps to existing Xero Tracking Categories (creating new categories through Apideck is not supported), with the usual two-dimension limit per line. |
| `yuki` | Read and write supported. Tracking categories are in coverage. |
| `zoho-books` | Read and write supported. Standard mapping. No known quirks beyond the unified model. |

### NetSuite

For OneWorld accounts, every journal entry must specify a subsidiary. Resolve the customer's subsidiary up front through [`GET /accounting/subsidiaries`](/apis/accounting/reference#operation/subsidiariesAll) and store it alongside the GL mapping. Keep the entire pay run inside one subsidiary; intercompany payroll allocations need a separate intercompany journal.

### Xero

Xero Manual Journals support a maximum of two tracking categories per line. If a customer's payroll cost model needs more dimensions than that, collapse them into a single concatenated category before posting, or split the journal entry across multiple lines per employee group.

### Sage Intacct

Set `journal_symbol` to the customer's payroll journal code (commonly `PR` or `PJ`) so the entry lands in the right book. Sage Intacct enforces dimension restrictions per GL account; if a line is rejected, inspect the response and remove the offending dimension before retrying.

### Microsoft Dynamics 365 Business Central

Business Central journal entries post into a general journal batch. Apideck targets the default batch unless the downstream account is configured otherwise. For multi-batch setups, use `pass_through` to specify the batch name.

### Odoo

Odoo creates the underlying `account.move` in `draft` unless the company has been configured to auto-post journal entries. If the workflow expects the entry to be posted immediately, confirm with the customer that auto-posting is enabled, or post the entry manually in Odoo after creation.

## Next steps

- [Journal Entries reference](/apis/accounting/reference#tag/Journal-Entries)
- [Ledger Accounts reference](/apis/accounting/reference#tag/Ledger-Accounts) for building the GL picker
- [Tracking Categories reference](/apis/accounting/reference#tag/Tracking-Categories) for departmental and project splits
- [Handling Bills and Expenses](/guides/expenses-bills) for non-payroll spend posting
