.NET C# library for the Amazon Business API. Wraps all 9 REST APIs (Document, Cart, Ordering, Product Search, Reconciliation, Reporting v2025-06-09 + v2021-01-08, User Management, Package Tracking, Application Management) behind a single AmazonBusinessConnection. LWA token refresh, rate-limit retry, regional endpoint routing, sandbox toggle, and debug logging are handled for you.
The generated clients are produced by NSwag from Amazon's published OpenAPI specs (fetched verbatim from developer-docs.amazon.com); the wrapper layer hides the per-spec type duplication so callers see Country.US instead of NSwag-generated Region2/Region3/etc.
Contents — Quick start · Configuration · Service surface · Onboarding (OAuth consent) · Roles · Troubleshooting · Status
Latest version is published on nuget.org. Install with the .NET CLI:
dotnet add package CSharpAmazonBusinessAPI…or via the legacy Package Manager Console:
Install-Package CSharpAmazonBusinessAPI…or in your .csproj directly:
<PackageReference Include="CSharpAmazonBusinessAPI" Version="*" />Targets netstandard2.0 — runs on .NET Framework 4.6.1+, .NET Core 2.0+, and every modern .NET (.NET 5/6/7/8/9/10).
using CSharpAmazonBusinessAPI;
using CSharpAmazonBusinessAPI.Utils;
var connection = new AmazonBusinessConnection(new AmazonBusinessCredential
{
ClientId = "amzn1.application-oa2-client.XXXX",
ClientSecret = "XXXX",
RefreshToken = "Atzr|XXXX",
MarketPlace = MarketPlace.UnitedStates,
// Environment = AmazonBusinessCredential.Environments.Sandbox, // for testing
});
// First call — token refresh, regional host, auth header are all handled.
var reports = await connection.Documents.GetReportsAsync(
createdSince: DateTime.UtcNow.AddDays(-7));
Console.WriteLine($"Got {reports.Reports?.Count ?? 0} report(s)");See Configuration for proxy / sandbox / debug-logging options, Onboard a customer for the OAuth consent flow used during one-time customer onboarding, Troubleshooting when something doesn't behave as expected, and Source/CSharpAmazonBusinessAPI.SampleCode/ for a per-API sample for every wrapper.
The roadmap below covers the full Amazon Business developer surface (docs index) and mirrors the structure used in the sister Amazon-SP-API-CSharp project. Foundation work is required before the per-API service wrappers can be built.
-
MarketPlace/Region/Countrylookup tables — 11 supported markets (register-as-a-developer) across 3 regional hosts (ab-api-endpoints):na.business-api.amazon.com,eu.business-api.amazon.com,jp.business-api.amazon.com. -
AmazonBusinessCredential— LWA-only (ClientId,ClientSecret,RefreshToken,MarketPlace/MarketPlaceID, optionalProxy,IsDebugMode,Environment). No AWS keys. - LWA token pipeline —
LwaClientPOSTs tohttps://api.amazon.com/auth/o2/token; per-credentialAccessTokenCache(thread-safe viaSemaphoreSlim, refreshes 60s before expiry);LwaAuthHandlerDelegatingHandlerinjectsx-amz-access-tokenand retries once on 401. - LWA client-secret rotation —
AmazonBusinessCredential.RotateClientSecret(newSecret)swaps the secret in place and invalidates the cache so the next call re-exchanges. -
AmazonBusinessConnectionfacade — validates credentials, resolvesMarketPlaceID→MarketPlace, builds a configuredHttpClient, exposes one property per API surface (Documents,Cart,Ordering,ProductSearch,Reconciliation,Reporting,ReportingLegacy,PackageTracking,Users,Applications). - HTTP layer for NSwag-generated clients — chained handlers
RateLimit → Auth → Debug → HttpClientHandlerwrap a sharedHttpClientwhoseBaseAddresscomes fromMarketPlace.Region. Generated clients consume theHttpClientin their constructor. - Exception types —
AmazonBusinessExceptionbase +Unauthorized,InvalidInput,NotFound,QuotaExceeded,InternalErrorsubclasses. Each carriesStatusCode+ResponseBody. - Rate-limit handling —
RateLimitHandlerDelegatingHandlerhonorsRetry-After(delta or HTTP-date), exponential-backoff fallback, capped byMaxThrottledRetryCount; throwsAmazonBusinessQuotaExceededExceptionif exhausted. - Sandbox toggle —
AmazonBusinessCredential.Environment(Sandbox/Production) switchesBaseAddressbetweenRegion.HostUrlandRegion.SandboxHostUrl. - Debug logging —
DebugLogHandlerpretty-prints request/response (with masked sensitive headers) whenIsDebugMode == true. Routes throughILoggerifAmazonBusinessConnectionwas constructed with anILoggerFactory, else falls back toConsole.
Each API has: (1) OpenAPI spec under Source/CSharpAmazonBusinessAPI/OpenAPIs/ (fetched by scripts/fetch_spec.py), (2) an <OpenApiReference> entry in the csproj, (3) a hand-written *Service wrapper exposed off AmazonBusinessConnection, (4) a sample under Source/CSharpAmazonBusinessAPI.SampleCode/, (5) sandbox-mode tests under Source/Tests/.
- Application Management API v1 — wired as
connection.Applications. Single op (RotateApplicationClientSecretAsync) triggers Amazon to deliver a new LWA client secret to the developer's registered SQS queue. - Cart API v1 — wired as
connection.Cart. All 7 operations exposed (List/Get/AddItems/ModifyItems/DeleteItems/GetItems/EstimateCost). Overview · model · sandbox. - Document API v1 — wired as
connection.Documents. Invoice-report retrieval viaGetReports/CreateReport/GetReport/CancelReport/GetReportDocument. Region-specific guides: NA invoices, EU/JP invoices. Sandbox. - Ordering API v1 — wired as
connection.Ordering(PlaceOrderAsync,OrderDetailsAsync). Overview. Workflow guides: - Package Tracking API v1 — wired as
connection.PackageTracking(GetPackageTrackingDetailsAsync). Push notifications are out-of-band (SNS); this API covers pull-based detail retrieval. Overview · push notifications · sandbox. - Product Search API v1 — wired as
connection.ProductSearch. 5 ops with very wide parameter lists (search/get-product/get-offers/by-asin/by-offer-ids); use.Clientdirectly for full IntelliSense. Overview. Workflow guides: - Reconciliation API v1 — wired as
connection.Reconciliation(GetTransactions,GetBatchInvoicePaymentDetails,GetInvoiceDetailsByOrderLineItems). Overview. Workflow guides: - Reporting API — both versions wired side-by-side; new is default, legacy still callable.
- Reporting API v2025-06-09 (current) — wired as
connection.Reporting(5 ops: order reports, order line items, order reports by PO, shipment reports, shipment line items). Use.Clientdirectly. Model. - Reporting API v2021-01-08 (legacy) — wired as
connection.ReportingLegacy(GetOrdersByOrderDateAsync,GetOrdersByOrderIdAsync). Model.
- Reporting API v2025-06-09 (current) — wired as
- User Management API v1 — wired as
connection.Users(CreateBusinessUserAccountAsync). Overview · model.
- REST cross-API sample — see
CartToOrderSample.csfor Product Search → Cart → Ordering. Note: Amazon's Integrated Quoting workflow is a separate cXML/cert-auth integration, not a REST workflow — it's out of scope for this SDK. - Third-party website authorization —
LwaConsentHelper.BuildAuthorizationUrl()+ExchangeCodeForTokensAsync(). Use during one-time onboarding to collect a customer's refresh token, then persist it forAmazonBusinessCredential. Runnable demo: seeSource/CSharpAmazonBusinessAPI.WebAuthSample— ASP.NET Core minimal-API project with the full consent flow. - App Center authorization workflow — same
LwaConsentHelper.ExchangeCodeForTokensAsync()covers the LWA token-exchange step. The Amazon-initiated callback dance (amazon_callback_uri,amazon_stateechoing, step-3 ack POST, step-6 return-to-App-Center redirect) is wired in the demo at/appcenter/login-uriand/appcenter/oauth/callback— seeSource/CSharpAmazonBusinessAPI.WebAuthSamplefor the working reference. -
Punch-in— out of scope (server-side cXML/SOAP-style endpoint your e-procurement system hosts, with TLS certs / shared-secret auth — same situation as Integrated Quoting). See Punch-in integration guide if you need to integrate the e-procurement side. - Amazon Business Integrations MCP Server — Amazon-hosted MCP server for AI assistants. Out of scope for the SDK; mentioned here for discoverability.
-
SampleCode/Program.csloads credentials fromappsettings.json+ User Secrets and prints the resolved region/host/marketplace. Live calls per API are pre-wired and commented — uncomment after dropping in real credentials. -
Tests/CSharpAmazonBusinessAPI.Tests— xUnit, 41 unit tests + 8 sandbox integration tests (read-only).- Unit:
MarketPlacelookup + regional routing,AmazonBusinessConnectionvalidation/marketplace resolution,AccessTokenCache(caching, invalidation, concurrent refresh, LWA failure),LwaAuthHandler(header injection, cache reuse, 401 retry-once with fresh token),RateLimitHandler(429 retry,Retry-Afterdelta + HTTP-date, exhaustion →AmazonBusinessQuotaExceededException),RotateClientSecretflow,ApiExceptionshape,LwaConsentHelperURL-builder + arg validation. - Integration:
SandboxIntegrationTests.cssmoke-tests Documents, Reconciliation, ReportingLegacy, Reporting (GetOrderReports / GetShipmentReports), ProductSearch, Cart, PackageTracking against the real sandbox. Skipped automatically whenAB_INTEGRATION_*env vars aren't set. Destructive ops (Ordering.PlaceOrder, Users.CreateBusinessUserAccount, Applications.RotateApplicationClientSecret) are intentionally not tested here — add a separate suite when you opt in. - Run with
dotnet test(ordotnet test --filter Category=Integrationfor just the sandbox tests).
- Unit:
- Amazon Business roles reference — see the Roles required per API table in the Usage section. Roles are requested via the Developer Registration Access Form (DRAF) at app creation; the API surface won't authorize without the matching role.
Amazon Business uses Login with Amazon (LWA) only — no AWS IAM, no STS, no role ARN. Onboarding flow:
- Register as a developer and request the roles your app needs (Developer Registration Access Form).
- Create an app client in the Solution Provider Portal — you'll receive a
ClientId+ClientSecret. - Authorize your app (or use the website-authorization workflow) to obtain a long-lived
RefreshTokenper Amazon Business customer.
| Field | Description |
|---|---|
MarketPlace / MarketPlaceID |
Target marketplace (list). Determines regional endpoint (NA/EU/FE). |
ClientId |
Your app's amzn1.application-oa2-client.… ID. |
ClientSecret |
Your app's secret (rotatable — see LWA client-secret rotation). |
RefreshToken |
Long-lived token issued per customer after consent. Exchanged for a 1-hour access token automatically by the SDK. |
See Program.cs for a runnable example that loads credentials from appsettings.json and User Secrets.
var connection = new AmazonBusinessConnection(new AmazonBusinessCredential
{
ClientId = "amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RefreshToken = "Atzr|XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
MarketPlace = MarketPlace.UnitedStates, // or: MarketPlaceID = "ATVPDKIKX0DER"
Environment = AmazonBusinessCredential.Environments.Sandbox,
IsDebugMode = true,
});AmazonBusinessConnection exposes one property per API surface:
| Property | API |
|---|---|
Documents |
Document API v1 (invoice reports) |
Cart |
Cart API v1 |
Applications |
Application Management API v1 |
Ordering |
Ordering API v1 |
PackageTracking |
Package Tracking API v1 |
ProductSearch |
Product Search API v1 |
Reconciliation |
Reconciliation API v1 |
Reporting |
Reporting API v2025-06-09 (current) |
ReportingLegacy |
Reporting API v2021-01-08 (legacy) |
Users |
User Management API v1 |
Each service exposes typed methods plus a .Client property for direct access to the NSwag-generated client when you need parameters not on the wrapper.
var connection = new AmazonBusinessConnection(new AmazonBusinessCredential
{
ClientId = "...", ClientSecret = "...", RefreshToken = "...",
MarketPlaceID = "ATVPDKIKX0DER",
Proxy = new System.Net.WebProxy("http://xxx.xxx.xxx.xxx:xxxx")
{
Credentials = new System.Net.NetworkCredential("username", "password"),
},
});For SaaS apps and App Center listings: send the customer to Amazon's consent page, then exchange the returned code for a long-lived refresh_token and persist it. A runnable end-to-end ASP.NET Core demo lives in Source/CSharpAmazonBusinessAPI.WebAuthSample — dotnet run from that folder, browse to https://localhost:7271, click Connect.
// 1. Generate a CSRF token, then redirect the customer's browser here:
var url = LwaConsentHelper.BuildAuthorizationUrl(
clientId: "amzn1.application-oa2-client.XXXX",
redirectUri: "https://my.app/oauth/callback",
state: Guid.NewGuid().ToString());
// 2. On callback, verify state matches, then exchange the code:
var tokens = await LwaConsentHelper.ExchangeCodeForTokensAsync(
code: queryParams["code"],
clientId: "amzn1.application-oa2-client.XXXX",
clientSecret: "...",
redirectUri: "https://my.app/oauth/callback");
// 3. Persist tokens.RefreshToken for this customer; pass it to AmazonBusinessCredential
// going forward. (The access_token expires in 1h — the SDK handles renewal automatically.)// 1. Trigger Amazon to send a new secret to your registered SQS queue.
await connection.Applications.RotateApplicationClientSecretAsync();
// 2. After picking up the new secret, swap it in place — the cached access
// token is invalidated automatically, so the next API call re-exchanges.
connection.Credentials.RotateClientSecret(newSecret);For more, see DocumentSample.cs.
// List recent invoice reports (marketplaceIds defaults to credential).
var reports = await connection.Documents.GetReportsAsync(
createdSince: DateTime.UtcNow.AddDays(-30));
// Create an invoice report, then poll for completion + download.
var reportId = (await connection.Documents.CreateReportAsync(new CreateReportSpecification
{
ReportType = "GET_FLAT_FILE_VAT_INVOICE_DATA_REPORT",
DataStartTime = DateTime.UtcNow.AddDays(-30),
DataEndTime = DateTime.UtcNow,
})).ReportId;
var report = await connection.Documents.GetReportAsync(reportId);
if (report.ProcessingStatus == ProcessingStatus.DONE)
{
var doc = await connection.Documents.GetReportDocumentAsync(report.ReportDocumentId);
// doc.Url is a 5-minute presigned URL; doc.CompressionAlgorithm == GZIP if compressed.
}For more, see CartSample.cs. The country parameter defaults to the connection's marketplace — pass Country.X explicitly only to override per call.
// Country defaults from connection.Credentials.MarketPlace.Country.
var carts = await connection.Cart.ListCartsAsync(
customerEmail: "buyer@example.com",
pageSize: 25);
await connection.Cart.AddItemsAsync("cart-123", new AddItemsRequest
{
Items = new List<AddItemRequest>
{
new AddItemRequest { ProductIdentifier = "B07HMBFZCZ", Quantity = 2 },
},
});For more, see ReportingSample.cs. The 5 ops have wrapper methods (country defaults from connection); .Client is also there for the raw generated surface.
var orderReports = await connection.Reporting.GetOrderReportsAsync(
orderStartDate: DateTimeOffset.UtcNow.AddDays(-7),
orderEndDate: DateTimeOffset.UtcNow);For more, see ReconciliationSample.cs.
var transactions = await connection.Reconciliation.GetTransactionsAsync(
feedStartDate: DateTimeOffset.UtcNow.AddDays(-30),
feedEndDate: DateTimeOffset.UtcNow);For more, see PackageTrackingSample.cs.
// Country defaults from the connection's marketplace.
var details = await connection.PackageTracking.GetPackageTrackingDetailsAsync(
orderId: "114-2589187-9801025",
shipmentId: "401971789238301",
packageId: "1");For more, see ProductSearchSample.cs. The two most common ops have wrappers; the other three (SearchOffersRequest, GetProductsByAsins, GetOffersByOfferIds) are reachable via .Client.
var results = await connection.ProductSearch.SearchProductsAsync(
keywords: "office chair",
customerEmail: "buyer@example.com",
pageSize: 24,
sortKey: SortKey.RELEVANCE);
var product = await connection.ProductSearch.GetProductByAsinAsync(
asin: "B07HMBFZCZ",
customerEmail: "buyer@example.com");- Ordering —
OrderingSample.cs - Reporting Legacy v2021-01-08 —
ReportingLegacySample.cs - User Management —
UserManagementSample.cs - Application Management —
ApplicationManagementSample.cs
Set IsDebugMode = true on the credential. Pretty-prints request and response (with sensitive headers masked) for every outbound call. Pass an ILoggerFactory to AmazonBusinessConnection to route through ILogger instead of Console.
Each API call needs a specific Amazon Business role granted at app-creation time via the Developer Registration Access Form. Calling without the matching role returns 403. (Source.)
| Role | API surfaces (this SDK) |
|---|---|
| Business Product Catalog | connection.ProductSearch |
| Amazon Business Analytics | connection.Reporting, connection.ReportingLegacy |
| Business Purchase Reconciliation | connection.Reconciliation, connection.Documents |
| Amazon Business Order Placement | connection.Ordering, connection.Cart, connection.PackageTracking |
| User Management (offline approval required) | connection.Users |
| (no role required) | connection.Applications — uses standard LWA scope |
All roles are available across NA, EU, and FE marketplaces.
Amazon Business's static sandbox does exact parameter matching, not subset matching. Sending any extra query parameter — even a sensible-looking one like marketplaceIds — breaks the match. Each operation has a documented sandbox match block in its OpenAPI spec under responses.200.x-amzn-api-sandbox.static[].request.parameters; send only those parameters with their literal values to get a 200.
Common offenders this SDK fixes automatically:
- Repeated query keys (
?foo=a&foo=b) — NSwag's default; sandbox needs csv (?foo=a,b). Handled byCsvArrayRewriteHandler. - Bare ISO timestamps (
2020-07-09T00:00:00) — NSwag's"s"format omits the timezone designator; sandbox needs the trailingZ. Handled byIsoDateTimeRewriteHandler. - Auto-defaulted
marketplaceIdsinDocuments.GetReports— defaults from your credential's marketplace, but the sandbox doesn't expect it. PassmarketplaceIds: Array.Empty<string>()explicitly to skip the default for sandbox testing.
Amazon's sandbox returns generic 500s for several scenarios where you'd hope for a clearer error. Check, in order:
- Role not granted on your sandbox app. Each API requires a specific role (see Roles required per API). Confirm in Solution Provider Portal → your app → Roles. The fastest signal: if some APIs work and others 500, it's role-shaped — APIs sharing a role will fail together.
- Cart in particular is broken in sandbox. Even with the documented
?region=USandcart-123values, all Cart ops return 500 InternalFailure. This is an Amazon-side issue (not the SDK); production calls work fine. If you need this fixed, open a ticket via SPP support. - Sandbox app not fully provisioned. If every API 500s, the sandbox refresh token may not be properly bound. Email Amazon Business Support with your
applicationIdand a failedx-amzn-RequestId.
These have no x-amzn-api-sandbox block in their OpenAPI spec — the sandbox doesn't mock them and will always 400:
ProductSearch.SearchProductsand the rest of Product SearchReportingLegacy(Reporting v2021-01-08) — both operations
They work fine against production endpoints. For local validation, hit production with Environment.Production once you have approved production credentials.
Per Amazon's sandbox docs, three APIs are intentionally not in sandbox because they're destructive:
connection.Ordering— would place real ordersconnection.Applications.RotateApplicationClientSecretAsync— rotates your real production secret even from a sandbox-context callconnection.Users.CreateBusinessUserAccountAsync— creates real Amazon Business users
The SDK exposes them; you just need to opt into testing them deliberately against production.
You're sending the customer to https://www.amazon.com/ap/oa (the standard LWA endpoint) instead of https://www.amazon.<tld>/b2b/abws/oauth (the Amazon Business endpoint). Use LwaConsentHelper.BuildBusinessAuthorizationUrl(applicationId, redirectUri, state, country) — not BuildAuthorizationUrl. The Business endpoint takes the SPP applicationId (amzn1.sp.solution.*), not the LWA client_id, and accepts no scope parameter.
Your sandbox app doesn't go through the consent flow — the sandbox refresh token is generated directly in SPP (Action → Create token), not via OAuth. The website-authorization workflow is for production customer onboarding, where real Amazon Business admins authorize your app for their org. Sandbox apps lack the registration metadata to consent against real accounts.
If you see this, you're on a build before the ErrorTranslationHandler was wired in (pre-0.1.1). Upgrade to the current package — non-2xx responses now throw AmazonBusinessException subclasses (InvalidInput / Unauthorized / NotFound / QuotaExceeded / InternalError) carrying StatusCode + ResponseBody + a parsed Amazon-error message.
You committed real LWA credentials. Rotate them in SPP first (they're already exposed). Then either re-create the bad commit without the secret files (git rm --cached <file>, amend or rebase) or use git filter-repo to scrub the file from the entire history. Don't take the "allow this secret" escape hatch — secrets in git history are permanent.
Amazon's Integrated Quoting workflow is a separate cXML over HTTPS integration with digital-certificate auth, used by enterprise eProcurement sourcing modules — it does not use the REST APIs this SDK wraps. If you need that flow, integrate it via your cXML stack alongside this library.