Skip to content
Hardcount
Run it locally
Pre-1.0 · one maintainer · Stripe and cards only · no SDKs (client libraries) yet · no production users yet. Docs describe v0.9.1 (29 Oct 2026) · What’s new →

The invoice object

A Hardcount invoice. Returned from create, finalize, void, list, get, rotate-public-token, and collect-payment endpoints.

Field Type Always present What it means
id string Yes
tenant_id string No Owning tenant. Returned for diagnostic clarity but redundant with the API key (which already determines the tenant). Omitted when empty.
customer_id string Yes
subscription_id string Yes Empty for one-off invoices not tied to a subscription.
invoice_number string Yes Set when the invoice is created, including drafts.
status enum Yes Lifecycle state of the invoice. uncollectible marks an invoice a dunning policy has given up collecting (no further retries) while keeping it on the books — distinct from voided (cancelled, owes nothing). One of: draft, finalized, paid, voided, uncollectible.
payment_status enum Yes Payment-attempt status. unknown marks a Stripe charge attempt that returned an ambiguous error (5xx, timeout, connection reset) where we cannot tell whether Stripe processed the charge. With a payment intent recorded, Hardcount asks Stripe for it after a short wait. With none (a parked invoice), Hardcount lists the customer’s payments at Stripe every billing tick: it records a payment for the invoice that appears, and if none has appeared 24 hours after the attempt the invoice goes back to pending (failed after a dunning retry) and is charged again as usual. One of: pending, processing, succeeded, failed, unknown.
currency string Yes
subtotal_cents integer (int64) Yes
discount_cents integer (int64) Yes
tax_amount_cents integer (int64) Yes
tax_rate number (double) Yes Tax rate as a percent (18.50 = 18.50%), up to 4-decimal precision. ADR-042/043.
tax_name string No
tax_country string No
tax_id string No
tax_provider string No Durable audit snapshot of the upstream tax engine that ran the calculation (e.g. stripe, none, manual).
tax_calculation_id string No Provider-side calculation reference captured at build time. For Stripe Tax this expires after 24 h; persisted so finalized invoices remain reconstructable past expiry.
tax_transaction_id string No Provider-side committed transaction reference, captured at finalize. For Stripe Tax this is tx_xxx and is the handle the provider needs for a later reversal when a credit note is issued.
tax_reverse_charge boolean No
tax_exempt_reason string No
tax_status enum No Whether tax has been calculated. ok: it has. pending: the calculation failed, and tax_error_code says why and whether Hardcount retries it. A pending invoice cannot be finalized, so a wrong tax amount is never sent. One of: ok, pending.
tax_deferred_at string (date-time) No Can be null.
tax_retry_count integer No
tax_pending_reason string No
tax_error_code enum No Why tax could not be calculated, as a code. Set while tax_status is pending; tax_pending_reason has the detail. One of: customer_data_invalid, jurisdiction_unsupported, provider_outage, provider_auth, provider_not_configured, unknown.
tax_next_retry_at string (date-time) No The earliest time the background retry may calculate tax again, after a failed attempt. Absent when a retry may run now.
tax_reversed_at string (date-time) No When the provider’s tax transaction was reversed after a void or write-off, or found to need no reversal. Absent while the reversal is not confirmed.
payment_anomaly_kind string No Set when a payment arrived that the invoice did not expect. The webhook event name of the cause: payment.duplicate_charge, payment.amount_mismatch, payment.currency_mismatch or payment.received_on_voided_invoice. Only the latest one is kept.
payment_anomaly_payment_intent_id string No The Stripe PaymentIntent of that unexpected payment.
payment_anomaly_captured_cents integer (int64) No The amount Stripe took in that unexpected payment.
payment_card_brand string No Brand of the saved card that paid the invoice. Absent when the card is unknown.
payment_card_last4 string No Last four digits of the saved card that paid the invoice.
attention Attention No Unified “this invoice needs operator attention” surface. Computed server-side from durable invoice fields (tax_status, tax_error_code, payment_status, last_payment_error). Never persisted — always derived. Omitted entirely when the invoice is healthy (matches Stripe’s last_finalization_error: null ergonomic). See ADR-009.
recovery_warning Recovery warning No What an operator must know BEFORE recording an OFFLINE payment on a written-off invoice — a consequence that will go unreconciled, NOT a refusal. Absent when there is nothing to say.
total_amount_cents integer (int64) Yes
amount_due_cents integer (int64) Yes
amount_paid_cents integer (int64) Yes
credits_applied_cents integer (int64) Yes
billing_period_start string (date-time) Yes
billing_period_end string (date-time) Yes
billing_period_display string No Human period string with the inclusive last covered day (“Jun 1, 2028 – Jun 30, 2028”), date-only in the invoice’s billing timezone (ADR-058 / ADR-077). Computed on read; the raw billing_period_start/end stay half-open. Empty/omitted for one-off invoices with no period.
billing_timezone string No IANA timezone the period boundaries are anchored in — the org billing timezone resolved and denormalized onto the invoice at issue (ADR-077). billing_period_display is rendered in this zone, so an issued invoice’s dates stay fixed even if the org later changes its timezone. Empty only on legacy invoices created before the zone was stamped (cycle invoices before migration 0134, manual invoices before 2026-10-16).
bill_to_snapshot Document bill to No The buyer details this invoice was issued with (ADR-109), copied when it left draft. The PDF, the hosted page and the dashboard render an issued invoice from this copy, so a later edit to the customer or billing profile does not change it. Absent on drafts and on invoices issued before this field existed; those render from the current customer details.
supplier_snapshot Document supplier No The seller (company) details this invoice was issued with (ADR-109), copied when it left draft. Absent on drafts and on invoices issued before this field existed. Branding (logo, brand color) is not part of it.
issued_at string (date-time) No Set when the invoice transitions out of draft. Can be null.
due_at string (date-time) No Can be null.
paid_at string (date-time) No Can be null.
voided_at string (date-time) No Can be null.
uncollectible_at string (date-time) No When the invoice was marked uncollectible.
stripe_payment_intent_id string No The Stripe PaymentIntent of the card charge that paid the invoice, or of its last charge that is in progress or failed. It can be absent after a charge was sent: for example when the invoice was then paid without a card (by credits, with nothing due, or outside Hardcount: paid_out_of_band), or when Stripe refused the last charge as below its minimum.
paid_out_of_band boolean Yes True when the payment was recorded as made outside Hardcount, such as a bank transfer or cheque (POST /v1/invoices/{id}/record-payment, or a payment recovery run resolved as payment_recovered). Such an invoice has no stripe_payment_intent_id, so there is no card to refund.
last_payment_error string No
auto_charge_pending boolean No
no_pm_notified_at string (date-time) No When the customer was emailed a link to add a payment method for this invoice. The email is sent once.
provider_payment_status string No Stripe’s PaymentIntent status the last time Hardcount checked it, word for word. For information only: payment_status is the invoice’s payment state. Absent until Hardcount has checked. An invoice checked by the Stripe Search sweep of releases before this one may still show search_not_found, search_terminal_only or search_error.
provider_synced_at string (date-time) No When Hardcount last asked Stripe about this invoice’s payment, whether or not Stripe answered: a read of provider_payment_status, or a check of a charge whose result was lost.
public_token string No Hosted-invoice-URL credential (Stripe-parity hosted_invoice_url). Generated at finalize; drafts have an empty token. Rotatable via POST /v1/invoices/{id}/rotate-public-token.
net_payment_term_days integer Yes
memo string No
footer string No The tenant’s invoice_footer as it was when this invoice was issued. Printed on the PDF and the hosted invoice page. Empty on a draft.
metadata object No
created_at string (date-time) Yes
updated_at string (date-time) Yes
source_plan_changed_at string (date-time) No Can be null.
source_subscription_item_id string No
source_change_type enum No Classifies per-item proration artifacts so the dedup index can distinguish a plan-change and a quantity-change that happen to share the same (subscription, item, timestamp). One of: plan, quantity, add, remove.
billing_reason enum No Mirrors Stripe’s invoice.billing_reason. Classifies the trigger that produced the invoice. Stamped at create time and never mutated. One of: subscription_cycle, subscription_create, subscription_cancel, subscription_update, manual, threshold.
is_simulated boolean Yes Whether this invoice’s domain timestamps (created/issued/due/paid) were stamped on a test clock’s simulated time rather than wall-clock. Captured at write time (engine: the subscription is clock-pinned; manual composer: the customer is clock-pinned). The dashboard renders a “simulated” badge from this authoritative flag instead of inferring it. Always false in live mode.
test_clock_id string No The test clock this invoice’s dates follow: the clock its customer is pinned to, for cycle and one-off invoices alike. Read from the customer on GET /v1/invoices and GET /v1/invoices/{id}; other responses may leave it out. Left out for a customer on wall-clock time, and always in live mode.

