orders:write Orders & fulfillment Allows only reporting sales and fulfillment data.
REST · JSON · OpenAPI 3.1 · v1
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.
https://api.rechnungskit.de/v1Authorization: Bearer rk_key_…application/json · integer centsQuickstart
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.
Integration flow
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.
Your orderNo becomes the stable key that links everything.
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.
GET /v1/orders/{orderNo} returns the invoices linked to the order.
Save the returned invoice UUID or invoice number for this document.
PDF and XML stay behind Bearer auth, so your server fetches them.
1 · Order
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.
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.POST /v1/orderscurl 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
}]
}'Order accepted{
"data": {
"id": "6d754aec-40d9-4c1a-a78c-50dbb6c8e307",
"orderNo": "CM-10482",
"status": "created"
},
"requestId": "9d61558e-3c00-465d-b980-c074db600047"
}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.| Field | Type | Notes |
|---|---|---|
orderNo | string | Unique within your project, permanently |
billingAddress | object | Country always; the full address for regular invoices |
items[] | array | At least one line item |
unitNetCents or unitGrossCents | integer | All amounts in cents, never decimals |
| Field | Type | Notes |
|---|---|---|
billingAddress.leitweg_id | string? | 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 |
discountNetCents | integer? | 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 |
shippingGrossCents | integer? | 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 |
paymentRef | string? | Unique provider reference for payment matching |
requiresShipping | boolean? | true holds physical goods back until fulfillment |
service_period | object? | 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 |
creditRedemptions | array? | 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
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.
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.POST /v1/fulfillmentscurl 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
Ask for the status with your own order number. Once an invoice exists, invoices[] contains its UUID, invoice number and archive status.
GET /v1/orders/CM-10482curl https://api.rechnungskit.de/v1/orders/CM-10482 \ -H "Authorization: Bearer $RK_API_KEY"
Invoice 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.
GET /v1/invoices/{id}/pdfcurl https://api.rechnungskit.de/v1/invoices/f77ba43d-6f06-4b84-bd24-5cfab76a1192/pdf \ -H "Authorization: Bearer $RK_API_KEY" \ --output RK-2026-0148.pdf
/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
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.
POST /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
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.
POST /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"
}
]
}200{ "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.
creditRedemptions so the invoice shows it as paid. Details: Taking over credit from your own shop.Reference
/api/health Process and database status
public/v1 API discovery and the available endpoints
public/v1/orders Create or update an order, idempotently
orders:write/v1/orders/{orderNo} Get the order, fulfillment and invoice status
invoices:read/v1/fulfillments Report shipping or service delivery
orders:write/v1/invoices Filter invoices and page through them with a cursor
invoices:read/v1/invoices/{reference} Metadata by UUID, invoice number, order number or payment reference
invoices:read/v1/invoices/{reference}/pdf Download the archived PDF
invoices:read/v1/invoices/{reference}/xml Download the archived EN 16931 XML
invoices:read/v1/credit-transactions Report credit movements (push), exactly once per id
orders:write/v1/credit-balances Report the balance per customer for reconciliation
orders:writeMachine-readable reference: Download OpenAPI 3.1 ↗
Errors & retries
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.
validation_failed{
"error": {
"code": "validation_failed",
"message": "billingAddress.country_code is required for VAT determination",
"field": "billingAddress.country_code"
},
"requestId": "9d61558e-3c00-465d-b980-c074db600047"
}| Status | Meaning | What to do |
|---|---|---|
400/422 | Invalid request or field values | Do not retry blindly; fix the data first |
401/403 | Invalid key or missing scope | Check the key and its scopes |
404 | Order or invoice does not exist yet | In an asynchronous flow, check again with backoff |
409 | Conflict, or the document is not archived yet | Read the error code and fetch again later |
429/503 | Temporarily unavailable | Retry-After header: wait that long, then back off exponentially |
5xx | Transient server error (for example during a deployment) | Required: retry with backoff for at least 24 hours |
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
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.
Security
status: ok confirms the process, db: true also confirms the database connection. Check both values, not just HTTP 200.
Ready?
Each key belongs to one project, and the full key is shown only once.