Endpoints

m8tes is the hosted agent platform: agents are API resources with per-user isolation (user_id), memory, scheduling, approvals, and 190+ integrations built in.

Base URL: https://api.m8tes.ai/api/v2

Machine-readable schema: GET https://api.m8tes.ai/api/v2/openapi.json (V2 routes only; no auth).

All requests require an API key in the Authorization header:

Authorization: Bearer m8_your_key_here

Signup

Create an account and get an API key. No authentication required. See the Quick Start for the Python equivalent (m8tes.signup()). Runs are prepaid: a new API account includes $1 test credit on deepseek-v4-1-flash. For other models or more runs, top up or connect a model subscription — a run with no balance fails with TOKEN_BALANCE_DEPLETED and a topup_url. Email verification is separate: once you can run, you can complete up to 25 runs before verifying (not free credit).

Signup

POST /signup

FieldTypeRequiredDescription
emailstringyesEmail address.
first_namestringyesFirst Name
passwordstringnoPassword
productstringnoProduct
require_end_user_idboolnoStrict user_id enforcement. Defaults to ON for API signups and OFF for Platform. Set false for personal development only; this persists account-wide. For customer-facing apps, keep strict mode on and pass user_id. Change via PATCH /settings.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "founder@acme.com","first_name": "Sam","product": "platform"}'

Token

Mint or rotate your API key. Rotating invalidates the previous key immediately.

Get Token

POST /token

FieldTypeRequiredDescription
emailstringyesEmail address.
passwordstringyesPassword
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/token \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com","password": "your-password"}'

Verify

Email verification is separate. Once you can run (test credit, top-up, or own-subscription OAuth), you can complete up to 25 runs before verifying; after that, runs fail with EMAIL_VERIFICATION_REQUIRED until the emailed link is clicked. Poll status to learn when that happened.

Resend Verify

POST /verify/resend

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/verify/resend \
  -H "Authorization: Bearer m8_your_key_here"

Verify Status

GET /verify/status

cURL
curl \
  https://api.m8tes.ai/api/v2/verify/status \
  -H "Authorization: Bearer m8_your_key_here"

Agents

Agents are reusable personas with instructions and tools. Coding: bind repositories under /agents/{id}/repos after connecting GitHub App.

Create Agent

POST /agents

FieldTypeRequiredDescription
namestringnoDefaults to a random name if omitted. Blank strings are rejected.
instructionsstringnoAgent system-prompt instructions.
toolsstring[]noTool names, e.g. ['gmail', 'slack']
enable_memoryboolnoDefault for the memory tool. Null = platform default (on).
enable_historyboolnoDefault for the task-history tool. Null = platform default (on).
enable_task_setup_toolsboolnoDefault for the same-scope task-setup tools. Null = platform default (on).
enable_feedbackboolnoDefault for the issue-reporting feedback tool. Null = platform default (on).
enable_self_improvementboolnoWhen true, the agent runs a weekly review-and-improve task: it reads its own recent runs and improves itself by rewriting its instructions, refining/creating its tasks, and recording lessons/memory. Implies the task-setup, history, and memory tools. Null/false = off.
rolestringnoAgent persona role, e.g. 'Customer support specialist'.
prompt_profilestringnoWhich system prompt this agent runs on. "platform" (default) is the full m8tes prompt: the agent knows it runs on m8tes and can guide the owner through the product. "bare" is for embedding the agent in YOUR product: it never names m8tes, never calls itself a mate, is never told our billing model, and cannot load m8tes' own Agent Skills. It keeps every capability and safety rule; only the branding is removed. Your own skills, tools, and instructions are unaffected. Read the exact prompt an agent will receive with GET /api/v2/agents/{id}/system-prompt.
disabled_builtin_toolsstring[]noBuilt-in tools this agent must NOT be given, by name from GET /api/v2/built-in-tools (e.g. ["feedback", "notify", "computer_use"]). Any built-in can be switched off. Useful when embedding an agent in your own product, where our issue-reporting and owner-notify tools have no place. Omit or [] to keep the platform defaults. Unknown names are rejected.
goalsstringnoAgent goals. Injected into system prompt.
user_idstringnoYour end-user ID for data isolation for this agent.
metadataobjectnoArbitrary key-value data
allowed_sendersstring[]noEmail addresses or @domain patterns allowed to email this agent. Overrides default.
inbound_imessage_enabledboolnoEnable inbound iMessage routing for this agent.
imessage_chat_guidstringnoBlueBubbles chat GUID used for inbound routing and outbound replies.
bridge_idintnoBlueBubblesBridge to route this agent's iMessage through (account-scoped).
allowed_imessage_sendersstring[]noSender handles (phone/email) allowed to trigger this agent via iMessage. Required (fail-closed) when iMessage is enabled.
inbound_slack_enabledboolnoDeprecated toggle. Prefer slack_channels: assigning channels turns Slack on for this agent; clearing them turns it off. The Lead mate does not need this: it already answers every unbound channel and every DM.
slack_slugstringnoDeprecated. Inbound Slack no longer routes by a typed handle; @m8tes in a bound channel reaches this agent. Kept for read compatibility.
slack_channelsany[]noSlack channels this agent answers. @m8tes in one of these rooms reaches this agent; every other channel and every DM reaches the Lead mate. One agent per channel. Omit on create to leave unbound.
allowed_slack_sendersstring[]noSlack user IDs allowed to trigger this agent. None/empty = any member of the workspace that installed the app (the installer is always allowed). This list narrows WHO may trigger inside that workspace; it cannot admit someone outside it. A guest in a shared/Slack Connect channel is refused even if listed, because a Slack user ID is scoped to one workspace.
email_inboxboolnoEnable email inbox on creation
webhookboolnoEnable webhook trigger on creation. Returns webhook_url once.
default_permission_modestringnoDefault execution mode for direct runs and the starting mode for new tasks. autonomous=auto-approve all tools, approval=pause and ask before tool use, plan=require plan approval before execution. Omitted defaults to approval.
modelstringnoModel for runs: a short Claude alias (sonnet, opus, fable) or any concentrate catalog id from GET /api/v2/models. Check zdr_supported / zdr_providers on each model (provider capability only, not ZDR on for every route or your account). Omit or null = platform default (the GET /api/v2/models entry flagged default: true, currently deepseek-v4-1-flash at high reasoning effort). Discover ids, prices, and ZDR via GET /api/v2/models.
effortstringnoReasoning effort for runs: low | medium | high | xhigh | max. Omit or null = platform default (max on Claude models: reason as hard as the model allows; high on all others). Higher effort = deeper reasoning, more tokens, higher cost per run; lower effort = faster and cheaper. xhigh and max are Claude-only tiers: on other models they are clamped down to high, never rejected (each model's ceiling is max_effort on GET /api/v2/models). Per-run effort overrides the task's, which overrides the agent's; resumes and replies inherit the run's effort. Effort currently takes effect on Claude models, the GPT-5.6 / GPT-6 families (incl. gpt-6.1-sol / gpt-6-sol / gpt-6-luna / gpt-6-astra), and grok-4.6; the platform-default gateway route (deepseek-v4-1-flash) pins high reasoning effort. Other models accept the field but may ignore it.
from_templatestringnoEnable a pre-built agent template by slug (e.g. 'ppc-manager'). When set, system_prompt + default tasks + required integrations are applied from the template. The agent stays linked: improvements we ship to the template flow through automatically unless you customize a field (which then takes precedence). Other body fields EXCEPT user_id, metadata, and onboarding/wizard fields may NOT be set alongside from_template. Use PATCH to customize after creation. See GET /api/v2/agent-templates for available slugs.
onboarding_answersobjectnoWizard answers keyed by OnboardingQuestion.key. Saved as account-level memories so the agent reads them on every run. Only valid alongside from_template. All keys optional; unknown keys are silently ignored.
enabled_task_slugsstring[]noWhich template default_tasks to seed. Null = all of them. Lets the wizard customer uncheck specific recurring tasks before enabling. Only valid alongside from_template.
enabled_bootstrap_slugsstring[]noWhich template bootstrap_tasks to seed. Null = all of them. Bootstrap tasks run once on enable. Only valid alongside from_template.
group_idintnoOptional mate group (GET /api/v2/groups) to place this agent in at create. Must be in the same end-user scope. The Lead mate cannot join a group.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support bot","instructions": "Handle tickets","tools": ["gmail"]}'

Response 201 Created

JSON
{
  "id": 1,
  "name": "support bot",
  "instructions": "Handle tickets",
  "tools": [
    "gmail"
  ],
  "default_permission_mode": "autonomous",
  "inbound_imessage_enabled": false,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

List Agents

GET /agents

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
include_archivedboolInclude archived agents (status 'archived') so they can be unarchived.
fieldsstringOptional list projection. Pass summary to omit heavy persona fields (instructions, goals, role, metadata, tools, slack channel bindings) for roster/sidenav clients. Sender allowlists still round-trip for managers so channel editors that save from the roster row cannot silently broaden access. Default (omit) keeps the full TeammateResponse for existing SDK and detail consumers.
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/agents?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

All list endpoints return a paginated envelope:

JSON
{
  "data": [...],
  "has_more": true
}

Use starting_after with the last item's ID to fetch the next page.

Get Agent

GET /agents/{agent_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/agents/1 \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 1,
  "name": "support bot",
  "instructions": "Handle tickets",
  "tools": [
    "gmail"
  ],
  "default_permission_mode": "autonomous",
  "inbound_imessage_enabled": false,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Update Agent

PATCH /agents/{agent_id}

FieldTypeRequiredDescription
namestringnoAgent name.
instructionsstringnoAgent system-prompt instructions.
toolsstring[]noTool names, e.g. ['gmail', 'slack'].
enable_memoryboolnoDefault for the memory tool.
enable_historyboolnoDefault for the task-history tool.
enable_task_setup_toolsboolnoDefault for the same-scope task-setup tools.
enable_feedbackboolnoDefault for the issue-reporting feedback tool.
enable_self_improvementboolnoTurn the weekly review-and-improve task on/off. When true the agent reads its own runs and improves itself (implies task-setup/history/memory).
rolestringnoAgent persona role.
prompt_profilestringnoSwitch this agent between the full m8tes prompt ("platform") and the embedding prompt ("bare"). Takes effect on the agent's next run. See the same field on agent create, and GET /api/v2/agents/{id}/system-prompt to read the exact result.
disabled_builtin_toolsstring[]noBuilt-in tools this agent must NOT be given, by name from GET /api/v2/built-in-tools (e.g. ["feedback", "notify", "computer_use"]). Any built-in can be switched off. Useful when embedding an agent in your own product, where our issue-reporting and owner-notify tools have no place. Omit or [] to keep the platform defaults. Unknown names are rejected.
goalsstringnoAgent goals. Injected into the system prompt.
metadataobjectnoArbitrary key-value data.
display_orderintnoManual roster position (lower sorts first). Sort agents by COALESCE(display_order, id) ascending: unplaced agents keep creation order and newly created ones append at the bottom. Send null to un-place the agent (back to creation order).
group_idintnoMove this agent into a mate group (GET /api/v2/groups), or send null to ungroup. Must be same end-user scope. Omit to leave unchanged. Lead mate cannot join a group.
visibilitystringnoWho can see this agent. Omit or send null to leave unchanged.
allowed_sendersstring[]noEmail addresses or @domain patterns allowed to email this agent.
inbound_imessage_enabledboolnoEnable inbound iMessage routing for this agent.
imessage_chat_guidstringnoBlueBubbles chat GUID used for inbound routing and outbound replies.
bridge_idintnoBlueBubbles bridge to route this agent's iMessage through (account-scoped).
allowed_imessage_sendersstring[]noSender handles (phone/email) allowed to trigger this agent via iMessage.
inbound_slack_enabledboolnoDeprecated toggle. Prefer slack_channels: assigning channels turns Slack on for this agent; clearing them turns it off.
slack_slugstringnoDeprecated. Inbound Slack routes by bound channel, not a typed handle.
slack_channelsany[]noReplace this agent's Slack channels. @m8tes in one of these rooms reaches this agent; every other channel and every DM reaches the Lead mate. Pass [] to unbind. Omit to leave unchanged. One agent per channel.
allowed_slack_sendersstring[]noSlack user IDs allowed to trigger this agent. None/empty = any member of the INSTALLED workspace. Narrows who may trigger within that workspace; it cannot admit a guest from another workspace.
default_permission_modestringnoDefault execution mode; null (or omitting the field) leaves it unchanged.
modelstringnoModel for runs: any id from GET /api/v2/models (short Claude aliases or concentrate slugs). Check zdr_supported (provider capability only, not 'ZDR always on'). Explicit null resets to the platform default; omitting the field leaves the current model unchanged.
effortstringnoReasoning effort for runs: low | medium | high | xhigh | max. Omit or null = platform default (max on Claude models: reason as hard as the model allows; high on all others). Higher effort = deeper reasoning, more tokens, higher cost per run; lower effort = faster and cheaper. xhigh and max are Claude-only tiers: on other models they are clamped down to high, never rejected (each model's ceiling is max_effort on GET /api/v2/models). Per-run effort overrides the task's, which overrides the agent's; resumes and replies inherit the run's effort. Effort currently takes effect on Claude models, the GPT-5.6 / GPT-6 families (incl. gpt-6.1-sol / gpt-6-sol / gpt-6-luna / gpt-6-astra), and grok-4.6; the platform-default gateway route (deepseek-v4-1-flash) pins high reasoning effort. Other models accept the field but may ignore it.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/agents/1 \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "renamed bot","tools": ["gmail","slack"]}'

Response 200 OK

JSON
{
  "id": 1,
  "name": "support bot",
  "instructions": "Handle tickets",
  "tools": [
    "gmail"
  ],
  "default_permission_mode": "autonomous",
  "inbound_imessage_enabled": false,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Delete Agent

Soft-deletes (archives) the agent. Returns 204 No Content.

DELETE /agents/{agent_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1 \
  -H "Authorization: Bearer m8_your_key_here"

Disable Agent

POST /agents/{agent_id}/disable

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/disable \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 1,
  "name": "support bot",
  "instructions": "Handle tickets",
  "tools": [
    "gmail"
  ],
  "default_permission_mode": "autonomous",
  "inbound_imessage_enabled": false,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Enable Email Inbox

POST /agents/{agent_id}/email-inbox

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/email-inbox \
  -H "Authorization: Bearer m8_your_key_here"

Enable Agent

POST /agents/{agent_id}/enable

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/enable \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 1,
  "name": "support bot",
  "instructions": "Handle tickets",
  "tools": [
    "gmail"
  ],
  "default_permission_mode": "autonomous",
  "inbound_imessage_enabled": false,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Enable Fetchmail

POST /agents/{agent_id}/fetchmail

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/fetchmail \
  -H "Authorization: Bearer m8_your_key_here"

Configure Agent Repo

POST /agents/{agent_id}/repos

FieldTypeRequiredDescription
repo_full_namestringyesRepo Full Name
modestringnoOmitted: new bindings default to trusted; reconfigure keeps existing mode
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/repos \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"repo_full_name": "..."}'

Reset Agent Overrides

POST /agents/{agent_id}/reset

FieldTypeRequiredDescription
fieldsstring[]noFields to reset. None = reset all overrides.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/reset \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"fields": ["instructions"]}'

Create Slack Channel

POST /agents/{agent_id}/slack-channel

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/slack-channel \
  -H "Authorization: Bearer m8_your_key_here"

Unarchive Agent

POST /agents/{agent_id}/unarchive

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/unarchive \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 1,
  "name": "support bot",
  "status": "disabled",
  "created_at": "2026-01-15T10:00:00Z"
}

List Agent Documents

GET /agents/{agent_id}/documents

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/agents/1/documents \
  -H "Authorization: Bearer m8_your_key_here"

List Agent Repos

GET /agents/{agent_id}/repos

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  https://api.m8tes.ai/api/v2/agents/1/repos \
  -H "Authorization: Bearer m8_your_key_here"

Get Agent System Prompt

GET /agents/{agent_id}/system-prompt

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/agents/1/system-prompt \
  -H "Authorization: Bearer m8_your_key_here"

Set Agent Webhook Enabled

PATCH /agents/{agent_id}/webhook

FieldTypeRequiredDescription
enabledboolyesTrue resumes the webhook, False pauses it
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/agents/1/webhook \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

Disable Email Inbox

DELETE /agents/{agent_id}/email-inbox

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1/email-inbox \
  -H "Authorization: Bearer m8_your_key_here"

Disable Fetchmail

DELETE /agents/{agent_id}/fetchmail

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1/fetchmail \
  -H "Authorization: Bearer m8_your_key_here"

Read Agent Document

GET /agents/{agent_id}/documents/{name}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/agents/1/documents/{name} \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 3,
  "name": "latest-report",
  "summary": "Weekly PPC report",
  "mime_type": "text/markdown",
  "size_bytes": 2048,
  "source": "agent",
  "source_run_id": 42,
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-01-22T10:00:00Z"
}

Remove Agent Repo

DELETE /agents/{agent_id}/repos/{repo_id}

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id} \
  -H "Authorization: Bearer m8_your_key_here"

