Documentation

MCP

Current MCP routes, scopes, execution envelopes, and runtime controls.

Copy-paste API examples
curl JavaScript Python Elixir
curl
curl https://api.texttree.ai/api/v1/messages \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"phone_number":"+15551234567","body":"Hello"}'

MCP

TextTree exposes a narrow MCP surface for controlled tool discovery and execution.

Base routes

  • GET /mcp/health
  • POST /mcp
  • POST /mcp/accounts
  • GET /mcp/tools
  • POST /mcp/tools/:name
  • GET /api/v1/mcp/oauth-clients
  • DELETE /api/v1/mcp/oauth-clients/:client_id
  • GET /.well-known/oauth-protected-resource/mcp
  • GET /.well-known/oauth-authorization-server/mcp
  • POST /oauth/register
  • GET /oauth/authorize
  • POST /oauth/authorize
  • POST /oauth/token
  • POST /oauth/revoke

Local Phoenix development runs at http://localhost:4001.

POST /mcp is the native MCP JSON-RPC endpoint. The /mcp/tools routes are REST compatibility routes for existing TextTree integrations and debugging. TextTree currently runs stateless Streamable HTTP: JSON-RPC messages are sent with POST /mcp. GET /mcp SSE stream probes and DELETE /mcp session termination requests return 405 Method Not Allowed because no server-side MCP session is allocated.

Discovery metadata

TextTree publishes MCP authorization discovery metadata for HTTP MCP clients:

curl https://api.texttree.ai/.well-known/oauth-protected-resource/mcp
curl https://api.texttree.ai/.well-known/oauth-authorization-server/mcp

Unauthenticated MCP requests return 401 with a WWW-Authenticate challenge that points to the protected-resource metadata URL. The authorization metadata advertises Client ID Metadata Document support, dynamic client registration, authorization-code grants, PKCE S256, and public-client token exchange for HTTP MCP clients. TextTree also continues to support first-party bearer-token bootstrap through POST /mcp/accounts. The backward-compatible POST /api/v1/onboarding/api-key route now issues the txt_... bearer access tokens used by MCP and the developer API.

OAuth MCP client flow:

  1. Prefer a Client ID Metadata Document: set client_id to an HTTPS URL that hosts the client metadata JSON. Use POST /oauth/register only as the dynamic-registration fallback for clients that cannot host metadata.
  2. Send the user to GET /oauth/authorize with response_type=code, client_id, redirect_uri, resource=https://api.texttree.ai/mcp, code_challenge, and code_challenge_method=S256.
  3. TextTree shows a browser consent screen with the MCP client, redirect URI, resource, and requested scopes. Approval submits POST /oauth/authorize and redirects back with code; denial redirects back with error=access_denied.
  4. Exchange the returned code at POST /oauth/token with the matching code_verifier and the same resource value. JSON and application/x-www-form-urlencoded requests are accepted.
  5. Use the returned TextTree bearer token on POST /mcp.
  6. Revoke a held bearer token with POST /oauth/revoke when the MCP client disconnects or rotates credentials.

Client ID Metadata Document example:

{
  "client_id": "https://agent.example.com/.well-known/oauth-client.json",
  "client_name": "Example Agent",
  "client_uri": "https://agent.example.com",
  "redirect_uris": ["http://localhost:8787/callback"],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "scope": "mcp:read mcp:execute onboarding:read",
  "token_endpoint_auth_method": "none"
}

When TextTree sees a URL-formatted client_id during authorization, it fetches and validates that document before rendering consent. The document URL must use HTTPS on the default port, must not include credentials or a fragment, and must resolve only to public IP addresses. Localhost, private, link-local, multicast, and unresolved hosts are rejected before fetch. The fetched JSON must contain a client_id that exactly matches the URL, a client_name, at least one supported scope, and the requested redirect_uri. If grant_types or response_types are present, they must include authorization_code and code. TextTree caches valid metadata according to Cache-Control: max-age or Expires; no-cache and no-store force revalidation on the next authorization request. Documents without cache headers use a short default cache window, and TextTree caps metadata cache lifetime at one day.

