Maton

Overview

Base URL

https://api.maton.ai

Quickstart

Get your API key

Go to Maton and navigate to Settings, then copy your API key and store it as an environment variable. See Authentication for more details.

export MATON_API_KEY="YOUR_API_KEY"

Connect an app

Create a connection for the app you want to use. See apps for a list of supported apps.

curl -X POST https://api.maton.ai/connections \
  -H "Authorization: Bearer $MATON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"app": "slack"}'

Then fetch connection details and open its url in a browser to complete the connection:

curl https://api.maton.ai/connections/{connection_id} \
  -H "Authorization: Bearer $MATON_API_KEY"
{
  "connection": {
    "connection_id": "{connection_id}",
    "status": "PENDING",
    "creation_time": "2025-12-08T07:20:53.488460Z",
    "last_updated_time": "2026-01-31T20:03:32.593153Z",
    "url": "https://connect.maton.ai/?session_token=5e9...",
    "app": "slack",
    "metadata": {}
  }
}

Make your first request

Call the connected app's native API through the gateway.

curl "https://api.maton.ai/slack/api/conversations.list?types=public_channel&limit=10" \
  -H "Authorization: Bearer $MATON_API_KEY"
const response = await fetch(
  'https://api.maton.ai/slack/api/conversations.list?types=public_channel&limit=10',
  {
    headers: {
      Authorization: `Bearer ${process.env.MATON_API_KEY}`,
    },
  }
)

const data = await response.json()
console.log(data)
import os
import requests

response = requests.get(
    "https://api.maton.ai/slack/api/conversations.list",
    params={"types": "public_channel", "limit": 10},
    headers={"Authorization": f"Bearer {os.environ['MATON_API_KEY']}"},
)

data = response.json()
print(data)

Response Format

Gateway returns the third-party API response verbatim including status code, headers, and body. Maton adds headers of its own: X-Request-Id, Proxy-Status (see Identifying the source of an error), and Maton-Connection, the connection the request used (see Specifying connection).

200 OK
Content-Type: application/json
X-Request-Id: 0ed40602759344318251935407661fa7
Proxy-Status: Maton; received-status=200
Maton-Connection: 21fd90f9-5935-43cd-b6c8-bde9d915ca80

{
  "ok": true,
  "channels": [
    {
      "id": "C0123456789",
      "name": "general",
      "is_channel": true,
      "is_private": false,
      "num_members": 12
    },
    {
      "id": "C0987654321",
      "name": "random",
      "is_channel": true,
      "is_private": false,
      "num_members": 8
    }
  ],
  "response_metadata": {
    "next_cursor": "dGVhbTpDMDYxRkE1UEI="
  }
}

Error Handling

Errors originate from one of two sources: Maton or the third-party API you're calling through the gateway.

Maton errors

Errors raised by Maton return a body with message, type, and code.

400 Bad Request
Content-Type: application/json
X-Request-Id: bcc61700ae274f6a9110b6a64b0f826a
Proxy-Status: Maton; error=http_request_error

{
  "message": "Invalid app name: `not-an-app`. The first path segment must be a supported app name, eg. `/google-mail/gmail/v1/users/me/messages`.",
  "type": "Bad Request",
  "code": 400
}

Authentication errors (a missing or invalid API key) nest the same fields under error:

401 Unauthorized
Content-Type: application/json
X-Request-Id: e9a4cf51dc8c424e923789c27ff4b284
Proxy-Status: Maton; error=http_request_error

{
  "error": {
    "message": "You provided an invalid Maton API key: not-********. ...",
    "type": "Unauthorized",
    "code": 401
  }
}
StatusMeaningWhat to do
400Invalid request, unknown app, or no usable connection for the target app (including a Maton-Connection that is FAILED, PENDING, or for another app)Check the app name. If the message names an ACTIVE connection, send it in Maton-Connection; otherwise reconnect or create one
401Missing or invalid API keyVerify your Authorization: Bearer header
404Resource not found or not owned by your account, including a Maton-Connection ID that isn't yoursVerify the ID. For Maton-Connection, use one of the ACTIVE connections the message lists
429Rate limit or daily quota exceededWait the number of seconds in Retry-After, then retry
500Internal server errorRecreate the connection and retry
502The third-party API could not be reached, or closed the connectionRetry after a short delay
504The third-party API did not respond in timeRetry after a short delay

Third-party API errors

Gateway returns third-party API errors verbatim including status code, headers, and body. Some APIs report errors with a 200 status, like Slack's "ok": false below; Proxy-Status still shows the response came from the third-party API.

200 OK
Content-Type: application/json
X-Request-Id: 361023c4920f4c8ab0e2fde62a8e7a39
Proxy-Status: Maton; received-status=200

{
  "ok": false,
  "error": "channel_not_found"
}

Identifying the source of an error

Gateway responses carry a Proxy-Status header (RFC 9209) that tells you whether the response came from the third-party API or from Maton. Status codes alone can't: a 404 or 429 can come from either.