Approve Agent Repo Commands

POST /agents/{agent_id}/repos/{repo_id}/approve-commands

FieldTypeRequiredDescription
commands_digeststringyesCommands Digest
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id}/approve-commands \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"commands_digest": "..."}'

Get Agent Repo Env

GET /agents/{agent_id}/repos/{repo_id}/env

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id}/env \
  -H "Authorization: Bearer m8_your_key_here"

Set Agent Repo Env

PUT /agents/{agent_id}/repos/{repo_id}/env

FieldTypeRequiredDescription
envobjectyesEnvironment variables the coding run may read (e.g. a test DB URL). Replaces the stored set; a key omitted here is removed. Values are 8-8192 characters: shorter ones cannot be redacted from run output and are refused.
curl \
  -X PUT \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id}/env \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"env": {}}'

Clear Agent Repo Commands

DELETE /agents/{agent_id}/repos/{repo_id}/commands

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id}/commands \
  -H "Authorization: Bearer m8_your_key_here"

Delete Agent Repo Env Key

DELETE /agents/{agent_id}/repos/{repo_id}/env/{key}

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/agents/1/repos/{repo_id}/env/{key} \
  -H "Authorization: Bearer m8_your_key_here"

Agent-Templates

Pre-built agent templates (e.g. ppc-manager). Enable one with agents.create(from_template=...); improvements we ship flow through automatically until you customize a field.

List Agent Templates

GET /agent-templates

curl \
  https://api.m8tes.ai/api/v2/agent-templates

Response 200 OK

JSON
{
  "data": [
    {
      "slug": "ppc-manager",
      "name": "Google Ads",
      "description": "Watches your Google Ads every week. Catches wasted spend.",
      "logo_ref": "google-ads",
      "required_integrations": [
        "google_ads"
      ],
      "role": "Paid search",
      "goals": "Reduce wasted spend, improve CTR.",
      "default_tasks": [],
      "bootstrap_tasks": [],
      "questions": []
    }
  ],
  "has_more": false
}

Models

The model catalog: ids, pricing, ZDR coverage, and reasoning-effort caps for every model an agent or run can use. The default: true flag reflects what your account's runs use when no model is set.

List Models

GET /models

ParameterTypeDescription
zdrboolIf set, filter by provider-level ZDR support (zdr_supported). Not the same as ZDR enabled for your concentrate key.
authorstringFilter by concentrate author/provider slug (e.g. openai, anthropic).
curatedboolIf true, return only the Platform curated short list (not full catalog).
limitintMax results (1-500, default 500)
curl \
  https://api.m8tes.ai/api/v2/models \
  -H "Authorization: Bearer m8_your_key_here"

Model-Connections

List account-level Claude connection status and manage native OpenAI/Codex, Grok/xAI, or Gemini authorization. Provider credentials are captured automatically, encrypted at rest, and never returned.

List Model Connections

GET /model-connections

curl \
  https://api.m8tes.ai/api/v2/model-connections \
  -H "Authorization: Bearer m8_your_key_here"

Clear Model Connection Default

DELETE /model-connections/preferred-default

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/model-connections/preferred-default \
  -H "Authorization: Bearer m8_your_key_here"

Disconnect Model Connection

DELETE /model-connections/{provider}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/model-connections/{provider} \
  -H "Authorization: Bearer m8_your_key_here"

Paste Claude Connection

POST /model-connections/claude/paste

FieldTypeRequiredDescription
access_tokenstringyesAccess Token
refresh_tokenstringnoRefresh Token
expires_at_msintnoExpires At Ms
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/model-connections/claude/paste \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"access_token": "..."}'

Apply Model Connection Default

POST /model-connections/{provider}/apply-default

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/apply-default \
  -H "Authorization: Bearer m8_your_key_here"

Start Model Connection Authorization

POST /model-connections/{provider}/authorizations

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/authorizations \
  -H "Authorization: Bearer m8_your_key_here"

Update Provider Default Model

PATCH /model-connections/{provider}/default-model

FieldTypeRequiredDescription
modelstringyesKnown model ID or alias for this provider; null restores its platform default.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/default-model \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"model": "..."}'

Complete Model Connection Authorization

POST /model-connections/{provider}/authorizations/{state}

FieldTypeRequiredDescription
codestringyesCode
project_idstringnoGemini Cloud project ID when the selected tier requires one
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/authorizations/{state} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"code": "..."}'

Poll Model Connection Authorization

GET /model-connections/{provider}/authorizations/{state}

curl \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/authorizations/{state} \
  -H "Authorization: Bearer m8_your_key_here"

Disconnect Model Connection Account

DELETE /model-connections/{provider}/accounts/{account_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/accounts/{account_id} \
  -H "Authorization: Bearer m8_your_key_here"

Cancel Model Connection Authorization

DELETE /model-connections/{provider}/authorizations/{state}

cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/model-connections/{provider}/authorizations/{state} \
  -H "Authorization: Bearer m8_your_key_here"

Runs

Runs are task executions. They stream Server-Sent Events by default.

Create Run

POST /runs

FieldTypeRequiredDescription
permission_modestringnoExecution mode override. If omitted, inherits from the agent default for direct runs or the saved task mode for task runs. autonomous=auto-approve all tools, approval=pause and ask before tool use, plan=require plan approval before execution
human_in_the_loopboolnoEnable human-in-the-loop features: clarifying questions, tool approval, and plan approval. If omitted, approval/plan inherit as true and autonomous inherits as false.
teammate_idintnoID of the agent to use.
messagestringyesThe user message that drives the run.
skillstringnoForce an invokable skill by slug (custom skill or a platform command like make-skill). Same as starting the message with /slug. See GET /api/v2/skills/invokable. Unknown slugs return 422.
toolsstring[]noTool names, e.g. gmail, slack.
modelstringnoPer-run model override: any id from GET /api/v2/models. Omit to use the agent's model, then the platform default.
effortstringnoPer-run reasoning-effort override. Reasoning effort for runs: low | medium | high | xhigh | max. Omit or null = platform default (max on Claude models: reason as hard as the model allows; high on all others). Higher effort = deeper reasoning, more tokens, higher cost per run; lower effort = faster and cheaper. xhigh and max are Claude-only tiers: on other models they are clamped down to high, never rejected (each model's ceiling is max_effort on GET /api/v2/models). Per-run effort overrides the task's, which overrides the agent's; resumes and replies inherit the run's effort. Effort currently takes effect on Claude models, the GPT-5.6 / GPT-6 families (incl. gpt-6.1-sol / gpt-6-sol / gpt-6-luna / gpt-6-astra), and grok-4.6; the platform-default gateway route (deepseek-v4-1-flash) pins high reasoning effort. Other models accept the field but may ignore it.
streamboolnoReturn Server-Sent Events when true, a JSON object when false.
namestringnoAgent name (creates new if no teammate_id). Blank strings are rejected.
instructionsstringnoAgent instructions
user_idstringnoYour end-user ID for data isolation. If the targeted agent is already scoped, this must match that scope. When omitted, the run inherits the agent's existing scope.
metadataobjectnoArbitrary key-value data. Keys reserved for internal use are ignored (backend run markers such as error_code, auto_retryable, interrupted, and nudged_* delivery stamps).
memoryboolnoSaved memories in agent context. Omit to inherit the agent default.
historyboolnoPrevious run results in agent context. Omit to inherit the agent default.
task_setup_toolsboolnoInternal same-scope management tools for agents, tasks, runs, approvals, files, memories, schedules, inboxes, webhooks, and app connections. Omit to inherit the agent default.
feedbackboolnoInternal issue-reporting feedback tool (report_issue). Omit to inherit the agent default.
email_notificationsboolnoEmail the account when this run finishes. Off by default for one-off runs; the Platform home composer turns it on.
email_inboxboolnoEnable email inbox on the created agent (only applies when creating new)
output_schemaobjectnoJSON Schema the run's final result must match. The structured result comes back on output_data (and on the run.completed webhook). Must be "type": "object"; inline your definitions ($ref/$defs are not supported). output_data is null when the model produced no structured result. Always null-check it.
max_turnsintnoHard cap on conversation turns for each REQUEST on this run (each model reply is one turn). Omit to use the platform default (2000). Persisted on the run, so an automatic continuation of the request in flight — an approval resume, an interrupted-run re-drive — inherits the REMAINING budget (cap - turns already spent on that request) rather than a fresh allotment. A new message the caller sends re-arms the cap. Capped at 2000; the per-run cost watchdog still bounds spend.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"teammate_id": 1,"message": "Close open tickets","stream": false}'

Streaming response: Server-Sent Events: raw flat Claude-native frames (the SDK normalizes these into text-delta, tool-call-start, tool-result-end):

data: {"type": "content_block_delta", "id": "block_1", "delta": {"type": "text_delta", "text": "Hello"}} data: {"type": "tool_use", "id": "tc_1", "name": "gmail_send", "input": {"to": "a@b.com"}} data: {"type": "tool_result", "tool_use_id": "tc_1", "content": "...", "result": "..."} data: {"type": "done", "completion_state": "complete", "stop_reason": "end_turn", "message_count": 2}

Non-streaming response 200 OK:

JSON
{
  "id": 42,
  "teammate_id": 1,
  "status": "running",
  "output": null,
  "user_id": "customer_123",
  "metadata": null,
  "created_at": "2026-01-15T10:05:00Z"
}

Polling: Non-streaming runs return immediately with status: "running". Poll GET /runs/{id} every 2 seconds until status is completed, failed, or cancelled.

Defaults: If permission_mode is omitted, the run inherits the agent's saved default. If human_in_the_loop is omitted, inherited approval and plan runs automatically enable it.

Validation: Explicitly setting human_in_the_loop=false is only valid with permission_mode="autonomous".

Internal tools: task_setup_tools=true by default. Set it to false when you do not want the agent to receive the internal same-scope management tools for agents, tasks, runs, approvals, files, memories, schedules, inboxes, webhooks, and app connections during this run.

End-user scope: when you target an existing scoped agent, the user_id in the request must match that agent's scope. If omitted, the run inherits the agent's existing scope.

Quick start: omit teammate_id and provide name to auto-create an agent:

Python
with client.runs.create(
    message="Draft release notes",
    name="Release Bot",
    instructions="Write concise release notes"
) as stream:
    for event in stream:
        if event.type == "text-delta":
            print(event.delta, end="", flush=True)
print(stream.text)

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

List Runs

GET /runs

ParameterTypeDescription
teammate_idintFilter by agent
task_idintOnly runs of this task: the pull-path for a scheduled/webhook task's run history and outputs
statusstringFilter by status (running, paused, awaiting_approval, completed, failed, cancelled, closed, archived)
archivedstringArchived runs: exclude (the default — archive means hidden), only to read the archive by itself, or include for both in one list.
exclude_platform_runsboolHide the platform's own work: the Lead mate's Day-1 onboarding, its twice-daily pulse, and context maintenance. Chat with the Lead mate still counts, because a user talking to it is the user working. Opt-in: used to ask whether the USER has run anything themselves yet.
include_end_user_runsboolInclude this account's end-user (API tenant) runs when user_id is omitted. The default is account-level only (end_user_id IS NULL), matching /runs/check and /runs/activity, so the operator Activity board does not mix tenant work into first-party runs. The Developer console passes this. Ignored when user_id is set.
sortstringcreated (newest first) or priority: runs needing a human first
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
fieldsstringOptional list projection. Pass summary to skip conversation_messages prose batching for platform list clients (sidenav / useUserRuns): closing_preview uses the stamped closing headline only, and latest_message_preview is omitted. Default (omit) keeps the full DevRunResponse including latest_message_preview for existing SDK and detail consumers.
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/runs?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Create First Session Run

POST /runs/first-session

FieldTypeRequiredDescription
permission_modestringnoExecution mode override. If omitted, inherits from the agent default for direct runs or the saved task mode for task runs. autonomous=auto-approve all tools, approval=pause and ask before tool use, plan=require plan approval before execution
human_in_the_loopboolnoEnable human-in-the-loop features: clarifying questions, tool approval, and plan approval. If omitted, approval/plan inherit as true and autonomous inherits as false.
teammate_idintyesID of the agent to use.
streamboolnoReturn Server-Sent Events when true, a JSON object when false.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/first-session \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"teammate_id": 1}'

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Create Run With Files

