DesktopAccountingAPI.QuickBooksDesktop 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package DesktopAccountingAPI.QuickBooksDesktop --version 0.1.0
                    
NuGet\Install-Package DesktopAccountingAPI.QuickBooksDesktop -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DesktopAccountingAPI.QuickBooksDesktop" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DesktopAccountingAPI.QuickBooksDesktop" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="DesktopAccountingAPI.QuickBooksDesktop" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DesktopAccountingAPI.QuickBooksDesktop --version 0.1.0
                    
#r "nuget: DesktopAccountingAPI.QuickBooksDesktop, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DesktopAccountingAPI.QuickBooksDesktop@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DesktopAccountingAPI.QuickBooksDesktop&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=DesktopAccountingAPI.QuickBooksDesktop&version=0.1.0
                    
Install as a Cake Tool

Desktop Accounting API .NET SDK

The official .NET client for Desktop Accounting API, a REST API for QuickBooks Desktop. It covers all 275 API operations with typed models and async methods, and handles pagination, retries with idempotency keys, timed-out requests, async requests and webhook verification.

  • Package: DesktopAccountingAPI.QuickBooksDesktop 0.1.0
  • Namespace: DesktopAccountingApi.QuickBooksDesktop (models in .Models)
  • Targets: netstandard2.0 (.NET Framework 4.6.2+, .NET Core 2.0+) and net8.0
  • Generated from API contract 1.0.0 (sha256 1cc3058cecb5); api.md lists every method

Requirements

.NET 8 or later, or any runtime that supports .NET Standard 2.0 (including .NET Framework 4.6.2 and later). Building this repository needs the .NET 8 SDK and Node.js (for the conformance mock server); mise install provides both at the pinned versions.

Install

dotnet add package DesktopAccountingAPI.QuickBooksDesktop

Install from source

git clone https://github.com/DesktopAccountingAPI/quickbooks-desktop-dotnet.git
dotnet add reference quickbooks-desktop-dotnet/src/DesktopAccountingApi.QuickBooksDesktop/DesktopAccountingApi.QuickBooksDesktop.csproj

Quickstart

using DesktopAccountingApi.QuickBooksDesktop;
using DesktopAccountingApi.QuickBooksDesktop.Models;

// Reads DAAPI_SECRET_KEY from the environment.
using var client = new DesktopAccountingApiClient(new ClientOptions { EndUserId = "eu_..." });

var health = await client.Qbd.HealthCheckAsync();
Console.WriteLine($"Connected to {health.Quickbooks.CompanyName}");

var invoice = await client.Qbd.Invoices.CreateAsync(new InvoiceCreateInput
{
    CustomerId = "80000001-1700000000",
    TransactionDate = new DateOnly(2026, 10, 5),
    Lines = new[]
    {
        new InvoiceLineCreateInput { ItemId = "80000005-1700000000", Quantity = 2, Rate = 52.75m },
    },
});
Console.WriteLine($"{invoice.RefNumber}: {invoice.Subtotal}");

examples/Quickstart is a runnable version. More examples (idempotent creates, network failures, pagination with cursor expiry, async requests with a webhook receiver, error handling) are in the examples repository.

Configuration

ClientOptions property Default Meaning
ApiKey DAAPI_SECRET_KEY Secret key, sk_live_... or sk_test_.... Checked locally (format and checksum) when the client is created; a malformed key throws DaapiException before any request.
BaseUrl DAAPI_BASE_URL, else https://api.desktopaccountingapi.com API base URL. May include a path prefix. Staging: https://api-staging.desktopaccountingapi.com.
EndUserId none Default end user (eu_...) for QuickBooks Desktop operations.
Timeout 100 s Client-side timeout per HTTP attempt, and the total wait for a request that timed out on the server.
MaxRetries 2 Retries after network errors, 429 and retryable 5xx. 0 disables retries.
ServerTimeout server default Sent as Daapi-Timeout-Seconds on operations that accept it.
HttpClient SDK-owned Your own HttpClient. The SDK does not dispose it or change its settings.
HttpMessageHandler SDK-owned A handler for the SDK's HttpClient (proxy, instrumentation, test double).
Logger none Action<DaapiLogLevel, string>: one line per HTTP attempt, retry and long poll. The SDK never logs the API key, the Authorization header or request and response bodies.

