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 →

Update workspace settings

PUT
/v1/settings
curl --request PUT \
--url http://localhost:8080/v1/settings \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "default_currency": "USD", "timezone": "UTC", "invoice_prefix": "example", "credit_note_prefix": "example", "net_payment_terms": 1, "credit_balance_low_threshold_cents": 1, "tax_provider": "none", "tax_rate": 1, "tax_name": "example", "tax_inclusive": true, "tax_on_failure": "block", "default_product_tax_code": "example", "company_name": "example", "company_address_line1": "example", "company_address_line2": "example", "company_city": "example", "company_state": "example", "company_postal_code": "example", "company_country": "example", "company_email": "example", "company_phone": "example", "logo_url": "example", "brand_color": "example", "tax_id": "example", "support_url": "example", "invoice_footer": "example" }'

Merges the body into the current settings. A field you leave out keeps its stored value. A field you send is set, even to "" or 0. Send credit_balance_low_threshold_cents: null to turn low-balance alerts off.

Empty default_currency, timezone, invoice_prefix, credit_note_prefix, tax_provider and tax_on_failure fall back to their defaults. default_currency, company_country, invoice_prefix and credit_note_prefix are stored in upper case, and brand_color in lower case. Choosing stripe_tax with no default_product_tax_code sets it to txcd_10103001 (SaaS, business use).

A save that changes a field writes one audit row listing each change.

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

Every field is optional. Fields you leave out keep their stored value.

object
default_currency

ISO 4217 code. Zero-decimal currencies (such as JPY) are not supported yet.

string

Example

USD
timezone

IANA zone name, such as America/New_York. Local is rejected. All billing date math uses this zone.

string

Example

UTC
invoice_prefix

Starts every invoice number, such as HC-000001.

string
/^[A-Za-z0-9-]{1,20}$/
credit_note_prefix

Starts every credit note number, such as CN-000001. A change applies to new credit notes only. Issued notes keep their numbers. It must differ from invoice_prefix, or an invoice and a credit note would get the same number.

string
/^[A-Za-z0-9-]{1,20}$/
net_payment_terms

Days until an invoice is due. 0 means due on issue.

integer
<= 365
credit_balance_low_threshold_cents

When a customer’s credit in one currency drops below this amount, in that currency’s minor units, the credit.balance_low webhook fires: 1000 means $10.00 of USD credit and €10.00 of EUR credit. null turns it off.

integer format: int64
nullable >= 1
tax_provider

none = no tax, manual = the flat tax_rate, stripe_tax = Stripe Tax.

string
Allowed values: none manual stripe_tax
tax_rate

Percent, up to 4 decimal places (18.5 = 18.5%). Used by the manual provider.

number
<= 100
tax_name

Label for the tax line, such as VAT.

string
tax_inclusive

Whether prices already include tax.

boolean
tax_on_failure

What to do when the tax provider fails. block holds the invoice until tax succeeds.

string
Allowed values: block fallback_manual
default_product_tax_code

Stripe product tax code used when a plan has none, such as txcd_10103001. Empty is allowed. Surrounding whitespace is trimmed before the check.

string
/^(txcd_[0-9]{8})?$/
company_name

Shown on invoices and as the sender name on emails. No line breaks.

string
<= 255 characters
company_address_line1
string
<= 200 characters
company_address_line2
string
<= 200 characters
company_city
string
<= 100 characters
company_state
string
<= 100 characters
company_postal_code
string
<= 20 characters
company_country

ISO 3166 alpha-2 code, such as US. Empty is allowed. Whitespace is trimmed and the code is upper-cased before it is checked and stored.

string
<= 2 characters /^([A-Za-z]{2})?$/
company_email
string
company_phone

7 to 20 characters of digits, spaces, +, -, ( and ).

string
logo_url

Shown in emails and on the hosted invoice page. PDFs do not print it.

string
brand_color

Accent color on invoice PDFs, as #rrggbb.

string
/^#[0-9a-fA-F]{6}$/
tax_id

Your own tax number (VAT, EIN, GSTIN), printed on invoices.

