Skip to main content
Every error the library raises derives from CharterError, so a host application can catch the whole surface with one except. The runtime decides nothing about what happens next: it raises a typed exception and hands the decision back.

CharterError

The base class.
Optional[str]
A documentation slug, such as "auth/oauth-flow", or None. Set on errors whose fix is a procedure rather than an edit: registering an OAuth app, choosing scopes, standing up a callback route. Errors whose message already states the fix carry none, and neither does ToolValidationError, whose message is written to be handed back to a model that cannot follow a link.
Optional[str]
The slug rendered against the documentation site, or None. Appended to the message by str(exc), and readable on its own for a host that formats errors itself. Appending .md to it returns the page as markdown, which is what an agent debugging a pack should fetch.
Errors carry a slug rather than a URL so that moving a page is one edit rather than a change at every raise site, and so tests/test_error_docs.py can resolve every slug the library can print against the pages that ship beside it. A link inside an exception reaches someone who is already stuck; a dead one is worse than none.

DeclarationError

A schema, marker or pack was declared in a shape the runtime cannot use: a field with no Path(), Query() or Body() marker, a Pagination naming half a style, an Envelope that could never detect a failure, a Format naming a transform nobody registered. It names whose mistake this is, not when it surfaced. Most are raised while a tool is built or a declaration validated. Some cannot be detected until a request is assembled, and come out of ainvoke instead — an unmarked field, a body that will not form-encode, a pack whose host was never configured. Either way the fix is in the declaration and the reader is its author, which is why these carry a docs link where an error written for a model does not.
Guarding only the declaration is not enough. A tool with an unmarked field builds without complaint and raises on the first call, so a host catching ToolValidationError, CredentialError and APIError around ainvoke and nothing else will let this one escape. except CharterError catches it.
Also a ValueError, deliberately. Several are raised inside a pydantic validator, which converts ValueError and nothing else, and a host that already catches ValueError around a declaration keeps working unchanged. except CharterError catches them too.

CredentialError

str
What went wrong.
Optional[str]
Whose credentials failed — "google" — or None when the failure is not attributable to one provider.
Optional[int]
The HTTP status that triggered it, when it came from a response — 401 by default, or whatever credential_statuses declared. None when the credentials were missing locally or a refresh failed.
str(exc) prefixes the provider in brackets when there is one. Raised by: a provider with nothing to hand back (EnvTokenProvider on an unset variable, an unconfigured pack, a SubjectProvider with no subject set); a token endpoint refusing a refresh or an exchange; a response whose status is in credential_statuses; an envelope failure whose code is in credential_errors; and the constructor guards on StaticTokenProvider, CallbackProvider, OAuth2Client and OAuth2Flow. The library never re-authenticates on its own. This is the signal to run whatever flow you own and retry — see the OAuth flow.

ToolValidationError

str
Written to be handed straight back to a model as the tool result: it names the offending fields and how to fix them.
Optional[str]
Which tool’s input failed, when known.
list[dict[str, Any]]
The structured per-field errors. [] when none were supplied — never None, so iterating is always safe.
Raised before the network, by ainvoke validating arguments against llm_schema(). A call that fails here cost no request, no rate-limit budget and no money. See handling it for the retry loop this is designed for, and the auto-corrections applied before it raises.

TransformError

str
What failed.
Optional[str]
The transform name, as written in the Format marker.
Optional[str]
The field it was applied to, when known. Appended to str(exc).
Raised by apply_transform when the name is not registered (the message lists what is), when the value does not validate as the transform’s semantic type, or when the transform function itself raises. Also before the network.

APIError

int
The status the API returned. 200 when a declared envelope found the failure inside a success — that really was the status.
str
An excerpt of the response body, truncated to 500 characters so it stays safe to pass into a context window.
Optional[str]
The request URL, when known.
Optional[int]
Seconds the server asked the caller to wait, parsed from Retry-After — usual on 429, common on 503. Only the delta-seconds form is parsed; the HTTP-date form is legal but rare in JSON APIs, and guessing at clock skew is worse than reporting nothing.Two fallbacks, for APIs that answer the same question elsewhere. When there is no Retry-After, X-RateLimit-Reset is read — but only when X-RateLimit-Remaining is 0, so an ordinary response carrying its quota headers is not reported as a wait. GitHub needs this: it sends no Retry-After and answers 403 rather than 429. And an Envelope with a retry_after resolver fills this in for an API that refuses inside a 200, which is how a cost-budgeted API like Shopify reports a throttle.
str(exc) joins the status, URL, message, retry hint and body excerpt with em dashes. Charter never retries, and retry_after is why: it refuses to discard the server’s own answer to “when?”, and leaves the policy to you.

Catching the surface

Order matters only in that the five subclasses are siblings; catching CharterError first would swallow them all. DeclarationError is not in that block, and the trailing except CharterError is what catches it: most of them are raised while a tool is built, but the ones that need a request to be detected arrive here instead. Because it is also a ValueError, code that already guards a declaration with except ValueError keeps catching the build-time ones.