Unified “this invoice needs operator attention” surface. Computed server-side from durable invoice fields (tax_status, tax_error_code, payment_status, last_payment_error). Never persisted — always derived. Omitted entirely when the invoice is healthy (matches Stripe’s last_finalization_error: null ergonomic). See ADR-009.

Field Type Always present What it means
severity enum Yes Urgency of an Attention surface. Operators sort/filter on this and the dashboard renders one colour per level. Priority descending: Critical > Warning > Info. One of: info, warning, critical.
reason enum Yes Closed, typed code naming why an invoice needs operator attention. Stable public-API contract — codes are never repurposed; deprecations keep the code reserved and add a successor. See Attention.code for the open, provider-specific sub-code. payment_anomaly (ADR-068) reports money that does not reconcile — double charge, captured-vs-booked amount mismatch, or a payment on a voided invoice — and is surfaced even on paid/voided invoices. dunning_exhausted reports a no-payment-method invoice whose automatic recovery has ended without collecting (2026-07-22). collection_paused reports an invoice that is queued for auto-charge but whose subscription has pause_collection set, so the charge sweeps deliberately skip it (2026-07-28). A dunning run already open on the invoice keeps retrying it through the pause, and message then says so; with no open run it is not charged automatically until collection resumes. commit_exposure reports an unpaid invoice that funded a prepaid commit whose credit is already spendable (ADR-078 funds commits at issue, not at payment) — note this reason is only returned when no other reason applies; when one does, the exposure is folded into that reason’s message and its actions instead, so clients must not treat the reason code as the presence test for exposure (2026-08-04). dispute_lost reports a payment on the invoice that the customer disputed with their bank and that the dispute took back (the dispute closed as lost). Hardcount leaves the invoice paid and moves no money; the operator records the loss. Surfaced on paid invoices. On an invoice that bought prepaid credit, the message also says how much of the credit is still spendable and names commit relief, because an ordinary credit note is refused there. When the invoice also has payment_anomaly, that reason stays and its message asks the operator to check Stripe before any refund. The disputes themselves are at GET /v1/invoices/{id}/disputes (2026-10-19). amount_below_minimum reports an invoice Stripe refused to charge, or refused a payment page for, because the amount is below Stripe’s minimum charge. The minimum depends on the currency the Stripe account settles in; Stripe’s reply, in provider_response, names it. Nothing was charged, the invoice stays finalized with payment_status pending, it is not charged again automatically, dunning does not start (a run already open ends), and the customer is not emailed. The operator voids it or records a payment; Collect payment applies the customer’s credit balance and pays it if that covers the amount, and otherwise asks Stripe again. Stripe’s answer can change (for a converted charge the minimum is the settlement currency’s), and a different answer ends this reason (2026-10-23). One of: tax_calculation_failed, tax_location_required, payment_failed, payment_unconfirmed, payment_anomaly, payment_processing, payment_scheduled, awaiting_payment, no_payment_method, dunning_exhausted, collection_paused, commit_exposure, dispute_lost, amount_below_minimum.
message string Yes Human-readable copy, safe to render verbatim: one plain sentence saying what happens and what to do. Facts that must show up front follow on more lines, separated by a newline (\n), one sentence each and up to three lines in all: the setup-link email on no_payment_method (and on dunning_exhausted when no link can be emailed), and the prepaid credit at risk on an unpaid commit invoice. A no-card commit invoice carries all three. Wording may change between releases; switch on reason and code, not on this text.
actions array of Attention action item No Prescribed next steps. First item is the primary CTA; the rest are secondary.
code string No Open, dotted, provider-specific code (e.g. tax.customer_data_invalid, payment.declined). New codes ship without a contract bump. Stripe parity.
doc_url string No Deep link to operator-facing documentation for this reason. Stripe parity.
param string No Dotted-path pointer at the entity field the operator must edit to resolve the issue (e.g. customer.address.postal_code). Stripe parity. Empty when the resolution isn’t field-scoped.
detail string No Hardcount’s own explanation behind message: why the state holds, what Hardcount does on its own, and ids support may ask for (for example the payment intent ids of a payment anomaly). Plain text; may contain blank-line paragraph breaks. Never a provider payload — that is provider_response.
provider_response string No The provider’s own response (Stripe Tax error envelope, Stripe’s last payment error), exactly as received. Present only when Hardcount called the provider.
since string (date-time) No When the attention condition started — operators triage by age. tax_deferred_at for tax reasons; due_at for overdue; updated_at as a proxy for payment failures.
next_attempt_at string (date-time) No When Hardcount will next retry on its own. Today only a tax retry sets it. Absent when no automatic retry is scheduled.
due_by string (date-time) No The invoice’s payment deadline (its due_at). Hardcount does not act on this date; it is shown so the operator knows it. Absent when the invoice has no due date.

