# WH-347 Payroll Arithmetic Check (`wheresprada/wh347-payroll-arithmetic-check`) Actor

Check construction payroll hours, project gross, cash fringes, deductions and net-pay arithmetic before review. Trace findings to supplied fields. No legal certification, tax calculation or wage-rate lookup.

- **URL**: https://apify.com/wheresprada/wh347-payroll-arithmetic-check.md
- **Developed by:** [Mahruf Hossain](https://apify.com/wheresprada) (community)
- **Categories:** Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.35 / completed payroll arithmetic review

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## WH-347 Payroll Arithmetic Check

Check the arithmetic in a prepared construction payroll before the next review. This Actor compares daily hours with weekly totals, recomputes project cash wages and cash fringes using your supplied rates, and checks deductions and net-pay subtraction. Every difference points back to the field your team should inspect.

Use it alongside your existing payroll or certified-payroll process. It does **not** certify a payroll, choose a prevailing wage, calculate taxes or submit WH-347. A clean arithmetic report is not evidence of legal compliance.

### Who is this for?

Construction payroll teams, outsourced construction bookkeepers and software integrators who already have their rates, classified hours and paycheck totals but want a repeatable arithmetic check. An API report can help a reviewer focus on a short list of differences instead of manually recalculating every row.

The U.S. Department of Labor's WH-347 instructions describe weekly payroll reporting and distinguish project earnings from all-jobs gross and net wages. This tool checks a limited subset of that arithmetic. It does not replace a payroll provider, professional review, wage determination or signed Statement of Compliance.

### Key features

- Add seven days of regular and overtime hours and compare them with the reported weekly row total.
- Recompute cash wage components from the rates you supply.
- Keep cash fringe payments separate from cash wages. Employer-paid benefit credits are outside this product's scope.
- Compare project earnings with the reported row gross; keep all-jobs paycheck gross separate.
- Sum deduction entries and check reported gross minus reported deductions against reported net.
- Aggregate split project/classification rows using pseudonymous worker IDs.
- Flag impossible cross-row daily totals above 24 hours.
- Deduplicate identical row IDs explicitly and reject conflicting duplicates.
- Produce one structured report with field paths, expected amounts, reported amounts and differences.

There is no network fetch, AI interpretation, tax service or external payroll integration inside the Actor. Financial arithmetic uses integer units, not floating-point multiplication.

### Practical use cases

1. **Pre-review checks:** inspect a prepared weekly export for totals that do not match its component hours and rates.
2. **Bookkeeper handoffs:** provide the payroll reviewer with exact fields to investigate rather than an unexplained error count.
3. **Split classifications:** total a worker's project rows without mistaking those amounts for their complete paycheck.
4. **Integration checks:** test the normalized data your own payroll-to-report mapping produces.

No claimed savings or adoption figures are included: they have not been measured. A discrepancy means the supplied fields do not reconcile under the documented arithmetic, not that a worker was actually underpaid.

### Quick start

Open the Input tab and use the prefilled demonstration. It contains invented worker alias `W-01`, 40 hours at $30 plus $5 cash fringe per hour, and two deliberate reported-field differences. A completed demonstration is billable.

For your own data, replace names with stable aliases, map the fields below and run one complete weekly review. Do not submit SSNs, bank accounts, addresses or employee contact details. Read both `findings` and `unresolvedChecks` before using the result.

### Input documentation

| Field | Type | Meaning and limit |
|---|---|---|
| `weekEnding` | string | Real date in `YYYY-MM-DD` form. |
| `rows` | array | 1–1000 project/classification rows. |
| `workers` | array | 1–1000 all-jobs paycheck summaries; one per included worker. |

Each row requires:

| Field | Meaning |
|---|---|
| `id` | Unique row alias; identical repeated IDs are counted and ignored, conflicting repeats fail. |
| `workerId` | Alias matching one worker summary. |
| `projectId` | Project alias. |
| `classification` | Short trade label; this tool does not verify classification entitlement. |
| `days` | Exactly seven `{ "regular": "8.00", "overtime": "0.00" }` objects from workweek start through `weekEnding`. |
| `regularRate` | Supplied regular cash wage rate, excluding cash fringe. |
| `overtimeRate` | Supplied overtime cash wage rate, excluding cash fringe. No legal rate is inferred. |
| `cashFringeRate` | Supplied cash fringe rate per hour; use `"0.00"` when none is paid in cash. |
| `reportedHours` | Reported total hours for this row. |
| `reportedProjectGross` | Reported cash earnings on this project/classification row, including cash fringe. |

Worker summaries require `workerId`, `allJobsGross`, `deductions`, `reportedDeductions` and `reportedNet`. Deductions are `{ "id": "TAX", "amount": "200.00" }` entries; use an empty array for none. They are supplied amounts, not calculated tax obligations.

All hours, rates and amounts must be nonnegative plain **decimal strings** with at most two decimal places. No exponent notation, negative adjustments or currency symbols. IDs start with a letter and contain up to 40 letters, digits, underscores or hyphens. Unknown fields are rejected to reduce accidental disclosure of unrelated payroll data.

### Tested example input

```json
{
  "weekEnding": "2026-10-10",
  "rows": [{
    "id": "ROW-1", "workerId": "W-01", "projectId": "P-01",
    "classification": "Electrician",
    "days": [
      {"regular":"8.00","overtime":"0.00"},
      {"regular":"8.00","overtime":"0.00"},
      {"regular":"8.00","overtime":"0.00"},
      {"regular":"8.00","overtime":"0.00"},
      {"regular":"8.00","overtime":"0.00"},
      {"regular":"0.00","overtime":"0.00"},
      {"regular":"0.00","overtime":"0.00"}
    ],
    "regularRate":"30.00", "overtimeRate":"45.00", "cashFringeRate":"5.00",
    "reportedHours":"39.00", "reportedProjectGross":"1390.00"
  }],
  "workers": [{
    "workerId":"W-01", "allJobsGross":"1400.00",
    "deductions":[{"id":"TAX","amount":"200.00"}],
    "reportedDeductions":"200.00", "reportedNet":"1200.00"
  }]
}
```

### Output example and field meanings

The implementation produces these actual findings for the invented example above:

```json
{
  "summary": {
    "rowCount": 1,
    "workerCount": 1,
    "duplicateRowsIgnored": 0,
    "findingCount": 2,
    "calculatedProjectGross": "1400.00",
    "reportedAllJobsGross": "1400.00",
    "reportedNet": "1200.00"
  },
  "findings": [
    {
      "code": "ROW_HOURS_MISMATCH",
      "path": "rows[0].reportedHours",
      "workerId": "W-01", "rowId": "ROW-1",
      "expected": "40.00", "reported": "39.00", "difference": "-1.00",
      "reviewRequired": true
    },
    {
      "code": "PROJECT_GROSS_MISMATCH",
      "path": "rows[0].reportedProjectGross",
      "workerId": "W-01", "rowId": "ROW-1",
      "expected": "1400.00", "reported": "1390.00", "difference": "-10.00",
      "reviewRequired": true
    }
  ]
}
```

The full report also contains normalized row calculations, worker totals, a timestamp, an input fingerprint, the rounding convention and checks outside scope. Monetary strings preserve exact cents. The sign of `difference` is reported minus expected; for an hours finding it represents hours, not dollars.

### How the calculation works

For each row, regular hours are multiplied by the supplied regular rate and overtime hours by the supplied overtime rate. Total hours are multiplied by the supplied cash fringe rate. Each of those three components is independently rounded half-up to cents before summation.

This explicit rounding convention can differ from your payroll provider. Investigate small rounding differences before treating them as substantive errors. The Actor never derives legal overtime entitlement from project hours alone: off-project work, multiple rates and applicable rules require a separate review.

For worker summaries, deduction entries are added and compared with the reported deduction total. Reported all-jobs gross minus reported deductions is compared with reported net. Project gross is not substituted for all-jobs gross.

### Pricing

**$0.35 per completed payroll arithmetic review**, covering up to 1000 rows and 1000 worker summaries within the input limit. There is one custom report event, not one event per worker, finding or dataset row.

| Completed reviews | Actor event charges |
|---:|---:|
| 1 | $0.35 |
| 4 | $1.40 |
| 100 | $35.00 |
| 1000 | $350.00 |

A report with findings is completed work. Malformed input does not trigger the custom report event. Apify platform or plan charges may apply according to the pricing page and current terms. These examples are arithmetic, not earnings forecasts. Separate reruns are separately billable; the tool does not offer free unlimited rechecks of a payroll week.

### API integration and recurring use

After publication, use the official Apify JavaScript client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('wheresprada/wh347-payroll-arithmetic-check').call(payrollInput);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].findings);
```

Keep the API token in a secret store or environment. You must map your payroll export to the documented input: there are no live ADP, QuickBooks, Sage or LCPtracker connectors. A weekly scheduled Apify Task can process a prepared input, but does not automatically fetch the next payroll. Update the task input through your authorized workflow before running it.

### Limits and accuracy

Input is limited to 2 MB, 1000 rows and 1000 summaries. The Actor uses a fixed 128 MB allocation and a 30-second runtime limit. Rates are capped at $10,000 per hour and hours at 24 per row per day. Unsupported inputs fail rather than being silently truncated.

This is not a complete WH-347 form validator. It does not generate forms, signatures or certifications. It does not verify applicable determinations, apprentice eligibility, legal deductions, employer benefit credits, tax withholding, state rules or the accuracy of recorded hours. `reviewRequired: false` means only that the supplied arithmetic passed these checks.

### Privacy and security

Use pseudonymous worker IDs. Names, SSNs, bank details and contact fields are not requested. The schema and runtime reject unexpected fields, but they cannot guarantee that a user has not put personal information inside a permitted label. Review your input before upload.

No external API or AI service receives the input from this code. Apify stores input and output under its platform policies and your account settings. The Actor does not promise automatic deletion, a particular legal certification or zero platform logging. Keep runs and datasets private and manage retention. Customer payroll content is not written to application error messages.

### Troubleshooting and FAQ

**Why did the input fail?** Check required fields, decimal strings, seven day entries, IDs and workload limits. Remove names or other unexpected fields. Do not paste confidential input into a public issue.

**Why is project gross lower than all-jobs gross?** The worker may have other earnings. That difference alone is not flagged. Reported project earnings exceeding all-jobs gross are flagged.

**Does a net-pay finding mean tax is wrong?** No. It checks subtraction against the supplied deduction total. Tax calculation and deduction legality are outside scope.

**What if the provider rounds differently?** Compare its documented rounding with the three-component, per-row half-up convention described above. The finding is an arithmetic review request, not a legal conclusion.

**Can it certify prevailing-wage compliance?** No. A reviewer must establish rates, classifications, overtime requirements and other obligations independently.

**Why did repeated rows disappear?** Only identical rows sharing the same row ID are deduplicated; the count is explicit. Conflicting repeated IDs stop the run. Different row IDs remain separate and aggregate into worker totals.

**Can I include negative corrections?** Not in this version. Reversals and adjustment payrolls require a different review model; do not disguise a negative amount as positive.

### Support

Use the Actor's Apify Store Issues tab with the run ID and a description of expected behavior. If needed, provide a small invented reproduction with the same arithmetic issue. Never post customer payroll files, employee identifiers, credentials or bank information.

References: [DOL WH-347 instructions](https://www.dol.gov/agencies/whd/forms/wh347), [commercial payroll workflow comparison](https://www.certifiedpayrollpro.com/pricing), and [Apify pay-per-event pricing](https://docs.apify.com/actors/publishing/monetize/pay-per-event). Independent product; not affiliated with the Department of Labor or these providers.

# Actor input Schema

## `weekEnding` (type: `string`):

Real calendar date, YYYY-MM-DD. Seven day entries run from workweek start through this date.

## `rows` (type: `array`):

1–1000 rows. Required: id, workerId, projectId, classification, seven days of regular/overtime hour strings, regularRate, overtimeRate, cashFringeRate, reportedHours, reportedProjectGross. Decimal strings only; rates and hours at most two decimals.

## `workers` (type: `array`):

1–1000 worker summaries. Required: workerId, allJobsGross, deductions \[{id,amount}], reportedDeductions, reportedNet. Use \[] for no deductions. All-jobs gross may exceed this project gross. Do not include personal data.

## Actor input object example

```json
{
  "weekEnding": "2026-10-10",
  "rows": [
    {
      "id": "ROW-1",
      "workerId": "W-01",
      "projectId": "P-01",
      "classification": "Electrician",
      "days": [
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "0.00",
          "overtime": "0.00"
        },
        {
          "regular": "0.00",
          "overtime": "0.00"
        }
      ],
      "regularRate": "30.00",
      "overtimeRate": "45.00",
      "cashFringeRate": "5.00",
      "reportedHours": "39.00",
      "reportedProjectGross": "1390.00"
    }
  ],
  "workers": [
    {
      "workerId": "W-01",
      "allJobsGross": "1400.00",
      "deductions": [
        {
          "id": "TAX",
          "amount": "200.00"
        }
      ],
      "reportedDeductions": "200.00",
      "reportedNet": "1200.00"
    }
  ]
}
```

# Actor output Schema

## `report` (type: `string`):

No description

## `reviews` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "weekEnding": "2026-10-10",
    "rows": [
        {
            "id": "ROW-1",
            "workerId": "W-01",
            "projectId": "P-01",
            "classification": "Electrician",
            "days": [
                {
                    "regular": "8.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "0.00",
                    "overtime": "0.00"
                },
                {
                    "regular": "0.00",
                    "overtime": "0.00"
                }
            ],
            "regularRate": "30.00",
            "overtimeRate": "45.00",
            "cashFringeRate": "5.00",
            "reportedHours": "39.00",
            "reportedProjectGross": "1390.00"
        }
    ],
    "workers": [
        {
            "workerId": "W-01",
            "allJobsGross": "1400.00",
            "deductions": [
                {
                    "id": "TAX",
                    "amount": "200.00"
                }
            ],
            "reportedDeductions": "200.00",
            "reportedNet": "1200.00"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wheresprada/wh347-payroll-arithmetic-check").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "weekEnding": "2026-10-10",
    "rows": [{
            "id": "ROW-1",
            "workerId": "W-01",
            "projectId": "P-01",
            "classification": "Electrician",
            "days": [
                {
                    "regular": "8.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "8.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "0.00",
                    "overtime": "0.00",
                },
                {
                    "regular": "0.00",
                    "overtime": "0.00",
                },
            ],
            "regularRate": "30.00",
            "overtimeRate": "45.00",
            "cashFringeRate": "5.00",
            "reportedHours": "39.00",
            "reportedProjectGross": "1390.00",
        }],
    "workers": [{
            "workerId": "W-01",
            "allJobsGross": "1400.00",
            "deductions": [{
                    "id": "TAX",
                    "amount": "200.00",
                }],
            "reportedDeductions": "200.00",
            "reportedNet": "1200.00",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("wheresprada/wh347-payroll-arithmetic-check").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "weekEnding": "2026-10-10",
  "rows": [
    {
      "id": "ROW-1",
      "workerId": "W-01",
      "projectId": "P-01",
      "classification": "Electrician",
      "days": [
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "8.00",
          "overtime": "0.00"
        },
        {
          "regular": "0.00",
          "overtime": "0.00"
        },
        {
          "regular": "0.00",
          "overtime": "0.00"
        }
      ],
      "regularRate": "30.00",
      "overtimeRate": "45.00",
      "cashFringeRate": "5.00",
      "reportedHours": "39.00",
      "reportedProjectGross": "1390.00"
    }
  ],
  "workers": [
    {
      "workerId": "W-01",
      "allJobsGross": "1400.00",
      "deductions": [
        {
          "id": "TAX",
          "amount": "200.00"
        }
      ],
      "reportedDeductions": "200.00",
      "reportedNet": "1200.00"
    }
  ]
}' |
apify call wheresprada/wh347-payroll-arithmetic-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wheresprada/wh347-payroll-arithmetic-check"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/lT90gba1cEheIXyp9/builds/50q7nP3VVPzxyLJmW/openapi.json
