Invoice line items

An invoice line item is a single billable row on an invoice. Each line has a quantity, a unit amount, an optional product reference, and an optional description. The sum of all lines is the invoice's gross_amount_cents — the amount before any promotion-code discounts apply.

Line items are how invoices itemize charges. A simple invoice has one line ("Pro Plan, 1 × $29 = $29"). A complex B2B invoice might have a dozen lines mixing products, services, and one-off charges.

Fields

curl --request POST \
  --url https://api.framepayments.com/v1/invoices/<invoice_id>/line_items \
  --header "Authorization: Bearer $FRAME_SECRET_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "product": "<product_id>",
    "quantity": 2
  }'

The API accepts a narrow set of fields on create — the line inherits pricing and description from the referenced product:

  • product — FK to a product. The line inherits the product's default_price as unit_amount_cents and the product's name as its description.
  • quantity — how many of this line. Defaults to 1.
  • metadata — k/v pairs for your internal tracking.

A line item's total is quantity × unit_amount_cents. The invoice's gross_amount_cents is the sum of all its lines' totals.

Pricing comes from the product

The line item record has unit_amount_cents, unit_amount_currency, and description columns, but the create API doesn't accept overrides for these — the line inherits them from the referenced product. To bill a non-standard amount, model the charge as a Product with the right default_price and reference it; for repeated one-off variants (consulting hours, setup fees), create a Product per variant rather than trying to override pricing inline.

This is more constrained than a free-form invoice builder. If your B2B billing needs free-text line items with arbitrary pricing per invoice, the workaround is to maintain a generic "Custom" product and update its default_price per-customer — though that mutates the catalog and isn't a great pattern. Talk to Frame support if free-form line items are a real requirement for your platform.

Editing line items

Line items can be added, updated, or deleted as long as the parent invoice is in draft status:

  • POST /v1/invoices/<invoice_id>/line_items — add a new line.
  • PATCH /v1/invoices/<invoice_id>/line_items/<id> — update an existing line.
  • DELETE /v1/invoices/<invoice_id>/line_items/<id> — remove a line.

Once the invoice is issued (outstanding / due), line items are frozen. Edits after issue don't take effect on the existing invoice — they'd require voiding and re-creating, or rolling into the next invoice for subscription contexts.

Lifecycle and webhooks

Line items don't carry their own state machine — they exist alongside the parent invoice. Frame fires invoice.line_item.created when a line is added. Updates and deletes happen quietly under the parent invoice's webhook surface (the invoice itself emits invoice.updated when its composition changes).

Relationships

  • Invoiceline_item.invoice_id. Mandatory. Each line belongs to exactly one invoice.
  • Productline_item.product_id. The line copies unit_amount_cents, unit_amount_currency, and description from the product at creation time, so future product price changes don't mutate historical invoices.

A line item delegates its merchant and dev_mode through its parent invoice; these aren't independently settable.

Gotchas

Symptom: you tried to pass unit_amount_cents on a line item create and the API ignored it. Why: the line items endpoint permits only product, quantity, and metadata on create/update — pricing comes from the referenced product. Fix: if you need a different price, model the variant as a separate Product. See the "Pricing comes from the product" section above.

Symptom: you tried to delete a line item on an issued invoice and got an error. Why: invoices freeze line items on issue. Fix: if you need to undo a charge, void the invoice and create a fresh one with the corrected lines. For subscription invoices, the correction lands on the next cycle's invoice automatically once the underlying subscription is updated.

Symptom: line item description doesn't match what you wanted on the invoice. Why: the description is inherited from the product's name at line-creation time; there's no per-line override on the API. Fix: update the product's name (which only affects future lines, not existing ones) or use a separate Product for the variant you want to surface differently.

Reference

For the full API surface, see POST/v1/invoices/{invoice_id}/line_items and the rest of the InvoiceLineItems resource.