TextTree bearer tokens can be audience-bound to the MCP resource. New MCP operator-issued tokens should use the canonical MCP resource URL as the audience, for example https://api.texttree.ai/mcp. Audience-free tokens remain accepted for compatibility, but an audience-bound token is rejected when used against a different TextTree resource.

The OAuth resource value is restricted to the MCP resource URL. Unsupported requested scopes fail with invalid_scope instead of being silently broadened or narrowed.

Authorized MCP OAuth clients can be listed and revoked through the API:

curl https://api.texttree.ai/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=$TEXTREE_ACCESS_TOKEN&token_type_hint=access_token"

curl https://api.texttree.ai/api/v1/mcp/oauth-clients \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"

curl -X DELETE https://api.texttree.ai/api/v1/mcp/oauth-clients/mcp_client_... \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"

POST /oauth/revoke follows the OAuth revocation convention and returns 200 for unknown tokens so callers do not leak token existence. Listing requires mcp:read. Revocation requires mcp:execute and revokes active TextTree bearer tokens issued through that MCP OAuth client for the current user.

POST /oauth/register and POST /oauth/token are rate-limited by client IP. Rate-limited requests return 429 with a Retry-After header and either oauth_client_registration_rate_limited or oauth_token_exchange_rate_limited. The buckets are separate so registration spikes do not starve authorization-code token exchange.

Authentication and scopes

Authenticated MCP requests use TextTree-issued bearer tokens. Humans, wallet users, and AI agents all present the same Authorization: Bearer <token> header after authenticating through TextTree.

AI agents can bootstrap their own TextTree identity through POST /mcp/accounts with a username and password. That route is unauthenticated, returns backup codes plus a TextTree bearer token, and is throttled to one successful account creation per IP address every five minutes.

The current route-level scopes are:

  • POST /mcp accepts initialize and ping with any authenticated token
  • POST /mcp tools/list, resources/list, resources/templates/list, prompts/list, and completion/complete require mcp:read
  • POST /mcp resources/read requires the resource-specific scope
  • POST /mcp tools/call requires mcp:execute
  • GET /mcp/tools requires mcp:read
  • POST /mcp/tools/:name requires mcp:execute
  • GET /api/v1/mcp/oauth-clients requires mcp:read
  • DELETE /api/v1/mcp/oauth-clients/:client_id requires mcp:execute

Tool entries can also declare a more specific required scope in the server registry. If that happens, TextTree records a blocked execution audit and returns 403 insufficient_scope.

Native MCP JSON-RPC

POST /mcp

TextTree supports MCP-style JSON-RPC over HTTP for clients that expect standard MCP methods. The endpoint requires a TextTree bearer token and returns the mcp-protocol-version response header. JSON-RPC request envelopes must include jsonrpc: "2.0". Request IDs must be strings or integers; explicit null IDs are rejected, and invalid ID shapes are not echoed in error responses. Method names must be strings. Request envelopes are strict: only jsonrpc, id, method, params, and the MCP-reserved top-level _meta field are accepted. Any _meta field must be a JSON object. When request params include _meta.progressToken, the token must be a string or integer. TextTree currently treats progress tokens as advisory metadata and does not emit progress notifications.

Supported protocol versions:

  • 2025-11-25
  • 2025-06-18
  • 2025-03-26

During initialize, TextTree negotiates the requested params.protocolVersion when it is supported. If an initialize request asks for an unsupported string version, TextTree responds with its latest supported version instead of failing the handshake. For subsequent requests, clients should send the MCP-Protocol-Version header with the negotiated version. If no header is present, TextTree falls back to 2025-03-26 for compatibility. Unsupported protocol-version headers return 400 Bad Request, matching the MCP Streamable HTTP transport requirement. If supplied, initialize.params.protocolVersion must be a string, capabilities must be an object, and clientInfo must be an object with a string name and optional string version. Unknown initialize params are rejected, except for the MCP-reserved _meta field. ping accepts an empty params object or _meta; other ping params are rejected.

