The official Python SDK, maton-ai, wraps the Maton API with typed accessors and automatic retries.
Installation
pip install maton-aiuv add maton-aipoetry add maton-aiQuickstart
import os
from maton_ai import Maton
maton = Maton(api_key=os.environ["MATON_API_KEY"])
gmail = maton.google_mail()
messages = gmail.messages.list(q="is:unread", max_results=10)
gmail.messages.send(to="alice@example.com", subject="hi", body="hello")Client
maton = Maton(
api_key=None, # defaults to MATON_API_KEY env var
connection=None, # default connection id for every call
timeout=30.0, # per-request timeout, seconds
max_retries=2, # retry attempts on transient failures
max_backoff=20.0, # cap on a single backoff sleep, seconds
)App Accessors
Every supported app is available as an accessor with resource-oriented methods:
slack = maton.slack(connection=slack_conn_id)
slack.messages.send(channel="#general", text="deploy finished ✅")
gh = maton.github(connection=gh_conn_id)
gh.issues.create(repo="maton-ai/maton-py", title="bug: ...", body="...")
notion = maton.notion(connection=notion_conn_id)
notion.data_sources.query(data_source_id="...", filter={"property": "Status", "status": {"equals": "Done"}})
hubspot = maton.hubspot(connection=hubspot_conn_id)
hubspot.contacts.list(limit=25)Selecting a connection
A connection can be set in three places. Precedence is per-call → accessor → constructor:
maton = Maton(connection=connection_id) # 1. client default
gmail = maton.google_mail(connection=connection_id) # 2. accessor
gmail.messages.list(q="is:unread")
maton.google_mail.messages.list( # 3. per-call (wins)
q="is:unread", connection=connection_id
)Connections
maton.connections.create(app="slack") # returns the PENDING connection + auth url
maton.connections.get(connection_id)
maton.connections.list(app="slack", status="ACTIVE")
maton.connections.delete(connection_id)create returns the freshly created connection with a url field — open it in a browser to complete OAuth, then poll get until status is ACTIVE.
Passthrough
For any endpoint without a typed accessor, use maton.api to reach it through the gateway. The first argument is the app-prefixed path:
maton.api.get("/slack/api/conversations.list", params={"limit": 10}, connection=connection_id)
maton.api.post(
"/google-mail/gmail/v1/users/me/messages/send",
json={"raw": "..."},
connection=connection_id,
)Functions
A function is code you deploy to Maton and call over HTTP. It gets its own URL, runs in an isolated sandbox, and can access your connected apps through the gateway. Manage them through maton.functions,
with maton.functions.versions, maton.functions.env, and maton.functions.runs
sub-resources:
fn = maton.functions.create(
name="greet",
runtime="python3.12",
files={"main.py": "def handler(event):\n return {'hello': 'ada'}\n"},
)
function_id = fn["function_id"]
# ``MATON_API_KEY`` is runtime-provided.
maton.functions.env.create(
function_id,
env=[{"key": "API_BASE", "value": "https://example.com", "type": "PLAIN"}],
)
maton.functions.env.update(function_id, "API_BASE", "https://example.org")
url = maton.functions.get(function_id)["url"]
try:
resp = maton.api.with_raw_response.post(url, json={"name": "ada"})
print(resp.function_run_id, resp.json())
except MatonError as exc:
for event in maton.functions.runs.logs.tail(function_id, exc.function_run_id):
print(event["message"], end="")
maton.functions.versions.list(function_id)
maton.functions.update(function_id, version=1)
maton.functions.update(
function_id,
files={
"main.py": "import json\ndef handler(event):\n body = json.loads(event.get('body') or '{}')\n return {'hi': body.get('name')}\n"
},
)
maton.functions.code.download(function_id)
maton.functions.search('"hello"', context=2)Triggers
Triggers register an event source (e.g. GitHub pull_request.opened) and fan matching events out to webhook destinations. Manage them through maton.triggers, with maton.triggers.destinations and maton.triggers.events sub-resources:
trigger = maton.triggers.create(
source="github",
event_type="pull_request.opened",
connection_id=gh_conn_id,
parameters={"repository_full_name": "maton-ai/cli"},
destinations=[{"url": "https://example.com/hook"}],
)
trigger_id = trigger["trigger"]["trigger_id"]
maton.triggers.list(source="github", status="ENABLED")
maton.triggers.get(trigger_id)
maton.triggers.update(trigger_id, status="DISABLED")
maton.triggers.delete(trigger_id)
# Destinations
dst = maton.triggers.destinations.create(trigger_id, url="https://example.com/hook")
destination_id = dst["destination"]["destination_id"]
maton.triggers.destinations.rotate_secret(trigger_id, destination_id)
# Events
events = maton.triggers.events.list(trigger_id, limit=20)
maton.triggers.events.replay(trigger_id, events["events"][0]["event_id"])Watching events
watch polls for new events and yields each one as it arrives, checkpointing so restarts resume after the last handled event:
for event in maton.triggers.events.watch(trigger_id):
handle(event)Reliability
Every call goes through an httpx-based client with automatic retries. Retries fire on connection errors and on 429 / 500 / 502 / 503 / 504; other 4xx/5xx surface immediately. Backoff is truncated exponential with full jitter and honors a server-provided Retry-After header.
Errors
All failures raise a subclass of MatonError, each carrying status_code, request_id, and the parsed body:
| Exception | When |
|---|---|
AccessDeniedError | 401/403 — bad/missing API key or insufficient scope |
ResourceNotFoundError | 404 |
TooManyRequestsError | 429 — also exposes .retry_after |
ValidationError | other 4xx (incl. 412 when an action needs a connection) |
InternalServerError | 5xx or unexpected upstream response |
APIConnectionError | network/transport failure |
from maton_ai import MatonError, TooManyRequestsError
try:
maton.google_mail.messages.list(q="is:unread", connection=connection_id)
except TooManyRequestsError as exc:
print("slow down; retry after", exc.retry_after)
except MatonError as exc:
print(exc.status_code, exc.request_id, exc.body)Some apps wrap their vendor-specific error envelopes in a dedicated subclass (GitHubError, GoogleDriveError, LinearError, SlackError, StripeError) so you can catch them by app while still falling back to MatonError.