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 →

Preview the next invoice for a customer (Stripe-equivalent)

POST
/v1/invoices/create_preview
curl --request POST \
--url http://localhost:8080/v1/invoices/create_preview \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "customer_id": "example", "subscription_id": "example", "period": { "from": "2026-04-15T12:00:00Z", "to": "2026-04-15T12:00:00Z" } }'

Answers “what is my next bill going to look like?” from the usage recorded so far. Without period, the preview mirrors the cycle close for the subscription’s current period: base fees prorate for a short first period and per segment after a mid-period item add, quantity change, plan swap or removal, and each meter’s usage counts only while a plan carrying it was on the subscription. For a trialing subscription it is the first invoice after the trial: the trial-end invoice when a plan bills in advance, otherwise the close of the first paid period, prorated the same way. With an explicit period, it is a flat estimate over that window and warns when a mid-period change makes the invoice differ. Composes across customer / subscription / pricing; registered as a sibling of /v1/invoices (chi picks the more specific pattern, otherwise /{id} would capture create_preview as an invoice ID).

Idempotency-Key
string

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.

Media typeapplication/json
object
customer_id
required

Customer to preview the invoice for.

string
subscription_id

Optional. Defaults to the customer’s primary active subscription if not supplied.

string
period

Optional explicit window. Both bounds must be supplied together; partial windows are rejected. Defaults to the resolved subscription’s current cycle.

object
from
required
string format: date-time
to
required
string format: date-time

Example generated

{
"customer_id": "example",
"subscription_id": "example",
"period": {
"from": "2026-04-15T12:00:00Z",
"to": "2026-04-15T12:00:00Z"
}
}

Preview result

Media typeapplication/json

A dry-run invoice built from the usage recorded so far. Nothing is saved.

object
customer_id
required
string
subscription_id
required
string
plan_name
required

The names of the subscription’s plans, comma-separated.

string
billing_period_start
required
string format: date-time
billing_period_end
required
string format: date-time
lines
required
Array<object>
object
line_type
required
string
Allowed values: base_fee usage
description
required
string
meter_id
string
meter_unit
string
price_id

On a usage line, the plan price that priced it.

string
dimension_match

On a usage line, the labels its price takes. Absent for a price that takes every event.

object
key
additional properties
string
currency
required
string
quantity
required

Exact quantity as a decimal string.

string
unit_amount_cents
required
integer format: int64
amount_cents
required
integer format: int64
pricing_mode
string
Allowed values: per_unit graduated package
totals
required

One total per currency on the lines.

Array<object>
object
currency
required
string
amount_cents
required
integer format: int64
warnings
required

Ways the real invoice can differ from this preview.

Array<string>
generated_at
required
string format: date-time

Example

{
"lines": [
{
"line_type": "base_fee",
"pricing_mode": "per_unit"
}
]
}

Invalid request body

Media typeapplication/json
object
error
object
type

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.

string
code
string
message
string
request_id
string
param

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.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

Customer or subscription not found

Media typeapplication/json
object
error
object
type

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.

string
code
string
message
string
request_id
string
param

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.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

Validation error (missing customer_id, partial period, no active sub, …)

Media typeapplication/json
object
error
object
type

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.

string
code
string
message
string
request_id
string
param

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.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}