POST /mcp requires an Accept header that includes both application/json and text/event-stream, plus Content-Type: application/json. GET /mcp stream probes require text/event-stream. Requests missing the advertised response types return 406 Not Acceptable; POST requests with a non-JSON content type return 415 Unsupported Media Type.

Browser-originated MCP transport requests must also pass Origin validation. Requests without an Origin header are accepted for server-side and CLI MCP clients. When an Origin header is present, it must match the request origin, the configured TextTree public app or site origin, or a loopback local development origin. Untrusted browser origins return 403 forbidden_origin.

Initialize

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "init-1",
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-11-25",
      "capabilities": {},
      "clientInfo": {
        "name": "agent-client",
        "version": "0.1.0"
      }
    }
  }'

Success returns server capabilities and identity:

{
  "jsonrpc": "2.0",
  "id": "init-1",
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "prompts": {
        "listChanged": false
      },
      "completions": {},
      "resources": {
        "listChanged": false
      },
      "tools": {
        "listChanged": false
      }
    },
    "serverInfo": {
      "name": "texttree",
      "version": "0.1.0"
    },
    "instructions": "TextTree exposes audited SMS onboarding and messaging workflows..."
  }
}

Clients can place the returned instructions into model context. The instructions summarize TextTree-specific workflow rules: prefer Dedicated Number for stable sender identity, read resources before executing tools, use messages.send only with recipient-approved SMS and a stable idempotency_key, and rely on TextTree’s suppression, spend, sender-readiness, and delivery-worker gates for production sends.

List MCP tools

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "tools-1",
    "method": "tools/list",
    "params": {}
  }'

tools/list requires mcp:read. Each tool includes a title, JSON Schema 2020-12 inputSchema, JSON Schema 2020-12 outputSchema, MCP safety annotations, and TextTree metadata for the required scope and timeout. tools/list supports MCP cursor pagination. Treat nextCursor as opaque and pass it back as params.cursor only on the next tools/list request. List requests accept only cursor and the MCP-reserved _meta field.

List MCP resources

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "resources-1",
    "method": "resources/list",
    "params": {}
  }'

resources/list requires mcp:read. Resources expose read-only context with scope metadata so agents can inspect state without executing a tool. resources/list supports MCP cursor pagination. Treat nextCursor as opaque and pass it back as params.cursor only on the next resources/list request. List requests accept only cursor and the MCP-reserved _meta field.

Current resources:

  • texttree://mcp/tools requires mcp:read
  • texttree://onboarding/status requires onboarding:read
  • texttree://billing/status requires onboarding:read
  • texttree://messages/recent requires messages:write

List MCP Resource Templates

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "resource-templates-1",
    "method": "resources/templates/list",
    "params": {}
  }'

resources/templates/list requires mcp:read and returns safe by-ID resource templates for agent polling:

  • texttree://invoices/{id} requires onboarding:read
  • texttree://messages/{id} requires messages:write
  • texttree://numbers/{id} requires numbers:read
  • texttree://onboarding/checklist requires onboarding:read
  • texttree://billing/readiness requires onboarding:read
  • texttree://elicitations/{correlation_id} requires onboarding:read

It supports the same cursor pagination contract as the other list methods, and accepts only cursor plus the MCP-reserved _meta field.

Read an MCP resource

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "resource-read-1",
    "method": "resources/read",
    "params": {
      "uri": "texttree://onboarding/status"
    }
  }'

resources/read returns JSON content in the MCP contents array. Resource reads enforce the resource-specific TextTree scope before returning data. texttree://messages/recent and texttree://messages/{id} omit phone numbers and message bodies; use them for delivery-state context, not private recipient content. texttree://numbers/{id} redacts the full phone number and returns status, usage, capabilities, and compliance state. All by-ID reads are scoped to the current TextTree user or workspace. texttree://elicitations/{correlation_id} returns the resumable status for a URL-mode funding, consent, or number setup handoff.