Multipart form-data: the payload field carries the exact POST /runs JSON body; files carries the attachments.

cURL
curl -X POST https://api.m8tes.ai/api/v2/runs/with-files \\
  -H "Authorization: Bearer m8_your_key_here" \\
  -F 'payload={"teammate_id": 1, "message": "Summarize this", "stream": false}' \\
  -F 'files=@report.pdf'

POST /runs/with-files

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/with-files \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Get Run Activity

GET /runs/activity

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/runs/activity?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Check Run Freshness

GET /runs/check

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/runs/check?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Needs You

GET /runs/needs-you

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/runs/needs-you?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Run

GET /runs/{run_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/runs/42 \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Answer Question

POST /runs/{run_id}/answer

FieldTypeRequiredDescription
answersobjectyesMap of question text to selected option label
request_idstringnoOptional permission request_id for this AskUserQuestion. Required when multiple questions are pending so the answer targets the right gate (Platform dogfood / multi-pending). Omit to answer the first unanswered ask.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/answer \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"answers": {"What priority?": "High"},"request_id": "req_abc123"}'

Use this for AskUserQuestion responses.

  • If the run is running, the answer is stored and picked up by the active run.
  • If the run is awaiting_approval, the answer resumes execution.
  • If the run is terminal (completed/failed/cancelled), the API returns 409.

Plan mode approvals use this endpoint with {"Plan Approval": "Approve"}.

Approve Permission

POST /runs/{run_id}/approve

FieldTypeRequiredDescription
request_idstringyesPermission request ID
decisionstringyesallow or deny the pending tool request.
rememberboolnoRemember this decision for subsequent matching tool requests during the current run, including after the run pauses and resumes. With decision=allow this also stores a cross-run always-allow policy for the tool (the same one /permissions manages); the response's remembered field reports whether that policy actually persisted. Tools whose meaning requires a fresh human decision cannot be remembered. Re-sending the same decision can upgrade this to true, but cannot turn it back off: the field defaults to false, so an omitted flag is indistinguishable from an explicit opt-out and never revokes a grant. To stop remembering, deny the next request.
reasonstringnoOptional steering in your own words, e.g. with decision=deny, "use the staging board instead". The agent reads it and adapts rather than silently skipping the action. Delivered on both decisions when the run is paused; a deny's reason also reaches a still-live run.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/approve \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "req_abc123","decision": "allow","remember": true}'

Response status: after approval this returns a resolved status (allowed or denied), not pending. The response's resumed and remembered fields report what actually happened: whether a paused run restarted, and whether an always-allow policy persisted.

Remember behavior: remember=true applies to subsequent matching requests during the current run, and with decision=allow it also stores a persistent cross-run always-allow policy (the same one POST /permissions manages; use that endpoint to pre-configure or revoke). Check remembered in the response: the backend refuses to store a policy it would never consult (e.g. strict human-decision tools).

Response 200 OK

JSON
{
  "request_id": "req_abc123",
  "tool_name": "gmail_send",
  "tool_input": {
    "to": "user@example.com"
  },
  "status": "allowed",
  "created_at": "2026-01-15T10:05:30Z",
  "resolved_at": "2026-01-15T10:05:45Z"
}

Archive Run

POST /runs/{run_id}/archive

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/archive \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Cancel Run

Cancel an active run. Returns 409 if the run is already completed, failed, or cancelled.

POST /runs/{run_id}/cancel

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/cancel \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Desktop Ticket

POST /runs/{run_id}/desktop-ticket

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/desktop-ticket \
  -H "Authorization: Bearer m8_your_key_here"

Reply To Run

Send a follow-up message on an existing run to continue the conversation.

Behavior note: runs.reply() inherits the run's settings: the permission mode (approval/plan gates keep applying) and whether the agent may ask questions (human_in_the_loop, as persisted at run creation). Pass human_in_the_loop: false on the reply to pin the legacy always-non-interactive behavior. Runs created before this setting existed stay non-interactive.

Internal tools: replies inherit the previous run's task_setup_tools setting unless you override it in the request body. Tools are automatically inherited from the previous run; to use different tools, start a new run.

POST /runs/{run_id}/reply

FieldTypeRequiredDescription
messagestringyesThe user message that drives the run.
skillstringnoForce an invokable skill by slug for this reply (custom skill or a platform command like make-skill). Same as starting the message with /slug. See GET /api/v2/skills/invokable.
streamboolnoReturn Server-Sent Events when true, a JSON object when false.
reset_auto_recoveryboolnoGrant a fresh automatic recovery budget for an explicit continuation. Applies once when this reply is accepted, including queued replies; cumulative retry history is preserved. Automated recovery loops should leave this false.
toolsstring[]noOverride this reply's app toolset (tool names from GET /api/v2/apps). Omitted or [] = inherit the run's current set ([] is NOT 'no tools'). A changed set persists to the run, so later replies inherit it. Custom MCP server slugs are not accepted here (422).
permission_modestringnoExecution-mode override for this and later replies. Omitted = inherit the mode persisted on the run (the mode it last ran with).
task_setup_toolsboolnoOverride whether the internal same-scope management tools are enabled for this reply. When omitted, inherits the previous run's setting.
feedbackboolnoOverride whether the internal issue-reporting feedback tool (report_issue) is enabled for this reply. When omitted, inherits the previous run's setting.
human_in_the_loopboolnoOverride whether the agent may ask questions (AskUserQuestion) during this reply. When omitted, inherits the setting persisted at run creation (runs created before this field existed stay non-interactive). Pass false to pin the legacy always-non-interactive reply behavior.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/reply \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"message": "What about VIP tickets?","stream": false}'

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Retry Run

POST /runs/{run_id}/retry

ParameterTypeDescription
confirmboolAcknowledge that retrying may repeat actions the run already took. Required when the run performed non-read-only work.
use_creditsboolRetry on the platform default model (m8tes credits) instead of replaying the original run's concrete model. Use after clearing a broken model-plan default so OAuth/subscription failures do not re-pin the same credential. Without this flag, provider auth, quota, own-subscription rate-limit, and availability failures are the one exception to concrete-model replay: recovery resolves the account's current connected/preferred provider and avoids the failed provider when an alternative is connected.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/retry \
  -H "Authorization: Bearer m8_your_key_here"

Response 201 Created

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Share Run

POST /runs/{run_id}/share

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/share \
  -H "Authorization: Bearer m8_your_key_here"

Unarchive Run

POST /runs/{run_id}/unarchive

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/unarchive \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Mark Run Viewed

POST /runs/{run_id}/view

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/view \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Read Desktop

GET /runs/{run_id}/desktop

curl \
  https://api.m8tes.ai/api/v2/runs/42/desktop \
  -H "Authorization: Bearer m8_your_key_here"

List Run Messages

GET /runs/{run_id}/messages

ParameterTypeDescription
include_traceboolAlso return each turn's raw stream events as event_metadata.event_trace. For debugging stream rendering; large, so off by default.
after_sequenceintReturn only messages with sequence greater than this value
before_sequenceintReturn the newest messages with sequence less than this value, oldest of that page first
tailboolReturn the newest page, oldest of that page first, instead of the oldest page
limitintMax results (1-1000, default 500)
curl \
  https://api.m8tes.ai/api/v2/runs/42/messages \
  -H "Authorization: Bearer m8_your_key_here"

Get Run Outcome

GET /runs/{run_id}/outcome

curl \
  https://api.m8tes.ai/api/v2/runs/42/outcome \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "run_id": 42,
  "status": "completed",
  "summary": "Audit done: paused 3 wasteful keywords, saving ~$120/mo.",
  "headline": "wasted spend cut",
  "needs_reply": false,
  "delivery_channel": "email",
  "message_count": 14,
  "input_tokens": 48210,
  "output_tokens": 3120,
  "total_tokens": 51330,
  "cost_usd": "0.4831"
}

List Run Permissions

Returns pending and resolved tool permission requests for runs using approval or plan mode. This list can include tool_name="AskUserQuestion" entries.

GET /runs/{run_id}/permissions

cURL
curl \
  https://api.m8tes.ai/api/v2/runs/42/permissions \
  -H "Authorization: Bearer m8_your_key_here"

Join Run Stream

GET /runs/{run_id}/stream

curl \
  https://api.m8tes.ai/api/v2/runs/42/stream \
  -H "Authorization: Bearer m8_your_key_here"

Update Permission Mode

PATCH /runs/{run_id}/permission-mode

FieldTypeRequiredDescription
permission_modestringyesExecution mode: autonomous, approval, or plan.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/runs/42/permission-mode \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"permission_mode": "autonomous"}'

Mid-run behavior: Call this while the run is running or awaiting_approval.

Autonomous switch: changing to autonomous auto-approves pending tool approval requests and resumes a paused tool approval run.

Questions still wait: AskUserQuestion and plan approvals are not auto-answered. Use POST /runs/{id}/answer for those pauses.

Unshare Run

DELETE /runs/{run_id}/share

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/runs/42/share \
  -H "Authorization: Bearer m8_your_key_here"

Reply To Run With Files

POST /runs/{run_id}/reply/with-files

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/runs/42/reply/with-files \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Audit-Logs

Audit logs provide account-scoped request history for the v2 API.

List Audit Logs

GET /audit-logs

ParameterTypeDescription
actionstringFilter by action: list, read, create, update, delete
resource_typestringFilter by resource type (for example run or task.trigger)
methodstringFilter by HTTP method: GET, POST, PATCH, PUT, DELETE
status_codeintFilter by HTTP status code
authstringFilter by how the request was authenticated: api_key (SDK/API calls made with an m8_ key), dashboard (web app sessions and auth/tool events), or all. Defaults to all so this stays a complete security trail.
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
cURL
curl \
  https://api.m8tes.ai/api/v2/audit-logs \
  -H "Authorization: Bearer m8_your_key_here"

Tasks

Tasks are reusable job definitions. Create a task, then attach triggers via tasks.triggers.create().

Create Task

POST /tasks

