Documentation
Developer API
Message send and status contracts for the current TextTree alpha API.
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"}' Developer API
The current JSON API is intentionally narrow. It is focused on one production-critical path: queueing outbound SMS and checking its status.
Base URL
Local Phoenix development runs at http://localhost:4001.
All documented developer endpoints live under /api/v1.
Authentication
Authenticated API calls require a TextTree-issued txt_... bearer access token.
The same local identity model is used for the browser app, developer API, and
MCP routes. Legacy txk_... API keys are retained as internal records and are
not accepted by /api/v1/messages.
Authorization: Bearer <textree_access_token>
Accept: application/json
POST /api/v1/messages requires messages:write. Number inventory endpoints
require numbers:read or numbers:write. Email, Google, wallet, and agent
identities are normalized into the same local TextTree identity before
authorization.
Headless account bootstrap
AI and API clients can create an account without email, Google OAuth, or wallet auth by using username/password bootstrap.
POST /api/v1/accounts
This route is public, JSON-only, and limited to one successful account creation per IP address every five minutes. TextTree stores a hash of the IP address for the throttle record; the raw IP is not stored.
Request body
{
"username": "agent-demo",
"password": "correct horse battery staple"
}
Success response
{
"account": {
"id": "9d7d9df7-58a0-4716-b82e-7ad5e73f7b36",
"username": "agent-demo"
},
"backup_codes": ["AAAA-BBBB-CCCC-DDDD"],
"token": {
"type": "Bearer",
"access_token": "txt_...",
"scopes": ["mcp:read", "mcp:execute", "messages:write"],
"expires_at": "2026-06-03T15:30:00Z"
}
}
Save the txt_... bearer token and backup codes immediately. The internal
synthetic email, token hash, password, and throttle IP hash are never returned.
Errors
422with{"error":"validation_failed","details":...}for invalid or duplicate usernames or invalid passwords429with{"error":"account_creation_rate_limited","retry_after_seconds":...}when the caller’s IP has already created an account in the last five minutes
Health
GET /api/v1/health
This route is public and is useful for deployment checks and local smoke tests.
Queue an outbound message
POST /api/v1/messages
Request body
{
"phone_number": "+15551234567",
"body": "Your verification code is 482019",
"estimated_cost_cents": 2,
"idempotency_key": "msg_2026_04_26_0001",
"metadata": {
"campaign": "alpha-invite",
"source": "api"
}
}
Fields
phone_number: required E.164 recipient phone numberbody: required string, 1 to 1600 charsestimated_cost_cents: optional positive integer, defaults to the configured SMS costidempotency_key: optional string, 8 to 128 chars, scoped per usermetadata: optional JSON object
phone_number is the V1 recipient field. Do not send to unless a future API version explicitly documents it.
API sends use automatic sender selection. Browser app sends can choose a
connected branded number; that selected sender becomes part of the idempotent
message request.
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": "Your verification code is 482019",
"idempotency_key": "msg_2026_04_26_0001"
}'
Success responses
Newly queued messages return 202 Accepted:
{
"message": {
"id": "8f3d0f4f-5ab3-4db9-bf6a-92c72bc1f2bb",
"status": "queued",
"phone_number": "+15551234567",
"body": "Your verification code is 482019",
"provider": "messaging_provider",
"consent_status": "missing",
"external_id": null,
"estimated_cost_cents": 2,
"idempotency_key": "msg_2026_04_26_0001",
"metadata": {
"campaign": "alpha-invite",
"source": "api"
},
"inserted_at": "2026-04-26T20:44:12Z",
"updated_at": "2026-04-26T20:44:12Z",
"links": {
"self": "/api/v1/messages/8f3d0f4f-5ab3-4db9-bf6a-92c72bc1f2bb"
}
},
"replayed": false
}
If the same user replays the same idempotency_key with the same message
payload and sender selection, TextTree returns 200 OK with the existing
message and "replayed": true.
Error responses
422with{"error":"validation_failed","details":...}when the body is malformed403with{"error":"workspace_suppressed"}when the recipient is blocked by a workspace suppression402with{"error":"spend_limit_exceeded"}when the current spend cap would be exceeded409with{"error":"idempotency_conflict"}when an idempotency key is reused with a different recipient, body, cost, or sender422or provider-bound failure details when a selected app sender is no longer available to the workspace
Fetch message status
GET /api/v1/messages/:id
This returns the same message envelope used in create responses.
The current message status lifecycle is:
queueddispatchingsentdeliveredfailedblocked
external_id is populated once provider-facing execution has a stable provider identifier.
Status example
curl https://api.texttree.ai/api/v1/messages/8f3d0f4f-5ab3-4db9-bf6a-92c72bc1f2bb \
-H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN"
{
"message": {
"id": "8f3d0f4f-5ab3-4db9-bf6a-92c72bc1f2bb",
"status": "delivered",
"phone_number": "+15551234567",
"estimated_cost_cents": 2,
"links": {
"self": "/api/v1/messages/8f3d0f4f-5ab3-4db9-bf6a-92c72bc1f2bb"
}
}
}
Before you send
Account creation is self-bootstrapping through POST /api/v1/accounts and
POST /mcp/accounts, but outbound sending still requires workspace setup.
Before POST /api/v1/messages can succeed for a new workspace, the caller or
operator must:
- authenticate through TextTree and include a token with
messages:write - confirm the recipient is not under an active workspace suppression
- set an active spend limit in
/app - configure a test sender, live sender number, or automatic sender mode
Numbers API
GET /api/v1/numbers requires numbers:read.
POST /api/v1/numbers requires numbers:write and buys a number through
the TextTree messaging provider boundary. Include an area_code when the
operator wants a local number. Live buys remain blocked unless the deployment
explicitly enables the live-number-buy guardrail.
curl https://api.texttree.ai/api/v1/numbers \
-H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"area_code": "415",
"friendly_name": "Support line"
}'
Successful buys return 201 Created:
{
"number": {
"id": "b86fbe4d-2b92-4664-8e91-d2ed1d37f827",
"number": "+14155550123",
"friendly_name": "Support line",
"capabilities": ["sms"],
"status": "connected",
"inbound_webhook_url": "https://app.texttree.ai/webhooks/provider/incoming",
"compliance_status": "pending"
},
"webhook_configured": false,
"warning": "provider_inbound_webhook_configuration_api_not_documented"
}
Current alpha boundaries
The developer API does not currently expose:
- legacy consent/contact metadata CRUD endpoints
- workspace-suppression CRUD endpoints
- spend-limit CRUD endpoints
- direct funding-session creation; use onboarding funding invoices for launch top-ups