List MCP prompts

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "prompts-1",
    "method": "prompts/list",
    "params": {}
  }'

prompts/list requires mcp:read. Prompts package common TextTree workflows into reusable agent instructions. prompts/list supports MCP cursor pagination. Treat nextCursor as opaque and pass it back as params.cursor only on the next prompts/list request. List requests accept only cursor and the MCP-reserved _meta field.

Current prompts:

  • texttree.onboard_agent accepts optional path (dedicated_number or fast_send), region, brand_name, website, funding_amount_cents, payment_method, recipient_phone_number, secret_storage, and execution_policy
  • texttree.first_send accepts optional sender_path (dedicated_number or fast_send) and optional string recipient_context

Prompt arguments are strict. Unknown argument names, wrong types, and invalid sender-path enum values return -32602 Invalid params.

Get an MCP prompt

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "prompt-1",
    "method": "prompts/get",
    "params": {
      "name": "texttree.onboard_agent",
      "arguments": {
        "path": "dedicated_number",
        "region": "US"
      }
    }
  }'

Prompt responses return MCP messages that a client can place into the model context before calling resources or tools.

Complete MCP prompt arguments

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "complete-1",
    "method": "completion/complete",
    "params": {
      "ref": {
        "type": "ref/prompt",
        "name": "texttree.onboard_agent"
      },
      "argument": {
        "name": "path",
        "value": "d"
      }
    }
  }'

completion/complete requires mcp:read and returns non-sensitive suggestions for TextTree prompt arguments. Current completions cover path, sender_path, and region. Free-form arguments return an empty completion list. TextTree does not expose resource templates, so resource-template completion requests return -32602 Invalid params.

Call an MCP tool

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "call-1",
    "method": "tools/call",
    "params": {
      "name": "onboarding.status",
      "arguments": {}
    }
  }'

tools/call requires mcp:execute plus the tool-specific scope declared in the registry. Results include MCP content, structuredContent, isError, MCP _meta, and the TextTree execution audit envelope. The structuredContent payload matches each tool’s advertised outputSchema. Tool-call result _meta contains safe correlation fields: texttree/execution_id, texttree/tool, texttree/outcome, and texttree/is_error. The same metadata is included under structuredContent._meta so the advertised outputSchema matches the machine-readable result payload. It does not include phone numbers or message bodies.

TextTree validates known tool arguments against each tool’s advertised inputSchema before execution. Missing required fields, unknown fields, wrong types, invalid enum values, and minimum violations return -32602 Invalid params without running the tool. JSON-RPC method params are also strict: prompts/get, resources/read, and tools/call reject unknown top-level params while accepting the MCP-reserved _meta field. Tool-call _meta is passed to the executor context as side channel MCP metadata and is not merged into tool arguments. If _meta.progressToken is present, it must be a string or integer. Message tool arguments.metadata is separate caller-owned metadata and may contain arbitrary JSON object fields for correlation.

messages.send is a real MCP tool. It queues through the same TextTree messaging path as POST /api/v1/messages, including suppression, spend, and delivery-worker gates. Its MCP annotations mark it as destructive and open-world because it can enqueue an outbound SMS and consume account balance.

JSON-RPC errors use standard MCP-style response envelopes and preserve TextTree safety details in error.data, including required_scope, quota_exceeded, tool_not_allowed, timeout, and execution audit data when available.

Batches and notifications

JSON-RPC batches are accepted only when the effective protocol version is the 2025-03-26 compatibility version. Later MCP revisions removed batch messages from the protocol schema, so clients that negotiate 2025-06-18 or 2025-11-25 should send one JSON-RPC message per POST /mcp; batch arrays on those versions return -32600 Invalid Request with error.data.error set to batch_not_supported.

