> ## Documentation Index
> Fetch the complete documentation index at: https://docs.soundchecklive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Invoice source links

> InvoiceLine.source_key ties an invoice line to what it bills: a deposit, the balance, an addendum, an overtime entry or a trip buyout. One live invoice per source.

Every gig invoice is a normal `Invoice` with `InvoiceLine` rows (the #3039 flow: `InvoiceEditor`, `invoiceForm.ts`, `db/invoices.ts`). A line that bills something Soundcheck tracks elsewhere carries a **source key** in `invoice_lines.source_key`. The key is the only link between an invoice line and its origin. Do not add a join table from a feature table to `invoices`.

## Key grammar

| Kind | Key | Origin |
| - | - | - |
| `DEPOSIT` | `deposit_1`, `deposit_2` | Billing terms deposit (split deposits use both) |
| `BALANCE` | `balance` | Contract price minus deposit |
| `ADDENDUM` | `addendum:<addendum id>` | A line in `event_billing_terms.addendum_lines` |
| `OVERTIME` | `overtime:<overtime entry uuid>` | A POSTED `overtime_entries` row |
| `BUYOUT` | `buyout:<trip id>:<category>` | A travel Trip's buyout for one category |

Addendum and trip ids are 1–64 of `A-Z a-z 0-9 _ -`. Categories are 1–40 of `a-z 0-9 _ -`. A key is at most 160 characters.

`invoiceSourceGroupOf(key)` names the invoice a key is drafted onto: the balance and every addendum share `balance`, every category of a trip shares `buyout:<trip id>`, and the rest stand alone.

## Guarantees

* **One live invoice per source per gig.** Creating or editing an invoice whose lines carry source keys takes the gig's billing lock (`lockEventBilling`) and refuses a key already on another non-void invoice with `409 INVOICE_SOURCE_TAKEN`. Voiding the invoice frees the key.
* **A key appears once per invoice.** The parser rejects a second line with the same key.
* **Unknown keys are rejected** at the API boundary (`parseInvoiceFields`) with 400.
* **Overtime keys must name a POSTED entry on the same gig.**
* Sending, voiding, receipts and receivables are the ordinary invoice lifecycle. A source key changes none of them.

The format is enforced in TypeScript, not by a database constraint. Always build and read keys through `invoiceSource.ts`.

## API

Pure helpers, `api-events/src/systems/invoices/invoiceSource.ts`:

```ts theme={null}
invoiceSourceKey({ kind: 'BUYOUT', tripId, category }) // 'buyout:<trip>:<category>'
parseInvoiceSourceKey(key)  // InvoiceSource | null
invoiceSourceKindOf(key)    // 'DEPOSIT' | 'BALANCE' | 'ADDENDUM' | 'OVERTIME' | 'BUYOUT' | null
invoiceSourceGroupOf(key)
buyoutSourcePrefix(tripId)  // 'buyout:<trip>:'
isBuyoutTripId(tripId), isBuyoutCategory(category)
```

`systems/billing-schedule/billingPlan.ts` re-exports the shorthand builders `addendumSourceKey`, `overtimeSourceKey`, `buyoutSourceKey`, `buyoutSourcePrefix` and the `DEPOSIT_1_SOURCE`, `DEPOSIT_2_SOURCE` and `BALANCE_SOURCE` constants.

Reads, `api-events/src/db/billingCharges.ts`:

* `listInvoiceSourceLinks(eventId, { sourceKeyPrefix?, includeVoid? }, client?)` returns one row per sourced line: `source_key`, `invoice_line_id`, `invoice_id`, `invoice_number`, `invoice_status`, `currency`, `amount`, `ledger_item_id`. Void invoices are left out unless `includeVoid` is set.
* `listBillingCharges(eventId)` returns the newest charge per key, preferring a non-void invoice, with its `kind` and due rule. `GET /api/v1/events/:eventId/billing` builds the schedule from it.

HTTP: `POST` and `PUT /api/v1/events/:eventId/invoices[/:invoiceId]` accept `lines[].source_key`. Invoice reads return it on each line.

## Trip buyouts

1. Build the draft lines with `buildBuyoutInvoicePrefill(summary)` (`systems/billing-schedule/buyoutInvoiceAdapter.ts`). It returns `ready` with one charge per priced category, each carrying `buyoutSourceKey(tripId, category)`, or `incomplete` / `invalid`.
2. Create the invoice on the Trip's primary gig through the normal invoice create (`createInvoiceOperation`, or `POST /api/v1/events/:eventId/invoices`), with `source_key` on every line. A second live invoice for the same trip category gets `409 INVOICE_SOURCE_TAKEN`.
3. Find the trip's invoices with `listInvoiceSourceLinks(primaryEventId, { sourceKeyPrefix: buyoutSourcePrefix(tripId) })`. The trailing colon in the prefix keeps `trip-1` from matching `trip-10`.

The key does not record a trip revision. To detect a stale invoice, compare each link's `amount` with the trip's current buyout for that category. The billing view has no trip summary, so it reports buyout charges with `planned_cents: null` and cannot flag the difference itself.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.