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.message.list({ q: "is:unread", maxResults: 10 });
await gmail.message.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.message.send({ channel: "#general", text: "deploy finished ✅" });
const gh = maton.github({ connection: ghConnId });
await gh.issue.create({ repo: "maton-ai/maton-ts", title: "bug: ...", body: "..." });
const hubspot = maton.hubspot({ connection: hubspotConnId });
await hubspot.contact.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.message.list({ q: "is:unread" });
await maton.google_mail.message.list({ // 3. per-call (wins)
q: "is:unread",
connection: connectionId,
});Connections
await maton.connection.create({ app: "slack" }); // PENDING connection + auth url
await maton.connection.get({ connectionId });
await maton.connection.list({ app: "slack", status: "ACTIVE" });
await maton.connection.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:
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,
});Triggers
Triggers register an event source (e.g. GitHub pull_request.opened) and fan matching events out to webhook destinations. Manage them through maton.trigger, with maton.trigger.destination and maton.trigger.event sub-resources:
const trigger = (await maton.trigger.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.trigger.list({ source: "github", status: "ENABLED" });
await maton.trigger.get({ triggerId });
await maton.trigger.update({ triggerId, status: "DISABLED" });
await maton.trigger.delete({ triggerId });
// Destinations
const dst = (await maton.trigger.destination.create({
triggerId,
url: "https://example.com/hook",
})) as { destination: { destination_id: string } };
await maton.trigger.destination.rotateSecret({
triggerId,
destinationId: dst.destination.destination_id,
});
// Events
const events = (await maton.trigger.event.list({ triggerId, limit: 20 })) as {
events: { event_id: string }[];
};
await maton.trigger.event.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.trigger.event.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.message.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.