Skip to content

AMANDA-Technology/CashCtrlApiNet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

181 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CashCtrlApiNet

Unofficial .NET 10 API client library for the CashCtrl REST API v1. Provides a fully typed C# client with models, JSON serialization, and ASP.NET Core DI integration.

CashCtrl is a Swiss cloud ERP for accounting and business management.

BuildNuGetAndPublish CodeQL SonarCloud

Features

  • 100% CashCtrl REST API v1 coverage (375 endpoints, 58 services, 10 domain groups)
  • Immutable record models with three-tier hierarchy (Create, Update, Full)
  • ASP.NET Core DI integration with typed HttpClient and optional HTTP resilience
  • Standalone usage without DI
  • Auto-pagination helper (PaginationHelper.ListAllAsync)
  • Filter, sort, and pagination support on all list endpoints

E2E Verification Status

Every release is covered by unit tests (NSubstitute) and WireMock-backed integration tests that run without credentials on every CI build. Separately, fixtures under tests/CashCtrlApiNet.E2eTests/ exercise every service against a live CashCtrl account.

Every domain group has been verified against the live API except Salary and Meta. Fixtures in those two groups are skipped with [Ignore] at the class level until a future verification pass. The library code for those services is fully implemented and usable, but if you hit a model or parameter mismatch there, follow the diagnostic playbook in doc/analysis/2026-03-29-e2e-test-verification.md — the same patterns that surfaced issues in Groups 1-6 (catalogued in doc/analysis/2026-03-29-api-doc-discrepancies.md §§1-33) are likely to surface in Salary and Meta too.

Two Group 1 fixtures in these folders are verified and not ignored: Meta/OrganizationE2eTests and Salary/SalaryFieldE2eTests.

Packages

Package Description
CashCtrlApiNet API client, connection handler, and all service endpoints
CashCtrlApiNet.Abstractions Models, enums, converters, and serialization helpers
CashCtrlApiNet.AspNetCore ASP.NET Core dependency injection registration

Getting Started

Installation

# ASP.NET Core (includes all packages)
dotnet add package CashCtrlApiNet.AspNetCore

# Standalone (no DI)
dotnet add package CashCtrlApiNet

Authentication

CashCtrl uses API key authentication. Generate an API key in your CashCtrl account under Settings > API. The base URL follows the pattern https://{yourorg}.cashctrl.com/.

ASP.NET Core (DI)

Register the client in Program.cs:

builder.Services.AddCashCtrl(options =>
{
    options.BaseUri = "https://myorg.cashctrl.com/";
    options.ApiKey = "your-api-key";
    options.Language = Language.De;       // optional, defaults to German
    options.EnableResilience = true;      // optional, defaults to true (retries, circuit breaker)
});

Then inject ICashCtrlApiClient wherever you need it:

public class MyAccountService(ICashCtrlApiClient cashCtrl)
{
    public async Task<IEnumerable<AccountListed>> GetAllAccounts()
    {
        var result = await cashCtrl.Account.Account.GetList();
        return result.ResponseData!.Data;
    }
}

Standalone (without DI)

using var connectionHandler = new CashCtrlConnectionHandler(
    new CashCtrlConfiguration
    {
        BaseUri = "https://myorg.cashctrl.com/",
        ApiKey = "your-api-key",
        DefaultLanguage = "de"
    });

// Use services directly via the connection handler
var accountService = new AccountService(connectionHandler);
var result = await accountService.GetList();

foreach (var account in result.ResponseData!.Data)
    Console.WriteLine($"{account.Number} - {account.Name}");

Usage Examples

List and filter

// List all active articles matching a search query
var result = await cashCtrl.Inventory.Article.GetList(
    new ListParams { Query = "widget", OnlyActive = true });

foreach (var article in result.ResponseData!.Data)
    Console.WriteLine(article.Name);

Create

// Create a new person
var result = await cashCtrl.Person.Person.Create(
    new PersonCreate { FirstName = "Jane", LastName = "Doe" });