FieldTypeRequiredDescription
teammate_idintyesID of the agent to use.
namestringnoDisplay name.
instructionsstringyesInstructions
toolsstring[]noTool names, e.g. gmail, slack.
enable_memoryboolnoDefault for the memory tool.
enable_historyboolnoDefault for the task-history tool.
enable_task_setup_toolsboolnoDefault for the same-scope task-setup tools.
enable_feedbackboolnoDefault for the issue-reporting feedback tool.
enable_lessonsboolnoWhether this task's agent accumulates self-improvement lessons across its runs. Task-level only (no agent/run cascade).
expected_outputstringnoDescription of expected output format.
goalsstringnoTask-specific goals.
user_idstringnoYour end-user ID for data isolation. If the targeted agent is already scoped, this must match that scope. When omitted, the task inherits the agent's existing scope.
email_notificationsboolnoSend email notification when scheduled run completes
webhookboolnoEnable webhook trigger on creation. Returns webhook_url once.
schedulestringnoCron expression, e.g. '0 9 * * 1-5'
schedule_timezonestringnoTimezone for schedule, e.g. 'America/New_York'
modelstringnoModel for this task's runs, overriding the agent's. Same ids as the agent model field. Omit or null = inherit the agent's model (then the platform default). Use it to put one expensive task on a stronger model without moving the agent's other work.
effortstringnoReasoning effort for this task's runs, overriding the agent's. Same tiers as the agent effort field. Omit or null = inherit the agent's effort. A tier above the resolved model's max_effort is clamped down, never rejected.
permission_modestringnoHow this task's runs handle approvals: autonomous, approval, or plan. Defaults to the agent's mode.
workflow_stagestringnoBoard position for this task. Omit to start unassigned.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/tasks \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"teammate_id": 1,"name": "Ticket Closer","instructions": "Close open support tickets"}'

Response 201 Created

JSON
{
  "id": 10,
  "teammate_id": 1,
  "name": "Ticket Closer",
  "instructions": "Close open support tickets",
  "tools": [],
  "app_trigger_count": 0,
  "email_notifications": true,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

List Tasks

GET /tasks

ParameterTypeDescription
teammate_idintFilter by agent
include_archivedboolInclude archived tasks. Off by default so the common case stays clean.
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/tasks?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Task

GET /tasks/{task_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/tasks/10 \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 10,
  "teammate_id": 1,
  "name": "Ticket Closer",
  "instructions": "Close open support tickets",
  "tools": [],
  "app_trigger_count": 0,
  "email_notifications": true,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Update Task

PATCH /tasks/{task_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
instructionsstringnoInstructions
toolsstring[]noTool names, e.g. gmail, slack.
enable_memoryboolnoDefault for the memory tool.
enable_historyboolnoDefault for the task-history tool.
enable_task_setup_toolsboolnoDefault for the same-scope task-setup tools.
enable_feedbackboolnoDefault for the issue-reporting feedback tool.
enable_lessonsboolnoWhether this task accumulates self-improvement lessons.
expected_outputstringnoDescription of the expected output format.
goalsstringnoGoals
email_notificationsboolnoEmail the responsible human when a scheduled run completes.
modelstringnoModel for this task's runs, overriding the agent's. Same ids as the agent model field. Omit or null = inherit the agent's model (then the platform default). Use it to put one expensive task on a stronger model without moving the agent's other work.
effortstringnoReasoning effort for this task's runs, overriding the agent's. Same tiers as the agent effort field. Omit or null = inherit the agent's effort. A tier above the resolved model's max_effort is clamped down, never rejected.
permission_modestringnoHow this task's runs handle approvals: autonomous, approval, or plan. Omit or pass null to leave unchanged.
statusstringnoEnable or disable the task. Disabling pauses its schedules and event triggers; re-enabling re-arms the paused schedules, but event triggers stay off until re-enabled explicitly (PATCH the trigger).
workflow_stagestringnoMove this task on its workflow board. Omit to leave unchanged.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/tasks/10 \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"instructions": "Updated instructions"}'

Response 200 OK

JSON
{
  "id": 10,
  "teammate_id": 1,
  "name": "Ticket Closer",
  "instructions": "Close open support tickets",
  "tools": [],
  "app_trigger_count": 0,
  "email_notifications": true,
  "status": "enabled",
  "created_at": "2026-01-15T10:00:00Z"
}

Delete Task

DELETE /tasks/{task_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/tasks/10 \
  -H "Authorization: Bearer m8_your_key_here"

Run Task

POST /tasks/{task_id}/runs

FieldTypeRequiredDescription
permission_modestringnoExecution mode override. If omitted, inherits from the agent default for direct runs or the saved task mode for task runs. autonomous=auto-approve all tools, approval=pause and ask before tool use, plan=require plan approval before execution
human_in_the_loopboolnoEnable human-in-the-loop features: clarifying questions, tool approval, and plan approval. If omitted, approval/plan inherit as true and autonomous inherits as false.
streamboolnoReturn Server-Sent Events when true, a JSON object when false.
modelstringnoPer-run model override: any id from GET /api/v2/models. Omit to use the agent's model, then the platform default.
effortstringnoPer-run reasoning-effort override. Reasoning effort for runs: low | medium | high | xhigh | max. Omit or null = platform default (max on Claude models: reason as hard as the model allows; high on all others). Higher effort = deeper reasoning, more tokens, higher cost per run; lower effort = faster and cheaper. xhigh and max are Claude-only tiers: on other models they are clamped down to high, never rejected (each model's ceiling is max_effort on GET /api/v2/models). Per-run effort overrides the task's, which overrides the agent's; resumes and replies inherit the run's effort. Effort currently takes effect on Claude models, the GPT-5.6 / GPT-6 families (incl. gpt-6.1-sol / gpt-6-sol / gpt-6-luna / gpt-6-astra), and grok-4.6; the platform-default gateway route (deepseek-v4-1-flash) pins high reasoning effort. Other models accept the field but may ignore it.
user_idstringnoYour end-user ID for data isolation. If the saved task is already scoped, this must match that scope. When omitted, the run inherits the task's existing scope.
metadataobjectnoArbitrary key-value data. Keys reserved for internal use are ignored (backend run markers such as error_code, auto_retryable, interrupted, and nudged_* delivery stamps).
output_schemaobjectnoJSON Schema the run's final result must match. The structured result comes back on output_data (and on the run.completed webhook). Must be "type": "object"; inline your definitions ($ref/$defs are not supported). output_data is null when the model produced no structured result. Always null-check it. Applies to THIS run only: a scheduled run of the same task does not inherit it.
max_turnsintnoHard cap on conversation turns for each REQUEST on this run (each model reply is one turn). Omit to use the platform default (2000). Persisted on the run, so an automatic continuation of the request in flight — an approval resume, an interrupted-run re-drive — inherits the REMAINING budget (cap - turns already spent on that request) rather than a fresh allotment. A new message the caller sends re-arms the cap. Capped at 2000; the per-run cost watchdog still bounds spend. Applies to THIS run only.
memoryboolnoSaved memories in agent context. Omit to inherit the agent default.
historyboolnoPrevious run results in agent context. Omit to inherit the agent default.
task_setup_toolsboolnoInternal same-scope management tools for agents, tasks, runs, approvals, files, memories, schedules, inboxes, webhooks, and app connections. Omit to inherit the agent default.
feedbackboolnoInternal issue-reporting feedback tool (report_issue). Omit to inherit the agent default.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/tasks/10/runs \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"stream": false}'

Defaults: If permission_mode is omitted, the run inherits the saved task's permission mode. If human_in_the_loop is omitted, inherited approval and plan runs automatically enable it.

Validation: Explicitly setting human_in_the_loop=false is only valid with permission_mode="autonomous".

Internal tools: task_setup_tools=true by default. Set it to false to run a saved task without the internal same-scope management tools.

End-user scope: when the saved task is already scoped, the user_id in the request must match that scope. If omitted, the run inherits the task's existing scope.

Response 200 OK

JSON
{
  "id": 42,
  "teammate_id": 1,
  "task_id": 10,
  "status": "running",
  "user_id": "customer_123",
  "permission_mode": "autonomous",
  "created_at": "2026-01-15T10:05:00Z"
}

Create Trigger

POST /tasks/{task_id}/triggers

FieldTypeRequiredDescription
typestringyesTrigger type, e.g. schedule, webhook, or an app event.
cronstringnoCron expression (for schedule triggers).
interval_secondsintnoInterval in seconds (for recurring triggers).
run_atstringnoISO 8601 datetime for a ONE-TIME run (e.g. '2027-07-20T09:00:00'). Fires once, then retires itself. Naive values are read in timezone.
timezonestringnoIANA timezone for schedule evaluation.
appstringnoApp name, e.g. 'github'
trigger_namestringnoTrigger slug, e.g. 'GITHUB_COMMIT_EVENT'
trigger_configobjectnoTrigger-specific config
user_idstringnoEnd-user whose connected account to use
allowed_sendersstring[]noAllowed Senders
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/tasks/10/triggers \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"type": "schedule","cron": "0 9 * * 1","timezone": "America/New_York"}'

Response 201 Created

JSON
{
  "id": "schedule_5",
  "type": "schedule",
  "enabled": true,
  "cron": "0 9 * * 1",
  "timezone": "America/New_York"
}

Enable Task Webhook

POST /tasks/{task_id}/webhook

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/tasks/10/webhook \
  -H "Authorization: Bearer m8_your_key_here"

List Triggers

GET /tasks/{task_id}/triggers

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/tasks/10/triggers \
  -H "Authorization: Bearer m8_your_key_here"

Set Task Webhook Enabled

PATCH /tasks/{task_id}/webhook

FieldTypeRequiredDescription
enabledboolyesTrue resumes the webhook, False pauses it
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/tasks/10/webhook \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

Disable Task Webhook

DELETE /tasks/{task_id}/webhook

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/tasks/10/webhook \
  -H "Authorization: Bearer m8_your_key_here"

Update Trigger

PATCH /tasks/{task_id}/triggers/{trigger_id}

FieldTypeRequiredDescription
enabledboolnoEnabled
cronstringnoCron expression (for schedule triggers).
interval_secondsintnoInterval in seconds (for recurring triggers).
run_atstringnoISO 8601 datetime. Reshapes this trigger into a one-time run.
timezonestringnoIANA timezone for schedule evaluation.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/tasks/10/triggers/schedule_5 \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

Response 200 OK

JSON
{
  "id": "schedule_5",
  "type": "schedule",
  "enabled": true,
  "cron": "0 9 * * 1",
  "timezone": "America/New_York"
}

Delete Trigger

DELETE /tasks/{task_id}/triggers/{trigger_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/tasks/10/triggers/schedule_5 \
  -H "Authorization: Bearer m8_your_key_here"

Triggers

List schedule, webhook, email, and app triggers across the account without fetching each task separately.

List Triggers

GET /triggers

ParameterTypeDescription
typestring
task_idint
limitintMax results (1-100, default 20)
starting_afterstringCursor from the last item: <task_id>:<trigger_id>.
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/triggers?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Lessons

Self-improvement lessons a task's agent writes for itself across runs. The agent authors them during runs; via the API you can list or clear them per task.

Clear Lessons

POST /tasks/{task_id}/lessons:clear

ParameterTypeDescription
confirmboolRequired to confirm. Pass ?confirm=true to actually clear.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/tasks/10/lessons:clear \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "capacity_limit": 20,
  "capacity_used": 0,
  "data": []
}

List Lessons

GET /tasks/{task_id}/lessons

cURL
curl \
  https://api.m8tes.ai/api/v2/tasks/10/lessons \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "capacity_limit": 20,
  "capacity_used": 0,
  "data": []
}

Delete Lesson

DELETE /tasks/{task_id}/lessons/{lesson_id}

cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/tasks/10/lessons/{lesson_id} \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "capacity_limit": 20,
  "capacity_used": 0,
  "data": []
}

Apps

List available tools, connect integrations via OAuth, and manage end-user connections.

List Apps

Pass user_id to check connection status for a specific end-user.

GET /apps

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/apps?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Connections

GET /apps/connections

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/apps/connections?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Claim Slack Install

POST /apps/{app_name}/claim

FieldTypeRequiredDescription
ticketstringyesTicket
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/claim \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"ticket": "..."}'

Connect App

POST /apps/{app_name}/connect

FieldTypeRequiredDescription
redirect_uristringyesURL to redirect after OAuth. For end-user connections (user_id set) this is your own callback. For ACCOUNT-level connections it must be a m8tes URL (https://www.m8tes.ai/apps): the claim ticket that authorizes completion is delivered here, so it has to reach the browser that authorized rather than the caller that created the link. That means an account-level connect finishes in the browser, not in your backend. Read composio_claim off the URL you land on and pass it to connect/complete. To connect on behalf of one of your own users from your own app, pass user_id: that keeps your callback and needs no ticket.
user_idstringnoEnd-user ID for multi-tenant connections
add_accountboolnoConnect ANOTHER account of this app next to the existing one (a second Gmail inbox) instead of replacing it. Agents then pick the account per tool call.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/connect \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uri": "https://yourapp.com/callback","user_id": "cust_123"}'

Response 200 OK

JSON
{
  "authorization_url": "https://accounts.google.com/o/oauth2/auth?...",
  "connection_id": "conn_abc123"
}

Start Slack Install

POST /apps/{app_name}/install

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/install \
  -H "Authorization: Bearer m8_your_key_here"

Provision App

For apps with auth_type: "platform_provisioned" (e.g. twilio), the platform allocates a dedicated resource (a phone number) rather than you supplying credentials. Pass user_id to provision a per-end-user resource (strictly isolated at run time); omit it for an account-level resource. Release it with DELETE /apps/{app_name}/connections (client.apps.release(...)).

POST /apps/{app_name}/provision

FieldTypeRequiredDescription
user_idstringnoEnd-user ID to provision for (omit for account-level)
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/twilio/provision \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "cust_123"}'

Response 200 OK

JSON
{
  "status": "provisioned",
  "app": "twilio",
  "phone_number": "+15551234567"
}

List Slack Channels

