{
  "openapi": "3.1.0",
  "info": {
    "title": "TextTree API",
    "version": "1.0.0-alpha",
    "summary": "Current TextTree REST API and OAuth surfaces.",
    "description": "TextTree API and MCP-adjacent REST routes. Authenticated /api/v1 and /mcp requests use TextTree-issued txt_... bearer access tokens with route-specific scopes. Legacy txk_... API keys are retained as internal records and are not accepted by developer API or MCP routes except for the temporary one-time migration endpoint."
  },
  "servers": [
    {
      "url": "https://api.texttree.ai",
      "description": "Production API"
    },
    {
      "url": "http://localhost:4001",
      "description": "Local Phoenix development"
    }
  ],
  "security": [
    {
      "TextTreeBearer": []
    }
  ],
  "tags": [
    { "name": "Health" },
    { "name": "Accounts" },
    { "name": "Messages" },
    { "name": "Campaigns" },
    { "name": "Numbers" },
    { "name": "Onboarding" },
    { "name": "MCP" },
    { "name": "OAuth" },
    { "name": "Webhooks" }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Root health check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StatusOk" }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "tags": ["Health"],
        "summary": "API health check",
        "security": [],
        "responses": {
          "200": {
            "description": "API is healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/APIHealth" }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/migrate-legacy-key": {
      "post": {
        "tags": ["Accounts"],
        "summary": "Exchange a legacy API key for a bearer access token",
        "description": "Temporary compatibility endpoint. Accepts a valid legacy txk_... API key in the Authorization bearer header, returns a new txt_... bearer access token with supported scopes preserved, and revokes the legacy key after successful token creation.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "name": { "type": "string", "description": "Optional display label for the new bearer token." }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/GenericJSON" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationFailed" }
        }
      }
    },
    "/mcp/health": {
      "get": {
        "tags": ["MCP"],
        "summary": "MCP health check",
        "security": [],
        "responses": {
          "200": {
            "description": "MCP surface is healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MCPHealth" }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts": {
      "post": {
        "tags": ["Accounts"],
        "summary": "Create a headless TextTree account",
        "description": "Creates a username/password agent account and returns one-time backup codes plus a txt_... TextTree bearer access token.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/HeadlessAccountRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HeadlessAccountResponse" }
              }
            }
          },
          "422": { "$ref": "#/components/responses/ValidationFailed" },
          "429": {
            "description": "Account creation rate limit exceeded",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitedError" }
              }
            }
          }
        }
      }
    },
    "/api/v1/messages": {
      "post": {
        "tags": ["Messages"],
        "summary": "Queue an outbound SMS",
        "description": "Requires a TextTree bearer access token with messages:write. Spend limits and workspace suppressions are checked before queueing.",
        "security": [{ "TextTreeBearer": ["messages:write"] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MessageCreateRequest" }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Message queued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MessageEnvelope" }
              }
            }
          },
          "200": {
            "description": "Idempotent replay returned an existing message",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MessageEnvelope" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/ValidationFailed" }
        }
      }
    },
    "/api/v1/messages/{id}": {
      "get": {
        "tags": ["Messages"],
        "summary": "Fetch message status",
        "security": [{ "TextTreeBearer": ["messages:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": {
          "200": {
            "description": "Message status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["message"],
                  "properties": {
                    "message": { "$ref": "#/components/schemas/Message" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/campaigns": {
      "get": {
        "tags": ["Campaigns"],
        "summary": "List campaigns",
        "security": [{ "TextTreeBearer": ["campaigns:read"] }],
        "responses": {
          "200": { "$ref": "#/components/responses/GenericJSON" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": {
          "201": { "$ref": "#/components/responses/GenericJSON" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationFailed" }
        }
      }
    },
    "/api/v1/campaigns/{id}": {
      "get": {
        "tags": ["Campaigns"],
        "summary": "Fetch a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:read"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "tags": ["Campaigns"],
        "summary": "Update a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/activate": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Activate a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/pause": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Pause a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/archive": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Archive a campaign",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/enrollments": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create campaign enrollments",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/stages": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create a campaign stage",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{id}/suppressions": {
      "get": {
        "tags": ["Campaigns"],
        "summary": "List campaign suppressions",
        "security": [{ "TextTreeBearer": ["campaigns:read"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      },
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create a campaign suppression",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/campaigns/{campaign_id}/suppressions/{suppression_id}": {
      "delete": {
        "tags": ["Campaigns"],
        "summary": "Delete a campaign suppression",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [
          { "name": "campaign_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "suppression_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/api/v1/snippets": {
      "get": {
        "tags": ["Campaigns"],
        "summary": "List snippets",
        "security": [{ "TextTreeBearer": ["campaigns:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      },
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create a snippet",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/api/v1/snippets/{id}": {
      "patch": {
        "tags": ["Campaigns"],
        "summary": "Update a snippet",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/api/v1/contact-lists": {
      "get": {
        "tags": ["Campaigns"],
        "summary": "List contact lists",
        "security": [{ "TextTreeBearer": ["campaigns:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      },
      "post": {
        "tags": ["Campaigns"],
        "summary": "Create a contact list",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/api/v1/contact-lists/{id}/contacts": {
      "post": {
        "tags": ["Campaigns"],
        "summary": "Add a contact to a contact list",
        "security": [{ "TextTreeBearer": ["campaigns:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/api/v1/numbers": {
      "get": {
        "tags": ["Numbers"],
        "summary": "List numbers",
        "security": [{ "TextTreeBearer": ["numbers:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      },
      "post": {
        "tags": ["Numbers"],
        "summary": "Buy or provision a number",
        "security": [{ "TextTreeBearer": ["numbers:write"] }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/NumberCreateRequest" }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/GenericJSON" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationFailed" }
        }
      }
    },
    "/api/v1/onboarding": {
      "get": {
        "tags": ["Onboarding"],
        "summary": "Fetch programmatic onboarding status",
        "security": [{ "TextTreeBearer": ["onboarding:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/api/v1/onboarding/api-key": {
      "post": {
        "tags": ["Onboarding"],
        "summary": "Create an onboarding bearer access token",
        "description": "Requires onboarding:write and returns a TextTree txt_... bearer access token for developer API and MCP authorization. The path name is retained for backward compatibility.",
        "security": [{ "TextTreeBearer": ["onboarding:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/onboarding/test-sms": {
      "post": {
        "tags": ["Onboarding"],
        "summary": "Send the fixed onboarding test SMS",
        "security": [{ "TextTreeBearer": ["onboarding:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "202": { "$ref": "#/components/responses/GenericJSON" }, "402": { "$ref": "#/components/responses/PaymentRequired" }, "429": { "$ref": "#/components/responses/TooManyRequests" } }
      }
    },
    "/api/v1/onboarding/funding-invoices": {
      "post": {
        "tags": ["Onboarding"],
        "summary": "Create or reuse a funding invoice",
        "security": [{ "TextTreeBearer": ["onboarding:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/onboarding/funding-invoices/{id}": {
      "get": {
        "tags": ["Onboarding"],
        "summary": "Fetch funding invoice status",
        "security": [{ "TextTreeBearer": ["onboarding:read"] }],
        "parameters": [{ "$ref": "#/components/parameters/Id" }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/api/v1/onboarding/branded-request": {
      "post": {
        "tags": ["Onboarding"],
        "summary": "Submit dedicated-number business details",
        "security": [{ "TextTreeBearer": ["onboarding:write"] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/onboarding/dedicated-number": {
      "post": {
        "tags": ["Onboarding"],
        "summary": "Provision a dedicated number after request and funding",
        "security": [{ "TextTreeBearer": ["onboarding:write"] }],
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "402": { "$ref": "#/components/responses/PaymentRequired" }, "403": { "$ref": "#/components/responses/Forbidden" }, "422": { "$ref": "#/components/responses/ValidationFailed" } }
      }
    },
    "/api/v1/mcp/oauth-clients": {
      "get": {
        "tags": ["MCP"],
        "summary": "List MCP OAuth clients authorized for the current user",
        "security": [{ "TextTreeBearer": ["mcp:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/api/v1/mcp/oauth-clients/{client_id}": {
      "delete": {
        "tags": ["MCP"],
        "summary": "Revoke tokens for one MCP OAuth client",
        "security": [{ "TextTreeBearer": ["mcp:execute"] }],
        "parameters": [{ "name": "client_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/mcp/accounts": {
      "post": {
        "tags": ["MCP", "Accounts"],
        "summary": "Create a headless account through the MCP namespace",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/HeadlessAccountRequest" },
                  {
                    "type": "object",
                    "required": ["account"],
                    "properties": {
                      "account": { "$ref": "#/components/schemas/HeadlessAccountRequest" }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": { "201": { "description": "Account created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HeadlessAccountResponse" } } } }, "422": { "$ref": "#/components/responses/ValidationFailed" }, "429": { "$ref": "#/components/responses/TooManyRequests" } }
      }
    },
    "/mcp/tools": {
      "get": {
        "tags": ["MCP"],
        "summary": "List legacy REST MCP tools",
        "security": [{ "TextTreeBearer": ["mcp:read"] }],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" } }
      }
    },
    "/mcp/tools/{name}": {
      "post": {
        "tags": ["MCP"],
        "summary": "Execute a legacy REST MCP tool",
        "security": [{ "TextTreeBearer": ["mcp:execute"] }],
        "parameters": [{ "name": "name", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "params": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "403": { "$ref": "#/components/responses/Forbidden" }, "422": { "$ref": "#/components/responses/GenericJSON" }, "504": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/mcp": {
      "post": {
        "tags": ["MCP"],
        "summary": "Native MCP JSON-RPC endpoint",
        "description": "Stateless Streamable HTTP endpoint. Requires Authorization: Bearer plus Accept: application/json, text/event-stream and Content-Type: application/json.",
        "security": [{ "TextTreeBearer": [] }],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "202": { "description": "Notification or response message accepted" }, "400": { "$ref": "#/components/responses/GenericJSON" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "406": { "$ref": "#/components/responses/GenericJSON" }, "415": { "$ref": "#/components/responses/GenericJSON" } }
      },
      "get": {
        "tags": ["MCP"],
        "summary": "MCP SSE stream probe",
        "description": "Returns 405 because TextTree currently runs stateless Streamable HTTP with POST only.",
        "security": [{ "TextTreeBearer": [] }],
        "responses": { "405": { "$ref": "#/components/responses/GenericJSON" } }
      },
      "delete": {
        "tags": ["MCP"],
        "summary": "MCP session termination probe",
        "description": "Returns 405 because no server-side MCP session is allocated.",
        "security": [{ "TextTreeBearer": [] }],
        "responses": { "405": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/.well-known/oauth-protected-resource/mcp": {
      "get": {
        "tags": ["MCP", "OAuth"],
        "summary": "OAuth protected resource metadata for MCP",
        "security": [],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/.well-known/oauth-authorization-server/mcp": {
      "get": {
        "tags": ["MCP", "OAuth"],
        "summary": "OAuth authorization server metadata for MCP",
        "security": [],
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/oauth/register": {
      "post": {
        "tags": ["OAuth"],
        "summary": "Register an OAuth client",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "201": { "$ref": "#/components/responses/GenericJSON" }, "400": { "$ref": "#/components/responses/GenericJSON" }, "429": { "$ref": "#/components/responses/TooManyRequests" } }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": ["OAuth"],
        "summary": "Exchange an authorization code for a TextTree bearer token",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSONOrForm" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "400": { "$ref": "#/components/responses/GenericJSON" }, "429": { "$ref": "#/components/responses/TooManyRequests" } }
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": ["OAuth"],
        "summary": "Revoke a TextTree bearer token",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSONOrForm" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" } }
      }
    },
    "/webhooks/dial/events": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Dial delivery event webhook",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/webhooks/dial/incoming": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Dial inbound SMS webhook",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/webhooks/peer/events": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Peer funding event webhook",
        "security": [],
        "requestBody": { "$ref": "#/components/requestBodies/GenericJSON" },
        "responses": { "200": { "$ref": "#/components/responses/GenericJSON" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "TextTreeBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "TextTree txt_... access token",
        "description": "TextTree-issued bearer access token. Tokens start with txt_... and carry route scopes. Legacy txk_... API keys are not accepted for developer API or MCP authorization except by POST /api/v1/auth/migrate-legacy-key for one-time migration."
      }
    },
    "parameters": {
      "Id": { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
    },
    "requestBodies": {
      "GenericJSON": {
        "required": false,
        "content": {
          "application/json": {
            "schema": { "type": "object", "additionalProperties": true }
          }
        }
      },
      "GenericJSONOrForm": {
        "required": false,
        "content": {
          "application/json": { "schema": { "type": "object", "additionalProperties": true } },
          "application/x-www-form-urlencoded": { "schema": { "type": "object", "additionalProperties": true } }
        }
      }
    },
    "responses": {
      "GenericJSON": {
        "description": "JSON response",
        "content": {
          "application/json": {
            "schema": { "type": "object", "additionalProperties": true }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, expired, revoked, wrong-audience, or unsupported bearer token",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "examples": { "unauthorized": { "value": { "error": "unauthorized" } } }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but forbidden or missing the required scope",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "examples": { "insufficient_scope": { "value": { "error": "insufficient_scope", "required_scope": "messages:write" } } }
          }
        }
      },
      "PaymentRequired": {
        "description": "Spend limit, balance, or funding gate failed",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": {
        "description": "Conflict, commonly idempotency-key reuse with a different payload",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitedError" } } }
      },
      "ValidationFailed": {
        "description": "Request validation failed",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } }
      }
    },
    "schemas": {
      "StatusOk": {
        "type": "object",
        "required": ["status"],
        "properties": { "status": { "const": "ok" } },
        "additionalProperties": false
      },
      "APIHealth": {
        "type": "object",
        "required": ["status", "version", "service"],
        "properties": {
          "status": { "const": "ok" },
          "version": { "const": "v1" },
          "service": { "const": "textree-api" }
        },
        "additionalProperties": false
      },
      "MCPHealth": {
        "type": "object",
        "required": ["status", "surface"],
        "properties": {
          "status": { "const": "ok" },
          "surface": { "const": "mcp" }
        },
        "additionalProperties": false
      },
      "HeadlessAccountRequest": {
        "type": "object",
        "required": ["username", "password"],
        "properties": {
          "username": { "type": "string", "minLength": 3 },
          "password": { "type": "string", "minLength": 12 }
        },
        "additionalProperties": false
      },
      "HeadlessAccountResponse": {
        "type": "object",
        "required": ["account", "backup_codes", "token"],
        "properties": {
          "account": {
            "type": "object",
            "required": ["id", "username"],
            "properties": {
              "id": { "type": "string" },
              "username": { "type": "string" }
            },
            "additionalProperties": false
          },
          "backup_codes": { "type": "array", "items": { "type": "string" } },
          "token": { "$ref": "#/components/schemas/BearerToken" }
        },
        "additionalProperties": false
      },
      "BearerToken": {
        "type": "object",
        "required": ["type", "access_token", "scopes", "expires_at"],
        "properties": {
          "type": { "const": "Bearer" },
          "access_token": { "type": "string", "pattern": "^txt_" },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "expires_at": { "type": "string", "format": "date-time" }
        },
        "additionalProperties": false
      },
      "MessageCreateRequest": {
        "type": "object",
        "required": ["phone_number", "body"],
        "properties": {
          "phone_number": { "type": "string", "description": "Recipient E.164 phone number." },
          "body": { "type": "string", "minLength": 1, "maxLength": 1600 },
          "estimated_cost_cents": { "type": "integer", "minimum": 1 },
          "idempotency_key": { "type": "string", "minLength": 8, "maxLength": 128 },
          "metadata": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": false
      },
      "MessageEnvelope": {
        "type": "object",
        "required": ["message", "replayed"],
        "properties": {
          "message": { "$ref": "#/components/schemas/Message" },
          "replayed": { "type": "boolean" }
        },
        "additionalProperties": false
      },
      "Message": {
        "type": "object",
        "required": ["id", "status", "phone_number", "body", "provider", "consent_status", "estimated_cost_cents", "metadata", "inserted_at", "updated_at", "links"],
        "properties": {
          "id": { "type": "string" },
          "status": { "type": "string", "enum": ["queued", "dispatching", "sent", "delivered", "failed", "blocked"] },
          "phone_number": { "type": "string" },
          "body": { "type": "string" },
          "provider": { "type": "string" },
          "consent_status": { "type": "string" },
          "external_id": { "type": ["string", "null"] },
          "estimated_cost_cents": { "type": "integer" },
          "idempotency_key": { "type": ["string", "null"] },
          "metadata": { "type": "object", "additionalProperties": true },
          "inserted_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "links": {
            "type": "object",
            "properties": {
              "self": { "type": "string" }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "NumberCreateRequest": {
        "type": "object",
        "properties": {
          "area_code": { "type": "string" },
          "friendly_name": { "type": "string" }
        },
        "additionalProperties": true
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "required_scope": { "type": "string" },
          "message": { "type": "string" }
        },
        "additionalProperties": true
      },
      "RateLimitedError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" },
          "retry_after_seconds": { "type": "integer" },
          "next_allowed_at": { "type": "string" }
        },
        "additionalProperties": true
      },
      "ValidationError": {
        "type": "object",
        "required": ["error", "details"],
        "properties": {
          "error": { "const": "validation_failed" },
          "details": { "type": "object", "additionalProperties": true }
        },
        "additionalProperties": true
      }
    }
  }
}
