Developer notes
sevDesk Accounting Sync (add-on id kv_sevdesk) talks to the sevDesk API only through the endpoints below. Every endpoint and field comes
from the official OpenAPI document https://api.sevdesk.de/openapi.yaml (info.version 2.0.0, read 2026-11-08; the same document is
rendered at https://api.sevdesk.de/). Section names in quotes are the headings of that document.
Connection
Section titled “Connection”| Item | Value | Source |
|---|---|---|
| Base URL | https://my.sevdesk.de/api/v1 |
servers[0] |
| Authentication | Authorization: <API token> (32 hex characters, no Bearer prefix) |
“Authentication and Authorization” |
| User-Agent | Kavio Labs sevDesk Accounting Sync for CS-Cart by Kavio Labs (...) |
“API Requests”: “please pass a meaningful entry at the header User-Agent … Integration-name by abc” |
| Lists | limit (1-1000), offset; answer {"objects": [...]} |
“Pagination” |
| Errors | {"error": {"message", "exceptionUUID"}}; 400, 401, 403, 404, 422, 500 |
components.schemas.validationError, “API Responses” |
| Test mode | a second (trial) sevDesk account with its own token; there is no sandbox | “Your First Request” (trial account recommended) |
Endpoints used
Section titled “Endpoints used”| Purpose | Method and path | Fields sent / read |
|---|---|---|
| Connection test, bookkeeping version | GET /Tools/bookkeepingSystemVersion |
objects.version (1.0 / 2.0) |
| Payment accounts (Mapping page) | GET /CheckAccount |
id, name, type (online, offline, register), status (0, 100), defaultAccount |
| Contact person guess | GET /Invoice?limit=1 |
contactPerson.id, else createUser.id of the newest invoice |
| Country ids (optional) | GET /StaticCountry?limit=1000 |
id, code (see “Not verifiable”) |
| Find a contact after a lost answer | GET /Contact?depth=1&name=...&limit=100&offset=N |
id, description (“How to filter for certain contacts”: depth, name) |
| Create a contact | POST /Contact |
name (organisation) or surename + familyname (person), category {id: 3, objectName: Category} (Customer), status 1000, vatNumber, description (our reference) |
| E-mail of a new contact | POST /CommunicationWay |
contact, type EMAIL, value, key {id 1 Private / 2 Work, objectName CommunicationWayKey}, main |
| Create an invoice (draft) | POST /Invoice/Factory/saveInvoice |
invoice: objectName Invoice, mapAll, invoiceType RE, status "100", invoiceDate (dd.mm.yyyy), deliveryDate (timestamp), contact, contactPerson {SevUser}, address, addressCountry {StaticCountry} (only when known), discount 0, taxRate 0, taxText, taxRule {id, TaxRule} (2.0) or taxType (1.0), currency, showNet, smallSettlement, timeToPay, headText, customerInternalNote, propertyIsEInvoice; invoicePosSave[]: objectName InvoicePos, mapAll, quantity, price, name, text, unity {id, Unity}, taxRate, positionNumber |
| Read an invoice | GET /Invoice/{id} |
status (100 / 200 / 750 / 1000), invoiceNumber, sumGross, paidAmount, enshrined |
| Find an invoice after a lost answer | GET /Invoice?contact[id]=..&contact[objectName]=Contact&createAfter=..&limit=100&offset=N |
id, customerInternalNote (“How to filter for certain invoices”) |
| Finalize (DRAFT -> OPEN, number) | PUT /Invoice/{id}/sendBy {sendType: VPDF, sendDraft: false} |
“Removed endpoint /Invoice/{invoiceId}/changeStatus”: sendBy / sendViaEmail change the status |
| Total check before finalizing | GET /Invoice/{id} |
sumGross of the draft must equal the order total (0.05); otherwise the draft is not finalized |
| Optional: e-mail from sevDesk | POST /Invoice/{id}/sendViaEmail |
toEmail, subject, text |
| Optional: enshrine | PUT /Invoice/{id}/enshrine |
only from OPEN; “This operation cannot be undone” |
GET /Invoice/{id}/getPdf?download=true&preventSendBy=true |
filename, mimeType, base64encoded, content |
|
| E-invoice XML (only for invoices made e-invoice in sevDesk) | GET /Invoice/{id}/getXml |
objects (XML string) |
| Book the payment | PUT /Invoice/{id}/bookAmount |
amount, date (timestamp), type FULL_PAYMENT / N, checkAccount {id, CheckAccount}, createFeed |
| Credit note from the invoice | POST /CreditNote/Factory/createFromInvoice {invoice: {id, objectName: Invoice}} |
creditNote, creditNotePos[], discount[] |
| Part credit: set quantities of the draft | POST /CreditNote/Factory/saveCreditNote |
creditNote (id + the fields as read incl. deliveryDate, invoiceType, mapAll), creditNotePosSave[] (id, quantity, ...), creditNotePosDelete (one object per call) |
| Read / find credit notes | GET /CreditNote/{id}, GET /CreditNote?contact[id]&contact[objectName] (create date filtered locally) |
status, creditNoteNumber, sumGross, create |
| Positions of an adopted credit note draft | GET /CreditNotePos?creditNote[id]=..&creditNote[objectName]=CreditNote |
id, quantity, price, taxRate, positionNumber |
| Finalize, enshrine, PDF, book a credit note | PUT /CreditNote/{id}/sendBy, PUT .../enshrine, GET .../getPdf, PUT .../bookAmount |
as for invoices |
| Delete a credit note draft that does not match | DELETE /CreditNote/{id} |
drafts only |
Tax rules (“sevdesk-Update 2.0” > “Tax Rules”, revenue)
Section titled “Tax rules (“sevdesk-Update 2.0” > “Tax Rules”, revenue)”| Situation in the order | taxRule | Rates sent |
|---|---|---|
| Small business setting on | 11 (§19 UStG) | 0 |
| Delivery address in Germany | 1 | 0, 7, 19 (a rate within 0.5 points is snapped; other rates stop the job) |
| Other EU country, buyer VAT ID, no VAT charged | 3 (goods) or 21 (§18b, services), setting | 0 (always) |
| Other EU country, VAT, “Registered for One Stop Shop” on | 18 / 19 / 20 One Stop Shop, setting | the destination rate, whatever it is (sevDesk validates it) |
| Other EU country, VAT, OSS setting off | 1 when every rate is German (0, 7, 19); any other rate stops the job | 0, 7, 19 |
| Other EU country, no VAT and no VAT ID | job stops with a message (no rule fits) | - |
| Outside the EU, no VAT | 2 (export) or 17 (not taxable in Germany), setting | 0 |
| Outside the EU, with VAT | 1 | 0, 7, 19 |
| Bookkeeping version 1.0 | taxType default / eu / noteu / ss instead of taxRule; OSS, §13b, §18b and §4 refuse |
as above |
Version 1.0.0 does not create e-invoices (propertyIsEInvoice is never sent): the documented buyer and invoice data an e-invoice needs (paymentMethod, address parts, buyerReference) are not mapped yet.
Rule 5 (§13b domestic reverse charge) and 4 (§4 UStG) are not chosen automatically.
CS-Cart data
Section titled “CS-Cart data”- Taxes are read with
OrderTax(both Settings > Checkout > Tax calculation shapes:P_<id>/S_<g>_<id>/PS_<id>and the subtotal shapeP/S/PSwithitemslists). Unit tests cover both. - The order discount (
subtotal_discount) is spread over the product lines (factor on the unit price, noted in the position text), so credit notes built from the stored lines are exact. sevDesk discounts (discountSave) are not used. - Dates:
invoiceDate= the shop day as dd.mm.yyyy (date()runs in the shop time zone); timestamp fields (deliveryDate, bookingdate) are noon of the shop day. - Text is cut with
mb_substr(..., 'UTF-8'); JSON is sent withJSON_UNESCAPED_UNICODE.
Idempotency and locks
Section titled “Idempotency and locks”- Every create is recorded first:
?:kv_sevdesk_docsrow in state P (invoice I, credit note C, booking B) or?:kv_sevdesk_maprow in state P (contact), then the call, then the remote id. All work on one order runs underGET_LOCK('sevd-order-<id>'); queue jobs are claimed atomically. - Invoice reference:
customerInternalNote=Bestellung #<order> / Ref KV-<order>-<10 hex>(per attempt). After a timeout or 5xx the next run lists the contact’s invoices created since the attempt and adopts the one with that reference. Nothing found after 20 minutes: the row becomes “unclear” and only the merchant’s “Issue now” creates it again. - Contact reference:
descriptionends withRef KV-C<company>-<user>(guestsKV-G<company>-<hash of e-mail, company, VAT ID>); after a lost answer only a description ending with exactly that reference is adopted; nothing found within 20 minutes waits, then creates. - Credit notes: createFromInvoice takes no reference, so a lost answer is adopted only when exactly one unknown DRAFT credit note of the contact with the invoice’s total was created around the attempt; otherwise the row becomes unclear (never created twice automatically). A part credit stays a draft until its total matches; a mismatch deletes the draft.
- Bookings: the row keeps the amount and what was paid before; a retry reads the invoice’s
paidAmountand adopts the booking only when paid-before + amount was reached. Credit notes have nopaidAmountin the model: a credit note pay-out is adopted only at status 1000, and a partly paid-out credit note (750) is never booked. - Credits never exceed the invoice minus earlier credits; the invoice must be finalized first.
- 401/403 make jobs wait 2 hours (the token was renewed); jobs never retry for days on an auth error.
- Retry delays use
JobException::retryInMinutes()/retryInSeconds()/permanent().
Multi-Vendor
Section titled “Multi-Vendor”- Owner mode on (default): one sevDesk account; vendors see their documents but cannot create, credit, book or retry.
- Owner mode off: each vendor connects their own token on the Connection page (vendor panel) and maps their own payment account.
cron,run_now,backfillare admin-only (schemas/permissions/vendor_multivendor.post.php); every order action compares the order’scompany_idwith the vendor’s.
Uninstall and upgrades
Section titled “Uninstall and upgrades”kv retention(retention.json): documents, map and accounts tables are kept on uninstall unless “Permanently delete all invoice data on uninstall” is ticked; settings are restored on install. Logs and jobs are dropped.Upgrade\Synchronizerrepairs tables, settings and language variables after an in-place upload.- Upgrade customers by uploading the new package over the installed add-on (or Upgrade Center), never “uninstall + install”.
PHP: change_order_status_post (invoice, refund credit note, payment booking), rma_update_details_post (credit note for a completed
RMA return), init_templater_post (Synchronizer, only while pending). Templates: orders:tabs_extra (admin/vendor order tab), customer
order page button (var/themes_repository/responsive/templates/addons/kv_sevdesk/hooks/orders/). The customer download is a session
page, not cached by the full page cache.
Not verifiable from the official document (flagged)
Section titled “Not verifiable from the official document (flagged)”| Item | What we do |
|---|---|
GET /StaticCountry is named in the ContactAddress text (“For all countries, send a GET to /StaticCountry”) but has no path entry; its fields (code) are not specified |
Used only to fill addressCountry; empty map = field omitted (“Can be omitted as complete address is defined in address attribute”) |
No endpoint lists sevDesk users (SevUser), although contactPerson is required in saveInvoice |
Read from the newest invoice; the merchant can enter the id on the Mapping page |
Unity ids are not listed; the schema example is 1 |
Default 1, configurable on the Mapping page |
Response envelopes: some answers are shown without objects (saveInvoice, Contact) while the prose and lists use objects |
Both shapes accepted |
No rate limit, no Retry-After header documented |
Calls of one token are spaced 250 ms across processes; a 429 is retried with back-off |
customerIntenalNote filter (spelled so in the docs) |
Not used; matching is done on the contact’s list |
Whether createFromInvoice copies customerInternalNote |
Not relied on (see credit note reconcile) |
deliveryDateUntil rules for credit notes of invoices with a delivery range |
We send deliveryDate only, no range |
createAfter is documented for invoices, not for GET /CreditNote |
Not sent for credit notes; the create date is compared locally |
Model_creditNoteResponse has no paidAmount and no enshrined |
Credit note pay-outs only from status 200; enshrining remembered locally |
saveInvoice required also lists addressCountry, taxType (with taxRule) and a top-level invoicePos, while the prose says addressCountry can be omitted and the field we send is invoicePosSave |
We follow the prose and the invoicePosSave schema; taxType only for bookkeeping 1.0 |
Model_InvoicePos.price: “either gross or net, depending on the sevdesk account setting” vs. the attribute table (showNet decides) |
We send showNet and price in that mode, and never finalize a draft whose sumGross differs from the order |
saveCreditNote requires deliveryDate and invoiceType without listing their values for credit notes |
Copied from the draft as read |
Sign of sumGross on credit notes (positive in the examples) |
Compared as an absolute value |
| E-invoice data (buyerReference, paymentMethod, address parts) | Not sent; no e-invoices in 1.0.0 |
sevdesk-Update 1.0: retired in 2024; ss is in the prose tables but not in the saveInvoice taxType enum |
Legacy path kept, not advertised |
Tested against tests/mock-server/server.js (built to the documented contract above), never against a real sevDesk account.