Skip to content

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.

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)
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”
PDF 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.

  • Taxes are read with OrderTax (both Settings > Checkout > Tax calculation shapes: P_<id> / S_<g>_<id> / PS_<id> and the subtotal shape P / S / PS with items lists). 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, booking date) are noon of the shop day.
  • Text is cut with mb_substr(..., 'UTF-8'); JSON is sent with JSON_UNESCAPED_UNICODE.
  • Every create is recorded first: ?:kv_sevdesk_docs row in state P (invoice I, credit note C, booking B) or ?:kv_sevdesk_map row in state P (contact), then the call, then the remote id. All work on one order runs under GET_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: description ends with Ref KV-C<company>-<user> (guests KV-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 paidAmount and adopts the booking only when paid-before + amount was reached. Credit notes have no paidAmount in 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().
  • 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, backfill are admin-only (schemas/permissions/vendor_multivendor.post.php); every order action compares the order’s company_id with the vendor’s.
  • 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\Synchronizer repairs 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.