The official TypeScript / JavaScript SDK, @maton/sdk, wraps the Maton API with typed accessors and automatic retries.
Installation
npm install @maton/sdkpnpm add @maton/sdkyarn add @maton/sdkQuickstart
import { Maton } from "@maton/sdk";
const maton = new Maton({ apiKey: process.env.MATON_API_KEY });
const gmail = maton.google_mail();
const messages = await gmail.messages.list({ q: "is:unread", maxResults: 10 });
await gmail.messages.send({
to: "alice@example.com",
subject: "hi",
body: "hello",
});Client
const maton = new Maton({
apiKey: process.env.MATON_API_KEY, // defaults to MATON_API_KEY env var
connection: undefined, // default connection id for every call
timeoutMs: 30_000, // per-request timeout, ms
maxRetries: 2, // retry attempts on transient failures
maxBackoffMs: 20_000, // cap on a single backoff sleep, ms
});App Accessors
Every supported app is available as an accessor with resource-oriented methods:
const slack = maton.slack({ connection: slackConnId });
await slack.messages.send({ channel: "#general", text: "deploy finished ✅" });
const gh = maton.github({ connection: ghConnId });
await gh.issues.create({ repo: "maton-ai/maton-ts", title: "bug: ...", body: "..." });
const hubspot = maton.hubspot({ connection: hubspotConnId });
await hubspot.contacts.list({ limit: 25 });Selecting a connection
A connection can be set in three places. Precedence is per-call → accessor → constructor:
const maton = new Maton({ connection: connectionId }); // 1. client default
const gmail = maton.google_mail({ connection: connectionId }); // 2. accessor
await gmail.messages.list({ q: "is:unread" });
await maton.google_mail.messages.list({ // 3. per-call (wins)
q: "is:unread",
connection: connectionId,
});Connections
await maton.connections.create({ app: "slack" }); // PENDING connection + auth url
await maton.connections.get({ connectionId });
await maton.connections.list({ app: "slack", status: "ACTIVE" });
await maton.connections.delete({ connectionId });create returns the 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:
await maton.api.get("/slack/api/conversations.list", {
query: { limit: 10 },
connection: connectionId,
});
await maton.api.post("/google-mail/gmail/v1/users/me/messages/send", {
json: { raw: "..." },
connection: connectionId,
});Functions
A function is 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, maton.functions.code, and
maton.functions.runs (whose logs sub-resource reads a run's output)
sub-resources:
const fn = (await maton.functions.create({
name: "greet",
runtime: "nodejs22.x",
files: { "index.js": "exports.handler = (event) => ({ hello: 'ada' });\n" },
})) as { function_id: string };
const functionId = fn.function_id;
// # ``MATON_API_KEY`` is runtime-provided.
await maton.functions.env.create({
functionId,
env: [{ key: "API_BASE", value: "https://example.com", type: "PLAIN" }],
});
await maton.functions.env.update({ functionId, key: "API_BASE", value: "https://example.org" });
const { url } = (await maton.functions.get({ functionId })) as { url: string };
try {
const { data, functionRunId } = await maton.api
.post(url, { json: { name: "ada" } })
.withResponse();
console.log(functionRunId, data);
} catch (err) {
if (err instanceof MatonError && err.functionRunId) {
for await (const event of maton.functions.runs.logs.tail({
functionId,
runId: err.functionRunId,
})) {
process.stdout.write(String(event.message));
}
}
}
await maton.functions.update({
functionId,
files: { "index.js": "exports.handler = (event) => ({ hi: JSON.parse(event.body ?? '{}').name });\n" },
});
await maton.functions.update({ functionId, version: 1 });
await maton.functions.versions.list({ functionId });
await maton.functions.code.download({ functionId });
const runs = (await maton.functions.runs.list({ functionId, limit: 20 })) as {
runs: { run_id: string }[];
};
await maton.functions.runs.logs.list({ functionId, runId: runs.runs[0].run_id });
await maton.functions.search({ q: '"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:
const trigger = (await maton.triggers.create({
source: "github",
eventType: "pull_request.opened",
connectionId: ghConnId,
parameters: { repository_full_name: "maton-ai/cli" },
destinations: [{ url: "https://example.com/hook" }],
})) as { trigger: { trigger_id: string } };
const triggerId = trigger.trigger.trigger_id;
await maton.triggers.list({ source: "github", status: "ENABLED" });
await maton.triggers.get({ triggerId });
await maton.triggers.update({ triggerId, status: "DISABLED" });
await maton.triggers.delete({ triggerId });
// Destinations
const dst = (await maton.triggers.destinations.create({
triggerId,
url: "https://example.com/hook",
})) as { destination: { destination_id: string } };
await maton.triggers.destinations.rotateSecret({
triggerId,
destinationId: dst.destination.destination_id,
});
// Events
const events = (await maton.triggers.events.list({ triggerId, limit: 20 })) as {
events: { event_id: string }[];
};
await maton.triggers.events.replay({ triggerId, eventId: events.events[0].event_id });Watching events
watch is an async iterator that yields each new event as it arrives:
for await (const event of maton.triggers.events.watch({ triggerId })) {
handle(event);
}Reliability
Every call goes through a fetch-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 throw a subclass of MatonError, each carrying statusCode, requestId, and the parsed body:
| Error | HTTP status |
|---|---|
AccessDeniedError | 401, 403 — bad/missing API key or insufficient scope |
ResourceNotFoundError | 404 |
TooManyRequestsError (carries retryAfter) | 429 |
ValidationError | other 4xx (incl. 412 when an action needs a connection) |
InternalServerError | 5xx or unexpected upstream response |
APIConnectionError | transport failure / timeout |
import { Maton, MatonError, TooManyRequestsError } from "@maton/sdk";
try {
await maton.google_mail.messages.get({ messageId, connection: connectionId });
} catch (err) {
if (err instanceof TooManyRequestsError) {
console.error("slow down; retry after", err.retryAfter);
} else if (err instanceof MatonError) {
console.error(err.statusCode, err.requestId, err.body);
}
throw err;
}Some apps wrap their vendor-specific error envelopes in a dedicated subclass (GitHubError, GoogleDriveError, LinearError, SlackError, StripeError, YouTubeError) so you can catch them by app while still falling back to MatonError.