GET /apps/{app_name}/channels

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  https://api.m8tes.ai/api/v2/apps/slack:messaging/channels \
  -H "Authorization: Bearer m8_your_key_here"

List App Connections

GET /apps/{app_name}/connections

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/apps/gmail/connections \
  -H "Authorization: Bearer m8_your_key_here"

List Google Ads Customers

GET /apps/{app_name}/customers

ParameterTypeDescription
refreshbool
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/apps/gmail/customers \
  -H "Authorization: Bearer m8_your_key_here"

List Slack Members

GET /apps/{app_name}/members

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  https://api.m8tes.ai/api/v2/apps/gmail/members \
  -H "Authorization: Bearer m8_your_key_here"

List Google Search Console Sites

GET /apps/{app_name}/sites

ParameterTypeDescription
refreshbool
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/apps/gmail/sites \
  -H "Authorization: Bearer m8_your_key_here"

List App Tools

GET /apps/{app_name}/tools

curl \
  https://api.m8tes.ai/api/v2/apps/gmail/tools \
  -H "Authorization: Bearer m8_your_key_here"

List App Trigger Types

GET /apps/{app_name}/triggers

curl \
  https://api.m8tes.ai/api/v2/apps/gmail/triggers \
  -H "Authorization: Bearer m8_your_key_here"

List Slack Workspaces

GET /apps/{app_name}/workspaces

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  https://api.m8tes.ai/api/v2/apps/gmail/workspaces \
  -H "Authorization: Bearer m8_your_key_here"

Update App Connection

PATCH /apps/connections/{connection_id}

FieldTypeRequiredDescription
labelstringnoName agents and people use for the account
notesstringnoWhat this account is for, in plain words
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/apps/connections/{connection_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"label": "ops@acme.com","notes": "Customer support replies only"}'

Response 200 OK

JSON
{
  "connection_id": "ca_abc123",
  "status": "active",
  "account_label": "ops@example.com",
  "scopes": [
    "https://mail.google.com/"
  ],
  "updated_at": "2026-08-18T12:00:00Z"
}

Select Google Ads Customer

PUT /apps/{app_name}/customer

FieldTypeRequiredDescription
user_idstringnoEnd-user connection scope
account_idstringyesAccount Id
curl \
  -X PUT \
  https://api.m8tes.ai/api/v2/apps/gmail/customer \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "..."}'

Select Google Search Console Site

PUT /apps/{app_name}/site

FieldTypeRequiredDescription
user_idstringnoEnd-user connection scope
account_idstringyesAccount Id
curl \
  -X PUT \
  https://api.m8tes.ai/api/v2/apps/gmail/site \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "..."}'

Delete App Connection

DELETE /apps/connections/{connection_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/apps/connections/{connection_id} \
  -H "Authorization: Bearer m8_your_key_here"

Disconnect App

DELETE /apps/{app_name}/connections

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/apps/gmail/connections \
  -H "Authorization: Bearer m8_your_key_here"

Connect App Api Key

POST /apps/{app_name}/connect/api-key

FieldTypeRequiredDescription
api_keystringyesAPI key for the integration
user_idstringnoEnd-user ID for multi-tenant connections
agent_idintnoAgent ID to grant this app after connecting (account scope)
add_accountboolnoConnect ANOTHER account of this app next to the existing one (a second Gmail inbox) instead of replacing it. Agents then pick the account per tool call.
optionsobjectnoProvider options, such as region
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/connect/api-key \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"api_key": "sk-...","user_id": "cust_123"}'

Response 200 OK

JSON
{
  "status": "connected",
  "app": "gmail"
}

Connect App Complete

After the user completes OAuth and is redirected back:

POST /apps/{app_name}/connect/complete

FieldTypeRequiredDescription
connection_idstringnoConnection to complete. Required for end-user connections (user_id set); ignored for account-level ones, where the connection comes from claim_ticket.
claim_ticketstringnoREQUIRED for account-level connections. Appended to your redirect_uri as composio_claim when the OAuth flow returns, so it reaches the browser that authorized. It is what proves the caller is the account that started the flow. without it a connect link could be forwarded and the recipient's provider account bound to whoever sent it. Not used for end-user connections, which the developer hands to their own end user by design.
user_idstringnoEnd-user ID for multi-tenant connections
add_accountboolnoConnect ANOTHER account of this app next to the existing one (a second Gmail inbox) instead of replacing it. Agents then pick the account per tool call.
agent_idintnoAgent ID to grant this app after connecting (account scope)
codestringnoAuthorization code for native OAuth apps such as Google Ads
redirect_uristringnoSame redirect URI used to start a native OAuth connection
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/connect/complete \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connection_id": "conn_abc123","user_id": "cust_123"}'

Response 200 OK

JSON
{
  "status": "connected",
  "app": "gmail"
}

Connect External Oauth

POST /apps/{app_name}/connect/external-oauth

FieldTypeRequiredDescription
redirect_uristringyesm8tes /apps URL on the configured frontend origin. The server adds the app callback marker; use this same URL at completion.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/connect/external-oauth \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uri": "..."}'

Disconnect Slack Workspace

DELETE /apps/{app_name}/workspaces/{team_id}

ParameterTypeDescription
user_idstringMust be omitted: this resource is account-scoped, never per end-user.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/apps/gmail/workspaces/{team_id} \
  -H "Authorization: Bearer m8_your_key_here"

Complete External Oauth

POST /apps/{app_name}/connect/external-oauth/complete

FieldTypeRequiredDescription
redirect_uristringyesm8tes /apps URL on the configured frontend origin. The server adds the app callback marker; use this same URL at completion.
codestringyesCode
statestringyesState
agent_idintnoAgent Id
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/apps/gmail/connect/external-oauth/complete \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uri": "...","code": "...","state": "..."}'

Response 200 OK

JSON
{
  "status": "connected",
  "app": "gmail"
}

Built-In-Tools

Discover the platform's built-in tools (memory, task history, task setup, feedback, and more). These are not passed in the tools array; the four configurable ones are toggled via the enable_* fields on agents, tasks, and runs.

List Built In Tools

GET /built-in-tools

ParameterTypeDescription
teammate_idintResolve enabled state for this teammate's config
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/built-in-tools?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Judgments

Advisory Jev judgments over supplied state or evidence. Platform-funded by default; connect a write-only TypeSafe key once for customer-funded account workloads. Neither mode debits your m8tes balance; customer funding is billed by TypeSafe. Invalid customer credentials never fall back to platform funding. Request-count limits apply to both modes. Results do not grant action permissions or certify source authenticity. Optional Idempotency-Key protects a request for 24 hours; successful results can be retrieved for 30 days. Evidence can reference scoped platform documents or stored tool results with content provenance. See built-in tools.

Create Judgment

Choose mode=decide with state and questions (Choice, Score, Noul), or mode=verify with claims and evidence. Each claim has id, text, and evidence_ids. Evidence requires either text or source (type: document|tool_result, positive record id), not both. References resolve stored content within the same account/end-user scope; tool-result IDs identify persisted conversation messages containing tool-result blocks. Optional user_id sets the end-user scope; run_id attributes a same-scope run. Inputs are limited to 32 questions/claims, 16,384 characters per source, and 128 KiB after source resolution. Oversized evidence is rejected. Results include typed answers, model, usage, latency_ms, and estimated provider cost_usd and funding_source (platform or customer). m8tes does not debit your balance; customer-funded requests are billed by TypeSafe. Verification includes rubric_version, claim/evidence coverage, and content provenance; independently_authenticated is always false. Missing evidence produces insufficient_evidence; if no provider call is needed, model is not_called. Pass the optional Idempotency-Key header (SDK idempotency_key) to protect an exact request for 24 hours. Success replays return the same ID and original cost without another provider charge. Conflicting, pending, or failed attempts return 409. Metadata-only scopes do not retain answers and return 410 on replay. SDK automatic POST retries require a key.

POST /judgments

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/judgments \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"mode": "verify","claims": [{"id": "c1","text": "Tests passed","evidence_ids": ["e1"]}],"evidence": [{"id": "e1","text": "pytest: 12 passed"}]}'

Get Connection

Read account-level TypeSafe metadata only: connected, enabled, funding_source, model, and nullable updated_at. The key is never returned. connected means a key is stored, not validated by the provider. No user_id is accepted: one connection covers all end-user and agent workloads.

GET /judgments/connection

curl \
  https://api.m8tes.ai/api/v2/judgments/connection \
  -H "Authorization: Bearer m8_your_key_here"

Get Judgment

Retrieve a saved successful judgment for 30 days without another provider call. Use the original user_id; missing or foreign IDs return 404. Pending/failed attempts return 409. Expired results and metadata-only retention return 410 result_not_retained. Returned cost_usd is the original estimate, not another charge. Settled known-cost metadata is retained for 90 days; unresolved or unknown-cost reservations remain for reconciliation.

GET /judgments/{judgment_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/judgments/{judgment_id} \
  -H "Authorization: Bearer m8_your_key_here"

Put Connection

Configure once per account from a trusted server, or replace the key to rotate it. Read api_key from a server environment variable or secret manager; never pass it in prompts or agent tool arguments. It is write-only and encrypted at rest. No paid validation occurs when storing the key. Invalid customer credentials never fall back to platform funding. The service kill switch and request-count limits still apply; platform dollar caps do not apply to customer funding. Returns the same metadata as GET.

PUT /judgments/connection

FieldTypeRequiredDescription
api_keystringyesApi Key
curl \
  -X PUT \
  https://api.m8tes.ai/api/v2/judgments/connection \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"api_key": "..."}'

Delete Connection

Remove the account's TypeSafe key. Returns HTTP 204 without a body. Future judgments return to platform funding if available; deletion does not enable a service disabled by the platform.

DELETE /judgments/connection

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/judgments/connection \
  -H "Authorization: Bearer m8_your_key_here"

Mcp-Servers

Register your own REST, remote MCP, or Python script tools. kind=script takes script_source (write-only; reads return script_sha256) and cannot take user_id; strict API accounts can still create one. See the Custom tools guide.

Create Mcp Server

kind is rest_api (default; typed tool_defs with method/path), mcp_http/mcp_sse (remote MCP; empty tool_defs), or script (Python script_source; tool_defs are name-only; user_id is forbidden). script_source is write-only; the response carries script_sha256.

POST /mcp-servers

FieldTypeRequiredDescription
namestringyesDisplay name.
urlstringyesHTTPS endpoint URL to receive deliveries.
kindstringnorest_api (typed endpoints in tool_defs), mcp_http/mcp_sse (remote MCP; empty tool_defs), or script (Python; script_source required, user_id forbidden).
auth_typestringnoAuth Type
auth_configobjectnoAuth Config
secretstringnoSecret
tool_defsany[]noTool Defs
descriptionstringnoDescription
user_idstringnoUser Id
auto_approveboolnoAuto Approve
script_sourcestringnoPython source for kind=script. Write-only; never returned. Must define def <tool_name>(**args) for each tool_def name. Injected names: http, json only.
script_allowliststring[]noExtra https origins a kind=script tool may call (max 8). The primary origin is url (canonical https origin, no path).
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/mcp-servers \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme billing","url": "https://api.acme.com/v1","auth_type": "bearer","secret": "sk-...","tool_defs": [{"method": "GET","name": "get_invoice","path": "/invoices/{id}"}]}'

Create Mcp Server

kind is rest_api (default; typed tool_defs with method/path), mcp_http/mcp_sse (remote MCP; empty tool_defs), or script (Python script_source; tool_defs are name-only; user_id is forbidden). script_source is write-only; the response carries script_sha256.

POST /mcp-servers

FieldTypeRequiredDescription
namestringyesDisplay name.
urlstringyesHTTPS endpoint URL to receive deliveries.
kindstringnorest_api (typed endpoints in tool_defs), mcp_http/mcp_sse (remote MCP; empty tool_defs), or script (Python; script_source required, user_id forbidden).
auth_typestringnoAuth Type
auth_configobjectnoAuth Config
secretstringnoSecret
tool_defsany[]noTool Defs
descriptionstringnoDescription
user_idstringnoUser Id
auto_approveboolnoAuto Approve
script_sourcestringnoPython source for kind=script. Write-only; never returned. Must define def <tool_name>(**args) for each tool_def name. Injected names: http, json only.
script_allowliststring[]noExtra https origins a kind=script tool may call (max 8). The primary origin is url (canonical https origin, no path).
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/mcp-servers \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme billing","url": "https://api.acme.com/v1","auth_type": "bearer","secret": "sk-...","tool_defs": [{"method": "GET","name": "get_invoice","path": "/invoices/{id}"}]}'

List Mcp Servers

GET /mcp-servers

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/mcp-servers?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Mcp Servers

GET /mcp-servers

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/mcp-servers?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Mcp Server

GET /mcp-servers/{server_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Mcp Server

PATCH /mcp-servers/{server_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
urlstringnoHTTPS endpoint URL to receive deliveries.
auth_typestringnoAuth Type
auth_configobjectnoAuth Config
secretstringnoSecret
tool_defsany[]noTool Defs
descriptionstringnoDescription
statusstringnoStatus
auto_approveboolnoAuto Approve
script_sourcestringnoReplace the Python source (kind=script only). Write-only. Clears auto_approve.
script_allowliststring[]noReplace extra https origins (kind=script only). Clears auto_approve.
cURL
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Delete Mcp Server

DELETE /mcp-servers/{server_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id} \
  -H "Authorization: Bearer m8_your_key_here"

Approve Mcp Server

POST /mcp-servers/{server_id}/approve

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id}/approve \
  -H "Authorization: Bearer m8_your_key_here"

Complete Mcp Server Oauth

POST /mcp-servers/{server_id}/oauth/complete

FieldTypeRequiredDescription
codestringyesCode
statestringyesState
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id}/oauth/complete \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"code": "...","state": "..."}'