var newPersonId = result.ResponseData!.InsertId;

Update

// Update an existing person
await cashCtrl.Person.Person.Update(
    new PersonUpdate { Id = 42, FirstName = "Jane", LastName = "Smith" });

Auto-pagination

// Iterate all journal entries across all pages
await foreach (var entry in PaginationHelper.ListAllAsync(
    cashCtrl.Journal.Journal.GetList,
    new ListParams { Sort = "dateAdded", Dir = "DESC" },
    pageSize: 50))
{
    Console.WriteLine($"{entry.DateAdded} - {entry.Amount}");
}

Result and Error Handling

The library uses result-based error handling — no exceptions are thrown for HTTP errors, validation failures, or rate limiting. All API calls return ApiResult<T>, which wraps every possible outcome.

ApiResult properties

Property Type Description
IsHttpSuccess bool Whether the HTTP request returned a 2xx status code
HttpStatusCode HttpStatusCode The HTTP status code from the API
CashCtrlHttpStatusCodeDescription string? Human-readable CashCtrl description of the status code
RequestsLeft int? Number of API requests remaining (rate limit)
RawResponseContent string? Raw response body when JSON deserialization fails (e.g. rate limit messages)
ResponseData T? Deserialized API response data

Checking for success

Write operations (create, update, delete) return ApiResult<NoContentResponse>. Always check both the HTTP status and the business logic result:

var result = await cashCtrl.Person.Person.Create(
    new PersonCreate { FirstName = "Jane", LastName = "Doe" });

if (!result.IsHttpSuccess)
{
    Console.WriteLine($"HTTP error {result.HttpStatusCode}: {result.CashCtrlHttpStatusCodeDescription}");
    return;
}

if (!result.ResponseData!.Success)
{
    foreach (var error in result.ResponseData.Errors ?? [])
        Console.WriteLine($"Validation error on '{error.Field}': {error.Message}");
    return;
}

Console.WriteLine($"Created person with ID {result.ResponseData.InsertId}");

Read operations return ApiResult<SingleResponse<T>> or ApiResult<ListResponse<T>>:

var result = await cashCtrl.Account.Account.GetList();

if (!result.IsHttpSuccess)
{
    Console.WriteLine($"HTTP error {result.HttpStatusCode}: {result.CashCtrlHttpStatusCodeDescription}");
    return;
}

foreach (var account in result.ResponseData!.Data)
    Console.WriteLine($"{account.Number} - {account.Name}");

Handling non-JSON responses

Some error responses (e.g. rate limiting) return plain text instead of JSON. When this happens, ResponseData is null and RawResponseContent contains the raw body:

var result = await cashCtrl.Inventory.Article.Get(new Entry { Id = 1 });

if (!result.IsHttpSuccess && result.ResponseData is null)
{
    // Non-JSON response (e.g. rate limit or HTML error page)
    Console.WriteLine($"HTTP {result.HttpStatusCode}: {result.RawResponseContent}");
    Console.WriteLine($"Requests left: {result.RequestsLeft}");
}

API Domain Groups

The client is organized into 10 domain groups, accessible via ICashCtrlApiClient:

Property Domain Examples
Account Chart of accounts Accounts, Categories, Cost Centers, Banks
Common Shared resources Currencies, Tax Rates, Custom Fields, Rounding
File File management Files, File Categories
Inventory Products & assets Articles, Fixed Assets, Units, Imports
Journal Bookkeeping Journal Entries, Imports
Meta Organization Settings, Fiscal Periods, Locations
Order Sales & purchases Orders, Book Entries, Documents, Payments
Person Contacts Persons, Categories, Titles, Imports
Report Financial reports Report Sets, Reports, Report Elements
Salary Payroll Statements, Templates, Certificates, Types

License

MIT

Acknowledgements

Special thanks to CashCtrl AG for the excellent cloud ERP platform.

Built and maintained by AMANDA Technology GmbH.

About

Unofficial API client implementation for the CashCtrl API v1

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages