Invoices

Retry tax calculation on a deferred invoice

Changed on

Re-runs tax calculation against a draft invoice whose tax_status is pending or failed. Backs the operator- facing "Retry tax" action surfaced by the unified Attention shape.

Idempotent: each call increments tax_retry_count and rewrites the per-line and invoice-level tax fields. A retry that succeeds clears tax_pending_reason / tax_error_code and unblocks finalize. A retry that still fails writes the new typed code so the dashboard banner refreshes — operators get an immediate signal of whether their fix worked.

Returns the updated invoice (carrying the re-derived attention) on success or post-retry failure. A retry that is still failing is not an HTTP error — the caller renders the new attention.

post/v1/invoices/{id}/retry-tax

Request

  • Base URL: http://localhost:8080
  • URL: http://localhost:8080/v1/invoices/{id}/retry-tax
  • Auth: HTTP bearer

Path parameters

idstring required

Response

Invoice updated; check attention for the new state.

idstring required
tenant_idstring

Owning tenant. Returned for diagnostic clarity but redundant with the API key (which already determines the tenant). Omitted when empty.

customer_idstring required
subscription_idstring required

Empty for one-off invoices not tied to a subscription.

invoice_numberstring required

Empty until the invoice is finalized.

status'draft' | 'finalized' | 'paid' | 'voided' | 'uncollectible' required

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).

payment_status'pending' | 'processing' | 'succeeded' | 'failed' | 'unknown' required

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 — a reconciler resolves these by querying Stripe after a cool-off window.

currencystring required
subtotal_centsinteger required
discount_centsinteger required
tax_amount_centsinteger required
tax_ratenumber double required

Tax rate as a percent (18.50 = 18.50%), up to 4-decimal precision. ADR-042/043.

tax_namestring
tax_countrystring
tax_idstring
tax_providerstring

Durable audit snapshot of the upstream tax engine that ran the calculation (e.g. stripe, none, manual).

tax_calculation_idstring

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_idstring

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_chargeboolean
tax_exempt_reasonstring
tax_status'ok' | 'pending' | 'failed'

Whether tax has been successfully calculated. Happy path is ok. pending means a transient calculation failure is awaiting retry; failed means retries are exhausted (operator resolves manually). Pending/failed invoices are blocked from finalize so we never send a wrong tax amount.

tax_deferred_atstring date-time nullable
tax_retry_countinteger
tax_pending_reasonstring
tax_error_codestring

Typed taxonomy of tax_pending_reason. One of customer_data_invalid, jurisdiction_unsupported, provider_outage, provider_auth, unknown. Empty for invoices deferred before this column existed (migration 0067).

total_amount_centsinteger required
amount_due_centsinteger required
amount_paid_centsinteger required
credits_applied_centsinteger required
billing_period_startstring date-time required
billing_period_endstring date-time required
billing_period_displaystring

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_timezonestring

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 for ad-hoc/legacy invoices.

issued_atstring date-time nullable

Set when the invoice transitions out of draft.

due_atstring date-time nullable
paid_atstring date-time nullable
voided_atstring date-time nullable
stripe_payment_intent_idstring
last_payment_errorstring
auto_charge_pendingboolean
public_tokenstring

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_daysinteger required
memostring
footerstring
metadataobject
created_atstring date-time required
updated_atstring date-time required
source_plan_changed_atstring date-time nullable
source_subscription_item_idstring
source_change_type'plan' | 'quantity' | 'add' | 'remove'

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).

billing_reason'subscription_cycle' | 'subscription_create' | 'subscription_cancel' | 'subscription_update' | 'manual' | 'threshold'

Mirrors Stripe's invoice.billing_reason. Classifies the trigger that produced the invoice. Stamped at create time and never mutated.

stripe_invoice_idstring

Source Stripe invoice id (in_xxx) for an invoice imported from a Stripe account. Currently always empty: the Stripe importer was removed (deferred until a design-partner cutover) and nothing writes this field today. The column and its unique index remain for a future importer. Empty for Velox-native invoices.

is_simulatedboolean

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.

Example response

{
  "id": "vlx_inv_42014438d82ddf33",
  "currency": "USD"
}

Changes

    • warning

      removed the optional property from the response with the status

    • info

      added to the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      added the optional property to the response with the status