For 2025-03-26 compatibility batches, TextTree returns one response object per request and omits responses for notifications and JSON-RPC response messages. Notification-only and response-only batches return 202 with an empty body. TextTree currently does not initiate server-to-client requests, so client response messages are accepted as no-op transport inputs when they include a valid string or integer id. Result response messages must include an object result; error response messages must include an object error with integer code and string message. Send initialize as a standalone request; if it appears inside a batch, TextTree returns -32600 Invalid Request for that batch item. Compatibility batches are capped at 100 items; larger batches return a single -32600 Invalid Request response with error.data.error set to batch_too_large. Methods in the notifications/ namespace are treated as fire-and-forget notifications and never receive JSON-RPC responses. TextTree currently acts on notifications/initialized and accepts cancellation notifications as advisory; unknown notification names are ignored. Ordinary request methods such as ping, tools/list, and tools/call must include an id.

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "jsonrpc": "2.0",
      "id": "ping-1",
      "method": "ping",
      "params": {}
    },
    {
      "jsonrpc": "2.0",
      "method": "notifications/initialized",
      "params": {}
    },
    {
      "jsonrpc": "2.0",
      "id": "tools-1",
      "method": "tools/list",
      "params": {}
    }
  ]'

Compatibility smoke test

For a running Phoenix server, TextTree includes a compatibility smoke task that drives the native MCP endpoint through initialize, initialized notification, tools, resources, prompts, and an onboarding status tool call.

cd apps/web
TEXTREE_ACCESS_TOKEN=txt_... mix textree.mcp.smoke --url http://localhost:4001/mcp
mix textree.mcp.smoke --url http://localhost:4001/mcp --bootstrap-local-token

The token should include mcp:read, mcp:execute, and onboarding:read. Pass --skip-execute to skip the onboarding status tool call when validating a read-only token. --bootstrap-local-token creates a one-hour token in the current local database and is refused for non-localhost MCP URLs.

For operator-issued bearer tokens, bind the token to the MCP resource:

mix textree.auth.issue_token agent@example.com \
  --scopes mcp:read,mcp:execute,onboarding:read,messages:write \
  --audience https://api.texttree.ai/mcp

List tools

GET /mcp/tools

curl https://api.texttree.ai/mcp/tools \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"

Success returns a registry-backed tool list:

{
  "tools": [
    {
      "name": "messages.send",
      "description": "Trigger a message send through the MCP surface.",
      "required_scope": "messages:write",
      "timeout_ms": 1000
    },
    {
      "name": "onboarding.status",
      "description": "Inspect token, balance, invoice, and SMS onboarding state.",
      "required_scope": "onboarding:read",
      "timeout_ms": 1000
    },
    {
      "name": "onboarding.create_invoice",
      "description": "Create or reuse a launch funding invoice.",
      "required_scope": "onboarding:write",
      "timeout_ms": 1000
    },
    {
      "name": "onboarding.invoice_status",
      "description": "Check funding invoice status for programmatic onboarding.",
      "required_scope": "onboarding:read",
      "timeout_ms": 1000
    },
    {
      "name": "onboarding.test_sms",
      "description": "Send the fixed Fast Send onboarding test SMS.",
      "required_scope": "onboarding:write",
      "timeout_ms": 1000
    },
    {
      "name": "onboarding.request_dedicated_number",
      "description": "Submit business details for the recommended Dedicated Number path.",
      "required_scope": "onboarding:write",
      "timeout_ms": 1000
    }
  ]
}

The current alpha registry is configured server-side. There is no public admin surface to mutate the registry at runtime.

Create a headless account

POST /mcp/accounts

Use this route when an AI client needs an account and bearer token without email, Google OAuth, or wallet auth.

curl https://api.texttree.ai/mcp/accounts \
  -H "Content-Type: application/json" \
  -d '{
    "account": {
      "username": "agent-demo",
      "password": "correct horse battery staple"
    }
  }'

Success returns the public username, one-time backup codes, and a bearer token:

{
  "account": {
    "id": "9d7d9df7-58a0-4716-b82e-7ad5e73f7b36",
    "username": "agent-demo"
  },
  "backup_codes": ["AAAA-BBBB-CCCC-DDDD"],
  "token": {
    "type": "Bearer",
    "access_token": "txt_...",
    "scopes": [
      "messages:write",
      "mcp:read",
      "mcp:execute",
      "campaigns:read",
      "campaigns:write",
      "numbers:read",
      "numbers:write",
      "onboarding:read",
      "onboarding:write"
    ],
    "expires_at": "2026-06-03T15:30:00Z"
  }
}

