Liveness probe
Answers without a key and without touching the database. Intended for container health checks; it reports that this process is alive and routing, not that its dependencies are reachable.
Svar
- 200
The service is up.
Referens
Create invoices, mark them paid, and fetch the receipt.
A server-to-server REST API for hobok.se, the Swedish hobby-business bookkeeping app (hobbyverksamhet). It lets your own system issue invoices, record that they were paid, and retrieve the payment receipt — without anyone logging in and filling a form.
Send your API key as a bearer token:
Authorization: Bearer hbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
A key is created per hobby business in the web UI, under that business's settings, and is shown exactly once — only a SHA-256 hash is stored. The key decides which business a call operates on, which is why no request ever carries a business id.
This is a server-to-server API. There are no CORS headers, and a key in a browser is a leaked key.
The API requires a Plus subscription, checked on every request. A downgrade stops it working immediately.
120 requests per minute per key. Exceeding it returns 429 with a Retry-After header.
Every id in this API is scoped to your own hobby business and starts at 1 — the same number the web UI shows in its URLs. Two customers both holding invoice id: 1 is normal.
Amounts are numbers in SEK with two decimals. VAT is always 0: a Swedish hobby business is not registered for VAT and may not charge it. Sending a vat_rate other than 0 is rejected rather than silently zeroed.
Send an Idempotency-Key header on every POST. Without one, a retried create issues a second invoice, and a retried mark-paid is refused with 409 instead of confirming the first one. See the header's description on those two operations.
The API is English. The documents it produces — invoices, receipts and the emails carrying them — are Swedish, because they are sent to Swedish customers under Skatteverket's rules.
Endpoints about the API itself. No key required.
Answers without a key and without touching the database. Intended for container health checks; it reports that this process is alive and routing, not that its dependencies are reachable.
The service is up.
Unauthenticated, because a code generator needs the specification before the customer has a key.
The OpenAPI document.
The OpenAPI document.
Who this key is, and the defaults the other endpoints apply.
The integrator's smoke test. Proves the key works, names the hobby business it belongs to, and returns the two values the other endpoints default to: the next invoice number and the payment terms used when due_date is omitted.
Note payment_options here means what the seller offers to be paid through and is printed on the invoice. It is not the same thing as GET /payment-methods, which is how an arrived payment is recorded.
Account and defaults.
The canonical list, so a client can build its own dropdown without hard-coding strings that then have to be kept in step with this server by hand.
value is what you send and what is stored — and what is printed on the receipt. It stays Swedish even in an English API: three of the five are Swedish payment rails with no English name, and translating the stored value would change what an issued receipt says.
The available payment methods.
Issuing invoices, recording payment, fetching documents.
Newest first by invoice number. Line items are omitted here — fetch a single invoice to get them.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| page | query | integer, standard 1 | |
| per_page | query | integer, standard 20, 1–100 | Rejected with 422 above the maximum rather than silently clamped — a client that asks for more and is quietly given less would page through the list believing it had seen everything. |
| status | query | string, sent | paid | credited | Return only invoices with this status. |
A page of invoices.
Creates an invoice with status sent, allocates the next number in the seller's own sequence, and stores an immutable HTML snapshot that /pdf renders from.
Identify the buyer with either client_id or an inline buyer object — sending both is rejected rather than resolved by a precedence rule. Either way the invoice keeps its own copy of the buyer's details, so editing the customer afterwards never changes an issued invoice.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| Idempotency-Key | header | string, max 255 tecken | A value of your own choosing that makes this request safe to retry. The first request with a given key does the work; later requests with the same key and the same body return the stored response, marked with Idempotency-Replayed: true, without doing anything again. Reusing a key with a different body is rejected with 409 rather than answered with the old response — otherwise every later request would silently return the first one's result. Keys are remembered for 24 hours. A request that failed does not consume its key, so you can correct the body and retry with the same one. |
| Fält | Typ | Beskrivning |
|---|---|---|
| client_id | integer | An existing customer's id. Mutually exclusive with buyer; sending both is a 422. |
| buyer | A one-off buyer. Mutually exclusive with client_id. | |
| issue_date | string | Defaults to today. |
| due_date | string | Defaults to issue_date plus the account's payment terms. May not be before issue_date. |
| our_reference | string, max 255 tecken | |
| their_reference | string, max 255 tecken | |
| notes | string | |
| send_email | boolean, standard false | Queue the invoice to the buyer's email address. Requires the buyer to have one. Defaults to false deliberately: sending a document to a real customer should be asked for, and cannot be undone. |
| lines * | array |
{
"buyer": {
"name": "Köparen AB",
"street_address": "Exempelgatan 1",
"postal_code": "12345",
"city": "Stockholm",
"email": "faktura@exempel.se"
},
"lines": [
{
"description": "Konsultation",
"quantity": 2,
"unit": "h",
"unit_price": 1250
}
]
}{
"client_id": 4,
"our_reference": "Eva Svensson",
"send_email": true,
"lines": [
{
"description": "Reparation",
"quantity": 1,
"unit": "st",
"unit_price": 900
}
]
}The invoice was created.
No client with that client_id in this hobby business.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| local_id * | path | integer | The invoice's id within this hobby business. |
The invoice, with its line items.
Rendered from the snapshot stored when the invoice was issued, never from the current templates or settings — an issued invoice must look exactly as it did when it was issued.
Invoices imported from Stripe's own invoicing product have no such snapshot and return 409; use stripe.pdf_url from the invoice instead, which is the document the buyer actually received.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| local_id * | path | integer | The invoice's id within this hobby business. |
The invoice document.
No hobok-rendered document exists (pdf_unavailable).
Flips the invoice to paid, stores a receipt snapshot, and books an income transaction so the payment appears in the year's figures.
Only an invoice with status sent can be marked paid; anything else is 409. That guard is what stops a retry booking the income twice — which would overstate the surplus, and the surplus is what the Swedish tax return is filed on. Send an Idempotency-Key to turn that 409 back into the original success.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| local_id * | path | integer | The invoice's id within this hobby business. |
| Idempotency-Key | header | string, max 255 tecken | A value of your own choosing that makes this request safe to retry. The first request with a given key does the work; later requests with the same key and the same body return the stored response, marked with Idempotency-Replayed: true, without doing anything again. Reusing a key with a different body is rejected with 409 rather than answered with the old response — otherwise every later request would silently return the first one's result. Keys are remembered for 24 hours. A request that failed does not consume its key, so you can correct the body and retry with the same one. |
| Fält | Typ | Beskrivning |
|---|---|---|
| payment_date | string | Defaults to today. |
| payment_method | string | null | One of the values from /payment-methods, or null. Anything else is a 422. |
| payment_reference | string, max 255 tecken | |
| send_receipt_email | boolean, standard false | Queue the receipt to the buyer. Requires the invoice to have a buyer email address. |
{
"payment_date": "2026-04-01",
"payment_method": "Swish",
"payment_reference": "REF-1001",
"send_receipt_email": true
}The invoice, with the transaction that was booked.
The invoice is not sent (invalid_status), or the Idempotency-Key conflicts.
The Swedish kvitto for a paid invoice, rendered from the snapshot taken when it was marked paid. Available only once the invoice is paid.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| local_id * | path | integer | The invoice's id within this hobby business. |
The receipt document.
The invoice has not been marked paid (not_paid).
Queues the receipt for delivery to the invoice's buyer address. A 200 means queued, never delivered — a separate mailer sends it.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| local_id * | path | integer | The invoice's id within this hobby business. |
The receipt was queued.
The invoice is not paid (not_paid), or has no stored receipt document (receipt_unavailable).
The reusable customer register for this hobby business.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| page | query | integer, standard 1 | |
| per_page | query | integer, standard 20, 1–100 | Rejected with 422 above the maximum rather than silently clamped — a client that asks for more and is quietly given less would page through the list believing it had seen everything. |
A page of customers.
A customer is a convenience for reuse. It is never required: an invoice can be created with an inline buyer instead, and keeps its own copy of the details either way.
| Fält | Typ | Beskrivning |
|---|---|---|
| name * | string, max 255 tecken | |
| co_address | string, max 255 tecken | |
| street_address | string, max 255 tecken | |
| postal_code | string, max 20 tecken | Normalised to the Swedish NNN NN form. |
| city | string, max 100 tecken | |
| string, max 255 tecken | ||
| phone | string, max 50 tecken |
{
"name": "Stamkunden AB",
"street_address": "Storgatan 2",
"postal_code": "11122",
"city": "Göteborg",
"email": "faktura@stamkunden.se"
}The customer was created. No Location header: there is no single-customer endpoint to point at.
The product catalogue. A product is a template you copy a line from, not something an invoice points at — invoices keep their own line items.
Ordered by name, which is how the catalogue is read — by someone choosing something, not in the order things were added.
| Namn | I | Typ | Beskrivning |
|---|---|---|---|
| page | query | integer, standard 1 | |
| per_page | query | integer, standard 20, 1–100 | Rejected with 422 above the maximum rather than silently clamped — a client that asks for more and is quietly given less would page through the list believing it had seen everything. |
A page of products.
A product is a copy source for invoice lines and nothing more. There is no product_id on an invoice line: fetch the catalogue and build the lines from it, and the invoice keeps its own copy of what was sold — so editing a product later never changes an invoice already issued.
| Fält | Typ | Beskrivning |
|---|---|---|
| name * | string, max 255 tecken | |
| description | string, max 500 tecken | |
| unit | string, max 50 tecken | |
| unit_price * | Required, unlike every optional field on a customer: a product without a price cannot serve as the copy source it exists to be. |
{
"name": "Konsultation",
"description": "Konsultation — hobbyverksamhet",
"unit": "h",
"unit_price": 1250
}The product was created. No Location header: there is no single-product endpoint to point at.
Den här sidan byggs ur OpenAPI-specifikationen (3.2.1), samma fil som API:et självt serverar. Den går att mata rakt in i en kodgenerator.
Vi vill gärna mäta hur sajten används med Google Analytics, vilket kräver cookies. Det är helt frivilligt — säger du nej fungerar allting precis lika bra, och inget skickas till Google. Läs mer