Every method takes an optional RequestOptions (EndUserId, IdempotencyKey, Timeout, MaxRetries, ServerTimeout, QueueTtl) and a CancellationToken. The client is thread-safe; create one and reuse it. Dispose() releases the HttpClient the SDK created.

End users

QuickBooks Desktop operations (client.Qbd...) act on one end user's company file and need an end user ID. Platform operations (client.EndUsers, client.AuthSessions, client.Requests) do not, and the SDK never sends Daapi-End-User-Id on them. A QuickBooks operation without an end user throws DaapiException before sending anything.

var acme = client.ForEndUser("eu_01j9x4m6v4c8k2t7q0r5s3w1zb"); // shares the connection pool
var customers = await acme.Qbd.Customers.ListAsync().GetFirstPageAsync();

// or per call
await client.Qbd.HealthCheckAsync(new RequestOptions { EndUserId = "eu_..." });

Inputs: omitted, set and null

Request bodies and query parameters are classes such as InvoiceUpdateInput and InvoiceListParams. Only the properties you set are sent. Properties documented as "Set to null to clear" send an explicit null, which clears the value in QuickBooks; setting any other property to null removes it from the request. IsSet(name) and Unset(name) inspect and undo assignments. Required properties that are missing throw DaapiException before the request is sent.

await client.Qbd.Invoices.UpdateAsync("7-1700000000", new InvoiceUpdateInput
{
    RevisionNumber = "1700000007",
    Memo = null,               // sends "memo": null and clears the memo
});                            // other fields are not sent and stay unchanged

Types follow the contract: money and other decimals are decimal and travel as strings with their scale preserved (5.00m is sent as "5.00"); dates are DateOnly (YYYY-MM-DD; on .NET Standard 2.0 through the Portable.System.DateTimeOnly package); timestamps are DateTimeOffset with the offset QuickBooks reported. Enums whose values can grow are string properties with a constants class listing the known values (RequestStatus.Succeeded); unknown values pass through. Enums the API fixes for inputs are C# enums (PaymentStatus.NotPaid). Response models ignore missing fields and keep unknown fields in AdditionalProperties. model.ToJson() and DesktopAccountingApiJson.Options serialize any model back to the wire format.

Pagination

Cursor-paginated lists return a Pager<T>:

// Every invoice, page after page.
await foreach (var invoice in client.Qbd.Invoices.ListAsync(new InvoiceListParams { Limit = 100 }))
{
    Console.WriteLine(invoice.RefNumber);
}

var page = await client.Qbd.Invoices.ListAsync(new InvoiceListParams { Limit = 10 }).GetFirstPageAsync();
// page.Data, page.NextCursor, page.HasMore, page.RemainingCount, page.CursorExpiresAt

await foreach (var p in client.Qbd.Customers.ListAsync().PagesAsync()) { /* one page at a time */ }
List<Customer> all = await client.Qbd.Customers.ListAsync().ListAllAsync();

The pager requests page N+1 as soon as page N arrives, so the next request stays inside the cursor's idle window while you process the current page. Continue requests send only cursor (and limit if you set one). A network error on a continue request retries the same cursor, which returns the same page.

QuickBooks cursors live inside one QuickBooks session and expire when it ends or after an idle period. Then iteration throws CursorExpiredException with ItemsYielded, PagesServed, LastId, LastUpdatedAt (as the API sent it) and Reason. The SDK never restarts a list on its own, because records may have changed in the meantime. Restart with a watermark and skip what you already have:

catch (CursorExpiredException ex)
{
    var resume = client.Qbd.Customers.ListAsync(new CustomerListParams { UpdatedAfter = ex.LastUpdatedAt, Limit = 100 });
    // skip IDs you already processed
}

Lists without pagination (x-daapi-pagination: none, such as accounts and classes) return their list envelope directly.

Errors

Every exception derives from DaapiException:

