Skip to main content
A factory captures the constants every tool for one API shares — base URL, auth, casing, envelope — and returns a callable that turns a schema plus a URL template into a Tool. Defaults set on the factory can be overridden per tool.

api_key_tool_factory

Tools for an API that authenticates with static headers: x-api-key, a bearer token you already hold, several headers at once.
str | Callable[[], str]
required
Base URL for the API, e.g. "https://api.example.com/". A callable is resolved on every request.
Dict[str, str] | Callable[[], Dict[str, str]]
default:"None"
Headers injected on every request. May be omitted here and supplied per tool via api_key_headers_override; a tool with neither raises DeclarationError. An empty value in a literal mapping is rejected at build time — on the factory and on the tool.
"camel" | "snake" | "pascal" | "kebab"
default:"\"camel\""
Case convention for body keys.
"camel" | "snake" | "pascal" | "kebab"
default:"\"snake\""
Case convention for query keys.
"camel" | "snake" | "pascal" | "kebab"
default:"\"snake\""
Case convention for path keys.
int
default:"20"
Request timeout in seconds.
Optional[str]
default:"None"
Link to the API’s own rate-limit documentation. Carried onto every tool.
Optional[Envelope]
default:"None"
How this API reports failure inside a 200 response. Declared once here, enforced on every tool the factory builds.
Optional[Iterable[ScopeRefusal]]
default:"None"
How this API says a credential’s grant does not cover the call, so each such answer raises InsufficientScope naming the scopes to add. The standard WWW-Authenticate challenge needs no declaration. See ScopeRefusal.
Optional[Pagination]
default:"None"
Where this API keeps its cursor. Declare it per tool instead when only some endpoints are lists — a retrieve labelled with a cursor parameter it does not accept is worse than no declaration.
"json" | "form"
default:"\"json\""
"form" for form-encoded APIs such as Stripe and Twilio.
"repeat" | "bracket"
default:"\"repeat\""
"bracket" for APIs expecting expand[0]=x in the query string.
Optional[Dict[str, Any]]
default:"None"
Query parameters sent verbatim on every request.
Optional[Dict[str, str]]
default:"None"
Headers sent verbatim on every request — an Azure api-version, a Notion-Version.
Optional[CallSink]
default:"None"
Receives a ToolCall after every invocation of every tool this factory builds. Declared here because observability belongs to the deployment, not to one endpoint.
Optional[Iterable[int]]
default:"None"
Statuses that mean “your credential” rather than “not allowed”. Defaults to {401}.
bool
default:"False"
Whether a 3xx is followed. Off by default: on a JSON API a redirect is usually a wrong URL, and following it quietly hides that. Turn it on per tool with follow_redirects_override where the answer is a redirect — GitHub serves Actions logs as a 302 to a signed URL. The headers named in api_key_headers are dropped when a redirect leaves the origin.
ToolBuilder
A callable that builds Tool instances with these defaults. Its parameters are listed under the builder.

oauth_tool_factory

Tools for an API that authenticates with a bearer token. The token is fetched from credential_provider on every call, so a provider that refreshes is picked up without rebuilding the tools. Charter never runs the OAuth flow itself: a missing, expired or rejected token raises CredentialError for the host application.
The keyword parameters it shares with api_key_tool_factory — body_case, query_case, path_case, timeout, quota_doc_url, envelope, scope_refusals, pagination, body_format, query_format, static_query, static_headers, on_call, credential_statuses, follow_redirects — behave identically. The five that differ:
str
required
Provider identifier passed to get_credentials, e.g. "google". Also the peer.service label on every record.
CredentialProvider
required
Supplies the bearer token. See Credentials.
Optional[ScopesDeclaration]
default:"None"
What these tools need from a grant: scope strings, or AnyOf(...) where the provider accepts several, narrowest first. A mapping of Kind to lists declares them for each kind of credential the provider issues. Carried as metadata for scopes_for, consent screens and approval UIs; Charter does not request them. A tool’s scopes_override list replaces the default kind’s and keeps the others; a mapping replaces the kinds it names.
int
default:"10"
How dead a token must be before the runtime refuses to send it. A guard against sending something already expired, not a refresh policy.
Optional[str]
default:"None"
The header the token goes in, for an API that does not read it from Authorization. None sends Authorization: Bearer <token>; a name sends the token bare in that header — "X-Shopify-Access-Token" for Shopify. Only where the token lands changes: it still comes from credential_provider on every call, so a provider that renews keeps renewing, which a key in api_key_headers cannot. Dropped on a redirect that leaves the origin, and masked in the debug log, as Authorization is.

The builder

Both factories return a callable with the same parameters, except that api_key_headers_override exists only on the API-key builder and scopes_override only on the OAuth one.
Two details of how overrides resolve:
  • body_case_override, query_case_override, path_case_override, timeout_override, envelope_override, pagination_override, on_call_override, follow_redirects_override, api_key_headers_override and scopes_override replace the factory value when they are not None. Passing None keeps the factory’s.
  • body_format_override is applied with or, so "json" and "form" both work but a falsy value falls back to the factory’s.
static_body is per tool rather than per factory because the constant belongs to the operation: every Linear tool POSTs to the same graphql URL, and the query document is what distinguishes one from another.

Errors