View all documentations

How to Submit Your Invoice

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.

Who gets billed

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.

Steps

  1. Wait for the monthly comment on your salary issue (posted on the 1st).
  2. Open the signed invoice link from the comment.
  3. Save the page as PDF using your browser's print function.
  4. Email the PDF to the Holdex billing email (also in the comment).

If HR identifies an issue after the invoice is sent, they'll contact you by email to request a corrected invoice.

When nothing is billed

  • If no PRs were merged and no leave was taken, the cron posts a comment noting nothing was billed that month — no link is included.
  • If leave was taken but no PRs were merged, an invoice is generated without the Product development line; Quality Adjustment and Dispute Resolution Credit lines still appear if applicable.
  • If there are bug attributions that month despite zero approved hours and no leave, those are shown in the summary and carry forward to the following month — no link is included.

Monthly Billing Summary

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 your profile is incomplete

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.

Page 1 — Invoice

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.

Page 2 — Breakdown

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

Cost Allocation

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.

Invoice variables

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.yamlfirst_name + last_name
Contractor email profile.yamlemail
Contractor address profile.yamlresidential_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.yamlmax_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

Access & Visibility

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.

Quality Adjustment

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.

Monthly hours cap

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.