Skip to main content

Declare a tool

A tool for an API Charter has never seen, with your own rules on it. httpbin.org stands in for the API: it echoes back the request it received, so everything on this page runs with no account.
1

Describe the arguments, and your rules

refund.py
Body() says where each field goes in the request; Query(), Path() and Header() are the others. Everything else on the model is a rule of yours, on top of the API’s: le=10_000 caps a refund at $100, the Literal leaves out "fraudulent" although the API accepts it, and the validator is any check you can write in Python. The Gloss is softer: a tip the model reads beside the field’s own description, which nothing enforces.
2

Build a factory

The factory carries everything shared across the tools of one API: the base URL, the credential, and the conventions its wire format follows.
refund.py
pack is a prefix for your tools’ names, so this one reaches the model as payments_refund. For an API that takes an OAuth token, oauth_tool_factory takes the same arguments and a credential provider.
3

Declare the tool

Point the arguments at an endpoint.
refund.py
4

Call it

refund.py
The key went out in the Authorization header and the model never saw it. A declaration also carries wire format, field policy and failure that arrives as HTTP 200; the wire contract is where those go.

Break a rule

Now the call a model gets wrong: a customer id where a charge belongs, $150, and a reason you ruled out.
refund.py
Nothing was sent. Every rule, the API’s and yours, runs before any request, so a call that breaks one never reaches the API. In an agent, this message is what the model gets back as the tool’s result, naming each field and what it sent, so it can fix the call or stop. Charter never improvises. And since no request is made, every rule can be tested offline with plain unit tests.

Pin a value, and see what the model sees

A pin removes a field from what the model sees and sends a fixed value on every call. Then print the boundary: it is computed from the same declarations the runtime executes, so it cannot drift from what actually happens.
refund.py
Commit that map and a change to it is a diff in review: a new tool, a widened field, a dropped pin. The audit artifact has the CI check.

Act for one customer

A rule like “only this customer’s charges” needs to know whose account the call is for. That has to come from your app, not from the model, which can be talked into naming someone else. Mark the field FromContext() and pass its value with each call: a pin whose value arrives per call.
refund.py
The model fills in the same four fields, and never customer_id. Asked for another customer’s charge, the call is refused before anything is sent, and so is a call your app made without saying whose it is. The map now says where that field comes from, which answers what a customer’s security team asks: can the agent choose whose data it touches? Per-call context has the rest.

Call a pack

A pack is the tools of one API, already declared the same way: a head start, not the limit. Configure the credential once and every tool in it is callable.
quickstart.py
Stripe takes an API key, so run this one first — a test key is enough. Look at the sizes on that second line. Stripe’s customer object comes back with twenty-three fields, and the model read six. You didn’t configure that. Every pack tool ships with a response handler tailored to its endpoint, there to keep your agent’s context window small — which is why Stripe’s twenty-three-field customer reached the model as six. Write your own with tool.derived(name=..., response_handler=...), or turn the trimming off with pass_through and get the response whole. The shape is yours either way: response handling, and seeing the wire for the headers and body.
Gmail, Calendar, Sheets, Docs, Drive and Forms need a Google OAuth client before their first call — Google issues no API key for them. Making one is ten minutes, once: Set up Google, a screenshot per screen. Every other pack’s credential, and the page that issues it, is on your own account.Gmail and the other OAuth packs take a credential provider instead of a key: gmail.configure(StaticTokenProvider(access_token)) is fine for a one-time script and wrong for a product, because the token expires in about an hour. See authorization servers for refresh against any OAuth 2.0 token endpoint, and getting the grant for where the refresh token comes from.

Hand it to a framework

Every pack exports TOOLS, and a list of your own declarations works the same way. An adapter turns either into what the framework expects.
The OpenAI format needs nothing beyond the core. LangChain needs pip install 'charter-ai[langchain]'. Client configuration for the MCP server is on its page, and the other two on adapters.

Next steps

The wire contract

Body format, static parameters, per-call headers, pagination.

Authentication

OAuth refresh against any token endpoint, and one set of tools for many users.