Save the token and backup codes immediately. TextTree does not return the internal synthetic email, token hash, raw IP, or IP hash. Validation failures return 422 validation_failed; repeated successful account creation from the same IP within five minutes returns 429 account_creation_rate_limited.

Headless account tokens include an expires_at value. Durable agent integrations should either use an operator-issued token with an intentional lifetime and audience, or include a token re-issuance and rotation plan. Do not store raw bearer tokens or backup codes in logs, shell history, or committed files.

Safe MCP onboarding prompt

Use a prompt with explicit real-world targets and approval boundaries before an agent starts setup:

Set up TextTree.ai SMS for my AI agent using https://texttree.ai/docs/mcp/.
Auto-generate account credentials; register the dedicated number under brand
"AlphaGrowth" / https://alphagrowth.io/ (US). Store the token and backup codes
in a gitignored .env. Create a $100 USDC funding invoice and give me the
deposit details to pay; do not attempt payment yourself. Once funded, send a
test SMS to +1XXXXXXXXXX and show me how to poll for inbound replies; I will
text the number myself to test inbound. Then save the assigned number, API base
URL, token location, and MCP connect steps to a README, and write live and
mocked bash/curl test scripts for send and receive. Pause for my OK before
anything that spends money, provisions or changes a number, or sends an SMS.

If any of the phone number, brand, website, funding method, secret-storage target, or approval boundary is missing, collect it before executing tools. Agents must not invent recipient numbers, make payments, or simulate inbound SMS that requires a human-owned phone.

MCP onboarding runbook

Agent-executable steps:

  1. Create or choose a bearer token and store it in the requested secret target.
  2. Read texttree://onboarding/status and texttree://billing/status.
  3. After explicit approval, call onboarding.request_dedicated_number only when brand name, HTTPS website, and region are explicit.
  4. After explicit approval, call onboarding.create_invoice with an explicit amount and supported method, then give the human the returned payment details.
  5. Poll onboarding.invoice_status or texttree://invoices/{id} until payment state changes.
  6. After approval, call onboarding.test_sms or messages.send with the explicit recipient phone number and a stable idempotency_key.
  7. Poll /api/v1/messages/$MESSAGE_ID or texttree://messages/{id} for delivery state.

Human-payment steps:

  1. Review the invoice amount, chain, token, wallet, and expiry.
  2. Pay the invoice outside the agent.
  3. Tell the agent to resume polling after payment is submitted.

Human-inbound-test steps:

  1. Wait until the assigned number is connected.
  2. Text the assigned number from a real phone.
  3. Ask the agent for receive-polling commands or webhook inspection steps.

Steps requiring explicit approval:

  • Creating a funding handoff or invoice intended for real payment.
  • Requesting, provisioning, or changing a dedicated number.
  • Sending any SMS to a real phone number.
  • Storing or rotating bearer tokens and backup codes.

Execute a tool

POST /mcp/tools/:name

Request body

{
  "params": {}
}

curl

curl https://api.texttree.ai/mcp/tools/onboarding.status \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"params": {}}'

JavaScript

const response = await fetch("https://api.texttree.ai/mcp/tools/onboarding.status", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TEXTREE_ACCESS_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ params: {} }),
});

const { execution } = await response.json();

Success response

{
  "execution": {
    "id": "513f6a41-2c33-4c15-b4e2-10da14ab806f",
    "tool": "onboarding.status",
    "outcome": "succeeded",
    "params_summary": {},
    "result_summary": {
      "complete": false,
      "product_mode": "instant_send"
    },
    "error_message": null,
    "duration_ms": 3,
    "inserted_at": "2026-04-26T21:02:15Z"
  }
}

