On the 1st of each month, a cron job updates your salary issue with the previous month's billing summary and posts comments so you can submit your invoice.
Monthly billing automation only processes contractors whose
profile.yaml engagement_mode is retainer (the
default when the field is absent). Members set to employee or project are
skipped: no billing summary is generated and no monthly comment or reminder is
posted on their salary issue.
If HR identifies an issue after the invoice is sent, they'll contact you by email to request a corrected invoice.
Your salary issue description always shows the current summary table — this is the source of truth for what was billed and why.
Example — normal month:
| Metric | Value |
|---|---|
| Approved Hours | 16h |
| Paid Leave | 2 days (16h) |
| Bug Hours | 9h (7h current + 2h carry-forward) |
| Dispute Credits | 4h |
| Expenses | $15.00 (2 items) |
| Charges | −$42.00 (1 item) |
| Net Payout | $108.00 |
Example — zero-PR month with bug attributions:
| Metric | Value |
|---|---|
| Approved Hours | 0h |
| Paid Leave | 0h |
| Bug Hours | 5h |
| Carry-forward | 5h (into following month) |
| Net Payout | $0.00 |
Example — partial charge recovery:
| Metric | Value |
|---|---|
| Approved Hours | 6h |
| Bug Hours | 0h |
| Charges | -$50.00 ($42.00 current + $8.00 carry-forward) (1 item) |
| Charges carry-forward | -$20.00 (into following month) |
| Net Payout | $0.00 |
If required fields are missing from profile.yaml when you open your
invoice link, the page shows an error instead of a partial invoice, listing
every missing field by name:
Invoice unavailable
Your contractor profile is incomplete. Add the following fields to profile.yaml
in your HR repo and re-open this link:
• first_name
• residential_address
See setup instructions: https://wizard.holdex.io/docs/....
Add the missing fields to profile.yaml in your HR repo and re-open the
invoice link.
Invoice janedoe-2026-05-31 Issue Date: 5 Jun 2026
Due Date: 30 Jun 2026
Period: 1–31 May 2026
From: Jane Doe Bill to: billing@holdex.io
jane@example.com
Address: 123 Main Street, Springfield, IL 62701, US
────────────────────────────────────────────────────────────────────
Description Qty Unit Price Amount
────────────────────────────────────────────────────────────────────
Product development (hours billed) 16h $5.00 $80.00
Paid leave (2 days × 8h) 16h $5.00 $80.00
Quality Adjustment (7h + 2h fwd) 9h −$5.00 −$45.00
Dispute Resolution Credit 4h $5.00 $20.00
Charges (1 item) 1 −$42.00 −$42.00
AWS hosting (10 May) 1 $10.00 $10.00
Domain registration (15 May) 1 $5.00 $5.00
────────────────────────────────────────────────────────────────────
Total due $108.00
Payment Details
───────────────
Bank Account 1
Bank: First National Bank
Account Name: Jane Doe
Account Number: 1234567890
SWIFT/BIC: FNBAUS12XXX
Bank Account 2
Bank: Second National Bank
Account Name: Jane Doe
IBAN: GB29NWBK60161331926819
Crypto Wallet
Network: Ethereum
Token: USDT (ERC-20)
Address: 0x742d35Cc6634C0532925a3b8D4C9b5A93b2c4D1f
Verify: https://wizard.holdex.io/invoices/janedoe-2026-05-31?token=<hmac>
Durations on both pages are rendered humanized — 30m, 1h, 1h30m —
rather than as raw decimal hours.
PR references and charge references are clickable links.
# Breakdown
Merged PRs
────────────────────────────────────────────────────────────────────────────────
Org Repository PR Hours Amount
────────────────────────────────────────────────────────────────────────────────
holdex wizard #1201 feat(ui): play music 4h $20.00
holdex wizard #1198 fix(api): mute sound 3h $15.00
holdex wizard #1195 docs(sdk): open door 5h $25.00
holdex wizard #1190 feat(db): close window 4h $20.00
────────────────────────────────────────────────────────────────────────────────
Subtotal 16h $80.00
Bug Attributions
────────────────────────────────────────────────────────────────────────────────
Org Repository PR Hours Amount
────────────────────────────────────────────────────────────────────────────────
holdex wizard #1201 feat(ui): play music 4h −$20.00
holdex wizard #1198 fix(api): mute sound 3h −$15.00
Carry-forward from April 2026 2h −$10.00
────────────────────────────────────────────────────────────────────────────────
Quality Adjustment 9h −$45.00
Dispute Resolutions
────────────────────────────────────────────────────────────────────────────────
Org Repository PR Hours Amount
────────────────────────────────────────────────────────────────────────────────
holdex wizard #1201 feat(ui): play music 4h $20.00
────────────────────────────────────────────────────────────────────────────────
Dispute Resolution Credit 4h $20.00
Charges
────────────────────────────────────────────────────────────────────
Date Description Reference Amount
────────────────────────────────────────────────────────────────────
Carry-forward from April 2026 −$8.00
2026-05-20 Undeprecated AWS server #1321 −$42.00
Carry-forward to June 2026 −$20.00
────────────────────────────────────────────────────────────────────
Charges −$30.00
Net (PR work) 11h $55.00
When you had merged PRs in the billing month, the invoice includes an additional Cost Allocation section (a separate page when you print to PDF). It groups your approved PR hours into buckets by organisation, repository, and PR title prefix, with hours and amounts per bucket and a subtotal on each table.
Amounts use your hourly rate for the billing month (one rate for all PRs in that month). If no rate is configured, hours are still shown without dollar columns.
Example:
Cost Allocation
### By org
| Bucket | Hours | Amount |
| holdex | 16h | $80.00 |
| **Subtotal** | **16h** | **$80.00** |
### By type(scope)
| Bucket | Hours | Amount |
| feat(ui) | 10h | $50.00 |
| feat | 3h | $15.00 |
| fix(api) | 3h | $15.00 |
| **Subtotal** | **16h** | **$80.00** |
(…plus By repo, By type, and By scope tables when those buckets are non-empty.)
The invoice is rendered on demand from current data — nothing is stored. The same signed link works at any time for any past month.
| Variable | Source |
|---|---|
| Invoice number | {login}-{YYYY}-{MM}-{DD} (last day of billing month) |
| Billing period | First–last day of the billing month |
| Billing month | Month the PR was merged |
| Issue date | Date you generate the PDF |
| Due date | Last day of the month following the billing period |
| Contractor name | profile.yaml → first_name + last_name |
| Contractor email | profile.yaml → email |
| Contractor address | profile.yaml → residential_address |
| Total approved hours | Sum of your submitted hours for PRs merged in the billing month |
| Paid leave hours | 8h per approved paid leave day taken in the month (leave.yaml) |
| Hourly rate (per PR) | profile.yaml rate history (rate effective for the billing month) |
| Bug hours | Hours from PRs attributed to you as bugs, or PRs you approved that were later reported as bugs |
| Carry-forward | Excess bug hours from a prior month where payout hit $0 |
| Dispute Resolution Credit hours | Hours from successfully disputed bug attributions |
| Per-PR org, repo, number, title, hours | Your merged PRs for the month |
| Monthly hours cap | profile.yaml → max_cap (applied to net hours — see Monthly hours cap) |
| Approved expenses | expenses.yaml entries with date in the billing month |
| Charges | charges.yaml entries with date in the billing month (retainer only) |
| Profile | profile.yaml |
These team-members.json fields don't appear on the invoice itself — they control who can see it and where the monthly billing comment is posted:
| Field | Effect |
|---|---|
billingManager |
When true on your entry, lets you view any contractor's invoice and run Cost Breakdown reports, not just your own. |
hr_team |
Top-level (not per-member) GitHub team tagged on the monthly billing comment. |
The Quality Adjustment follows the formula in the Standard Terms:
payout = max(0, (totalHours - bugHours) × hourlyRate)
Expenses are added and charges are subtracted after that calculation. See Expenses and Charges.
If your agreement sets a monthly hours cap, it is stored as max_cap in
profile.yaml. The billing engine applies the cap to
net hours, not to raw approved hours:
netHours = approvedHours − bugHours + disputeCredits + paidLeaveHours
billedHours = min(netHours, max_cap)
Bug-attributed hours free up space under the cap — they are re-earnable in
the same month. Hours above max_cap are unpaid and do not carry forward.
Expenses are dollar amounts, not hours, so they sit outside the cap.
Example — max_cap: 160:
You have already logged 160h of approved work this month (at the cap). Then 10h are attributed as bugs:
netHours = 160 − 10 + 0 + 0 = 150h
billedHours = min(150, 160) = 150h
You now have 10h of headroom. If you merge another 10h of approved work before month end, net hours return to 160h and you are paid for the full cap again — the bug hours were deducted, then re-earned.