Skip to main content

Decide what your agent sees, sends and gets back, in every API it touches.

Documentation · Quickstart · Packs · Write a pack · Request a pack

PyPI Python versions License CI Docs

Charter

Charter is an open-source Python library for building agent tools from API declarations. You describe an API call as a Pydantic model, with each field marked as path, query or body. Charter makes the request and handles auth, and you control what the model sees and what it gets back. It works with any HTTP API, including your own.

A library, not a service. Runs in your process. No proxy, no per-call pricing, no telemetry.

Highlights

  • Any HTTP API. A tool is a Pydantic model and a URL. A coding agent can write one from the API's docs with the skill.
  • Shape what the model sees. Price a tool's schema, cut it to the job, add guidance on a single field, and keep hundreds of tools behind one search tool.
  • Control what it sends. Wire formats (MIME, base64, GraphQL) are built for the model, bad arguments are refused before sending with errors it can act on, and values can be pinned out of its reach.
  • Control what comes back. Responses are trimmed to what the model needs, and APIs that report failure inside a 200 raise.
  • Your users' accounts. Per-user OAuth with refresh and rotation, and API keys, with no OAuth library or vendor SDK.
  • Auditable. egress_map lists what every tool lets the model reach, so a change to it is a diff in review.
  • Fits your stack. LangChain, any OpenAI-compatible API, and MCP.

Installation

pip install charter-ai

Requires Python 3.10+. The only dependencies are pydantic and httpx.

Quickstart

from datetime import datetime
from typing import Annotated

from pydantic import BaseModel, Field

from charter import Body, Path, Query, api_key_tool_factory


class CreateEvent(BaseModel):
    calendar: Annotated[str, Path()] = Field(description="Which calendar, e.g. 'primary'.")
    notify: Annotated[bool, Query()] = Field(False, description="Email the attendees.")
    title: Annotated[str, Body()] = Field(description="What the meeting is about.")
    start: Annotated[datetime, Body()] = Field(description="When it starts.")
    attendees: Annotated[list[str], Body()] = Field([], description="Who to invite, by email.")


calendar_api = api_key_tool_factory(
    pack="calendar",
    base_url="https://httpbin.org/anything/",  # stands in for a calendar API
    api_key_headers={"Authorization": "Bearer sk_live_123"},
)
create_event = calendar_api(
    name="create_event",
    description="Put a meeting on a calendar.",
    method="POST",
    url_template="calendars/{calendar}/events",
    args_schema=CreateEvent,
)

print(create_event.to_json_schema())  # the function definition the model gets
create_event.invoke({"calendar": "primary", "notify": True, "title": "Coffee with Ada",
                     "start": "2026-10-20T10:00:00", "attendees": ["ada@example.com"]})

The request that goes out (httpbin echoes it back):

POST https://httpbin.org/anything/calendars/primary/events?notify=true
Authorization: Bearer sk_live_123
{"title": "Coffee with Ada", "start": "2026-10-20T10:00:00", "attendees": ["ada@example.com"]}

Charter adds the key. The model never sees it.

Examples

examples/ has fifteen short scripts, numbered in reading order. Most run without an account.

Example Shows
02_use_a_pack.py A shipped tool: GitHub sends 346.6 KB, the model gets 10.7 KB
03_hand_to_a_model.py Tools in Charter's own agent loop, with any OpenAI-compatible model
05_errors_for_the_model.py A bad argument refused before sending, with a fix the model can apply
10_many_tools.py 613 tools in one session, starting from 4,476 tokens
13_one_tool_per_plan.py One tool with a different field set per plan
connect/ Your own account and your users', by OAuth or API key

Shape a tool

A tool's schema is in the prompt on every turn, so its size and shape change what the model does. We tested this on Linear's issue list, thirty runs per cell at temperature 0:

the filter the model was given bytes glm-5p3-flash deepseek-v4p1 nemotron-lightning
removed entirely 1,131 0/30 0/30 0/30
the full mirror 187,655 22/30 400 ×30 400 ×30
curated 15,531 28/30 30/30 4/30

paths(by_cost=True) shows where a tool's tokens go:

from charter import schema_tokens
from charter.packs import linear

schema_tokens(linear.issues_list_full)            # 45072
linear.issues_list_full.paths(under="variables", by_cost=True)
# [PathCost(path='filter',   tokens=44828),
#  PathCost(path='order_by', tokens=52), ...]

derived(keep=...) trims it to what a triage agent needs:

from charter import schema_tokens
from charter.packs import linear

F = "variables.filter."

issues_list_triage = linear.issues_list_full.derived(
    name="issues_list_triage",
    keep={
        F + "id", F + "number", F + "title", F + "priority",
        F + "due_date", F + "created_at", F + "updated_at", F + "completed_at",
        F + "state.type", F + "state.name",
        F + "assignee.email", F + "assignee.name",
        F + "team.key", F + "team.name",
        F + "labels.name",
    },
)

schema_tokens(issues_list_triage)    # 3458