Error responses

  • 403 with {"error":"tool_not_allowed"} when the tool is outside the configured allowlist
  • 403 with {"error":"insufficient_scope","required_scope":"..."} when the token lacks the route-level or tool-level scope
  • 429 with {"error":"quota_exceeded"} when the per-identity quota window is exhausted
  • 504 with {"error":"execution_timed_out"} when the execution exceeds the configured timeout
  • 422 with {"error":"execution_failed"} when the executor returns an error

All of these execution paths return an execution envelope when TextTree was able to create an audit record.

MCP 403 insufficient_scope responses also include a WWW-Authenticate challenge with error="insufficient_scope", the required scope, and the protected-resource metadata URL. MCP clients can use that header to trigger a step-up authorization flow and request the missing scope without guessing.

Runtime controls

The current alpha MCP runtime is intentionally strict:

  • execution is gated by TextTree bearer-token scopes
  • tools must exist in the server registry
  • tools must also be present in the allowlist boundary
  • executions are persisted with actor, token identity, tool, params summary, outcome, and timestamp
  • per-identity quotas are enforced over a rolling window
  • per-tool or global timeouts stop long-running work
  • OAuth client registration and token exchange are separately IP rate-limited

OAuth rate-limit defaults and production environment variables:

  • client registration: 20 requests per IP, refill every 60 seconds with TEXTREE_OAUTH_CLIENT_REGISTRATION_RATE_LIMIT_CAPACITY and TEXTREE_OAUTH_CLIENT_REGISTRATION_RATE_LIMIT_REFILL_MS
  • token exchange: 60 requests per IP, refill every 60 seconds with TEXTREE_OAUTH_TOKEN_EXCHANGE_RATE_LIMIT_CAPACITY and TEXTREE_OAUTH_TOKEN_EXCHANGE_RATE_LIMIT_REFILL_MS

Programmatic onboarding pattern

Use the onboarding API and MCP tools to complete setup without guessing which step is next. Dedicated Number is the recommended path when an agent needs a persistent sender that people can save and reply to. Fast Send is available when the goal is the quickest first outbound test through pooled instant senders.

1. Create or choose a token

Create a headless account through POST /mcp/accounts, or create a TextTree bearer token in the authenticated app with the scopes needed for your path:

  • onboarding:read to inspect onboarding status and invoices
  • onboarding:write to create invoices, test sends, and dedicated number requests
  • messages:write to send the first production SMS through /api/v1/messages
  • mcp:read and mcp:execute to list and execute MCP tools

Do not use a legacy txk_... API key for this flow. POST /api/v1/onboarding/api-key returns a txt_... bearer token for callers that already have an onboarding-scoped token.

Operator-issued bearer tokens should be created with the exact scopes needed by the client. For MCP onboarding plus first send, use:

cd apps/web && mix textree.auth.issue_token agent@example.com \
  --scopes onboarding:read,onboarding:write,mcp:read,mcp:execute,messages:write \
  --audience https://api.texttree.ai/mcp

Save returned tokens immediately. Raw tokens are shown once.

2. Check onboarding status

Use the API or the MCP tool to inspect token, balance, invoice, number, and first-SMS progress.

curl https://api.texttree.ai/api/v1/onboarding \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"
curl https://api.texttree.ai/mcp/tools/onboarding.status \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"params": {}}'

3. Choose a number path

Recommended: Dedicated Number. Use this when the agent needs a stable sender, inbound replies, customer recognition, or a number operators can manage. The native MCP tool saves the business details and returns URL-mode elicitation metadata pointing at the hosted TextTree setup flow.

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "dedicated-number-1",
    "method": "tools/call",
    "params": {
      "name": "onboarding.request_dedicated_number",
      "arguments": {
        "brand_name": "Acme Support",
        "website": "https://acme.example",
        "region": "US"
      }
    }
  }'

Fast Send. Use this for the quickest first outbound test when a dedicated number is not needed yet. The native MCP tool sends the fixed onboarding test copy through the same Fast Send test path as the API.

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "test-sms-1",
    "method": "tools/call",
    "params": {
      "name": "onboarding.test_sms",
      "arguments": {
        "phone_number": "+15551234567"
      }
    }
  }'

