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 →

Set up a new installation

POST
/v1/bootstrap
curl --request POST \
--url http://localhost:8080/v1/bootstrap \
--header 'Content-Type: application/json' \
--data '{ "token": "example", "tenant_name": "Demo Tenant", "owner_email": "admin@hardcount.local", "owner_password": "example" }'

One-time setup for a new installation. It works only while no tenant exists and only when the server has HARDCOUNT_BOOTSTRAP_TOKEN set. Send that token as token in the body or as Authorization: Bearer <token>. Never send it in the query string.

In one transaction it creates the first tenant, its owner user, a test secret key and a live secret key. The response is the only place the keys appear, and the password too when Hardcount generated it. The response has Cache-Control: no-store.

Defaults: tenant name “Demo Tenant”, owner email admin@hardcount.local, and a generated password when owner_password is empty. The hardcount-bootstrap command does the same setup from the shell.

Media typeapplication/json
object
token

The value of HARDCOUNT_BOOTSTRAP_TOKEN. Or send it as a Bearer token.

string
tenant_name
string
default: Demo Tenant
owner_email

Must contain @.

string
default: admin@hardcount.local
owner_password

12 or more characters, at most 72 bytes, and not a common password from a leaked-password list. Omit to have Hardcount generate one.

string
>= 12 characters

Installation set up. Save these credentials; they are not shown again.

Media typeapplication/json
object
tenant
required
object
id
required
string
name
required
string
status
required
string
Allowed values: active suspended deleted
created_at
required
string format: date-time
updated_at
required
string format: date-time
owner_email
required

Dashboard login email.

string
owner_password
required

Dashboard login password (the one you sent, or the generated one).

string
password_generated
required
boolean
secret_key_test
required

Test-mode secret key (hc_secret_test_...).

string
secret_key_live
required

Live-mode secret key (hc_secret_live_...).

string
message
required
string

Example

{
"tenant": {
"id": "hc_ten_1a2b3c4d5e6f7890",
"status": "active"
}
}

Bootstrap is disabled (HARDCOUNT_BOOTSTRAP_TOKEN not set) or the token is wrong

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 tenant already exists (code already_bootstrapped), or a user with owner_email already exists (code already_exists).

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

owner_email is not an email, or owner_password is too short or too long. Nothing was written.

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

Rate limited

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