string
<= 50 characters
support_url

Must start with http:// or https://. Linked in emails and on the hosted invoice page, and printed at the foot of invoice and credit-note PDFs.

string
<= 500 characters
invoice_footer

Printed under the totals on invoice PDFs and on the hosted invoice page. Each invoice keeps the footer it was issued with, so a change applies to invoices issued after it. A draft shows the current text.

string
<= 1000 characters

Saved settings

Media typeapplication/json
object
default_currency
required

ISO 4217 code. Zero-decimal currencies (such as JPY) are not supported yet.

string
timezone
required

IANA zone name, such as America/New_York. Local is rejected. All billing date math uses this zone.

string
invoice_prefix
required

Starts every invoice number, such as HC-000001.

string
/^[A-Za-z0-9-]{1,20}$/
credit_note_prefix
required

Starts every credit note number, such as CN-000001. A change applies to new credit notes only. Issued notes keep their numbers. It must differ from invoice_prefix, or an invoice and a credit note would get the same number.

string
/^[A-Za-z0-9-]{1,20}$/
net_payment_terms
required

Days until an invoice is due. 0 means due on issue.

integer
<= 365
credit_balance_low_threshold_cents

When a customer’s credit in one currency drops below this amount, in that currency’s minor units, the credit.balance_low webhook fires: 1000 means $10.00 of USD credit and €10.00 of EUR credit. null turns it off.

integer format: int64
nullable >= 1
tax_provider
required

none = no tax, manual = the flat tax_rate, stripe_tax = Stripe Tax.

string
Allowed values: none manual stripe_tax
tax_rate
required

Percent, up to 4 decimal places (18.5 = 18.5%). Used by the manual provider.

number
<= 100
tax_name

Label for the tax line, such as VAT.

string
tax_inclusive
required

Whether prices already include tax.

boolean
tax_on_failure

What to do when the tax provider fails. block holds the invoice until tax succeeds.

string
Allowed values: block fallback_manual
default_product_tax_code

Stripe product tax code used when a plan has none, such as txcd_10103001. Empty is allowed. Surrounding whitespace is trimmed before the check.

string
/^(txcd_[0-9]{8})?$/
company_name

Shown on invoices and as the sender name on emails. No line breaks.

string
<= 255 characters
company_address_line1
string
<= 200 characters
company_address_line2
string
<= 200 characters
company_city
string
<= 100 characters
company_state
string
<= 100 characters
company_postal_code
string
<= 20 characters
company_country

ISO 3166 alpha-2 code, such as US. Empty is allowed. Whitespace is trimmed and the code is upper-cased before it is checked and stored.

string
<= 2 characters /^([A-Za-z]{2})?$/
company_email
string
company_phone

7 to 20 characters of digits, spaces, +, -, ( and ).

string
logo_url

Shown in emails and on the hosted invoice page. PDFs do not print it.

string
brand_color

Accent color on invoice PDFs, as #rrggbb.

string
/^#[0-9a-fA-F]{6}$/
tax_id

Your own tax number (VAT, EIN, GSTIN), printed on invoices.

string
<= 50 characters
support_url

Must start with http:// or https://. Linked in emails and on the hosted invoice page, and printed at the foot of invoice and credit-note PDFs.

string
<= 500 characters
invoice_footer

Printed under the totals on invoice PDFs and on the hosted invoice page. Each invoice keeps the footer it was issued with, so a change applies to invoices issued after it. A draft shows the current text.

string
<= 1000 characters
tenant_id
required
string
next_invoice_sequence
required

The number the next invoice in this mode (test or live) gets, before the prefix: 43 means the next invoice is <invoice_prefix>-000043. Test and live count separately, and changing invoice_prefix keeps the count. Not a setting: sending it on PUT does nothing.

integer
created_at
required

Zero time when the tenant has never saved settings.

string format: date-time
updated_at
required
string format: date-time

Example

{
"default_currency": "USD",
"timezone": "UTC",
"tax_provider": "none",
"tax_on_failure": "block"
}

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

Caller lacks the apikey:write permission

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

A field failed validation. error.param names the field.

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