Class When
DaapiException Client-side problems: missing or malformed API key, missing end user, missing required input, unparseable response.
ApiException Any error response from the API (base class; an unknown error type stays this class).
InvalidRequestException (CursorExpiredException) INVALID_REQUEST_ERROR
AuthenticationException AUTHENTICATION_ERROR
PermissionException PERMISSION_ERROR
BillingException BILLING_ERROR
RateLimitException RATE_LIMIT_ERROR
IntegrationConnectionException INTEGRATION_CONNECTION_ERROR (Web Connector not running, company file closed, modal dialog open)
IntegrationException INTEGRATION_ERROR (QuickBooks rejected the request)
OutcomeUnknownException OUTCOME_UNKNOWN_ERROR (a write was sent and its result is unknown)
InternalException INTERNAL_ERROR
ApiConnectionException (ApiTimeoutException) No response after all retries.
RequestPendingException A request is still running when the SDK stops waiting (see Timeouts).
WebhookVerificationException A webhook failed verification.

ApiException exposes every field of the API error: Status (HTTP status), Type, Code, Message, UserFacingMessage, HttpStatusCode, IntegrationCode, RequestId (from the error, else the Daapi-Request-Id header), Cause (why the error happens; this is the API's cause field, not the .NET exception chain, which stays in InnerException), Fixes (Actor, Action), DocsUrl, Retryable, Outcome, Param, Details and the response Headers. ErrorCodes and ErrorTypes hold a constant for every code and type in the contract.

try
{
    await client.Qbd.Customers.RetrieveAsync("80000099-1700000000");
}
catch (IntegrationException ex) when (ex.Code == ErrorCodes.QbdObjectNotFound)
{
    Console.WriteLine(ex.UserFacingMessage);
    foreach (var fix in ex.Fixes) Console.WriteLine($"{fix.Actor}: {fix.Action}");
    Console.WriteLine($"{ex.DocsUrl} (request {ex.RequestId})");
}

Retries and idempotency

The SDK retries network errors that happen before a response arrives (connection failures, resets, dropped connections, client timeouts), 429 responses, and 5xx responses that carry Daapi-Should-Retry: true. It never retries when the response says Daapi-Should-Retry: false, when the error outcome is unknown or pending, or when an error response is not JSON. Backoff starts at 0.5 s and doubles up to 8 s with jitter; a Retry-After header (seconds, including 0, or an HTTP date) takes precedence.

Every write (create, update, delete, void, passthrough) sends an Idempotency-Key. The SDK generates a UUID per call and reuses it on every retry of that call, so a retried create cannot create a second record. Pass your own key to make a create safe across process restarts:

await client.Qbd.Invoices.CreateAsync(input, new RequestOptions { IdempotencyKey = $"order-{orderId}-invoice" });

Timeouts

There are two timeouts:

  • Client timeout (ClientOptions.Timeout / RequestOptions.Timeout, default 100 s): how long the SDK waits for each HTTP attempt.
  • Server timeout (ServerTimeout, sent as Daapi-Timeout-Seconds): how long the API waits for QuickBooks before answering 504 QBD_REQUEST_TIMEOUT.

After 504 QBD_REQUEST_TIMEOUT the request is still queued or running at the end user's QuickBooks. The SDK does not resend it. It long-polls GET /v1/requests/{id}?waitSeconds=N until the call's client timeout has passed since the call started. When the request succeeds you get the normal typed result; when it fails you get the typed exception; when time runs out you get RequestPendingException with RequestId, which you can check later with client.Requests.RetrieveAsync(id) or a webhook.

Async requests

Operations that QuickBooks runs can also be queued without waiting. Each resource with such operations has an Enqueue accessor with the same methods, returning a RequestHandle<T>:

var handle = await client.Qbd.Invoices.Enqueue.CreateAsync(input, new RequestOptions { QueueTtl = TimeSpan.FromHours(1) });
Console.WriteLine($"{handle.Id} is {handle.Request.Status}");

Invoice invoice = await handle.WaitAsync(TimeSpan.FromMinutes(5)); // long-polls; typed result or typed exception
Request current = await handle.StatusAsync();                       // one GET, no waiting
Invoice result = await handle.ResultAsync();                        // throws RequestPendingException if not finished

The request is sent with Prefer: respond-async (and Daapi-Queue-Ttl-Seconds when QueueTtl is set). A request.succeeded or request.failed webhook tells you when it finished.

Webhooks

Webhooks follow Standard Webhooks. Verify the raw body with the endpoint's signing secret; no API key is needed:

// ASP.NET Core minimal API
app.MapPost("/webhooks", async (HttpRequest request) =>
{
    using var reader = new StreamReader(request.Body);
    var body = await reader.ReadToEndAsync();
    try
    {
        var ev = WebhookVerifier.Verify(body, name => request.Headers[name].ToString(), secret);
        if (ev.Type == WebhookEventTypes.RequestSucceeded) { /* ev.Data, ev.DataAs<Request>() */ }
        return Results.Ok();
    }
    catch (WebhookVerificationException)
    {
        return Results.BadRequest();
    }
});

client.Webhooks.Verify(...) does the same. Header names are matched case-insensitively, the secret may include the whsec_ prefix or not, several v1, signatures are accepted during secret rotation, signatures are compared in constant time, and timestamps more than 5 minutes from the clock are rejected (WebhookVerifyOptions.Tolerance, WebhookVerifyOptions.Clock). WebhookVerifier.VerifySignature checks only the signature; WebhookVerifier.Sign creates one for tests. Delivery is at least once: deduplicate on ev.Id.

Raw responses

Every non-paginated method has a ...WithResponseAsync variant that returns ApiResponse<T> with Data, StatusCode, Headers, RequestId (Daapi-Request-Id) and Warnings (Daapi-Warnings):

var response = await client.Qbd.Customers.RetrieveWithResponseAsync("80000001-1700000000");
Console.WriteLine($"{response.StatusCode} {response.RequestId}: {response.Data.Name}");

Pages expose the request ID of their response as Page<T>.RequestId.

Passthrough

client.EndUsers.PassthroughAsync sends qbXML request elements directly to an end user's QuickBooks Desktop, as JSON or as XML:

var json = await client.EndUsers.PassthroughAsync("eu_...", new PassthroughInput
{
    ["CustomerQueryRq"] = new Dictionary<string, object?> { ["MaxReturned"] = 5 },
});

string xml = await client.EndUsers.PassthroughXmlAsync("eu_...", "<CustomerQueryRq><MaxReturned>5</MaxReturned></CustomerQueryRq>");

Passthrough calls always send an idempotency key, because a body with anything other than a query element is a write.

Versioning

The SDK follows semantic versioning. .daapi-sdk.json records the API contract digest, the generator version and the list of generated files for this release; DesktopAccountingApiClient.ContractSha256 exposes the same digest at runtime. New API fields and enum values do not break older SDK versions: unknown fields are kept and unknown enum values pass through.

Development

mise install
mise run check

mise run check restores, builds both target frameworks with warnings as errors and analyzers on, verifies formatting, runs the unit tests and the cross-language conformance suite (it starts node conformance/mock-server.mjs), builds examples/, packs the NuGet package and inspects its contents. Code under src/DesktopAccountingApi.QuickBooksDesktop/Generated, api.md and conformance/fixtures is generated from the API contract; see CONTRIBUTING.md.

Releases are published by .github/workflows/publish.yml when a v* tag is pushed. node scripts/publish.mjs release --dry-run builds, packs, inspects and smoke-tests the package locally without uploading anything.


QuickBooks is a registered trademark of Intuit Inc. Desktop Accounting API is an independent product and is not affiliated with, endorsed by, or approved by Intuit Inc.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.0 0 10/13/2026
0.7.0 49 10/12/2026
0.6.0 53 10/12/2026
0.5.4 59 10/9/2026
0.5.3 55 10/9/2026
0.5.2 55 10/9/2026
0.5.1 54 10/9/2026
0.5.0 57 10/9/2026
0.4.0 64 10/8/2026
0.3.0 51 10/8/2026
0.2.1 59 10/7/2026
0.2.0 58 10/7/2026
0.1.1 54 10/6/2026
0.1.0 57 10/6/2026