Add a line item to a draft invoice
const url = 'http://localhost:8080/v1/invoices/example/line-items';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"description":"example","line_type":"base_fee","quantity":1,"unit_amount_cents":1,"commit_granted_cents":1,"commit_expires_at":"2026-04-15T12:00:00Z"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8080/v1/invoices/example/line-items \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "description": "example", "line_type": "base_fee", "quantity": 1, "unit_amount_cents": 1, "commit_granted_cents": 1, "commit_expires_at": "2026-04-15T12:00:00Z" }'Adds one line to a draft invoice and recomputes its totals.
amount_cents is quantity × unit_amount_cents. Only draft invoices
accept new lines.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Header Parameters
Section titled “Header Parameters”Makes the call safe to retry. Send a unique value (a UUID works) and
the same value on every retry. For 24 hours, a repeat with the same
key, method, path and body gets the first response again, with an
Idempotent-Replayed: true header, instead of running again. The
error cases are in docs/api-surface.md. The usage routes do not take
it: each usage event carries its own idempotency_key.
Request Bodyrequired
Section titled “Request Bodyrequired”A line to add to a draft invoice.
object
Defaults to add_on.
Makes this line a prepaid commit purchase: when the invoice is
finalized, the customer gets this many cents of credit. Only on
add_on lines of manual invoices, at most one per invoice.
When the granted credit expires. The credit can be drawn up to
and including this instant; a period invoice draws it when its
period ends at or before it. The dashboard sends the local
midnight after the picked day. Must be in the future. Needs
commit_granted_cents. Absent means it never expires.
Responses
Section titled “Responses”Line item created.
A single line on an invoice.
object
Owning tenant. Omitted when empty.
The meter’s unit (“tokens”, “seconds”, …), stamped on usage lines at write time so invoice surfaces can apply the display-scale convention (token rates read “$3.00 / 1M tokens”). Empty on non-usage lines and on lines written before the 0173 backfill.
The line’s exact quantity as a decimal string, for example “3” or “1.5”. Line amounts stay whole cents.
The unit price in cents at full precision, for example “0.3” for $0.003. On a usage line priced per_unit it is that rate; on any other line it is amount_cents ÷ quantity. Line amounts and totals stay whole cents.
Tax rate as a percent (18.50 = 18.50%), up to 4-decimal precision. ADR-042/043.
Stripe-canonical structured taxability_reason
(standard_rated, reverse_charge, not_collecting,
customer_exempt, product_exempt, zero_rated, etc.).
prepaid_credit_purchase is Hardcount’s own value, on a prepaid
commit purchase line under the manual or Stripe Tax provider:
the line is not taxed, and the usage the credit pays for is
taxed when invoiced. Otherwise empty for non-Stripe providers.
On a usage line, the shape of its price: per_unit, graduated or package. Absent on other lines.
On a usage line, the plan price that billed it. Absent on other lines.
On a usage line, the labels its price takes. Absent for a price that takes every event, and on other lines.
object
object
Marks the line as a prepaid-commit purchase (ADR-078): when the invoice finalizes, a credit block of this many cents is granted to the customer. May differ from the line price — discounted commits sell $10k of credits for $9k. add_on lines on manual invoices only, at most one per invoice. Absent on normal lines.
Expiry of the granted commit block. The credit can be drawn up to and including this instant; a period invoice draws it when its period ends at or before it. The dashboard sends the local midnight after the picked day. Absent/null = the credits never expire (the default).
Example
{ "line_type": "base_fee"}Body is not valid JSON.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Invoice not found.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Validation error: the invoice is not a draft, a line field is
invalid (for example a quantity of 0, or a second commit line), or
the body has an unknown field (param names it; the body must use only the fields listed here).
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}