test-sms uses fixed onboarding copy, can run before funding when the account has an available Fast Send Sandbox allowance, and is rate-limited. A repeated request can return 429 test_sms_rate_limited with next_allowed_at; after the included sandbox SMS is used, continue by adding balance.

4. Fund the workspace

Create or reuse a launch funding invoice with an amount that satisfies the current onboarding minimum. Prefer the MCP invoice tool during agent onboarding. The result includes URL-mode elicitation metadata with a hosted TextTree action URL for completing payment.

curl https://api.texttree.ai/mcp/tools/onboarding.create_invoice \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "amount_cents": 10000,
      "payment_method": "direct_usdc"
    }
  }'

Poll the invoice until it becomes paid, underpaid, expired, or review_required.

curl https://api.texttree.ai/api/v1/onboarding/funding-invoices/$INVOICE_ID \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"
curl https://api.texttree.ai/mcp/tools/onboarding.invoice_status \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "id": "'"$INVOICE_ID"'"
    }
  }'

For onboarding.invoice_status, provide exactly one of id or invoice_id. The by-ID resource template can also be used for polling:

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "invoice-resource-1",
    "method": "resources/read",
    "params": {
      "uri": "texttree://invoices/'"$INVOICE_ID"'"
    }
  }'

URL-mode elicitation payloads include mode: "url", a kind, a correlation_id, a status_uri, a hosted url, a human-readable prompt, expires_at, and status: "pending". Treat the URL as a user-action handoff; do not treat model output alone as funding, consent, or number-provisioning approval. Agents can resume by polling status_uri:

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "elicitation-resource-1",
    "method": "resources/read",
    "params": {
      "uri": "texttree://elicitations/'"$CORRELATION_ID"'"
    }
  }'

The elicitation resource is scoped to the current TextTree user. Unknown or cross-user correlation IDs return resource_not_found; pending handoffs move to expired after their expires_at timestamp.

5. Send the first production SMS

After the workspace is funded and, for the recommended path, the dedicated number is connected, send through either tools/call with messages.send or the normal V1 message endpoint. Both paths use the same suppression, spend, idempotency, sender-readiness, and delivery-worker gates.

MCP tool call:

curl https://api.texttree.ai/mcp \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "send-1",
    "method": "tools/call",
    "params": {
      "name": "messages.send",
      "arguments": {
        "phone_number": "+15551234567",
        "body": "Thanks for connecting with Acme Support. Reply here any time.",
        "idempotency_key": "first-send-2026-06-07"
      }
    }
  }'

V1 API compatibility route:

curl https://api.texttree.ai/api/v1/messages \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+15551234567",
    "body": "Thanks for connecting with Acme Support. Reply here any time.",
    "idempotency_key": "first-send-2026-06-07"
  }'

Poll the message for delivery state and inspect dashboard logs for provider state.

curl https://api.texttree.ai/api/v1/messages/$MESSAGE_ID \
  -H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"

Agent integration pattern

Use MCP tools for bounded context and audited sends. An AI agent should:

  1. read onboarding and billing resources before execution
  2. collect explicit recipient, funding, brand, website, storage, and approval details
  3. hand off to your application or human operator for final send approval when required
  4. call messages.send or send through /api/v1/messages so suppression, spend, sender-readiness, and provider gates run

This keeps agent workflows behind the same production controls as human and API sends.

Backup-code recovery

Backup codes are returned during headless account bootstrap or generated in account settings. They are shown once, stored hashed, and consumed on first use. Generating a new set invalidates unused existing codes after explicit confirmation. Recovery attempts are rate-limited and logged without storing raw codes.

Current alpha constraints

  • MCP tooling is still config-backed rather than tenant-configurable
  • the default executor is intentionally simple and not a full dynamic tool runtime
  • there is no public audit-log viewer outside the Phoenix operator surfaces and repo database