Overview
Base URL
https://api.maton.aiQuickstart
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
}
}| Status | Meaning | What to do |
|---|---|---|
400 | Invalid 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 |
401 | Missing or invalid API key | Verify your Authorization: Bearer header |
404 | Resource not found or not owned by your account, including a Maton-Connection ID that isn't yours | Verify the ID. For Maton-Connection, use one of the ACTIVE connections the message lists |
429 | Rate limit or daily quota exceeded | Wait the number of seconds in Retry-After, then retry |
500 | Internal server error | Recreate the connection and retry |
502 | The third-party API could not be reached, or closed the connection | Retry after a short delay |
504 | The third-party API did not respond in time | Retry 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-Status | Source | Meaning |
|---|---|---|
Maton; received-status=404 | Third-party API | Maton relayed the third-party response. received-status is the status the third-party API sent. |
Maton; error=http_request_error | Maton | Maton 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_error | Maton | An internal Maton error (500). |
Maton; error=connection_timeout | Maton | Maton 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:
error | Status | What happened |
|---|---|---|
connection_timeout | 504 | Opening a connection to the third-party API timed out |
connection_read_timeout | 504 | The third-party API did not respond in time |
connection_write_timeout | 504 | Sending the request to the third-party API timed out |
connection_limit_reached | 504 | No connection to the third-party API became available in time |
dns_error | 502 | The third-party API's hostname could not be resolved |
connection_refused | 502 | The third-party API refused the connection |
destination_unavailable | 502 | The third-party API could not be reached |
tls_certificate_error | 502 | The third-party API's TLS certificate could not be verified |
tls_protocol_error | 502 | The TLS handshake with the third-party API failed |
connection_terminated | 502 | The connection closed before a complete response arrived |
http_protocol_error | 502 | The third-party API sent a malformed HTTP response |
http_response_incomplete | 502 | The 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_statusProxy-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:
| Policy | Sent on | Meaning |
|---|---|---|
maton-daily | Responses for apps with a daily quota | q is the daily quota, w the window in seconds, r what is left, t seconds until the window resets |
maton-burst | A 429 from the request rate limit | q 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=86363When 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
| App | Daily quota | Cost per request |
|---|---|---|
| Google Ads | 1,000 | 1 |
| QuickBooks | 1,000 | 1 |
| YouTube | 3,000 | Based on YouTube's quota cost, for example 100 for search and 50 for most writes |
| Google Business Profile | 5,000 | 500 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
tvalue inRateLimitshows 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 a4xxdon't count either. - When a request costs more than what is left, Maton returns
429and 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"