Hoppa till innehållet
hobok

Referens

hobok.se invoicing API

Create invoices, mark them paid, and fetch the receipt.

Bas-URL
https://api.hobok.se/v1
Version
1.0.0
OpenAPI
3.2.1

Översikt

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.

Authentication

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.

Requirements

The API requires a Plus subscription, checked on every request. A downgrade stops it working immediately.

Rate limit

120 requests per minute per key. Exceeding it returns 429 with a Retry-After header.

Ids

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.

Money and VAT

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.

Retries

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.

Language

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.

Service

Endpoints about the API itself. No key required.

GET /health Utan nyckel

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.

GET /openapi.yaml Utan nyckel

This document, as YAML

Unauthenticated, because a code generator needs the specification before the customer has a key.

Svar

  • 200

    The OpenAPI document.

GET /openapi.json Utan nyckel

This document, as JSON

Svar

  • 200

    The OpenAPI document.

Account

Who this key is, and the defaults the other endpoints apply.

GET /me

What this key is bound to

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.

Svar

  • 200

    Account and defaults.

  • 401
  • 403
  • 429
GET /payment-methods

Values accepted by mark-paid

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.

Svar

  • 200

    The available payment methods.

  • 401
  • 403
  • 429

Invoices

Issuing invoices, recording payment, fetching documents.

GET /invoices

List invoices

Newest first by invoice number. Line items are omitted here — fetch a single invoice to get them.

Parametrar

NamnITypBeskrivning
pagequeryinteger, standard 1
per_pagequeryinteger, standard 20, 1–100Rejected 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.
statusquerystring, sent | paid | creditedReturn only invoices with this status.

Svar

  • 200

    A page of invoices.

  • 401
  • 403
  • 422
  • 429
POST /invoices

Issue an invoice

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.

Parametrar

NamnITypBeskrivning
Idempotency-Keyheaderstring, max 255 teckenA 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.

Kropp

FältTypBeskrivning
client_idintegerAn existing customer's id. Mutually exclusive with buyer; sending both is a 422.
buyerA one-off buyer. Mutually exclusive with client_id.
issue_datestringDefaults to today.
due_datestringDefaults to issue_date plus the account's payment terms. May not be before issue_date.
our_referencestring, max 255 tecken
their_referencestring, max 255 tecken
notesstring
send_emailboolean, standard falseQueue 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

Exempel — One-off customer

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

Exempel — Existing customer, emailed on creation

{
  "client_id": 4,
  "our_reference": "Eva Svensson",
  "send_email": true,
  "lines": [
    {
      "description": "Reparation",
      "quantity": 1,
      "unit": "st",
      "unit_price": 900
    }
  ]
}

Svar

  • 201

    The invoice was created.

  • 400
  • 401
  • 403
  • 404

    No client with that client_id in this hobby business.

  • 409
  • 422
  • 429
GET /invoices/{local_id}

Fetch one invoice

Parametrar

NamnITypBeskrivning
local_id *pathintegerThe invoice's id within this hobby business.

Svar

  • 200

    The invoice, with its line items.

  • 401
  • 403
  • 404
  • 429
GET /invoices/{local_id}/pdf

Download the invoice as PDF

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.

Parametrar

NamnITypBeskrivning
local_id *pathintegerThe invoice's id within this hobby business.

Svar

  • 200

    The invoice document.

  • 401
  • 403
  • 404
  • 409

    No hobok-rendered document exists (pdf_unavailable).

  • 429
POST /invoices/{local_id}/mark-paid

Record that an invoice was paid

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.

Parametrar

NamnITypBeskrivning
local_id *pathintegerThe invoice's id within this hobby business.
Idempotency-Keyheaderstring, max 255 teckenA 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.

Kropp (valfri)

FältTypBeskrivning
payment_datestringDefaults to today.
payment_methodstring | nullOne of the values from /payment-methods, or null. Anything else is a 422.
payment_referencestring, max 255 tecken
send_receipt_emailboolean, standard falseQueue the receipt to the buyer. Requires the invoice to have a buyer email address.

Exempel

{
  "payment_date": "2026-04-01",
  "payment_method": "Swish",
  "payment_reference": "REF-1001",
  "send_receipt_email": true
}

Svar

  • 200

    The invoice, with the transaction that was booked.

  • 400
  • 401
  • 403
  • 404
  • 409

    The invoice is not sent (invalid_status), or the Idempotency-Key conflicts.

  • 422
  • 429
GET /invoices/{local_id}/receipt.pdf

Download the payment receipt as PDF

The Swedish kvitto for a paid invoice, rendered from the snapshot taken when it was marked paid. Available only once the invoice is paid.

Parametrar

NamnITypBeskrivning
local_id *pathintegerThe invoice's id within this hobby business.

Svar

  • 200

    The receipt document.

  • 401
  • 403
  • 404
  • 409

    The invoice has not been marked paid (not_paid).

  • 429
POST /invoices/{local_id}/receipt/email

Email the receipt to the buyer

Queues the receipt for delivery to the invoice's buyer address. A 200 means queued, never delivered — a separate mailer sends it.

Parametrar

NamnITypBeskrivning
local_id *pathintegerThe invoice's id within this hobby business.

Svar

  • 200

    The receipt was queued.

  • 401
  • 403
  • 404
  • 409

    The invoice is not paid (not_paid), or has no stored receipt document (receipt_unavailable).

  • 422
  • 429

Customers

The reusable customer register for this hobby business.

GET /clients

List customers

Parametrar

NamnITypBeskrivning
pagequeryinteger, standard 1
per_pagequeryinteger, standard 20, 1–100Rejected 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.

Svar

  • 200

    A page of customers.

  • 401
  • 403
  • 422
  • 429
POST /clients

Add a customer

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.

Kropp

FältTypBeskrivning
name *string, max 255 tecken
co_addressstring, max 255 tecken
street_addressstring, max 255 tecken
postal_codestring, max 20 teckenNormalised to the Swedish NNN NN form.
citystring, max 100 tecken
emailstring, max 255 tecken
phonestring, max 50 tecken

Exempel

{
  "name": "Stamkunden AB",
  "street_address": "Storgatan 2",
  "postal_code": "11122",
  "city": "Göteborg",
  "email": "faktura@stamkunden.se"
}

Svar

  • 201

    The customer was created. No Location header: there is no single-customer endpoint to point at.

  • 400
  • 401
  • 403
  • 422
  • 429

Products

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.

GET /products

List products

Ordered by name, which is how the catalogue is read — by someone choosing something, not in the order things were added.

Parametrar

NamnITypBeskrivning
pagequeryinteger, standard 1
per_pagequeryinteger, standard 20, 1–100Rejected 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.

Svar

  • 200

    A page of products.

  • 401
  • 403
  • 422
  • 429
POST /products

Add a product

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.

Kropp

FältTypBeskrivning
name *string, max 255 tecken
descriptionstring, max 500 tecken
unitstring, 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.

Exempel

{
  "name": "Konsultation",
  "description": "Konsultation — hobbyverksamhet",
  "unit": "h",
  "unit_price": 1250
}

Svar

  • 201

    The product was created. No Location header: there is no single-product endpoint to point at.

  • 400
  • 401
  • 403
  • 422
  • 429

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.