Changelog
The latest APIMatic releases, with new features, improvements, and bug fixes across the APIMatic platform.
Changelog
Subscribe to RSSThe latest APIMatic releases, with new features, improvements, and bug fixes across the APIMatic platform.
October 2026
Server-Sent Event Streaming in TypeScript v4 SDKs
TypeScript v4 SDKs now stream server-sent events. An operation whose success response is text/event-stream resolves to an EventStream<T> typed over the event's data schema, and you read it with for await.
Details
The call resolves as soon as the response headers arrive, and events are read from the connection only as your loop asks for them. Each iteration yields one event's data, already decoded: a string, a model, an array, or a discriminated union you narrow on its kind. The stream ends cleanly on the sentinel named by the x-sse-sentinel extension, which is consumed and never yielded, or when the server closes the connection.
Draining the loop releases the connection, and so does leaving it early with break, return or a throw. A stream you decide not to read is released with await stream.close(), or with await using where your runtime supports it, and aborting the signal you passed stops it from elsewhere.
The loop raises the same error family as the call itself: a DecodeError for an event whose data does not fit the schema, a ConnectionError when the connection drops, and a TimeoutError when the server stays silent for longer than the eventStream.idleTimeout client option, which defaults to 60 seconds. An error status rejects the call with ApiError before any event is read, and .asApiResult() gives you the opening status and headers of the stream.
Usage Example
Iterate the stream, and stop as soon as you have what you need:
import { PaymentsClient, PaymentsError } from "payments";
const client = new PaymentsClient({ eventStream: { idleTimeout: 30_000 } });
try {
const updates = await client.payments.streamUpdates({ paymentId: "pay_123" });
// Each event's data arrives as a typed model; breaking out releases the connection.
for await (const update of updates) {
console.log(update.status);
if (update.settledAt) break;
}
} catch (err) {
if (err instanceof PaymentsError) {
console.error(`${err.kind}: ${err.message}`);
} else {
throw err;
}
}Put the try around the loop, not only the call, because the call settles as soon as the headers arrive.
This feature is available in the TypeScript v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=typescriptFor the full set of flags and options, see the APIMatic CLI documentation.
Adding Support for Pagination in Python v4 SDKs
Python v4 SDKs now support pagination, covering the four widely used strategies: Offset, Page, Cursor and Link based pagination. A paginated operation returns a Paginator[Page, Item] on the synchronous client and an AsyncPaginator on the asynchronous one.
Details
The SDK detects paginated operations from the pagination OpenAPI extension and generates the walk for each strategy the operation declares, selecting request and response fields with the JSON Pointers in your extension configuration. Iterate the paginator to receive items across every page, or call iter_pages() to work with one page at a time.
The paginator is lazy. Building one sends nothing, the first request goes out when iteration starts, a break ends the walk with no further request, and each new iteration starts again from the first page. The arguments you pass set the first request only, and the SDK moves through the pages the way the API defines them. Every page is an ordinary request, so authentication, retries and request_options apply to each one.
A page that fails raises ApiError from the loop, after every item before it, so wrap the loop rather than the call. A link walk refuses a next link to a different origin and raises ValueError.
Usage Example
Iterate the items across every page, or go page by page when you need the page itself:
from payments import PaymentsClient
from payments.core import ApiError
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN")
try:
# Every payment across every page; pages are fetched as you iterate.
for payment in client.payments.list_payments(limit=50):
print(payment.id)
# One page at a time.
for page in client.payments.list_payments(limit=50).iter_pages():
print(f"Fetched {len(page.data)} payments in this page")
except ApiError as e:
print("A page failed with status", e.status_code)On the async client the call is not awaited. It returns an AsyncPaginator that you iterate with async for:
async for payment in async_client.payments.list_payments(limit=50):
print(payment.id)This feature is available in the Python v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=pythonFor the full set of flags and options, see the APIMatic CLI documentation.
HTTP Logging in Python v4 SDKs
Python v4 SDKs now log HTTP requests and responses through Python's standard logging module. Nothing is printed until you turn the SDK's logger on, the same way you would for any library, or set one environment variable.
Details
The SDK writes under a logger named after its package and configures nothing itself: no handler, no level, and never the root logger. At INFO it writes one line as each request is sent and one when its response arrives, with the status and duration. WARNING covers responses outside 2xx and each retry, and ERROR a call that ends in an exception. For a run you cannot edit, an environment variable named after the package, such as PAYMENTS_LOG=info or debug, sets the level and adds a stderr handler when none is listening.
The logging_options keyword on the client, a dict or a LoggingOptions, turns on the DEBUG dumps of the request headers, the request body and the response headers. Header, query and form values print REDACTED unless they are known to be safe, so Authorization, cookies and API keys never reach the log, and a response body is never logged.
The per-call request_options accept a log_level floor that quietens one call. It never turns on a record the client's options leave off.
Usage Example
Raise the SDK's logger, choose the dumps on the client, and quieten a single call:
import logging
from payments import PaymentsClient
logging.basicConfig()
logging.getLogger("payments").setLevel(logging.DEBUG)
client = PaymentsClient(
bearer_auth="YOUR_ACCESS_TOKEN",
logging_options={"log_request_headers": True, "log_response_headers": True},
)
# The request and response lines, then both header dumps with Authorization masked.
client.payments.get_payment("pay_123")
# Nothing below WARNING is written for this call.
client.payments.get_payment("pay_456", request_options={"log_level": logging.WARNING})log_request_body prints a JSON body as sent, so turn it on only where that content may be logged.
This feature is available in the Python v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=pythonFor the full set of flags and options, see the APIMatic CLI documentation.
HTTP Logging in TypeScript v4 SDKs
TypeScript v4 SDKs now log HTTP requests, responses and retries. Logging is off by default and is switched on through the logging client option, or from the environment without a code change.
Details
The logger writes the request line, the response line with its status and duration, a line before each retry, and a line for a call that ends without a response. Request headers, response headers and the request body are off by default and switched on with logRequestHeaders, logResponseHeaders and logRequestBody. Lines go to console unless you pass your own logger, which can be any object with error, warn, info and debug methods.
Query values and headers are redacted by allow-list, so credentials, cookies and API keys are masked. redactedKeys, redactedHeaders and unmaskHeaders adjust what is masked. A JSON request body is logged as it is, so leave logRequestBody off on endpoints that carry personal or payment data.
When the code sets neither a logger nor a level, the client reads an environment variable named after the client class in upper snake case, such as PAYMENTS_CLIENT_LOG for PaymentsClient. Set it to error, warn, info or debug to turn logging on. A call's second argument can set the level for that call alone.
Usage Example
Send the SDK's lines to your application's logger, and turn on full detail for one call:
import { PaymentsClient } from "payments";
const client = new PaymentsClient({
logging: {
logger: appLogger, // any object with error, warn, info and debug methods
level: "info",
logRequestHeaders: true,
logResponseHeaders: true,
},
});
// This call alone logs at debug, request body included.
await client.payments.get({ paymentId: "pay_123" }, { logging: { level: "debug" } });To switch logging on in a deployed application without a code change, run it with PAYMENTS_CLIENT_LOG=info.
This feature is available in the TypeScript v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=typescriptFor the full set of flags and options, see the APIMatic CLI documentation.
Invalid Example Values Are Now Removed During Import with Warnings
We've improved how example values are validated during API import. An example that doesn't match its declared type or format is now removed from the API definition, and a warning tells you exactly where it was found. Previously, some invalid examples slipped through into your SDKs and documentation, while others were removed without any warning.
Heads-up! Behavior Change
If your API definition contains invalid example values, they may have previously carried through to SDK generation and documentation unchanged. After this release, those examples are removed during import and APIMatic may generate a sample value in their place. Your SDKs and docs will still generate, but the examples shown in them may change or disappear. Check the warnings logged during import to find and fix the affected examples in your API definition.
What was happening before
Example validation during import had gaps. Some invalid examples weren't caught at all, so they ended up as-is in generated SDKs, code samples, and documentation. Others were removed, but no warning was logged, so a different, auto-generated sample appeared in the output with no explanation of why the authored one was gone.
What happens now
Every invalid example is removed with a warning that names the component it belongs to. An example is considered invalid when, for instance:
- Its value doesn't match the declared type, such as a number given for a
stringfield. - A date value can't be parsed, or is an impossible date like
2026-02-31. - A
format: bytevalue isn't valid Base64. - An explicit
nullis given for a field that's not nullable. - An array example's nesting doesn't match the declared array dimensions.
- The example value is empty.
Validation now also covers places it previously missed, including examples on response headers and in callback bodies.
One common authoring mistake is repaired instead of removed: a bare value given as the example for an array of strings, such as example: pending on a string array, is wrapped into an array (["pending"]) and kept.
What you should do
Valid examples aren't affected, so no action is needed if your examples already match their declared types and formats. If examples disappear from your SDKs or documentation after this release, look through the validation warnings logged during import. Each warning points to the example that was removed, so you can correct its value in your API definition and keep your authored examples in the output.
Precise Integer and Float Validation in TypeScript v4 SDKs
TypeScript v4 SDKs now check numbers by the kind your specification declares rather than accepting any number for every numeric field.
Details
Every numeric field is still typed number, and its schema decides which check runs. An integer field, whether int32, int64 or type: integer with no format, is checked with s.int(), which rejects a fraction and any value outside JavaScript's safe-integer range. A float, a double, a big decimal or type: number with no format is checked with s.float64(), which rejects NaN and ±Infinity.
An int64 larger than Number.MAX_SAFE_INTEGER is rejected rather than silently rounded. A request value that fails its check rejects the call with an EncodeError before anything is sent, and a response value that fails rejects with a DecodeError.
Usage Example
A value the specification types as an integer is checked before the request leaves:
import { PaymentsClient, PaymentsError } from "payments";
const client = new PaymentsClient();
try {
// quantity is an integer in the specification, so 2.5 is rejected.
await client.orders.create({ body: { sku: "SKU-1", quantity: 2.5 } });
} catch (err) {
if (err instanceof PaymentsError && err.kind === "encode") {
console.error("Nothing was sent:", err.cause);
} else {
throw err;
}
}The cause is the SchemaError that names the field and the check it failed.
This feature is available in the TypeScript v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=typescriptFor the full set of flags and options, see the APIMatic CLI documentation.
Moving Python v4 SDKs to httpx 2
Python v4 SDKs now depend on httpx2 in place of httpx. The upgrade brings the SDK onto the 2.x line of its HTTP library while keeping the client surface you already use unchanged.
Details
The default transports are now Httpx2Client and AsyncHttpx2Client, replacing HttpxClient and AsyncHttpxClient. TLS verification stays on by default and keeps the trust order the SDK shipped with: SSL_CERT_FILE, then SSL_CERT_DIR, then the certifi bundle, so no configuration change is needed to keep the behavior you had. To set a proxy, a private CA or a client certificate, build an Httpx2Client yourself and pass it to the client as custom_http_client.
The generated pyproject.toml now declares httpx2 (>=2.13.1,<3.0.0). Regenerate to pick up the new dependency, and update any code that imported the old transport names or caught httpx exceptions, which are now raised from httpx2.
Usage Example
Build the transport yourself when the defaults do not fit, and hand it to the client:
import ssl
from payments import PaymentsClient
from payments.core import Httpx2Client
transport = Httpx2Client(
timeout=10.0,
proxy_url="http://proxy.internal:3128",
verify=ssl.create_default_context(cafile="corp-ca.pem"),
)
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN", custom_http_client=transport)The async client takes an AsyncHttpx2Client through custom_async_http_client instead.
This change is available in the Python v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=pythonFor the full set of flags and options, see the APIMatic CLI documentation.
September 2026
Adding Retries to TypeScript v4 SDKs
TypeScript v4 SDKs now retry failed requests, and retrying is on by default. The retry client option controls how many retries are made, which status codes and methods qualify, how long each attempt may take, and how the backoff grows between attempts.
Details
By default the SDK retries up to 3 times after a dropped connection, a timed-out attempt, or a 408, 429, 500, 502, 503 or 504. The wait starts at 1 second and doubles with each retry, with up to 25% added jitter and a ceiling of 60 seconds. When the server sends a Retry-After header, the SDK waits that long instead. Each attempt has its own 60-second timeout, which bounds the attempt rather than the whole call.
Only GET, HEAD, PUT and OPTIONS are retried by default, so a POST, PATCH or DELETE is never sent twice unless you add it to httpMethodsToRetry. Every retry of a call carries the same Idempotency-Key, which makes widening the list safe for APIs that honor it. A request whose body is a stream is never retried, because a stream cannot be replayed.
A call's second argument can override maxRetries, timeout and statusCodesToRetry for that call alone. A call that exhausts its retries rejects with the last attempt's error, from the same error family as every other failure.
Usage Example
Tune the policy on the client, and override it for a single call:
import { PaymentsClient } from "payments";
const client = new PaymentsClient({
retry: {
maxRetries: 5,
timeout: 10_000,
httpMethodsToRetry: ["GET", "HEAD", "PUT", "OPTIONS", "POST"],
onRetry: ({ attemptNumber, delay, reason }) => {
console.warn(`retry ${attemptNumber} in ${delay}ms after a ${reason.kind}`);
},
},
});
// This one call is sent exactly once.
const payment = await client.payments.get({ paymentId: "pay_123" }, { retry: { maxRetries: 0 } });Pass retry: { maxRetries: 0 } to the client to turn retrying off everywhere, for example in a test that stubs an error response.
This feature is available in the TypeScript v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=typescriptFor the full set of flags and options, see the APIMatic CLI documentation.
C#, Python and TypeScript v4 SDKs Now Generally Available
The v4 SDKs for C#, Python and TypeScript are now generally available. All three leave beta today and generate as stable SDKs from version 2.x of the APIMatic CLI.
What's New Since Beta
- Authentication: Every scheme your specification declares,
ANDandORcombinations, and the three OAuth2 grants, in all three languages - Resilience: Configurable retries with exponential backoff, request timeouts and cancellation in C#
- Errors: One error family per SDK, with a typed error per operation that carries the declared payload as a model
- Files and streams: Binary bodies, multipart forms and binary responses in all three languages, and server-sent event streaming in C# and Python
- Identification and idempotency: SDK identification headers on every request and an automatic
Idempotency-Keyon every non-GET request - Documentation: A README, a complete API reference and an SDK map inside every package
Generating the SDK
The GA SDKs are generated with version 2.x of the APIMatic CLI. Version 2.x generates v4 SDKs only, so the --codegen-version and --stability flags are no longer needed:
apimatic sdk generate --language=csharp
apimatic sdk generate --language=python
apimatic sdk generate --language=typescriptNote
v4 SDKs are available for C#, Python and TypeScript, the languages version 2.x of the APIMatic CLI generates.
For the full set of flags and options, see the APIMatic CLI documentation.
Configurable Retries in Python v4 SDKs
Python v4 SDKs now retry failed requests, and retrying is on by default. The client accepts a retry_options keyword that controls how many retries are made, which status codes and methods qualify, and how the backoff grows between attempts.
Details
By default the SDK retries up to 3 times, after a connection that failed or timed out and after a 408, 429, 500, 502, 503 or 504. The wait starts at 1 second and doubles with each retry, with up to half a second of jitter and a ceiling of 60 seconds. When the server sends a Retry-After header, in seconds or as an HTTP date, the SDK waits that long instead, capped at the same ceiling.
Only GET, HEAD, PUT and OPTIONS are retried by default, so a POST, PATCH or DELETE is never repeated unless you add it to http_methods_to_retry. Every retry of a call carries the same Idempotency-Key, which makes adding POST safe for APIs that deduplicate on it. request_options overrides max_retries and status_codes_to_retry for one call.
When the last attempt fails, you get what a single attempt would give you: the ApiError for an error status, or the httpx2 exception for a connection that never answered. The policy is the same on the synchronous and asynchronous clients.
Usage Example
Keep the default policy, tune it on the client, or change it for one call:
from payments import PaymentsClient
from payments.core import RetryOptions
# The default policy: up to 3 retries of GET, HEAD, PUT and OPTIONS.
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN")
# More retries, a shorter first wait, and POST retried because the API deduplicates on Idempotency-Key.
client = PaymentsClient(
bearer_auth="YOUR_ACCESS_TOKEN",
retry_options=RetryOptions(
max_retries=5,
initial_delay=0.5,
http_methods_to_retry=frozenset({"GET", "HEAD", "PUT", "OPTIONS", "POST"}),
),
)
# No retries for this one call.
payment = client.payments.get_payment("pay_123", request_options={"max_retries": 0})Pass retry_options=0 to turn retrying off for the whole client, for example in a test that stubs an error response.
This feature is available in the Python v4 SDK. Generate it with version 2.x of the APIMatic CLI:
apimatic sdk generate --language=pythonFor the full set of flags and options, see the APIMatic CLI documentation.
Server-Sent Event Streaming in Python v4 SDKs (Beta)
Python v4 SDKs now stream server-sent events. An operation whose success response is text/event-stream returns an EventStream[T] typed over the event's data schema, and the asynchronous client returns an AsyncEventStream[T] you read with async for.
Details
The runtime now has one request seam: a call returns as soon as the response head arrives, and the decoder owns the body from there. That is what lets a stream start yielding events before the connection closes, and the same seam serves JSON, text, binary and empty responses.
Each iteration yields one event's data, parsed into the schema the specification declares, and a discriminated oneOf event arrives already routed to its variant. The stream ends cleanly on the sentinel named by the x-sse-sentinel extension, which is consumed and never yielded, or when the server closes the connection. Draining the loop releases the connection, and a with block releases it when you stop early.
Usage Example
Iterate the stream directly, and open it in a with block whenever you might stop before it ends:
from payments import PaymentsClient
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN")
# Each event's data arrives as a typed model; the loop ends with the stream.
for update in client.payments.stream_payment_updates("pay_123"):
print(update.status)
# The with block releases the connection when you break out early.
with client.payments.stream_payment_updates("pay_123") as stream:
for update in stream:
if update.settled_at is not None:
breakOn the async client, await the call and iterate the result:
from payments import AsyncPaymentsClient
async def main() -> None:
async with AsyncPaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN") as client:
async for update in await client.payments.stream_payment_updates("pay_123"):
print(update.status)This feature is available in the Python v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=python --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
A Rooted API Error Family in TypeScript v4 SDKs (Beta)
TypeScript v4 SDKs now reject with one error family. Every failure a call can produce derives from a single root named after your API, such as PaymentsError, and carries a closed kind discriminator, so one catch with a switch on err.kind handles every outcome exhaustively.
Details
The six kinds are api for an error status, decode for a response that did not fit its declared type, encode for a request value that failed its schema before anything was sent, connection for a request that never got an answer, timeout for an attempt that ran out of time, and auth for a credential that could not be obtained. Every error names the method and URI of the call that raised it.
ApiError carries the status, the headers and a payload. An operation whose specification declares error responses rejects with its own subclass, such as Payments.CreateError, whose payload is a union of those declared bodies narrowed on payload.kind. A status the specification does not describe arrives as the undeclared arm, with the raw bytes in rawBody. Call .asApiResult() on an operation's promise to receive an error status as a value instead of a rejection.
ConfigurationError stays outside the family. The constructor throws it for a defect in the client options, such as an unknown server environment.
Usage Example
Narrow the operation's own error on its payload, then the rest of the family on kind:
import { PaymentsClient, PaymentsError, Payments } from "payments";
const client = new PaymentsClient();
try {
await client.payments.create({ accountId: "acc_1", body: { amount: 1000, currency: "USD" } });
} catch (err) {
if (err instanceof Payments.CreateError) {
switch (err.payload.kind) {
case "paymentError":
console.error(err.status, err.payload.body.message);
break;
case "undeclared":
console.error(err.status, new TextDecoder().decode(err.payload.rawBody));
break;
}
} else if (err instanceof PaymentsError) {
console.error(`${err.kind}: ${err.message}`);
} else {
throw err;
}
}A caller's abort is not part of the family and rejects with the signal's own reason.
This feature is available in the TypeScript v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=typescript --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
Hardened Open and Closed Enums in C# v4 SDKs (Beta)
C# v4 SDKs now generate a hardened enum shape. Every enum is a sealed record over an open or closed root, so the SDK is explicit about whether a value the API sends but the specification does not list is accepted or rejected.
Details
Enums generated from your schemas are open. An open enum accepts a value the specification does not list, keeps it with the server's own casing and writes it back unchanged, so a new member on the server never breaks an existing client. The server-environment enum is closed and rejects an unknown value on deserialization.
Every enum exposes a get-only Value and converts implicitly to its underlying string or long. TryGetKnownValue resolves a raw value to a declared constant, IsKnownValue tells a declared value from an undeclared one, and Is and == compare against a raw value without constructing an enum. A generated Match takes one arm per declared value plus an otherwise arm for anything undeclared. Integer enums hold long values.
The converters support enums as dictionary keys without reflection, so a map keyed by an enum round-trips through System.Text.Json as the specification describes it.
Usage Example
Set an enum from its constant, and branch on a received value with Match:
var request = new ListOrdersRequest { Status = OrderStatus.Shipped };
var orders = await client.Orders.ListOrders(request);
foreach (var order in orders)
{
var label = order.Status.Match(
onPending: () => "Waiting to ship",
onShipped: () => "On its way",
onDelivered: () => "Delivered",
otherwise: raw => $"Unknown status '{raw}'");
Console.WriteLine($"{order.Id}: {label} ({order.Status.Value})");
}Use .Value rather than ToString() or string interpolation when the wire value leaves your process.
This feature is available in the C# v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=csharp --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
Per-Operation Request Records in C# v4 SDKs (Beta)
C# v4 SDKs now take one request record per operation. Instead of a long positional parameter list, each endpoint method accepts a {Operation}Request record whose members mirror the operation's parameters and body, so a call reads the same way the API reference does.
Details
Every operation that takes input gets a public sealed record {Operation}Request. Records for operations on a controller live under Requests/{ApiGroup}/, and records for operations on the client live under Requests/. Path, query and header parameters, the body and every form field become members of the record, named in PascalCase. A JSON, text or binary body is always the Body member.
Required inputs are required members, so a missing required value is a compile error rather than a runtime one. Optional inputs are nullable and are omitted from the request when left unset, inputs with a default keep it unless you override it, and constants are fixed on the record. The record carries the same constraint attributes a model property gets.
The method shape is now Task<T> Op({Op}Request request, RequestOptions? requestOptions = null, CancellationToken cancellationToken = default). An operation with no input has no request parameter. The README, API reference, SDK map and plugin skills all teach the new call shape.
Usage Example
Build the record with an object initializer, setting members by name:
var orders = await client.Orders.ListOrders(
new ListOrdersRequest
{
CustomerId = "cus_123",
Status = OrderStatus.Shipped,
PerPage = 50,
},
cancellationToken: cancellationToken);
await client.Orders.DeleteOrder(new DeleteOrderRequest { OrderId = "ord_456" });Pass cancellationToken by name so it never lands in the requestOptions slot.
This change alters every generated call site. Update your calls when you regenerate.
This feature is available in the C# v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=csharp --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
A Unified SdkException Family in C# v4 SDKs (Beta)
C# v4 SDKs now raise one family of exceptions. Everything the SDK throws for a call derives from SdkException, so a single catch covers every failure while the subclasses let you handle the ones you care about precisely.
Details
SdkException carries the failed call's Method and RequestUri, and every message starts with that call, so a log line identifies the failing request without a typed catch. The underlying cause is always the InnerException.
| Exception | When it is raised | Members it adds |
|---|---|---|
ApiException<TError> | The server answered with an error status | Error, plus StatusCode, Headers and ContentType from ApiException |
ResponseDeserializationException | A response body did not match the type the specification declares | TargetType, plus the ApiException members |
SdkConnectionException | The request could not be sent, or the response body could not be read | |
SdkTimeoutException | An attempt, the transport or an event stream went silent | Timeout |
AuthSchemeException | A credential could not be applied, such as an OAuth2 token request being refused | SchemeFailures |
TError is the operation's own error type when the specification declares error responses for it, with one TryGet accessor per declared error body and TryGetRawError for any other status. When the operation declares none, TError is RawError, which carries the status code and the raw body. Your own cancellation always surfaces as the usual OperationCanceledException and is never wrapped.
Usage Example
Catch from the most specific exception to the most general:
try
{
var payment = await client.Payments.CreatePayment(new CreatePaymentRequest { Body = newPayment });
}
catch (ApiException<CreatePaymentError> ex)
{
if (ex.Error.TryGetValidationError(out var validation))
Console.WriteLine($"Rejected ({(int)ex.StatusCode}): {validation.Message}");
else if (ex.Error.TryGetRawError(out var raw))
Console.WriteLine($"HTTP {(int)raw.StatusCode}: {raw.ReadAsString()}");
}
catch (SdkTimeoutException ex)
{
Console.WriteLine($"No answer within {ex.Timeout}");
}
catch (SdkConnectionException ex)
{
Console.WriteLine($"Could not reach the API: {ex.InnerException?.Message}");
}
catch (SdkException ex)
{
Console.WriteLine(ex.Message);
}ResponseDeserializationException and AuthSchemeException both land in the final SdkException arm here. Give them their own arms when you need to tell them apart.
This feature is available in the C# v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=csharp --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
SDK Identification Headers and Automatic Idempotency Keys in TypeScript v4 SDKs (Beta)
TypeScript v4 SDKs now identify themselves on every request and protect non-idempotent calls against accidental replay, matching the C# and Python v4 SDKs.
Details
Identification headers
Every request carries a User-Agent and the X-APIMatic-* headers that name the SDK, its version, the generator version, the operating system and the JavaScript runtime it is running on. The operating system and runtime are detected when the client is built, so the same package reports correctly under Node.js, Deno, Bun, Cloudflare Workers and the browser. The values sit at the lowest header layer, so a header an operation declares in the specification takes precedence.
Idempotency keys
Every non-GET request is sent with an Idempotency-Key header holding a UUID minted once per call, and every retry of that call carries the same key. An API that honors the header can recognize a retried request and avoid applying it twice.
Example Request
A request sent by the SDK on Node.js carries these headers with no setup:
POST /payments HTTP/1.1
User-Agent: PaymentsClient/1.0.0 TypeScript
X-APIMatic-Lang: TypeScript
X-APIMatic-Package-Version: 1.0.0
X-APIMatic-Gen-Version: 4.0.0
X-APIMatic-OS: linux x64
X-APIMatic-Runtime: Node.js/22.11.0
Idempotency-Key: 6f1c2e8a-4b7d-4a39-9c1e-2f5d8b3a7c41The first four values are fixed when the SDK is generated, and the OS and runtime are detected from the host. On Deno, X-APIMatic-Runtime reads Deno/2.1.4, and a runtime that exposes neither fact sends neither header. A GET request carries the same identification headers but no Idempotency-Key.
This feature is available in the TypeScript v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=typescript --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
Discriminated Unions and Enum Defaults in Python v4 SDKs (Beta)
Python v4 SDKs now resolve discriminated unions precisely. A oneOf union or an allOf hierarchy that declares a discriminator is tagged by the wire key, so the SDK picks the right variant on the way in and writes the right tag on the way out, even when the variant model does not declare the tag field itself.
Details
Discriminated unions
A discriminated union is a type alias over its variants, read through the runtime's WireDiscriminator. On deserialization it reads the discriminator property your specification names and returns the matching variant, so you narrow the result with isinstance or match on its class rather than on a tag string. On serialization the union writes the tag for the variant you built. Polymorphic allOf hierarchies are handled the same way as oneOf unions.
Dict companions and enum defaults
Every model now has a {Model}Dict companion, a closed TypedDict with the same fields, so any parameter that takes a model also takes a plain dictionary and your type checker still checks its keys. A union's dictionary spelling carries the tag key, because a bare dictionary has no class to read it from. An enum property with a default in the specification now defaults to that member in the generated model.
Usage Example
Send the variant you mean as a model or a dictionary, and narrow what comes back on its class:
from payments import PaymentsClient
from payments.models import BankPayment, CardPayment
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN")
# The union writes the "paymentType" tag; currency takes the spec's default, Currency.USD.
payment = client.payments.create_payment(CardPayment(card_token="tok_visa", amount=1000))
# The dictionary spelling carries the tag itself.
client.payments.create_payment({"paymentType": "bank", "iban": "DE89370400440532013000", "amount": 1000})
# The response arrives as the matching variant.
match payment:
case CardPayment() as card:
print("Card ending", card.last4)
case BankPayment() as bank:
print("Bank transfer from", bank.iban)A dictionary without the tag key raises a pydantic ValidationError (union_tag_not_found) at the call.
This feature is available in the Python v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=python --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
File and Multipart Payloads in TypeScript v4 SDKs (Beta)
TypeScript v4 SDKs now handle files end to end. Binary request bodies, multipart/form-data forms whose parts are files, typed JSON documents or text fields, and binary success and error responses all generate with typed signatures.
Details
A file is passed as a FileInput: a Blob, a Uint8Array, an ArrayBuffer, a web ReadableStream, or a Node stream such as createReadStream(path). Wrap any of them as { data, fileName, contentType } to name the file or override its media type. A path on its own is not a file, so open it first. A multipart form takes each part as its own request field, and a field declared as an array of files sends one part per file.
A binary success response resolves to BinaryContent, a handle whose stream is the unread response body together with its contentType and fileName. Read the stream once or cancel it. A binary error response arrives as BinaryErrorContent on the thrown error's payload, with its bytes already read, so the body is never lost.
Usage Example
Upload a multipart form from a file on disk, then download a file and save it:
import { openAsBlob } from "node:fs";
import { writeFile } from "node:fs/promises";
import { PaymentsClient } from "payments";
const client = new PaymentsClient();
const uploaded = await client.files.uploadForm({
file: { data: await openAsBlob("report.pdf"), fileName: "report.pdf" },
metadata: { title: "Q3 report" },
label: "finance",
});
const download = await client.files.download({ fileId: uploaded.id });
const bytes = new Uint8Array(await new Response(download.stream).arrayBuffer());
await writeFile("report-copy.pdf", bytes);The server's fileName on a download is untrusted input, so name the file you write yourself.
This feature is available in the TypeScript v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=typescript --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
File and Binary Payloads in Python v4 SDKs (Beta)
Python v4 SDKs now handle files. An operation that uploads a binary body or a multipart/form-data form, and one that returns a binary body on success or on error, all generate with typed parameters and typed results.
Details
A file is passed as a Path, bytes, any binary reader or an iterator of chunks, and the same FileInput type is accepted for a raw binary body and for a multipart file part. A Path streams from disk, keeps its Content-Length and names the file on the wire. Wrap any source in NamedFile to set the filename or the media type yourself. A multipart form takes each part as its own parameter: a file, a typed JSON document or a text field.
A binary success response is returned as a FileResponse whose body has not been read yet. Save it to disk, read it into memory or iterate its chunks, and each of those closes the connection when it finishes. A binary error response arrives as a RawError on the raised ApiError, with its bytes in content, so the body is never lost.
The asynchronous client takes AsyncFileInput, which also accepts async readers and async iterables, and returns an AsyncFileResponse.
Usage Example
Upload from a path, override the filename where the source has none, and stream a download to disk:
from pathlib import Path
from payments import PaymentsClient
from payments.core import NamedFile
client = PaymentsClient(bearer_auth="YOUR_ACCESS_TOKEN")
# A Path streams from disk and sends its own filename.
client.documents.upload_document(Path("invoice.pdf"))
# In-memory bytes carry no name, so give them one and the media type the server expects.
client.documents.upload_document(NamedFile(pdf_bytes, filename="invoice.pdf", media_type="application/pdf"))
# The download streams to disk, and save_to releases the connection when it is done.
client.documents.download_document("doc_123").save_to("invoice-copy.pdf")Every file goes out as application/octet-stream unless you set media_type on a NamedFile.
This feature is available in the Python v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=python --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.
README, API Reference, SDK Map and Context Plugin for TypeScript v4 SDKs (Beta)
TypeScript v4 SDKs now ship with their documentation and with a context plugin. A README.md gets a developer started, api-reference.md documents every operation, sdk-map.md with a page per resource indexes the package, and the plugin skills teach AI coding tools how to use it.
Details
The README covers installation from npm or from the SDK source, constructing the client with a placeholder credential for every declared authentication scheme, and the CommonJS form. The API reference documents each operation with a sample for awaiting it and for the non-throwing .asApiResult() form, a description of every parameter, and the success and error types it resolves or rejects with.
The SDK map is a lookup index of the TypeScript surface: client construction, the client options, servers and auth, the error-handling model, and one page per resource giving each operation's signature, route, request fields and the file behind every type. The context plugin wraps it with two skills generated from your API, an integration skill and a getting-started skill, beside the API-agnostic companions that cover client construction, authentication, calling endpoints, models, error handling, configuration and testing.
Usage Example
The documentation lands at the root of the generated package, and the skills land in the context plugin:
payments/ # the generated npm package
├── README.md # install and construct the client
├── api-reference.md # every operation, with samples
├── sdk-map.md # index of the whole TypeScript surface
├── map/
│ └── operations/
│ ├── payments.md # one page per resource
│ └── invoices.md
├── package.json
└── src/
plugin/
└── skills/
└── typescript/
├── typescript-integrate-payments/SKILL.md
├── typescript-getting-started/SKILL.md
└── typescript-error-handling/SKILL.md # one of the companion skillsThe README points to the SDK map first, so a reader or a coding assistant starts from the index rather than the source.
This feature is available in the TypeScript v4 SDK (beta). Generate it from the APIMatic CLI:
apimatic sdk generate --language=typescript --codegen-version=v4 --stability=betaFor the full set of flags and options, see the APIMatic CLI documentation.