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
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
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
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 fieldFromContext() and pass its value with each call: a pin whose value arrives per call.
refund.py
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
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 exportsTOOLS, and a list of your own declarations works the same way. An adapter turns either into what the framework expects.
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.