Skip to content
Rechnungskit

REST · JSON · OpenAPI 3.1 · v1

Rechnungskit API

POST /orders sends orders, POST /fulfillments reports shipping. Rechnungskit turns them into validated ZUGFeRD e-invoices (PDF with embedded XML), archives them under the German GoBD bookkeeping rules and prepares the DATEV export for your tax advisor. Bearer auth, integer cents, idempotent through your orderNo. PDF and XML via GET.

BASE URLhttps://api.rechnungskit.de/v1
AUTHAuthorization: Bearer rk_key_…
FORMATapplication/json · integer cents

Quickstart

One key, two scopes

Create a project-bound key under Connections → API in the app. The full key is shown exactly once and belongs only in your backend or secret manager.

orders:write Orders & fulfillment

Allows only reporting sales and fulfillment data.

invoices:read Status & documents

Allows reading invoice metadata, PDF and XML.

Recommendation: For a permanent production integration, use a key without an expiry date. Create separate keys for each environment and system, and grant only the scopes each one needs. Keys with an expiry date are meant for tests or transitions. You can rotate keys without downtime, because several keys can be active at the same time.

Integration flow

Your own order number links every step

You never need an internal Rechnungskit ID. Use an orderNo that stays unique for good. If the customer pays through Stripe, Mollie or another provider, also send that provider's unique paymentRef.

  1. 1
    Send the order

    Your orderNo becomes the stable key that links everything.

  2. 2
    Payment or fulfillment

    The payment webhook is matched on paymentRef. Only physical goods (requiresShipping: true) wait for fulfillment. SaaS and digital services skip this step, and the invoice is created as soon as the payment arrives.

  3. 3
    Check the status

    GET /v1/orders/{orderNo} returns the invoices linked to the order.

  4. 4
    Store the exact invoice ID

    Save the returned invoice UUID or invoice number for this document.

  5. 5
    Download the document

    PDF and XML stay behind Bearer auth, so your server fetches them.

1 · Order

Send the order idempotently

Every POST needs a stable Idempotency-Key. After a timeout, you can repeat the same request with the same key without creating a second order.

For SaaS and digital services, this is the only call you need. Leave out requiresShipping (the default is false). There is then no fulfillment wait, and step 2 (/v1/fulfillments) drops out entirely. Send paymentRef along. As soon as the payment from your payment provider matches the order, the invoice is created automatically. There is no "paid" field on purpose, because the connected payment provider reports the payment status.
cURLPOST /v1/orders
curl https://api.rechnungskit.de/v1/orders \
  -X POST \
  -H "Authorization: Bearer $RK_API_KEY" \
  -H "Idempotency-Key: order:CM-10482:v1" \
  -H "Content-Type: application/json" \
  -d '{
    "orderNo": "CM-10482",
    "paymentRef": "pi_3PqK8w2eZvKYlo2C",
    "currency": "EUR",
    "requiresShipping": false,
    "billingAddress": {
      "name": "Lisa Brandt",
      "street": "Torstraße 99",
      "postal_code": "10119",
      "city": "Berlin",
      "country_code": "DE"
    },
    "service_period": {
      "start_date": "2026-06-01",
      "end_date": "2026-06-30"
    },
    "items": [{
      "name": "Monatsabo Pro · Juni 2026",
      "sku": "saas-pro",
      "quantity": 1,
      "unitNetCents": 4118,
      "discountNetCents": 500,
      "taxRate": 19
    }]
  }'
201 CreatedOrder accepted
{
  "data": {
    "id": "6d754aec-40d9-4c1a-a78c-50dbb6c8e307",
    "orderNo": "CM-10482",
    "status": "created"
  },
  "requestId": "9d61558e-3c00-465d-b980-c074db600047"
}
The data.id returned here is the internal order ID, not the invoice ID that comes later. For your own flow, CM-10482 stays the reference that counts.

Required fields

FieldTypeNotes
orderNostringUnique within your project, permanently
billingAddressobjectCountry always; the full address for regular invoices
items[]arrayAt least one line item
unitNetCents or unitGrossCentsintegerAll amounts in cents, never decimals

Optional fields

