Documentation
MCP
Current MCP routes, scopes, execution envelopes, and runtime controls.
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/healthPOST /mcpPOST /mcp/accountsGET /mcp/toolsPOST /mcp/tools/:nameGET /api/v1/mcp/oauth-clientsDELETE /api/v1/mcp/oauth-clients/:client_idGET /.well-known/oauth-protected-resource/mcpGET /.well-known/oauth-authorization-server/mcpPOST /oauth/registerGET /oauth/authorizePOST /oauth/authorizePOST /oauth/tokenPOST /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:
- Prefer a Client ID Metadata Document: set
client_idto an HTTPS URL that hosts the client metadata JSON. UsePOST /oauth/registeronly as the dynamic-registration fallback for clients that cannot host metadata. - Send the user to
GET /oauth/authorizewithresponse_type=code,client_id,redirect_uri,resource=https://api.texttree.ai/mcp,code_challenge, andcode_challenge_method=S256. - TextTree shows a browser consent screen with the MCP client, redirect URI,
resource, and requested scopes. Approval submits
POST /oauth/authorizeand redirects back withcode; denial redirects back witherror=access_denied. - Exchange the returned code at
POST /oauth/tokenwith the matchingcode_verifierand the sameresourcevalue. JSON andapplication/x-www-form-urlencodedrequests are accepted. - Use the returned TextTree bearer token on
POST /mcp. - Revoke a held bearer token with
POST /oauth/revokewhen 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 /mcpacceptsinitializeandpingwith any authenticated tokenPOST /mcptools/list,resources/list,resources/templates/list,prompts/list, andcompletion/completerequiremcp:readPOST /mcpresources/readrequires the resource-specific scopePOST /mcptools/callrequiresmcp:executeGET /mcp/toolsrequiresmcp:readPOST /mcp/tools/:namerequiresmcp:executeGET /api/v1/mcp/oauth-clientsrequiresmcp:readDELETE /api/v1/mcp/oauth-clients/:client_idrequiresmcp: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-252025-06-182025-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/toolsrequiresmcp:readtexttree://onboarding/statusrequiresonboarding:readtexttree://billing/statusrequiresonboarding:readtexttree://messages/recentrequiresmessages: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}requiresonboarding:readtexttree://messages/{id}requiresmessages:writetexttree://numbers/{id}requiresnumbers:readtexttree://onboarding/checklistrequiresonboarding:readtexttree://billing/readinessrequiresonboarding:readtexttree://elicitations/{correlation_id}requiresonboarding: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_agentaccepts optionalpath(dedicated_numberorfast_send),region,brand_name,website,funding_amount_cents,payment_method,recipient_phone_number,secret_storage, andexecution_policytexttree.first_sendaccepts optionalsender_path(dedicated_numberorfast_send) and optional stringrecipient_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:
- Create or choose a bearer token and store it in the requested secret target.
- Read
texttree://onboarding/statusandtexttree://billing/status. - After explicit approval, call
onboarding.request_dedicated_numberonly when brand name, HTTPS website, and region are explicit. - After explicit approval, call
onboarding.create_invoicewith an explicit amount and supported method, then give the human the returned payment details. - Poll
onboarding.invoice_statusortexttree://invoices/{id}until payment state changes. - After approval, call
onboarding.test_smsormessages.sendwith the explicit recipient phone number and a stableidempotency_key. - Poll
/api/v1/messages/$MESSAGE_IDortexttree://messages/{id}for delivery state.
Human-payment steps:
- Review the invoice amount, chain, token, wallet, and expiry.
- Pay the invoice outside the agent.
- Tell the agent to resume polling after payment is submitted.
Human-inbound-test steps:
- Wait until the assigned number is connected.
- Text the assigned number from a real phone.
- 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
403with{"error":"tool_not_allowed"}when the tool is outside the configured allowlist403with{"error":"insufficient_scope","required_scope":"..."}when the token lacks the route-level or tool-level scope429with{"error":"quota_exceeded"}when the per-identity quota window is exhausted504with{"error":"execution_timed_out"}when the execution exceeds the configured timeout422with{"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_CAPACITYandTEXTREE_OAUTH_CLIENT_REGISTRATION_RATE_LIMIT_REFILL_MS - token exchange: 60 requests per IP, refill every 60 seconds with
TEXTREE_OAUTH_TOKEN_EXCHANGE_RATE_LIMIT_CAPACITYandTEXTREE_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:readto inspect onboarding status and invoicesonboarding:writeto create invoices, test sends, and dedicated number requestsmessages:writeto send the first production SMS through/api/v1/messagesmcp:readandmcp:executeto 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:
- read onboarding and billing resources before execution
- collect explicit recipient, funding, brand, website, storage, and approval details
- hand off to your application or human operator for final send approval when required
- call
messages.sendor send through/api/v1/messagesso 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