Start Mcp Server Oauth

POST /mcp-servers/{server_id}/oauth/start

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/mcp-servers/{server_id}/oauth/start \
  -H "Authorization: Bearer m8_your_key_here"

Skills

Reusable SKILL.md playbooks agents load on demand. Create account- or agent-scoped skills; see the Custom Skills guide. Force a skill from chat with /slug or pass skill on create/reply. List picker entries with GET /skills/invokable (platform commands like make-skill / own-task / feedback plus your custom skills — never the baked snapshot catalog).

Create Skill

POST /skills

FieldTypeRequiredDescription
namestringyesDisplay name.
descriptionstringyesDescription
bodystringyesBody
scopestringnoScope
teammate_idintnoID of the agent to use.
user_idstringnoUser Id
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/skills \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme refund playbook","description": "How to process an Acme refund end-to-end.","body": "# Steps\n1. Pull the order in Stripe\n2. ...","scope": "account"}'

Create Skill

POST /skills

FieldTypeRequiredDescription
namestringyesDisplay name.
descriptionstringyesDescription
bodystringyesBody
scopestringnoScope
teammate_idintnoID of the agent to use.
user_idstringnoUser Id
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/skills \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "acme refund playbook","description": "How to process an Acme refund end-to-end.","body": "# Steps\n1. Pull the order in Stripe\n2. ...","scope": "account"}'

List Skills

GET /skills

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/skills?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Skills

GET /skills

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/skills?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Invokable Skills

GET /skills/invokable

ParameterTypeDescription
teammate_idintFilter by agent
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/skills/invokable?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Invokable Skills

GET /skills/invokable

ParameterTypeDescription
teammate_idintFilter by agent
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/skills/invokable?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Skill

GET /skills/{skill_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  https://api.m8tes.ai/api/v2/skills/{skill_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Skill

PATCH /skills/{skill_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
descriptionstringnoDescription
bodystringnoBody
statusstringnoStatus
cURL
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/skills/{skill_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Delete Skill

DELETE /skills/{skill_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/skills/{skill_id} \
  -H "Authorization: Bearer m8_your_key_here"

Bridges

Per-account BlueBubbles bridges that connect an agent to iMessage. Register a bridge, then enable inbound iMessage on an agent with bridge_id.

Create Bridge

POST /bridges

FieldTypeRequiredDescription
namestringnoDisplay name.
server_urlstringyeshttps URL of the BlueBubbles server
passwordstringyesBlueBubbles API password (stored encrypted, never returned)
owner_handlestringnoYour own iMessage handle (phone or email). Authorizes you to text the Lead mate (your inbound-default agent) right away, without editing its allowlist.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"server_url": "https://bluebubbles.example.com","password": "your-password"}'

List Bridges

GET /bridges

cURL
curl \
  https://api.m8tes.ai/api/v2/bridges \
  -H "Authorization: Bearer m8_your_key_here"

Provision Bridge

POST /bridges/provision

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges/provision \
  -H "Authorization: Bearer m8_your_key_here"

Provision Blooio

POST /bridges/provision-blooio

FieldTypeRequiredDescription
numberstringyesThe dedicated Blooio iMessage number (E.164) to route to this account.
api_keystringnoOptional BYO Blooio API key (stored encrypted); omit to use the platform key.
user_idstringnoOptional end-user to bind this dedicated line to (multi-tenant isolation).
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges/provision-blooio \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"number": "+15551234567"}'

Get Bridge

GET /bridges/{bridge_id}

cURL
curl \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Bridge

PATCH /bridges/{bridge_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
server_urlstringnoServer Url
passwordstringnoPassword
statusstringnoStatus
owner_handlestringnoOwner Handle
cURL
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Delete Bridge

DELETE /bridges/{bridge_id}

cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id} \
  -H "Authorization: Bearer m8_your_key_here"

POST /bridges/{bridge_id}/link-code

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id}/link-code \
  -H "Authorization: Bearer m8_your_key_here"

Rotate Bridge Secret

POST /bridges/{bridge_id}/rotate-secret

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id}/rotate-secret \
  -H "Authorization: Bearer m8_your_key_here"

Test Bridge Connection

POST /bridges/{bridge_id}/test

cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id}/test \
  -H "Authorization: Bearer m8_your_key_here"

List Bridge Handles

GET /bridges/{bridge_id}/handles

cURL
curl \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id}/handles \
  -H "Authorization: Bearer m8_your_key_here"

Delete Bridge Handle

DELETE /bridges/{bridge_id}/handles/{handle_id}

cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/bridges/{bridge_id}/handles/{handle_id} \
  -H "Authorization: Bearer m8_your_key_here"

Channels

Slack and GitHub channel identity for this account. With no identity the shared @m8tes Slack app / m8tes GitHub App is used; upsert_identity stores your own Slack app or GitHub App credentials (bot/App name and avatar stay in Slack/GitHub dashboards). channel=github requires github_app_id, github_app_slug, and github_private_key. install_links mints Add-to-Slack and GitHub App install URLs; it 503s if Slack is unavailable even when GitHub is configured.

List Channels

GET /channels

curl \
  https://api.m8tes.ai/api/v2/channels \
  -H "Authorization: Bearer m8_your_key_here"

GET /channels/install-links

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/channels/install-links?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Upsert Identity

PUT /channels/identities

FieldTypeRequiredDescription
channelstringyesslack or github. Email custom domains are a later slice.
client_idstringyesClient Id
client_secretstringyesClient Secret
signing_secretstringyesSigning Secret
github_app_idstringnoRequired when channel is github.
github_app_slugstringnoRequired when channel is github.
github_private_keystringnoRequired when channel is github. Never returned.
curl \
  -X PUT \
  https://api.m8tes.ai/api/v2/channels/identities \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"channel": "slack","client_id": "...","client_secret": "...","signing_secret": "..."}'

Github-App

Coding-agent GitHub install. Account-scoped (not per user_id). m8tes GitHub App: open install_url, then claim(ticket) if the redirect returns one. Your GitHub App: open setup_url, then complete_setup(ticket) stores credentials and returns the install URL. Same claim flow after that. clear_identity reverts after disconnect. Per-agent repos: /agents/{id}/repos. See Coding Agents.

Github App Disconnect

DELETE /github-app

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/github-app \
  -H "Authorization: Bearer m8_your_key_here"

Github App Claim

POST /github-app/claim

FieldTypeRequiredDescription
ticketstringyesTicket
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/github-app/claim \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"ticket": "..."}'

Github App Complete Setup

POST /github-app/complete-setup

FieldTypeRequiredDescription
ticketstringyesTicket
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/github-app/complete-setup \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"ticket": "..."}'

Github App Install Url

GET /github-app/install-url

curl \
  https://api.m8tes.ai/api/v2/github-app/install-url \
  -H "Authorization: Bearer m8_your_key_here"

Github App Repos

GET /github-app/repos

curl \
  https://api.m8tes.ai/api/v2/github-app/repos \
  -H "Authorization: Bearer m8_your_key_here"

Github App Setup Url

GET /github-app/setup-url

ParameterTypeDescription
cancel_epochintusers.github_manifest_epoch from status at click time — rejects if Use m8tes App cleared since
orgstring
namestring
curl \
  https://api.m8tes.ai/api/v2/github-app/setup-url?cancel_epoch=... \
  -H "Authorization: Bearer m8_your_key_here"

Github App Status

GET /github-app/status

curl \
  https://api.m8tes.ai/api/v2/github-app/status \
  -H "Authorization: Bearer m8_your_key_here"

Github App Clear Identity

DELETE /github-app/identity

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/github-app/identity \
  -H "Authorization: Bearer m8_your_key_here"

Memories

Pre-populate or manage end-user memories. Memories are injected into the agent's context during execution to personalize responses.

Create Memory

POST /memories

FieldTypeRequiredDescription
user_idstringnoEnd-user ID this memory belongs to. Omit for an account-level memory (seen by runs that have no user_id).
contentstringyesThe text content to store.
audiencestringnoWhether this memory is about the end-user themselves or about their business. Optional; omit when you cannot tell. Both are read by every agent today. This records the distinction for future visibility rules rather than changing what is injected now.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/memories \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "customer_123","content": "Prefers email over Slack"}'

Response 201 Created

JSON
{
  "id": 7,
  "user_id": "customer_123",
  "content": "Prefers email over Slack",
  "source": "api",
  "created_at": "2026-01-15T10:00:00Z"
}

List Memories

Pass user_id to list one end-user's memories; omit it for account-level memories. The two scopes never mix.

GET /memories

ParameterTypeDescription
querystringOptional keyword filter: case-insensitive substring match on content.
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/memories?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Update Memory

PATCH /memories/{memory_id}

FieldTypeRequiredDescription
contentstringnoThe text content to store.
audiencestringnoReclassify this memory as personal or company. Omitted leaves the existing classification alone. Send it explicitly to correct one. Classification is one-way: you can change personal to company or vice versa, but not back to unclassified, which means 'never classified' rather than 'we decided we cannot tell'. Delete and re-create if you truly need that. Ignored for teammate-scoped memories, whose audience is the teammate.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/memories/7 \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"content": "prefers a concise Monday summary, no charts"}'

Response 200 OK

JSON
{
  "id": 7,
  "user_id": "customer_123",
  "content": "Prefers email over Slack",
  "source": "api",
  "created_at": "2026-01-15T10:00:00Z"
}

Delete Memory

Matches exactly the scope you pass: an end-user memory is only reachable with its user_id, an account-level memory only without one.