FieldTypeNotes
billingAddress.leitweg_idstring?Only for public-sector buyers (authorities, municipalities, universities): their Leitweg-ID, the routing ID German public bodies use to receive e-invoices. If it is set, the invoice for this order is created as XRechnung 3.0 (the German public-sector e-invoice format: pure XML with a PDF copy for reading) instead of ZUGFeRD. Format: 2 to 12 digits, an optional sub-address and a two-digit check number, separated by hyphens (e.g. 04011000-1234512-68). If the ID is formally invalid, the API answers with 422 and field: "billingAddress.leitweg_id". The invoice contact must have a phone number (XRechnung rule BR-DE-6); without one, Rechnungskit holds the invoice back as a task
discountNetCentsinteger?Net discount per line item (ZUGFeRD BG-27); the unit price stays undiscounted. Only for real discounts. Redeemed credit is a payment method, never a discount
shippingGrossCentsinteger?Shipping as the customer paid it (gross). Rechnungskit treats shipping as an ancillary service and determines its VAT; carts with mixed VAT rates are split by their gross ratio (Federal Fiscal Court, BFH XI R 22/22). Alternatively, send shippingNetCents + shippingTaxRate if your checkout already calculates the VAT on shipping
paymentRefstring?Unique provider reference for payment matching
requiresShippingboolean?true holds physical goods back until fulfillment
service_periodobject?Service period for time-based services (SaaS, subscriptions): start_date and end_date as ISO dates (YYYY-MM-DD), both or neither. It sets the deferred revenue (pRAP, the German deferred income item) directly and takes precedence over the product default. A start_date before the invoice date is allowed. If it falls in an earlier (usually closed) financial year and you use accrual accounting, Rechnungskit creates a review task (to assign the revenue to the prior year) but still creates the invoice. Without service_period and without a SaaS default on the product, accrual accounting creates no invoice yet: the order waits as the task "Product without revenue recognition" until you set the revenue recognition on the product or send the period later. With cash-basis accounting (EÜR) this does not apply, and the revenue counts right away
creditRedemptionsarray?Redeemed referral credit: [{ "type": "referral_credit", "amountCents": 1000, "externalRef": "tx_81" }]. Credit is a payment, not a discount: the invoice shows VAT on the full amount and marks the credit share as paid, and your payment provider collects only the rest. Requires grossTotalCents; the sum must not exceed it. If the credit covers everything (sum = grossTotalCents, financialStatus "paid"), the invoice is created without an incoming payment

2 · Fulfillment

Report shipping or service delivery

For physical goods, the shipping date is the date of supply that counts. So report the complete shipment. Partial fulfillments do not produce a complete invoice yet.

SaaS and digital services: skip this step entirely. Without requiresShipping (the default is false) there is no fulfillment wait: the invoice is created as soon as the payment from the payment provider matches the order. POST /v1/orders with paymentRef is then the only call you need; /v1/fulfillments is never called. There is no "paid" field on purpose, because the connected payment provider reports the payment status.
cURLPOST /v1/fulfillments
curl https://api.rechnungskit.de/v1/fulfillments \
  -X POST \
  -H "Authorization: Bearer $RK_API_KEY" \
  -H "Idempotency-Key: fulfillment:CM-10482:fulfilled:v1" \
  -H "Content-Type: application/json" \
  -d '{
    "orderNo": "CM-10482",
    "status": "fulfilled",
    "shippedAt": "2026-07-27T10:30:00Z",
    "trackingNumber": "003404341612"
  }'

3 · Status & documents

How to get the invoice ID

Ask for the status with your own order number. Once an invoice exists, invoices[] contains its UUID, invoice number and archive status.

cURLGET /v1/orders/CM-10482
curl https://api.rechnungskit.de/v1/orders/CM-10482 \
  -H "Authorization: Bearer $RK_API_KEY"
200 OKInvoice found
{
  "data": {
    "orderNo": "CM-10482",
    "status": "invoiced",
    "fulfillmentStatus": "fulfilled",
    "invoices": [{
      "id": "f77ba43d-6f06-4b84-bd24-5cfab76a1192",
      "number": "RK-2026-0148",
      "status": "archived",
      "url": "/v1/invoices/f77ba43d-6f06-4b84-bd24-5cfab76a1192"
    }]
  },
  "requestId": "3a61c30d-4386-4c60-9354-841342cbc372"
}

Then use the UUID whenever you want to download one specific document. It stays unambiguous after a cancellation or when an order has several documents.

cURLGET /v1/invoices/{id}/pdf
curl https://api.rechnungskit.de/v1/invoices/f77ba43d-6f06-4b84-bd24-5cfab76a1192/pdf \
  -H "Authorization: Bearer $RK_API_KEY" \
  --output RK-2026-0148.pdf
{reference} accepts: Invoice UUIDInvoice numberOrder numberPayment reference
After a cancellation, one order number can have several documents. A direct request to /invoices/CM-10482 returns the newest one. For an exact original or corrective document, always use the UUID from invoices[] instead.

Alternative · Pay by invoice

Invoice first, payment later

When a customer buys on invoice, there is no provider payment at first. Instead, send the gateway name you approved in Rechnungskit under Connect. Once fulfillment is complete, the invoice is created as an open item with a payment term.

JSON excerptPOST /v1/orders
{
  "orderNo": "CM-10483",
  "paymentGateway": "invoice",
  "financialStatus": "pending",
  "requiresShipping": true,
  "billingAddress": { "...": "full billing address" },
  "items": [ "..." ]
}
paymentGateway must exactly match a gateway enabled under Connect → Pay by invoice. Without a matching payment or an approved invoice-first gateway, the order stays without an invoice, on purpose.

Store credit · Push

Report referral and customer credit

If your application manages credit itself, for example referral rewards, send every movement to POST /v1/credit-transactions: earning (earn), redeeming (redeem), reversal (reverse), payout (payout) and expiry (expire). Each transaction carries your unique id, so repeating a call never creates anything twice. Up to 500 per call.

