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 →

Add or remove credit

POST
/v1/credits/adjust
curl --request POST \
--url http://localhost:8080/v1/credits/adjust \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "customer_id": "example", "currency": "USD", "amount_cents": 1, "description": "example" }'

Writes one adjustment entry to the customer’s credit ledger in currency. A positive amount adds credit in that currency; a negative amount removes credit in that currency only. Audit action is credit.adjustment or credit.deduction.

Errors (422, param names the field): customer_id, currency or description missing; currency not supported (an unknown code or a zero-decimal currency such as JPY); amount_cents is zero, above 100000000 (1,000,000.00 in that currency), or a removal larger than the credit in that currency that can be drawn (expired grants not yet written off do not count).

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
string
currency
required

ISO 4217 code of the balance to change. Case-insensitive; stored uppercase.

string

Example

USD
amount_cents
required

Positive adds credit, negative removes it. Not zero.

integer format: int64
description
required
string

The ledger entry written.

Media typeapplication/json
object
id
required
string
tenant_id
string
customer_id
required
string
entry_type
required
string
Allowed values: grant usage expiry adjustment
currency
required

The entry’s currency, uppercase. Credit pays only invoices in its own currency.

string
amount_cents
required

Positive adds credit, negative removes it.

integer format: int64
balance_after
required

The customer’s credit balance in this entry’s currency after it, in cents.

integer format: int64
description
required
string
invoice_id
string
expires_at

On a grant, its expiry. 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.

string format: date-time
metadata
object
key
additional properties
any
created_at
required
string format: date-time
consumed_cents

For entries that add credit, how much has been used or expired.

integer format: int64
grant_kind
string
Allowed values: commit promotional
source_invoice_id

For a commit grant, the invoice that paid for it.

string
source_subscription_id

With source_subscription_item_id, source_plan_changed_at and source_change_type, set on a proration credit: the subscription change that produced it.

string
source_subscription_item_id
string
source_plan_changed_at
string format: date-time
source_change_type

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

string
Allowed values: plan quantity add remove
source_credit_note_id

The credit note that granted this credit to the balance.

string
source_invoice_reversal_id

The voided invoice whose applied credit this entry gave back.

string

Example

{
"entry_type": "grant",
"grant_kind": "commit",
"source_change_type": "plan"
}

The body is not valid JSON.

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 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 failed (see description), or an unknown field (param names it; the body must use only the fields listed here).

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"
}
}