DELETE /memories/{memory_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/memories/7 \
  -H "Authorization: Bearer m8_your_key_here"

Documents

Manage persistent company- or agent-scoped context. Agents author document content; API clients can list, read, rename, summarize, and delete it.

List Documents

GET /documents

ParameterTypeDescription
scopestring
agent_idint
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/documents?user_id=customer_123&scope=... \
  -H "Authorization: Bearer m8_your_key_here"

Get Document

GET /documents/{document_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/documents/{document_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Document

PATCH /documents/{document_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
summarystringnoSummary
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/documents/{document_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Delete Document

DELETE /documents/{document_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/documents/{document_id} \
  -H "Authorization: Bearer m8_your_key_here"

Artifacts

Durable, shareable copies of what a run produced. Promote a run file (or its latest-report.md) into an artifact that outlives the sandbox, then mint a public link the way you would for a run.

Create Artifact

POST /artifacts

FieldTypeRequiredDescription
run_idintyesRun Id
filenamestringyesA basename from GET /runs/{id}/files, e.g. latest-report.md
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/artifacts \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"run_id": 1,"filename": "..."}'

List Artifacts

GET /artifacts

ParameterTypeDescription
run_idint
agent_idint
limitintMax results (1-100, default 20)
starting_afterintCursor: ID of last item from previous page
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/artifacts?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Artifact

GET /artifacts/{artifact_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/artifacts/{artifact_id} \
  -H "Authorization: Bearer m8_your_key_here"

Delete Artifact

DELETE /artifacts/{artifact_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/artifacts/{artifact_id} \
  -H "Authorization: Bearer m8_your_key_here"

Share Artifact

POST /artifacts/{artifact_id}/share

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/artifacts/{artifact_id}/share \
  -H "Authorization: Bearer m8_your_key_here"

Get Artifact Content

GET /artifacts/{artifact_id}/content

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/artifacts/{artifact_id}/content \
  -H "Authorization: Bearer m8_your_key_here"

Unshare Artifact

DELETE /artifacts/{artifact_id}/share

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/artifacts/{artifact_id}/share \
  -H "Authorization: Bearer m8_your_key_here"

Feedback

Send product feedback to the m8tes team, optionally attaching a run. Same chokepoint as the /feedback slash command and the MCP submit_feedback tool. Pass user_id when attaching an end-user-scoped run.

Submit feedback

POST /feedback

FieldTypeRequiredDescription
messagestringyesFeedback text sent to the m8tes team.
run_idintnoOptional run to attach so we can inspect that execution.
titlestringnoOptional short title. Defaults to the first line of message.
user_idstringnoYour end-user ID for data isolation. When set, an attached run must belong to this end-user. Required when the account has require_end_user_id enabled.
cURL
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/feedback \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"message": "This run stalled on the Google Ads connect step.","run_id": 42,"user_id": "customer_123"}'

Response 201 Created

JSON
{
  "id": 9,
  "title": "This run stalled on the Google Ads connect step.",
  "run_id": 42,
  "created_at": "2026-09-27T12:00:00Z"
}

Groups

Flat mate groups for organizing agents (Team axis). Assign via agents.create(group_id=...) or agents.update(group_id=...). Not human Organizations — those stay under Teams. POST /groups/{id}/share bulk-sets mate visibility (selection set); the group organizes which mates flip — it is not a group ACL.

Create Group

POST /groups

FieldTypeRequiredDescription
parent_idintnoParent Team; null creates a root.
namestringyesGroup name.
user_idstringnoEnd-user scope for this group. Omit for account-level.
display_orderintnoOptional list position (lower sorts first).
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/groups \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

List Groups

GET /groups

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/groups?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Group

GET /groups/{group_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/groups/{group_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Group

PATCH /groups/{group_id}

FieldTypeRequiredDescription
parent_idintnoOmit to keep parent; null moves to root.
namestringnoDisplay name.
display_orderintnoDisplay Order
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/groups/{group_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"parent_id": 1}'

Delete Group

DELETE /groups/{group_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/groups/{group_id} \
  -H "Authorization: Bearer m8_your_key_here"

Accept Group Invite

POST /groups/invites/accept

FieldTypeRequiredDescription
tokenstringyesToken
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/groups/invites/accept \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"token": "..."}'

Create Group Invite

POST /groups/{group_id}/invites

FieldTypeRequiredDescription
rolestringnoRole
emailstringyesEmail address.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/groups/{group_id}/invites \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

Share Group

POST /groups/{group_id}/share

FieldTypeRequiredDescription
visibilitystringyesTarget mate visibility. The group organizes which mates flip; organization visibility is what org members can see — not a group ACL.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/groups/{group_id}/share \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"visibility": "personal"}'

Preview Group Invite

GET /groups/invites/{token}

curl \
  https://api.m8tes.ai/api/v2/groups/invites/{token} \
  -H "Authorization: Bearer m8_your_key_here"

List Group Invites

GET /groups/{group_id}/invites

curl \
  https://api.m8tes.ai/api/v2/groups/{group_id}/invites \
  -H "Authorization: Bearer m8_your_key_here"

List Group Members

GET /groups/{group_id}/members

curl \
  https://api.m8tes.ai/api/v2/groups/{group_id}/members \
  -H "Authorization: Bearer m8_your_key_here"

Cancel Group Invite

DELETE /groups/invites/{invite_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/groups/invites/{invite_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Group Member

PATCH /groups/{group_id}/members/{member_id}

FieldTypeRequiredDescription
rolestringyesRole
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/groups/{group_id}/members/{member_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"role": "viewer"}'

Remove Group Member

DELETE /groups/{group_id}/members/{member_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/groups/{group_id}/members/{member_id} \
  -H "Authorization: Bearer m8_your_key_here"

Permissions

Pre-configure tool allow-lists for your end-users. Tools added here are auto-approved when runs use approval mode.

Create Permission

Idempotent: re-creating the same permission returns the existing record.

POST /permissions

FieldTypeRequiredDescription
user_idstringnoEnd-user ID. Omit to target the account-level scope.
toolstringyesTool name to allow, e.g. 'gmail'
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/permissions \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "customer_123","tool": "gmail"}'

Response 201 Created

JSON
{
  "id": 3,
  "user_id": "customer_123",
  "tool_name": "gmail",
  "created_at": "2026-01-15T10:00:00Z"
}

List Permissions

GET /permissions

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/permissions?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Delete Permission

DELETE /permissions/{permission_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/permissions/3 \
  -H "Authorization: Bearer m8_your_key_here"

Users

Manage end-user profiles. Profiles are auto-created when user_id is passed to any endpoint, or created explicitly with profile data.

Create End User

POST /users

FieldTypeRequiredDescription
user_idstringyesYour end-user identifier
namestringnoDisplay name.
emailstringnoEmail address.
companystringnoCompany name.
metadataobjectnoArbitrary key-value data
run_limitintnoOverride: this end-user's completed-runs cap per billing period (0 = blocked entirely; null = inherit the account-wide default)
cost_limit_centsintnoOverride: this end-user's metered-cost cap in US cents (null = inherit)
rate_per_minuteintnoOverride: this end-user's run-starts-per-minute rate limit (null = inherit)
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/users \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "cust_123","name": "Alice Smith","email": "alice@acme.com"}'

Response 201 Created

JSON
{
  "id": 1,
  "user_id": "cust_123",
  "name": "Alice Smith",
  "email": "alice@acme.com",
  "company": "Acme Corp",
  "created_at": "2026-01-15T10:00:00Z"
}

List End Users

GET /users

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
curl \
  https://api.m8tes.ai/api/v2/users \
  -H "Authorization: Bearer m8_your_key_here"

Get End User

GET /users/{user_id}

curl \
  https://api.m8tes.ai/api/v2/users/{user_id} \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 1,
  "user_id": "cust_123",
  "name": "Alice Smith",
  "email": "alice@acme.com",
  "company": "Acme Corp",
  "created_at": "2026-01-15T10:00:00Z"
}

Update End User

PATCH /users/{user_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
emailstringnoEmail address.
companystringnoCompany name.
metadataobjectnoArbitrary key-value data
run_limitintnoOverride: this end-user's completed-runs cap per billing period (0 = blocked entirely; null = inherit the account-wide default)
cost_limit_centsintnoOverride: this end-user's metered-cost cap in US cents (null = inherit)
rate_per_minuteintnoOverride: this end-user's run-starts-per-minute rate limit (null = inherit)
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/users/{user_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Smith-Jones","company": "Acme Corp"}'

Response 200 OK

JSON
{
  "id": 1,
  "user_id": "cust_123",
  "name": "Alice Smith",
  "email": "alice@acme.com",
  "company": "Acme Corp",
  "created_at": "2026-01-15T10:00:00Z"
}

Delete End User

DELETE /users/{user_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/users/{user_id} \
  -H "Authorization: Bearer m8_your_key_here"

Teams

Organizations the account belongs to, their seats, and email invites. Invite and accept are gated by the Teams beta flag; listing, revoking an invite, and removing a member always work for an existing organization.

List Teams

GET /teams

curl \
  https://api.m8tes.ai/api/v2/teams \
  -H "Authorization: Bearer m8_your_key_here"

Accept Team Invite

POST /teams/invites/accept

FieldTypeRequiredDescription
tokenstringyesToken
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/teams/invites/accept \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"token": "..."}'

Create Team Invite

POST /teams/{org_id}/invites

FieldTypeRequiredDescription
emailstringyesEmail address.
rolestringnoRole
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/teams/{org_id}/invites \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

Preview Team Invite

GET /teams/invites/{token}

curl \
  https://api.m8tes.ai/api/v2/teams/invites/{token} \
  -H "Authorization: Bearer m8_your_key_here"

Revoke Team Invite

DELETE /teams/invites/{invite_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/teams/invites/{invite_id} \
  -H "Authorization: Bearer m8_your_key_here"

Remove Team Member

DELETE /teams/{org_id}/members/{member_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/teams/{org_id}/members/{member_id} \
  -H "Authorization: Bearer m8_your_key_here"

Settings

Account-level configuration: data retention and per-end-user sub-caps.

Get Settings

GET /settings

curl \
  https://api.m8tes.ai/api/v2/settings \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "retention_mode": "standard"
}

Update Settings

PATCH /settings

FieldTypeRequiredDescription
per_end_user_run_limitintnoCap each end-user's (user_id) completed runs per billing period so one end-user can't drain the account budget. Send null to remove the cap; omit to leave unchanged.
per_end_user_cost_limit_centsintnoCap each end-user's metered run cost (US cents) per billing period. Send null to remove the cap; omit to leave unchanged.
per_end_user_rate_per_minuteintnoRate-limit each end-user to this many agent turns per minute; run starts AND replies (429 END_USER_RATE_LIMITED past it). Send null to turn off; omit to leave unchanged.
retention_modestringnoData retention. 'standard' stores conversation content; 'metadata_only' (zero data retention) never persists message content, tool I/O, or generated reports; only metadata (token/cost metrics, tool names). Omit to leave unchanged.
require_end_user_idboolnoStrict multi-tenant mode: reject requests that would land data in the account-level scope because user_id was omitted (422) instead of silently assuming the global account scope. Enforced on agent, task, run, skill, and custom-MCP-server creation; on every memory operation; and on LISTING agents, tasks, runs, skills, custom MCP servers, memories and permissions; an unscoped list is how a forgotten user_id returns one end-user another's rows. Catalogue reads (apps, built-in tools), app connections, and replies/retries of pre-existing unscoped runs are exempt. Recommended for multi-tenant integrations. Omit to leave unchanged.
approval_email_enabledboolnoEmail you when a run has waited a few minutes for your approval or answer. false stops those emails; the run waits in the dashboard and runs.permissions() until someone answers. Omit to leave unchanged.
approval_slack_enabledboolnoSlack DM you about a run waiting on your approval (needs Slack connected). false stops the DM; a run started in Slack still asks in its own thread. Omit to leave unchanged.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/settings \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"retention_mode": "metadata_only"}'

Response 200 OK

JSON
{
  "retention_mode": "standard"
}

Keys

Create Named Key

POST /keys

FieldTypeRequiredDescription
namestringyesA label, e.g. 'production'.
expires_in_daysintnoOptional expiry in days; omit for no expiry.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/keys \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Get Key Info

GET /keys

curl \
  https://api.m8tes.ai/api/v2/keys \
  -H "Authorization: Bearer m8_your_key_here"

Revoke Key

DELETE /keys

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/keys \
  -H "Authorization: Bearer m8_your_key_here"

Rotate Key

POST /keys/rotate

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/keys/rotate \
  -H "Authorization: Bearer m8_your_key_here"

List Named Keys

GET /keys/all

curl \
  https://api.m8tes.ai/api/v2/keys/all \
  -H "Authorization: Bearer m8_your_key_here"

Revoke Named Key

DELETE /keys/{key_id}

curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/keys/{key_id} \
  -H "Authorization: Bearer m8_your_key_here"

Rotate Named Key

POST /keys/{key_id}/rotate

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/keys/{key_id}/rotate \
  -H "Authorization: Bearer m8_your_key_here"

Usage

Inspect the current account plan, run usage, cost usage, and billing period.

Get Usage

GET /usage

curl \
  https://api.m8tes.ai/api/v2/usage \
  -H "Authorization: Bearer m8_your_key_here"

client.billing.usage() is an alias for client.auth.get_usage().

Response 200 OK

JSON
{
  "plan": "trial",
  "runs_used": 2,
  "runs_limit": 50,
  "cost_used": "0.12",
  "cost_limit": "20.00",
  "period_end": "2026-03-27T10:00:00Z",
  "free_path_available": true,
  "overage_enabled": false,
  "overage_used_cents": 0,
  "overage_cap_cents": 0,
  "overage_rate_cents": 200,
  "trial_ends_at": "2026-04-01T10:00:00Z",
  "unlimited_runs": false,
  "concurrent_runs": 1,
  "concurrent_runs_limit": 10
}

List End User Usage

GET /usage/end-users

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/usage/end-users?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Usage Timeseries

GET /usage/timeseries

ParameterTypeDescription
start_datestringFirst UTC day (default: 29 days before end_date)
end_datestringLast UTC day, inclusive (default: today)
teammate_idintFilter to one teammate
surfacestringFilter by the run's stamped billing surface: "api" (end-user-scoped / embedding work) or "platform" (first-party work). This is the surface a run was CREATED on, not the meter it finally settled on. A run's settlement can differ (own-subscription turns, plan finalisation). Omit for every surface.
settled_meterstringFilter by where the run actually settled: wallet (prepaid ledger), plan (subscription), released (non-billable), or own_sub (user's own model subscription). This is Run.settled_meter, not billing_surface. The API billing page passes wallet so the chart cannot show spend the prepaid ledger never saw. Cost is still the usage estimate, not SUM(ledger debits). Omit for every settlement.
group_bystringAdd per-model slices to each bucket ("model")
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/usage/timeseries?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "start_date": "2026-06-14",
  "end_date": "2026-07-13",
  "buckets": [
    {
      "cache_creation_tokens": 12000,
      "cache_read_tokens": 91000,
      "cost_usd": "3.41",
      "date": "2026-07-13",
      "input_tokens": 182000,
      "output_tokens": 24500,
      "total_tokens": 309500
    }
  ],
  "totals": {
    "cache_creation_tokens": 12000,
    "cache_read_tokens": 91000,
    "cost_usd": "3.41",
    "input_tokens": 182000,
    "output_tokens": 24500,
    "total_tokens": 309500
  }
}

Billing

Inspect the public Hobby and paid-plan catalog, manage opt-in usage overage for eligible team plans (per-run charges past the included runs, capped at a monthly ceiling you set), and top up or read a prepaid token balance (a micro-USD balance plus its ledger).

Activate Hobby Plan

POST /billing/activate-free

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/billing/activate-free \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "plan": "trial",
  "runs_used": 2,
  "runs_limit": 50,
  "cost_used": "0.12",
  "cost_limit": "20.00",
  "period_end": "2026-03-27T10:00:00Z",
  "free_path_available": true,
  "overage_enabled": false,
  "overage_used_cents": 0,
  "overage_cap_cents": 0,
  "overage_rate_cents": 200,
  "trial_ends_at": "2026-04-01T10:00:00Z",
  "unlimited_runs": false,
  "concurrent_runs": 1,
  "concurrent_runs_limit": 10
}

Create Checkout

POST /billing/checkout

FieldTypeRequiredDescription
plan_idstringyesPlan Id
billing_periodstringnoBilling Period
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/billing/checkout \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"plan_id": "individual"}'

Create Portal

POST /billing/portal

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/billing/portal \
  -H "Authorization: Bearer m8_your_key_here"

Create Topup

POST /billing/topup

FieldTypeRequiredDescription
amount_centsintyesAmount to add to the token balance, in cents ($5 min, $1M max).
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/billing/topup \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"amount_cents": 5000}'

The call returns a Stripe Checkout URL; the balance is credited asynchronously after payment, so balance() won't reflect it until checkout completes.

Response 200 OK

JSON
{
  "checkout_url": "https://checkout.stripe.com/..."
}

Get Balance

GET /billing/balance

curl \
  https://api.m8tes.ai/api/v2/billing/balance \
  -H "Authorization: Bearer m8_your_key_here"

Amounts are in micro-USD (1 micro-USD = 1e-6 USD); balance_usd is a rounded display string. Each transaction type is one of topup, debit, refund, or adjustment.

Response 200 OK

JSON
{
  "balance_micros": 4992500,
  "balance_usd": "4.9925",
  "currency": "usd",
  "transactions": [
    {
      "type": "debit",
      "amount_micros": -7500,
      "balance_after_micros": 4992500,
      "run_id": 42,
      "description": "run 42 inference",
      "created_at": "2026-03-27T10:00:00Z"
    }
  ]
}

Get Plans

GET /billing/plans

ParameterTypeDescription
include_freeboolInclude the non-purchasable Hobby plan in the catalog.
curl \
  https://api.m8tes.ai/api/v2/billing/plans?include_free=true

Response 200 OK

JSON
[
  {"slug": "free", "display_name": "Hobby", "included_runs": 150, "monthly_price_cents": 0, "annual_price_cents": 0, "inference_mode": "own_subscription", "overage_available": false, "overage_rate_cents": 200, "fair_use_cost_limit_cents": 50000, "max_concurrent_runs": 10},
  {"slug": "individual", "display_name": "Individual", "included_runs": 1000, "monthly_price_cents": 2000, "annual_price_cents": 20000, "inference_mode": "own_subscription", "overage_available": false, "overage_rate_cents": 200, "fair_use_cost_limit_cents": 50000, "max_concurrent_runs": 25},
  {"slug": "pro", "display_name": "Teams", "included_runs": 500, "monthly_price_cents": 100000, "annual_price_cents": 1000000, "inference_mode": "platform", "overage_available": true, "overage_rate_cents": 200, "fair_use_cost_limit_cents": 75000, "max_concurrent_runs": 50},
  {"slug": "max_5x", "display_name": "Teams", "included_runs": 2000, "monthly_price_cents": 300000, "annual_price_cents": 3000000, "inference_mode": "platform", "overage_available": true, "overage_rate_cents": 200, "fair_use_cost_limit_cents": 300000, "max_concurrent_runs": 75},
  {"slug": "max_20x", "display_name": "Teams", "included_runs": 5000, "monthly_price_cents": 500000, "annual_price_cents": 5000000, "inference_mode": "platform", "overage_available": true, "overage_rate_cents": 200, "fair_use_cost_limit_cents": 750000, "max_concurrent_runs": 100}
]

List Receipts

GET /billing/receipts

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
curl \
  https://api.m8tes.ai/api/v2/billing/receipts \
  -H "Authorization: Bearer m8_your_key_here"

Update Alert Settings

PATCH /billing/alert-settings

FieldTypeRequiredDescription
low_balance_threshold_centsintyesBalance (cents) at which the low-balance warning fires (0 to $100k).
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/billing/alert-settings \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"low_balance_threshold_cents": 2000}'

Update Auto Reload

PATCH /billing/auto-reload

FieldTypeRequiredDescription
enabledboolyesTurn auto-reload on or off.
threshold_centsintnoReload when the balance falls below this (cents). Required to enable.
amount_centsintnoAmount charged per reload, in cents ($5 min, $10k max). Required to enable.
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/billing/auto-reload \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true,"threshold_cents": 500,"amount_cents": 2000}'

Update Overage Settings

Opt in/out of usage overage and set the monthly spend cap (cents, max $10,000). The response echoes the full usage payload (the same shape as GET /usage).

PATCH /billing/overage

FieldTypeRequiredDescription
enabledboolyesTurn usage overage on or off.
monthly_cap_centsintyesMonthly overage spend ceiling in cents (0 to 1,000,000, i.e. up to $10,000).
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/billing/overage \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true,"monthly_cap_cents": 5000}'

Response 200 OK

JSON
{
  "plan": "trial",
  "runs_used": 2,
  "runs_limit": 50,
  "cost_used": "0.12",
  "cost_limit": "20.00",
  "period_end": "2026-03-27T10:00:00Z",
  "free_path_available": true,
  "overage_enabled": false,
  "overage_used_cents": 0,
  "overage_cap_cents": 0,
  "overage_rate_cents": 200,
  "trial_ends_at": "2026-04-01T10:00:00Z",
  "unlimited_runs": false,
  "concurrent_runs": 1,
  "concurrent_runs_limit": 10
}

Get Receipt Invoice

GET /billing/receipts/{receipt_id}/invoice

curl \
  https://api.m8tes.ai/api/v2/billing/receipts/{receipt_id}/invoice \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "receipt_id": 7,
  "number": "A1B2C3D4-0001",
  "invoice_pdf_url": "https://pay.stripe.com/invoice/acct_\u2026/pdf",
  "hosted_invoice_url": "https://invoice.stripe.com/i/acct_\u2026"
}

Value

Maintain customer-specific use cases inferred from real agent work, attribute run cost, record sourced outcomes, confirm inferred financial claims, and compare verified value with metered platform cost. Pending and unquantified outcomes stay separate from ROI.

Create Use Case

POST /value/use-cases

FieldTypeRequiredDescription
namestringyesDisplay name.
goalstringyesGoal
measurement_modelobjectnoMeasurement Model
responsible_user_idintnoResponsible User Id
source_run_idintnoSource Run Id
user_idstringnoUser Id
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/value/use-cases \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent","goal": "..."}'

Get Value Report

GET /value/report

ParameterTypeDescription
start_datestring
end_datestring
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/value/report?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

List Use Cases

GET /value/use-cases

ParameterTypeDescription
statusstringFilter by status (running, paused, awaiting_approval, completed, failed, cancelled, closed, archived)
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/value/use-cases?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Use Case

GET /value/use-cases/{use_case_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/value/use-cases/{use_case_id} \
  -H "Authorization: Bearer m8_your_key_here"

Update Use Case

PATCH /value/use-cases/{use_case_id}

FieldTypeRequiredDescription
namestringnoDisplay name.
goalstringnoGoal
measurement_modelobjectnoMeasurement Model
responsible_user_idintnoResponsible User Id
statusstringnoStatus
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/value/use-cases/{use_case_id} \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "support agent"}'

Confirm Observation

POST /value/observations/{observation_id}/confirm

FieldTypeRequiredDescription
decisionstringyesallow or deny the pending tool request.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/value/observations/{observation_id}/confirm \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"decision": "confirmed"}'

Create Observation

POST /value/use-cases/{use_case_id}/observations

FieldTypeRequiredDescription
kindstringyesKind
evidence_levelstringyesEvidence Level
titlestringyesTitle
amount_usdnumbernoAmount Usd
metric_namestringnoMetric Name
metric_valuenumbernoMetric Value
metric_unitstringnoMetric Unit
evidenceobject[]yesEvidence
source_run_idintnoSource Run Id
observed_atstringyesObserved At
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/value/use-cases/{use_case_id}/observations \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"kind": "revenue_generated","evidence_level": "measured","title": "...","evidence": [{"source": "system-of-record","record_id": "record_123"}],"observed_at": "..."}'

POST /value/use-cases/{use_case_id}/runs

FieldTypeRequiredDescription
run_idsint[]yesRun Ids
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/value/use-cases/{use_case_id}/runs \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"run_ids": []}'