Keep team.key, not team: a whole relation brings the recursive filter back and lands you at 46,313 tokens. The shipped linear.issues_list is curated at 7,652 tokens.

See also 08_cut_a_tool.py, and 11_rules_on_fields.py for hints on a single field instead of in the system prompt.

Control what goes out

Format builds wire formats so the model fills in plain fields. Here the model writes to, subject and body, and Charter sends base64url RFC 2822 under raw:

from typing import Annotated

from pydantic import BaseModel

from charter import Body, EmailContent, Format


class SendEmail(BaseModel):
    raw: Annotated[EmailContent, Body(envelop=True), Format("rfc822_base64")]

We measured this over 534 runs on live accounts, against giving the model a generic HTTP tool instead (methodology):

Across 534 measured runs raw HTTP tool Charter
malformed GraphQL documents 32 0
calls to endpoints never declared 10 0
base64 the API rejected 4 0

scopes_for works out the OAuth scopes to request from the tools you hand out:

from charter.auth import scopes_for
from charter.packs import gmail

scopes_for([gmail.messages_list])    # ['https://www.googleapis.com/auth/gmail.modify']
scopes_for([gmail.threads_delete])   # ['https://mail.google.com/']

More controls:

Control what comes back

Packs trim their responses by default, and you can do the same on your own tools with response_handler (06_trim_responses.py). Some APIs answer a failure with 200, like Slack's {"ok": false} or a GraphQL errors list. An Envelope turns those into errors:

from charter import Envelope

Envelope(errors_field=("errors", "data.*.userErrors"))

Use with your framework

from charter import ToolSession
from charter.adapters.openai import to_openai_tools
from charter.packs import github

session = ToolSession(github.TOOLS)
tools = to_openai_tools(session)  # function definitions for any chat-completions API
  • charter.adapters.openai.run() runs the tool-calling loop with any OpenAI-compatible client (03_hand_to_a_model.py)
  • CharterMiddleware adds a session to a LangChain agent (frameworks/)
  • charter-mcp --pack gmail serves a pack over MCP

Ready-made tools

613 tools across sixteen APIs ship with Charter, from Gmail and Slack to Stripe and Linear (full list). You can extend them with your own tools, and contributions are welcome.

How Charter compares

If you're connecting an agent to your users' Gmail, Stripe or Notion, there are usually two ways to go.

You can use each vendor's own library and wire it up yourself. That's direct and it's yours, but every API comes with its own client, its own auth and its own plumbing to write and keep up.

Or you can use a platform like Zapier or Composio. Easy too, but your users see "Authorize Zapier" instead of your product's name, right when they're deciding whether to connect. You pay for every tool call and get rate-limited on top. And your users' data goes through someone else's servers.

Charter works like going direct. You register your own app with Google, Stripe or Notion, and your users authorize you. It's as direct as the vendors' own libraries and about as easy as a platform, without the platform's downsides: no per-call billing, no remote server, all yours.

It's not an agent framework either. LangChain, the OpenAI SDK or an MCP client runs the loop, and Charter is the tools it calls.

Limitations

Charter describes one request; loops, retries and multi-step flows belong in your agent. Multipart upload, request signing and streaming are not supported yet. A session applies one Mode label at a time, and Mode controls what a tool exposes, not who may call it. See limitations.

Documentation

Start Quickstart · Installation · Why Charter
Packs Overview · Write one with a coding agent
The boundary Egress control · Mode system · Quick reference
The wire Wire contract · Envelopes · Transforms · Key case
Credentials Getting the grant · Authorization servers · API keys
Running it What a call cost · Adapters and MCP · Errors
Guarantees Conformance · Measured results · Limitations
Reference API reference · AGENTS.md

Contributing

uv venv
uv pip install -e ".[dev,langchain,mcp]"
uv run pytest
uv run ruff check src tests examples
uv run pyright --project . --pythonpath .venv/bin/python src

See CONTRIBUTING.md for how to contribute, and AGENTS.md for the codebase rules.

License

Apache 2.0. See LICENSE and NOTICE.

Charter is built by R28.

Metadata

Release files for charter-ai 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for charter-ai 0.5.2
File Size Uploaded
charter_ai-0.5.2.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for charter-ai 0.5.2
File Interpreter ABI Platform
charter_ai-0.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 2.1 MB

Release files / charter_ai-0.5.2.tar.gz

Download URL charter_ai-0.5.2.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
5e06300d6b90b7609d2add045d9bc10249f4792dc6b4319341d23dd7ed263307
BLAKE2b-256 checksum
How to use checksums
bff9a354fb57c265e49385e41255b079307bf6e757c772fcd58273de0eae2a5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / charter_ai-0.5.2-py3-none-any.whl

Download URL charter_ai-0.5.2-py3-none-any.whl
Size 921.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2da531af4a1499a591b8a3a4b696561b0611c73a98f60a4581cfa8ff34554d25
BLAKE2b-256 checksum
How to use checksums
f9db00147151da105cf4aad44d51f2c4a8b2f79493a0cc9a7dfb84e2d94e4950
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.2 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page