What an operator must know BEFORE recording an OFFLINE payment on a written-off invoice (status = uncollectible) — a consequence that will go unreconciled, never a refusal. The money already arrived, and refusing to record it would only make the books wrong too. A written-off invoice is settled by RECORDING writers only — an offline payment, or Hardcount recording a card charge that Stripe shows succeeded (its webhook, or the check of an unresolved charge). Nothing initiates a card charge against one (ADR-113): recovery of a returned customer runs on normal rails via a fresh recovery invoice. tax_reversed_unrecoverable — the invoice’s tax was reversed with the tax provider and cannot be re-reported automatically; the recorded payment collects tax already reported as not collected. relief_not_reissued — a credit the invoice was owed was never applied, so amount_due_cents is higher than what is owed and the customer may have overpaid.

Field Type Always present What it means
blocked boolean Yes Always false — this shape only ever warns.
code enum No One of: tax_reversed_unrecoverable, relief_not_reissued.
message string No Operator-facing sentence naming the consequence that will go unreconciled if the payment is recorded.

What an issued invoice or credit note prints about its buyer, copied when the document was issued (ADR-109). The printed name is legal_name when set, otherwise display_name.

Field Type Always present What it means
display_name string Yes
legal_name string No
email string No
address_line1 string No
address_line2 string No
city string No
state string No
postal_code string No
country string No
tax_id string No On an invoice, the tax ID its tax was calculated with when set, otherwise the billing profile’s tax ID at issue.
tax_id_type string No