List Observations

GET /value/use-cases/{use_case_id}/observations

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/value/use-cases/{use_case_id}/observations \
  -H "Authorization: Bearer m8_your_key_here"

Account

Account-level data controls: export all of your account's data, or delete the account and its data (GDPR/CCPA).

Delete Account

DELETE /account

cURL
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/account \
  -H "Authorization: Bearer m8_your_key_here"

Change Password

POST /account/password

FieldTypeRequiredDescription
current_passwordstringyesThe account's current password, to prove possession.
new_passwordstringyesThe replacement password, at least 8 characters. Revokes every session (a fresh pair is returned, or an MFA challenge when 2FA is on) but deliberately leaves API keys and webhook tokens working; only a password RESET, which cannot prove you knew the old password, revokes those.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/account/password \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"current_password": "...","new_password": "..."}'

Export Account

GET /account/export

cURL
curl \
  https://api.m8tes.ai/api/v2/account/export \
  -H "Authorization: Bearer m8_your_key_here"

Revoke Sessions

Session-authenticated only. An m8_ API key is refused with HTTP 403 — a credential pasted into CI must not be able to sign the account owner out of their browser. Pass a browser/CLI session access token instead.

POST /account/sessions/revoke

curl \
  -X POST \
  https://api.m8tes.ai/api/v2/account/sessions/revoke \
  -H "Authorization: Bearer your_session_token_here"

Webhooks

Register URLs to receive signed event notifications when runs change status.

Create Webhook

POST /webhooks

FieldTypeRequiredDescription
urlstringyesHTTPS URL to receive events
eventsstring[]noEvents: run.started, run.completed, run.failed, run.cancelled, run.awaiting_input, run.message_received, run.message_cancelled
user_idstringnoEnd-user scope (maps to end_user_id). Omit for account-level — only account-level runs deliver here; include it so that end-user's runs deliver here and nowhere else.
curl \
  -X POST \
  https://api.m8tes.ai/api/v2/webhooks \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-app.com/webhooks/m8tes","events": ["run.completed"],"user_id": "customer_123"}'

Note: The secret is only returned on creation. Store it securely.

Response 201 Created

JSON
{
  "id": 2,
  "url": "https://your-app.com/webhooks/m8tes",
  "events": [
    "run.completed"
  ],
  "secret": "a1b2c3d4e5f6...",
  "active": true,
  "user_id": "customer_123",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-01-15T10:00:00Z"
}

List Webhooks

Secrets are masked in list responses.

GET /webhooks

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last item ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/webhooks?user_id=customer_123 \
  -H "Authorization: Bearer m8_your_key_here"

Get Webhook

GET /webhooks/{webhook_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/webhooks/2 \
  -H "Authorization: Bearer m8_your_key_here"

Response 200 OK

JSON
{
  "id": 2,
  "url": "https://your-app.com/webhooks/m8tes",
  "events": [
    "run.completed"
  ],
  "secret": "a1b2c3d4e5f6...",
  "active": true,
  "user_id": "customer_123",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-01-15T10:00:00Z"
}

Update Webhook

PATCH /webhooks/{webhook_id}

FieldTypeRequiredDescription
urlstringnoHTTPS endpoint URL to receive deliveries.
eventsstring[]noEvent types to subscribe to, e.g. run.completed.
activeboolnoWhether it is enabled.
rotate_secretboolnoGenerate a new signing secret
curl \
  -X PATCH \
  https://api.m8tes.ai/api/v2/webhooks/2 \
  -H "Authorization: Bearer m8_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"events": ["run.started","run.completed"]}'

Response 200 OK

JSON
{
  "id": 2,
  "url": "https://your-app.com/webhooks/m8tes",
  "events": [
    "run.completed"
  ],
  "secret": "a1b2c3d4e5f6...",
  "active": true,
  "user_id": "customer_123",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-01-15T10:00:00Z"
}

Delete Webhook

DELETE /webhooks/{webhook_id}

ParameterTypeDescription
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  -X DELETE \
  https://api.m8tes.ai/api/v2/webhooks/2 \
  -H "Authorization: Bearer m8_your_key_here"

List Deliveries

View delivery attempts for a webhook endpoint.

GET /webhooks/{webhook_id}/deliveries

ParameterTypeDescription
limitintMax results (1-100, default 20)
starting_afterintCursor: last delivery ID
user_idstringScope to one end-user (maps to end_user_id). Omit for the account view.
curl \
  https://api.m8tes.ai/api/v2/webhooks/2/deliveries \
  -H "Authorization: Bearer m8_your_key_here"

Errors

All errors follow a standard format:

JSON
{
  "error": {
    "type": "not_found",
    "message": "Agent not found",
    "code": 404,
    "request_id": "req_abc123",
    "doc_url": "https://www.m8tes.ai/docs/api-errors#error-types"
  }
}

doc_url links to the docs page for that error type. Include request_id when you contact support.

code is always the HTTP status. When a semantic app code exists (e.g. RUN_LIMIT_REACHED, TOKEN_BALANCE_DEPLETED, unknown_query_parameter) it is on error.error_code. Branch on that, never on the message text. error.details.error_code carries the same value for backward compatibility, alongside any extra context fields.

StatusTypeSDK ExceptionDescription
400invalid_request_errorValidationErrorInvalid request
401authentication_errorAuthenticationErrorInvalid or missing API key
402billing_errorBillingErrorRun or cost limit reached, or subscription issue
403permission_errorPermissionDeniedErrorInsufficient permissions
404not_foundNotFoundErrorResource not found
405method_not_allowedAPIErrorHTTP method not supported on this path
409conflict_errorConflictErrorResource conflict (duplicate, not active)
422validation_errorValidationErrorInvalid request parameters
429rate_limit_errorRateLimitErrorToo many requests (auto-retried by SDK)
500+api_errorAPIErrorServer error (auto-retried by SDK)
Was this page helpful?