RequestPOST /v1/credit-transactions
{
  "transactions": [
    {
      "id": "tx_81",
      "type": "earn",
      "email": "kundin@example.com",
      "amount_cents": 500,
      "occurred_at": "2026-10-02T09:14:00+02:00",
      "order_ref": "103002"
    },
    {
      "id": "tx_82",
      "type": "redeem",
      "email": "kundin@example.com",
      "amount_cents": 500,
      "occurred_at": "2026-10-05T18:40:00+02:00",
      "order_ref": "103950"
    }
  ]
}
Response200
{ "accepted": 2, "duplicates": 0, "applied": 1 }

Once a day, report the balance per customer to PUT /v1/credit-balances ({ "balances": [{ "email": "…", "balance_cents": 1250 }] }). Rechnungskit compares them with the history and creates a task when a customer's balance differs. That way, a lost message gets noticed too.

Bookings start on the cut-over date you set in Rechnungskit under Vouchers; before that date it is a dry run. When a customer redeems credit at checkout, also send the amount on the order as creditRedemptions so the invoice shows it as paid. Details: Taking over credit from your own shop.

Reference

API v1 endpoints

GET /api/health

Process and database status

public
GET /v1

API discovery and the available endpoints

public
POST /v1/orders

Create or update an order, idempotently

orders:write
GET /v1/orders/{orderNo}

Get the order, fulfillment and invoice status

invoices:read
POST /v1/fulfillments

Report shipping or service delivery

orders:write
GET /v1/invoices

Filter invoices and page through them with a cursor

invoices:read
GET /v1/invoices/{reference}

Metadata by UUID, invoice number, order number or payment reference

invoices:read
GET /v1/invoices/{reference}/pdf

Download the archived PDF

invoices:read
GET /v1/invoices/{reference}/xml

Download the archived EN 16931 XML

invoices:read
POST /v1/credit-transactions

Report credit movements (push), exactly once per id

orders:write
PUT /v1/credit-balances

Report the balance per customer for reconciliation

orders:write

Errors & retries

Every response carries a request ID

The X-Request-Id header and the JSON field requestId identify the same request. You can find it in your project's API activity for 30 days.

422 Unprocessable Entityvalidation_failed
{
  "error": {
    "code": "validation_failed",
    "message": "billingAddress.country_code is required for VAT determination",
    "field": "billingAddress.country_code"
  },
  "requestId": "9d61558e-3c00-465d-b980-c074db600047"
}
StatusMeaningWhat to do
400/422Invalid request or field valuesDo not retry blindly; fix the data first
401/403Invalid key or missing scopeCheck the key and its scopes
404Order or invoice does not exist yetIn an asynchronous flow, check again with backoff
409Conflict, or the document is not archived yetRead the error code and fetch again later
429/503Temporarily unavailableRetry-After header: wait that long, then back off exponentially
5xxTransient server error (for example during a deployment)Required: retry with backoff for at least 24 hours
Delivery contract (required): Your system must retry failed pushes. Every 5xx or network error is transient, so retry with exponential backoff for at least 24 hours. Retries are always safe: orders are idempotent through orderNo, and the Idempotency-Key header deduplicates parallel deliveries. We also recommend a daily reconciliation job that resends everything your system marked as failed. The classic failure: the payment arrives because the payment provider retries it, while your own order push fails on a single 502 and is never retried. Then the invoice cannot be created.

Sandbox

Test first, then go live

Every account starts in test mode, with the same API, the same pipeline and the same rk_key_… key as in production, including EN 16931 validation and PDF/A-3 rendering. So you test exactly the behavior that runs live later, with the key that also works live.

TEST-2026-0001 Separate number range

Test invoices are clearly marked as test documents, in both PDF and XML, and use their own TEST number range.

€0.00 Not billed

Test invoices do not count toward your bill and do not appear in the DATEV export or in OSS (EU One-Stop Shop) or EC Sales List reports.

isolated No sending, no effect

Sending invoices is blocked in the sandbox. Test invoices have no tax effect, and you can discard them at any time with a reset.

Recommendation: Build your whole integration in test mode, then go live. Code and key stay the same; only your project's go-live decides whether invoices are real. Test mode is meant for integration and acceptance tests (fair use), not for load tests.

Security

Secrets stay in the backend

  • Never put API keys into browser code, mobile apps or a public repository.
  • Store keys in a secret manager or in encrypted environment variables.
  • Rotate production keys without an expiry date on a schedule you choose; use short-lived keys only for tests or transitions.
  • Review keys that have not been used for more than 90 days in the API settings, and revoke them when the integration is no longer needed.
  • If you suspect a key has leaked, create a new one right away, roll it out and revoke the old one.
  • Fetch documents on your server; never give customers permanent Bearer links.
Health Check

status: ok confirms the process, db: true also confirms the database connection. Check both values, not just HTTP 200.

/api/health ↗

Ready?

Create your first API key

Each key belongs to one project, and the full key is shown only once.

Go to API settings
de en