What an issued invoice or credit note prints about its seller (the tenant’s company), copied when the document was issued (ADR-109).

Field Type Always present What it means
name string No
email string No
phone string No
address_line1 string No
address_line2 string No
city string No
state string No
postal_code string No
country string No
tax_id string No
Field Type Always present What it means
code enum Yes Operator’s recommended next step. Closed enum because every code maps to a concrete server endpoint or frontend route, and audit logs key off the code. reconcile_payment opens the payment in Stripe; it is offered on payment_anomaly, where the refund or correction is made there. mark_uncollectible opens the write-off and is offered only on a parked invoice (payment status unknown with no payment intent recorded), for a customer who paid another way: Hardcount records a payment that Stripe shows, and charges the invoice again if none appears within 24 hours. Dashboards render it as a secondary action. void_invoice is offered on prepaid-commit exposure, only while some of the granted credit is still unspent: voiding retires the unspent balance (ADR-078) and is the sole recovery for an unpaid invoice whose credit is already live. Credit the customer has already spent is never clawed back (2026-08-04). It is also offered on amount_below_minimum, an amount Stripe will not charge (2026-10-23). One of: edit_billing_profile, retry_tax, retry_payment, wait_provider, rotate_api_key, reconcile_payment, review_registration, charge_now, send_reminder, add_payment_method, update_payment_method, connect_tax_provider, void_invoice, mark_uncollectible.
label string No Default rendering label. UIs are free to substitute localized copy.