Maton
Guide

TypeScript

The official TypeScript / JavaScript SDK, @maton/sdk, wraps the Maton API with typed accessors and automatic retries.

Installation

npm install @maton/sdk
pnpm add @maton/sdk
yarn add @maton/sdk

Quickstart

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:

ErrorHTTP status
AccessDeniedError401, 403 — bad/missing API key or insufficient scope
ResourceNotFoundError404
TooManyRequestsError (carries retryAfter)429
ValidationErrorother 4xx (incl. 412 when an action needs a connection)
InternalServerError5xx or unexpected upstream response
APIConnectionErrortransport 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.

On this page