A strongly-typed .NET client library for the sevDesk API. Manage invoices, contacts, vouchers, orders, credit notes, and more — with full async support and dependency injection.
Unofficial project. sevDesk.NET is an independent community library. It is not affiliated with, endorsed by, or supported by sevDesk GmbH. See Trademarks and disclaimer.
Feature highlights:
- 20 typed clients covering the entire sevDesk REST API
- Strongly typed models and enums for all resources
- Transaction operations: save invoice/order/voucher with positions atomically
- PDF generation, email sending, status management, and document upload
- Pagination with
SevDeskListResponse<T>and filtering - Proper exception hierarchy (
SevDeskAuthenticationException,SevDeskNotFoundException,SevDeskValidationException) IHttpClientFactoryintegration with automatic auth header injection- Dependency injection via
IServiceCollection.AddSevDesk()
- A sevDesk account and API token
- .NET 10.0
dotnet add package sevDesk.NETIn Program.cs, register sevDesk.NET with your API token:
builder.Services.AddSevDesk(options =>
{
options.ApiToken = "your-api-token";
});For custom base URLs (e.g. self-hosted or proxy):
builder.Services.AddSevDesk(options =>
{
options.ApiToken = "your-api-token";
options.CustomBaseUrl = "https://my-proxy.example.com/api/v1";
});public class InvoiceService
{
private readonly ISevDeskClient _client;
public InvoiceService(ISevDeskClient client)
{
_client = client;
}
public async Task ListRecentInvoicesAsync()
{
var result = await _client.Invoices.ListAsync();
foreach (var invoice in result.Items)
{
Console.WriteLine($"{invoice.InvoiceNumber}: {invoice.SumGross} {invoice.Currency}");
}
}
}{
"SevDesk": {
"ApiToken": "your-api-token"
}
}builder.Services.AddSevDesk(
builder.Configuration.GetSection("SevDesk"));| Client | Property | Operations |
|---|---|---|
| Invoices | client.Invoices |
CRUD, SaveInvoice, ChangeStatus, GetPdf, SendViaEmail, Duplicate, Cancel, MarkAsSent, BookAmount |
| Invoice Positions | client.InvoicePositions |
CRUD, filter by invoice |
| Orders | client.Orders |
CRUD, SaveOrder, ChangeStatus, GetPdf, SendViaEmail, Duplicate |
| Order Positions | client.OrderPositions |
CRUD, filter by order |
| Vouchers | client.Vouchers |
CRUD, SaveVoucher, BookAmount, MarkAsPaid, MarkAsOpen, UploadFile |
| Voucher Positions | client.VoucherPositions |
CRUD, filter by voucher |
| Credit Notes | client.CreditNotes |
CRUD, SaveCreditNote, CreateFromInvoice, GetPdf, SendViaEmail |
| Credit Note Positions | client.CreditNotePositions |
CRUD, filter by credit note |
| Client | Property | Operations |
|---|---|---|
| Contacts | client.Contacts |
CRUD, GetNextCustomerNumber |
| Contact Addresses | client.ContactAddresses |
CRUD, filter by contact |
| Communication Ways | client.CommunicationWays |
CRUD, filter by contact |
| Accounting Contacts | client.AccountingContacts |
List, Get, filter by contact |
| Client | Property | Operations |
|---|---|---|
| Check Accounts | client.CheckAccounts |
CRUD, GetBalance |
| Check Account Transactions | client.CheckAccountTransactions |
CRUD, filter by account |
| Client | Property | Operations |
|---|---|---|
| Parts | client.Parts |
CRUD |
| Client | Property | Operations |
|---|---|---|
| Tags | client.Tags |
Create, List, Get, Delete |
| Categories | client.Categories |
CRUD, filter by object type |
| Documents | client.Documents |
List, Get, Upload, Download |
| Client | Property | Operations |
|---|---|---|
| Unities | client.Unities |
List, Get |
| Tax Rules | client.TaxRules |
List, Get |
| Currency Exchange Rates | client.CurrencyExchangeRates |
List, Get |
| Static Countries | client.StaticCountries |
List, Get |
var invoice = await client.Invoices.SaveInvoiceAsync(
new Invoice
{
Contact = new SevDeskObjectReference { Id = 123, ObjectName = "Contact" },
InvoiceDate = DateTime.Today,
Header = "Invoice 2024-001",
TimeToPay = 14
},
new[]
{
new InvoicePos
{
Name = "Consulting",
Quantity = 10,
Price = 150.00m,
Unity = new SevDeskObjectReference { Id = 1, ObjectName = "Unity" },
TaxRate = 19
}
});byte[] pdf = await client.Invoices.GetPdfAsync(invoiceId);
File.WriteAllBytes("invoice.pdf", pdf);await client.Invoices.SendViaEmailAsync(
invoiceId,
email: "customer@example.com",
subject: "Your Invoice",
text: "Please find your invoice attached.");var page = await client.Contacts.ListAsync(new PaginationParameters
{
Limit = 50,
Offset = 100
});
Console.WriteLine(page.Total is int total
? $"Showing {page.Items.Count} of {total} contacts"
: $"Showing {page.Items.Count} contacts (server reported no total)");SevDeskListResponse<T>.Total is int?. The API sends total only for countAll=true — which
every ListAsync requests — and not reliably even then, so null (no total reported, result set
size unknown) and 0 (an empty result set) are distinct. To page through everything, loop while a
page comes back full and use Total only as an early exit.
await using var stream = File.OpenRead("receipt.pdf");
var document = await client.Vouchers.UploadFileAsync(stream, "receipt.pdf");decimal balance = await client.CheckAccounts.GetBalanceAsync(
accountId,
date: DateTime.Today);sevDesk.NET uses a typed exception hierarchy:
try
{
var invoice = await client.Invoices.GetAsync(id);
}
catch (SevDeskNotFoundException)
{
// 404 — invoice not found
}
catch (SevDeskAuthenticationException)
{
// 401 — invalid API token
}
catch (SevDeskValidationException ex)
{
// 422 — validation error
Console.WriteLine(ex.RawResponse);
}
catch (SevDeskApiException ex)
{
// Other API errors
Console.WriteLine($"{ex.StatusCode}: {ex.Message}");
}| Exception | HTTP Status | When |
|---|---|---|
SevDeskAuthenticationException |
401 | Invalid or missing API token |
SevDeskNotFoundException |
404 | Resource not found |
SevDeskValidationException |
422 | Invalid request data |
SevDeskWriteSucceededException |
Various | A Save…Async write succeeded but its follow-up failed |
SevDeskApiException |
Various | Other API errors |
SevDeskException |
— | Base exception (network errors, etc.) |
SaveInvoiceAsync, SaveCreditNoteAsync, CreateFromInvoiceAsync, SaveOrderAsync and
SaveVoucherAsync post the document and then read it back. Only the post writes. If the read-back
fails, the document already exists and repeating the call would create a duplicate.
SevDeskWriteSucceededException makes that outcome distinguishable from a write that never arrived:
try
{
var invoice = await client.Invoices.SaveInvoiceAsync(invoice, positions);
}
catch (SevDeskWriteSucceededException ex)
{
// Written. Do not send it again.
// ex.ObjectId is null when even the id could not be read — look the document up.
}
catch (SevDeskApiException)
{
// Not written. Retrying is safe.
}Each of those methods has a …ReferenceAsync counterpart that skips the read-back and returns just
the identifier — one request instead of two:
var reference = await client.Invoices.SaveInvoiceReferenceAsync(invoice, positions);
Console.WriteLine(reference.Id);See the error handling guide for the full picture.
| Property | Type | Default | Description |
|---|---|---|---|
ApiToken |
string |
(required) | 32-character API token from sevDesk |
CustomBaseUrl |
string? |
null |
Override the default API base URL |
BaseUrl |
string |
https://my.sevdesk.de/api/v1 |
Computed base URL (uses CustomBaseUrl if set) |
AddSevDesk() validates options on registration:
ApiTokenmust not be emptyCustomBaseUrl(if set) must use HTTPS or be localhost
sevDesk.NET is an independent, community-maintained open-source project. It is not an official sevDesk product and is not affiliated with, authorized by, endorsed by, or sponsored by sevDesk GmbH.
"sevDesk" and the sevDesk logo are trademarks or registered trademarks of sevDesk GmbH. All other product names, logos and brands are the property of their respective owners. They are used in this project, in its documentation and in its package names solely to identify the third-party APIs this library communicates with; such descriptive use implies no endorsement, sponsorship or business relationship.
The library is provided under the MIT license, as-is and without warranty of any kind. It carries no support agreement or service level of any kind from sevDesk, and using it does not change your obligations under sevDesk's own terms of service — you still need your own sevDesk account and API token. Questions about the APIs themselves belong to their respective providers; questions about this library belong in its issue tracker.
If you hold rights to any of the marks named above and object to their use here, please open an issue and it will be addressed.
MIT