Documentation

Developer API

Message send and status contracts for the current TextTree alpha API.

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"}'

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

  • 422 with {"error":"validation_failed","details":...} for invalid or duplicate usernames or invalid passwords
  • 429 with {"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 number
  • body: required string, 1 to 1600 chars
  • estimated_cost_cents: optional positive integer, defaults to the configured SMS cost
  • idempotency_key: optional string, 8 to 128 chars, scoped per user
  • metadata: 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

  • 422 with {"error":"validation_failed","details":...} when the body is malformed
  • 403 with {"error":"workspace_suppressed"} when the recipient is blocked by a workspace suppression
  • 402 with {"error":"spend_limit_exceeded"} when the current spend cap would be exceeded
  • 409 with {"error":"idempotency_conflict"} when an idempotency key is reused with a different recipient, body, cost, or sender
  • 422 or 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:

  • queued
  • dispatching
  • sent
  • delivered
  • failed
  • blocked

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