Proxy-StatusSourceMeaning
Maton; received-status=404Third-party APIMaton relayed the third-party response. received-status is the status the third-party API sent.
Maton; error=http_request_errorMatonMaton rejected the request before sending it to the third-party API: invalid API key, unknown app, missing or inactive connection, or a Maton rate limit or daily quota.
Maton; error=proxy_internal_errorMatonAn internal Maton error (500).
Maton; error=connection_timeoutMatonMaton could not get a response from the third-party API. The error value says what failed; see below.

When Maton sent the request but couldn't get a usable response, error is one of the RFC 9209 types:

errorStatusWhat happened
connection_timeout504Opening a connection to the third-party API timed out
connection_read_timeout504The third-party API did not respond in time
connection_write_timeout504Sending the request to the third-party API timed out
connection_limit_reached504No connection to the third-party API became available in time
dns_error502The third-party API's hostname could not be resolved
connection_refused502The third-party API refused the connection
destination_unavailable502The third-party API could not be reached
tls_certificate_error502The third-party API's TLS certificate could not be verified
tls_protocol_error502The TLS handshake with the third-party API failed
connection_terminated502The connection closed before a complete response arrived
http_protocol_error502The third-party API sent a malformed HTTP response
http_response_incomplete502The third-party API started responding, then the connection dropped. received-status shows the status it had sent.

A simple rule: if Proxy-Status has received-status and no error, handle the response as the third-party API's. Otherwise Maton produced it.

proxy_status = response.headers.get("Proxy-Status", "")
from_third_party = "received-status=" in proxy_status and "error=" not in proxy_status

Proxy-Status is informational. It can be missing, for example on errors returned by the load balancer in front of the gateway, so fall back to the status code and body when it's absent. If an upstream service sends its own Proxy-Status, Maton keeps it and appends its entry after it (ExampleCDN, Maton; received-status=503).

Rate Limits

Maton enforces two limits per account, in addition to each third-party API's own limits:

  • Request rate: 10 requests per second.
  • Daily quota: a per-app daily budget, for the apps listed below.

To raise either limit, reach out to support@maton.ai.

Rate limit headers

Maton reports its limits with the RateLimit and RateLimit-Policy headers from the IETF RateLimit header fields draft. Each entry is named after the Maton limit it describes:

PolicySent onMeaning
maton-dailyResponses for apps with a daily quotaq is the daily quota, w the window in seconds, r what is left, t seconds until the window resets
maton-burstA 429 from the request rate limitq is the requests allowed per w seconds; retry after t
200 OK
Proxy-Status: Maton; received-status=200
RateLimit-Policy: "maton-daily";q=3000;w=86400
RateLimit: "maton-daily";r=2898;t=86363

When Maton itself returns a 429, it also sends Retry-After with the seconds to wait:

429 Too Many Requests
Proxy-Status: Maton; error=http_request_error
RateLimit-Policy: "maton-burst";q=10;w=1
RateLimit: "maton-burst";r=0;t=1
Retry-After: 1

{
  "message": "Rate limit of 10 requests per second per account exceeded. Retry after 1 second.",
  "type": "Too Many Requests",
  "code": 429
}

A 429 from the third-party API is passed through unchanged, with Proxy-Status: Maton; received-status=429. Follow its own Retry-After header if it sends one. Maton never adds or changes Retry-After on a relayed response, and keeps any RateLimit entries the third-party API sends ahead of its own.

Daily quotas

AppDaily quotaCost per request
Google Ads1,0001
QuickBooks1,0001
YouTube3,000Based on YouTube's quota cost, for example 100 for search and 50 for most writes
Google Business Profile5,000500 to create a location, 250 for googleLocations:search, 1 for everything else
  • The window is 24 hours and starts with your first request for that app. The t value in RateLimit shows when it resets.
  • Requests that fail with a 5xx, or that Maton rejects before calling the third-party API, don't count. For QuickBooks, requests the API rejects with a 4xx don't count either.
  • When a request costs more than what is left, Maton returns 429 and reports the remaining quota, so cheaper requests can still go through:
429 Too Many Requests
Proxy-Status: Maton; error=http_request_error
RateLimit-Policy: "maton-daily";q=3000;w=86400
RateLimit: "maton-daily";r=99;t=86374
Retry-After: 86374

{
  "message": "Not enough daily quota left for app `youtube`: this request costs 100 units and 99 of 3000 remain. It resets at 2026-10-02T03:21:37Z (in 24h 0m). Contact support@maton.ai to request quota increase.",
  "type": "Too Many Requests",
  "code": 429
}

Pagination

The list response includes a next_token. Pass it back on the next request to fetch the following page. When next_token is absent, you have reached the last page.

# First page
curl "https://api.maton.ai/triggers?limit=20" \
  -H "Authorization: Bearer $MATON_API_KEY"

# Next page — use next_token from the previous response
curl "https://api.maton.ai/triggers?limit=20&next_token=gAAAAABq..." \
  -H "Authorization: Bearer $MATON_API_KEY"

On this page