# KaiCalls public API — OpenAPI document.
# GENERATED FILE — do not edit by hand.
# Source of truth: src/lib/api/platformDiscovery.ts (buildOpenApiDocument),
# served live (authenticated) at https://www.kaicalls.com/api/v1/openapi.json.
# Regenerate with: pnpm tsx scripts/generate-static-openapi.ts
{
  "openapi": "3.1.0",
  "info": {
    "title": "KaiCalls Public API",
    "version": "2026-06-05",
    "description": "Machine-readable surface for KaiCalls external developers and client agents."
  },
  "servers": [
    {
      "url": "https://www.kaicalls.com/api"
    },
    {
      "url": "http://localhost:3000/api"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "KaiCalls API key. Per-operation scope requirements are published on each operation as `x-required-scopes` — a scope string, or a nested `{ allOf: [...] }` / `{ anyOf: [...] }` expression. An operation with no `x-required-scopes` requires a valid key but no particular scope.",
        "x-scopes": [
          "workspaces:read",
          "workspaces:write",
          "numbers:read",
          "numbers:write",
          "agents:read",
          "agents:write",
          "communication-runs:read",
          "communication-runs:write",
          "calls:read",
          "calls:write",
          "sms:read",
          "sms:write",
          "webhooks:read",
          "webhooks:write",
          "events:read",
          "events:replay",
          "lifecycle:write",
          "leads:read",
          "leads:write",
          "emails:read",
          "emails:write",
          "sdr:read",
          "sdr:write",
          "evals:read",
          "evals:write",
          "appointments:read",
          "appointments:write",
          "knowledge:read",
          "knowledge:write",
          "products:read",
          "products:write",
          "approvals:write",
          "concierge:write",
          "billing:read",
          "billing:write",
          "call_evidence:read",
          "call_evidence:write",
          "support:write"
        ]
      },
      "sessionAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "Authenticated dashboard session (Supabase auth cookie). Used by first-party flows that mint or manage API keys."
      }
    },
    "schemas": {
      "productUpsert": {
        "title": "Create or update a business product",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "price": {
            "type": "number"
          },
          "sku": {
            "type": "string"
          },
          "attributes": {
            "type": "object",
            "additionalProperties": true
          },
          "quantity": {
            "type": "number"
          }
        },
        "oneOf": [
          {
            "required": [
              "business_id",
              "name"
            ],
            "not": {
              "required": [
                "id"
              ]
            }
          },
          {
            "required": [
              "id"
            ]
          }
        ],
        "description": "Omit id to create. Supply the existing product id to update selected fields; business_id, if supplied, must match that product. SKU is stored data, not an upsert key. Persist the returned product.id for later listing sync updates."
      },
      "knowledgeUpsert": {
        "title": "Create or update business knowledge",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "business_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "minLength": 1
          },
          "content": {
            "type": "string",
            "minLength": 1
          },
          "content_type": {
            "type": "string",
            "minLength": 1
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          }
        },
        "oneOf": [
          {
            "required": [
              "title",
              "content",
              "content_type"
            ],
            "not": {
              "required": [
                "id"
              ]
            }
          },
          {
            "required": [
              "id"
            ]
          }
        ],
        "description": "business_id is required for both create and update. Omit id to create; retain the returned knowledge.id and send it to update that row. Title is not an upsert key."
      },
      "agentCreate": {
        "title": "Create a business agent",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "business_id",
          "name",
          "system_prompt"
        ],
        "properties": {
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "system_prompt": {
            "type": "string",
            "minLength": 1
          },
          "first_message": {
            "type": "string"
          },
          "voice_catalog_id": {
            "type": "string",
            "description": "Preferred voice ID from GET /v1/voices."
          },
          "voice": {
            "type": "object",
            "additionalProperties": true
          },
          "model": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string"
              },
              "model": {
                "type": "string"
              },
              "temperature": {
                "type": "number"
              }
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "agentUpdate": {
        "title": "Update an existing agent",
        "type": "object",
        "required": [
          "id"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "system_prompt": {
            "type": "string",
            "description": "Legacy alias for inbound_prompt."
          },
          "inbound_prompt": {
            "type": "string"
          },
          "outbound_prompt": {
            "type": "string"
          },
          "sms_prompt": {
            "type": "string"
          },
          "first_message": {
            "type": "string"
          },
          "voice_catalog_id": {
            "type": "string"
          },
          "voice": {
            "type": "object",
            "additionalProperties": true
          },
          "model": {
            "type": "object",
            "additionalProperties": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "max_duration": {
            "type": "number",
            "description": "Maximum call duration in seconds."
          },
          "vapi_config": {
            "type": "object",
            "additionalProperties": true
          },
          "transfer_enabled": {
            "type": "boolean"
          },
          "transfer_phone_number": {
            "type": "string"
          },
          "owner_change_summary": {
            "type": "string"
          },
          "owner_change_reason": {
            "type": "string"
          },
          "override_owner_edits": {
            "type": "boolean"
          }
        }
      },
      "signupOtpRequest": {
        "title": "Signup: request an email verification code",
        "type": "object",
        "required": [
          "business_name",
          "email"
        ],
        "properties": {
          "business_name": {
            "type": "string",
            "minLength": 1
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "business_type": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "phone_forward_to": {
            "type": "string",
            "description": "Owner forwarding phone number, preferably E.164."
          },
          "phone": {
            "type": "string",
            "description": "Fallback owner phone when phone_forward_to is absent."
          },
          "referral_code": {
            "type": "string"
          },
          "via": {
            "type": "string"
          },
          "attribution": {
            "type": "object",
            "additionalProperties": true
          },
          "how_heard": {
            "type": "string"
          },
          "api_key_scopes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "workspaces:read",
                "workspaces:write",
                "numbers:read",
                "numbers:write",
                "agents:read",
                "agents:write",
                "communication-runs:read",
                "communication-runs:write",
                "calls:read",
                "calls:write",
                "sms:read",
                "sms:write",
                "webhooks:read",
                "webhooks:write",
                "events:read",
                "events:replay",
                "lifecycle:write",
                "leads:read",
                "leads:write",
                "emails:read",
                "emails:write",
                "sdr:read",
                "sdr:write",
                "evals:read",
                "evals:write",
                "appointments:read",
                "appointments:write",
                "knowledge:read",
                "knowledge:write",
                "products:read",
                "products:write",
                "billing:read",
                "billing:write",
                "call_evidence:read",
                "call_evidence:write",
                "support:write"
              ]
            },
            "default": [
              "workspaces:read",
              "numbers:read",
              "agents:read",
              "communication-runs:read",
              "calls:read",
              "sms:read",
              "webhooks:read",
              "events:read"
            ],
            "description": "OTP mode only: optional explicit scopes for the new business-scoped key. Omit for read-only defaults. Request customer write scopes such as agents:write and calls:write when needed. Unknown, wildcard, empty, and platform-only scopes are rejected. Send the same scopes in both OTP requests; the email challenge is bound to the normalized scope set."
          },
          "activation_mode": {
            "type": "string",
            "const": "otp",
            "default": "otp"
          }
        },
        "not": {
          "anyOf": [
            {
              "required": [
                "challenge_id"
              ]
            },
            {
              "required": [
                "otp_code"
              ]
            }
          ]
        }
      },
      "signupOtpVerify": {
        "title": "Signup: verify email and provision the account",
        "type": "object",
        "required": [
          "business_name",
          "email",
          "challenge_id",
          "otp_code"
        ],
        "properties": {
          "business_name": {
            "type": "string",
            "minLength": 1
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "business_type": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "phone_forward_to": {
            "type": "string",
            "description": "Owner forwarding phone number, preferably E.164."
          },
          "phone": {
            "type": "string",
            "description": "Fallback owner phone when phone_forward_to is absent."
          },
          "referral_code": {
            "type": "string"
          },
          "via": {
            "type": "string"
          },
          "attribution": {
            "type": "object",
            "additionalProperties": true
          },
          "how_heard": {
            "type": "string"
          },
          "api_key_scopes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "workspaces:read",
                "workspaces:write",
                "numbers:read",
                "numbers:write",
                "agents:read",
                "agents:write",
                "communication-runs:read",
                "communication-runs:write",
                "calls:read",
                "calls:write",
                "sms:read",
                "sms:write",
                "webhooks:read",
                "webhooks:write",
                "events:read",
                "events:replay",
                "lifecycle:write",
                "leads:read",
                "leads:write",
                "emails:read",
                "emails:write",
                "sdr:read",
                "sdr:write",
                "evals:read",
                "evals:write",
                "appointments:read",
                "appointments:write",
                "knowledge:read",
                "knowledge:write",
                "products:read",
                "products:write",
                "billing:read",
                "billing:write",
                "call_evidence:read",
                "call_evidence:write",
                "support:write"
              ]
            },
            "default": [
              "workspaces:read",
              "numbers:read",
              "agents:read",
              "communication-runs:read",
              "calls:read",
              "sms:read",
              "webhooks:read",
              "events:read"
            ],
            "description": "OTP mode only: optional explicit scopes for the new business-scoped key. Omit for read-only defaults. Request customer write scopes such as agents:write and calls:write when needed. Unknown, wildcard, empty, and platform-only scopes are rejected. Send the same scopes in both OTP requests; the email challenge is bound to the normalized scope set."
          },
          "activation_mode": {
            "type": "string",
            "const": "otp",
            "default": "otp"
          },
          "challenge_id": {
            "type": "string",
            "minLength": 1
          },
          "otp_code": {
            "type": "string",
            "pattern": "^\\d{6}$"
          },
          "replay_token": {
            "type": "string",
            "description": "Returned replay proof for an idempotent retry."
          }
        }
      },
      "signupPayment": {
        "title": "Signup: paid carrier activation",
        "type": "object",
        "required": [
          "business_name",
          "email",
          "activation_mode"
        ],
        "not": {
          "required": [
            "api_key_scopes"
          ]
        },
        "properties": {
          "business_name": {
            "type": "string",
            "minLength": 1
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "business_type": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "phone_forward_to": {
            "type": "string",
            "description": "Owner forwarding phone number, preferably E.164."
          },
          "phone": {
            "type": "string",
            "description": "Fallback owner phone when phone_forward_to is absent."
          },
          "referral_code": {
            "type": "string"
          },
          "via": {
            "type": "string"
          },
          "attribution": {
            "type": "object",
            "additionalProperties": true
          },
          "how_heard": {
            "type": "string"
          },
          "activation_mode": {
            "type": "string",
            "const": "payment"
          },
          "plan_id": {
            "type": "string",
            "description": "Current plan name or allowed Stripe price ID; defaults to the entry public plan."
          }
        }
      },
      "communicationRunCreate": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/communicationRunCreate",
        "title": "Communication Run Create",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "business_id",
          "external_ref",
          "touches"
        ],
        "properties": {
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_ref": {
            "type": "string",
            "minLength": 1
          },
          "assigned_number_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "phone_number_registry phone_number (E.164) or business_phone_numbers id (uuid)."
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "touches": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/$defs/communicationTouch"
            }
          }
        },
        "$defs": {
          "communicationTouch": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "touch_id",
              "channel",
              "target",
              "scheduled_at"
            ],
            "properties": {
              "touch_id": {
                "type": "string",
                "minLength": 1
              },
              "channel": {
                "type": "string",
                "enum": [
                  "call",
                  "sms"
                ]
              },
              "target": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "phone"
                ],
                "properties": {
                  "phone": {
                    "type": "string",
                    "minLength": 1
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email"
                  },
                  "timezone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "state": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "scheduled_at": {
                "type": "string",
                "format": "date-time"
              },
              "timing_window": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "properties": {
                  "not_before": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "not_after": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              "context": {
                "type": "object",
                "additionalProperties": true
              },
              "metadata": {
                "type": "object",
                "additionalProperties": true
              },
              "message": {
                "type": "string"
              },
              "sms_category": {
                "type": "string"
              }
            }
          }
        }
      },
      "webhookUpsert": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/webhookUpsert",
        "title": "Webhook Upsert",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "business_id",
          "webhook_url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "lead.created",
                "lead.scored",
                "call.received",
                "call.started",
                "call.completed",
                "call.failed",
                "sms.received",
                "sms.sent",
                "sms.delivered",
                "sms.failed",
                "voicemail.received",
                "dispatcher.briefing_created",
                "dispatcher.handoff_dispatched",
                "phone_number.assigned",
                "phone_number.released",
                "phone_number.purchased",
                "workspace.paused",
                "workspace.canceled",
                "workspace.created",
                "workspace.provisioning_failed",
                "workspace.ready",
                "workspace.teardown_completed",
                "lead_email.sent",
                "lead_email.failed",
                "callback.scheduled",
                "callback.completed",
                "conversation.handoff"
              ]
            },
            "uniqueItems": true
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "preset": {
            "type": "string",
            "enum": [
              "generic_crm",
              "litify",
              "filevine",
              "clio_grow",
              "lead_docket",
              "gohighlevel"
            ],
            "description": "Expands to an editable starting-point field map for that CRM. Ignored when payload_template is sent."
          },
          "payload_template": {
            "type": [
              "object",
              "null"
            ],
            "description": "Field map { \"fields\": { \"<field>\": \"<dot path>\" | { \"value\": <constant> } } }. Paths read the standard event body; \"a|b\" takes the first with a value; a dot in the field name nests it. Missing paths render null. null = standard body.",
            "properties": {
              "fields": {
                "type": "object",
                "maxProperties": 100,
                "additionalProperties": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "object",
                      "required": [
                        "value"
                      ],
                      "properties": {
                        "value": {
                          "type": [
                            "string",
                            "number",
                            "boolean",
                            "null"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "custom_headers": {
            "type": [
              "object",
              "null"
            ],
            "maxProperties": 20,
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Extra request headers. A null value keeps the saved value. Content-Type, Host, Content-Length and X-KaiCalls-* are rejected. Values are never returned."
          },
          "http_method": {
            "type": "string",
            "enum": [
              "POST",
              "PUT",
              "PATCH"
            ],
            "default": "POST"
          },
          "body_format": {
            "type": "string",
            "enum": [
              "json",
              "form"
            ],
            "default": "json"
          }
        }
      },
      "apiKeyCreate": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/apiKeyCreate",
        "title": "API Key Create",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "workspaces:read",
                "workspaces:write",
                "numbers:read",
                "numbers:write",
                "agents:read",
                "agents:write",
                "communication-runs:read",
                "communication-runs:write",
                "calls:read",
                "calls:write",
                "sms:read",
                "sms:write",
                "leads:read",
                "leads:write",
                "emails:read",
                "emails:write",
                "evals:read",
                "evals:write",
                "webhooks:read",
                "webhooks:write",
                "events:read",
                "events:replay",
                "lifecycle:write",
                "appointments:read",
                "appointments:write",
                "knowledge:read",
                "knowledge:write",
                "products:read",
                "products:write",
                "billing:read",
                "billing:write"
              ]
            },
            "uniqueItems": true
          },
          "partner_id": {
            "type": "string",
            "format": "uuid",
            "description": "Platform operators only (the operator key or a platform admin session). Mints a partner key: it reaches exactly the businesses that partner created, and businesses it creates (POST /v1/workspaces, POST /v1/signup) are linked to the partner. Not combinable with business_id."
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "Partner keys only: the account the key acts as (linked as owner of workspaces it creates). Defaults to the minting operator."
          }
        }
      },
      "agentPersonaLoad": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/agentPersonaLoad",
        "title": "Agent Persona Load",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "agent_id",
          "persona"
        ],
        "properties": {
          "agent_id": {
            "type": "string",
            "format": "uuid"
          },
          "first_message": {
            "type": "string",
            "description": "Optional override of the opener derived from the persona."
          },
          "voice": {
            "type": "object",
            "additionalProperties": true
          },
          "model": {
            "type": "object",
            "additionalProperties": true
          },
          "persona": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "lead_name",
              "scenario"
            ],
            "properties": {
              "lead_name": {
                "type": "string",
                "minLength": 1
              },
              "scenario": {
                "type": "string",
                "minLength": 1
              },
              "objective": {
                "type": "string"
              },
              "script": {
                "type": "string"
              },
              "persona_traits": {
                "type": "string"
              },
              "facts_to_reveal": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "facts_to_withhold": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "callback_number": {
                "type": "string"
              },
              "opener": {
                "type": "string"
              }
            }
          }
        }
      },
      "updateRequest": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/updateRequest",
        "title": "On-Behalf Update Request",
        "description": "Governed mutation request for the KaiCalls update broker. Carries the safety envelope (idempotency_key, actor, authority) required for auditable on-behalf changes.",
        "type": "object",
        "additionalProperties": true,
        "required": [
          "intent"
        ],
        "properties": {
          "intent": {
            "type": "string",
            "enum": [
              "phone.emergency_address.set",
              "transcripts.sink.configure",
              "agent.patch"
            ]
          },
          "business_id": {
            "type": "string",
            "format": "uuid"
          },
          "target": {
            "type": "object",
            "additionalProperties": true
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "description": "Required for mutating requests; replays return the original outcome."
          },
          "actor": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "agent",
                  "human",
                  "system",
                  "integration"
                ]
              },
              "id": {
                "type": "string"
              },
              "display_name": {
                "type": "string"
              }
            }
          },
          "authority": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "none",
                  "agent_asserted",
                  "human_confirmed",
                  "dashboard_session",
                  "system_policy"
                ]
              },
              "confirmed_by": {
                "type": "string"
              },
              "confirmed_at": {
                "type": "string",
                "format": "date-time"
              },
              "confirmation_ref": {
                "type": "string"
              }
            }
          },
          "dry_run": {
            "type": "boolean"
          },
          "queue_for_approval": {
            "type": "boolean"
          },
          "reason": {
            "type": "string"
          },
          "source_ref": {
            "type": "string"
          }
        }
      },
      "approvalRequestCreate": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://www.kaicalls.com/api/v1/schemas/approvalRequestCreate",
        "title": "Approval Channel Request Create",
        "description": "Generic, provider-neutral approval-channel intake — lets an external agent control plane (first consumer: Zehrava Gate) reach a human by voice and get the interaction tracked through the normal call lifecycle.",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "source",
          "source_request_id",
          "principal_id",
          "channel",
          "summary",
          "risk_level",
          "expires_at",
          "callback_url"
        ],
        "properties": {
          "source": {
            "type": "string",
            "description": "Lowercase control-plane slug, e.g. \"zehrava-gate\"."
          },
          "source_request_id": {
            "type": "string"
          },
          "source_intent_id": {
            "type": "string"
          },
          "message_id": {
            "type": "string"
          },
          "responds_to_hash": {
            "type": "string",
            "pattern": "^sha256:[a-f0-9]{64}$"
          },
          "principal_id": {
            "type": "string"
          },
          "channel": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "type",
              "address"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "voice",
                  "voice_then_sms",
                  "sms_then_voice",
                  "sms"
                ],
                "description": "Only voice-prefixed types dispatch a live call today; \"sms\" returns 501."
              },
              "address": {
                "type": "string",
                "description": "E.164 phone number."
              }
            }
          },
          "summary": {
            "type": "string",
            "maxLength": 2000
          },
          "risk_level": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ]
          },
          "required_factors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Must be https and host-allowlisted."
          },
          "callback_secret_id": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/v1/capabilities": {
      "get": {
        "summary": "Return enabled scopes, workspace state, supported events, and platform capabilities.",
        "responses": {
          "200": {
            "description": "Capabilities response"
          }
        }
      }
    },
    "/v1/openapi.json": {
      "get": {
        "summary": "Return this OpenAPI document.",
        "responses": {
          "200": {
            "description": "OpenAPI document"
          }
        }
      }
    },
    "/v1/schemas": {
      "get": {
        "summary": "Return JSON Schemas by name or as a catalog.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "productUpsert",
                "knowledgeUpsert",
                "agentCreate",
                "agentUpdate",
                "signupOtpRequest",
                "signupOtpVerify",
                "signupPayment",
                "communicationRunCreate",
                "webhookUpsert",
                "apiKeyCreate",
                "agentPersonaLoad",
                "updateRequest",
                "approvalRequestCreate"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Schema catalog or named schema"
          }
        }
      }
    },
    "/v1/workspaces": {
      "get": {
        "summary": "List accessible workspaces or retrieve one workspace by id.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workspace list or workspace object"
          }
        },
        "x-required-scopes": "workspaces:read"
      },
      "post": {
        "summary": "Create a workspace (business) owned by the calling key user.",
        "responses": {
          "201": {
            "description": "Created workspace"
          }
        },
        "x-required-scopes": "workspaces:write"
      },
      "patch": {
        "summary": "Update a workspace name/metadata, or run a lifecycle action (pause, resume, cancel, teardown). Teardown is soft: it releases keys, webhooks, and numbers and cancels runs, but preserves evidence.",
        "responses": {
          "200": {
            "description": "Updated workspace"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "lifecycle:write",
            "workspaces:write"
          ]
        }
      }
    },
    "/v1/communication-events": {
      "get": {
        "summary": "List durable communication events with webhook delivery attempts.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Communication event list"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:read",
            "webhooks:read",
            "events:read"
          ]
        }
      }
    },
    "/v1/events": {
      "get": {
        "summary": "List durable communication events with webhook delivery attempts (PRD alias).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Communication event list"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "events:read",
            "communication-runs:read",
            "webhooks:read"
          ]
        }
      }
    },
    "/v1/event-deliveries": {
      "get": {
        "summary": "List webhook delivery attempts for reconciliation.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery attempt list"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "events:read",
            "webhooks:read"
          ]
        }
      }
    },
    "/v1/observability-events": {
      "get": {
        "summary": "List compact call-runtime and redacted integration-delivery events for a workspace.",
        "description": "Requires events:read. Results are ordered by occurred_at and id descending and use an opaque keyset cursor. Source-specific filters select only the matching stream.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "runtime",
                "delivery"
              ],
              "default": "all"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque next_cursor returned by the previous page."
          },
          {
            "name": "event_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "severity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "info",
                "warning",
                "error"
              ]
            }
          },
          {
            "name": "call_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversation_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "correlation_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "outcome",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "success",
                "failure"
              ]
            }
          },
          {
            "name": "operation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "connection_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Observability event page"
          },
          "400": {
            "description": "Invalid or incompatible filter"
          },
          "403": {
            "description": "Missing scope or inaccessible business"
          }
        },
        "x-required-scopes": "events:read"
      }
    },
    "/v1/change-history": {
      "get": {
        "summary": "List a business's admin_change_history audit trail (agents:read) — every mutation KaiCalls makes to an agent (greeting/prompt/voice/lead edits, rollback lineage), regardless of source (admin call-in, dashboard, editor chat, or the public API).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "change_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "change_source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Change history list"
          }
        },
        "x-required-scopes": "agents:read"
      }
    },
    "/v1/call-evidence": {
      "get": {
        "summary": "List tenant-scoped call evidence packets (call_evidence:read), or fetch one with ?packet_id=. Foundation slice of #1290 — single-call packets only; sharing/download/reconcile routes are not yet built.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "packet_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Packet list, or a single packet with its artifacts"
          },
          "404": {
            "description": "Packet not found for this business"
          }
        },
        "x-required-scopes": "call_evidence:read"
      },
      "post": {
        "summary": "Create and synchronously finalize a redacted evidence packet for exactly one call_id (call_evidence:write). Supports idempotency_key for exactly-once replay.",
        "responses": {
          "200": {
            "description": "Idempotent replay of an already-created packet"
          },
          "202": {
            "description": "Packet created and finalized or blocked on a quality gate"
          },
          "400": {
            "description": "Invalid selection_reason, export_level, or call_ids (>1 not yet supported)"
          },
          "404": {
            "description": "call_id not found for this business"
          }
        },
        "x-required-scopes": "call_evidence:write"
      }
    },
    "/v1/tool-execution-logs": {
      "get": {
        "summary": "List per-call Vapi tool execution traces (events:read) from vapi_tool_execution_logs — outcome, latency, timeout, and a redacted result preview for each routed tool call.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "call_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tool_call_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tool_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "outcome_status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool execution log list"
          }
        },
        "x-required-scopes": "events:read"
      }
    },
    "/v1/subscription-history": {
      "get": {
        "summary": "List plan/price change history (billing:read) from subscription_change_history — the billing analogue of admin_change_history, written from the Stripe webhook and the right-size apply job.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "change_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscription change history list"
          }
        },
        "x-required-scopes": "billing:read"
      }
    },
    "/v1/overage-charges": {
      "get": {
        "summary": "List the idempotent overage-minutes ledger (billing:read) from billing_overage_charges — legacy per-minute-overage tiers only. GET /v1/balance returns an aggregate only; this returns the underlying per-period rows.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Overage charge ledger list"
          }
        },
        "x-required-scopes": "billing:read"
      }
    },
    "/v1/rightsize-recommendations": {
      "get": {
        "summary": "List per-period auto-right-size decisions (billing:read) from plan_rightsize_recommendations, including the dry_run -> notified -> (kept | applied | superseded) lifecycle.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Right-size recommendation list"
          }
        },
        "x-required-scopes": "billing:read"
      }
    },
    "/v1/events/replay": {
      "post": {
        "summary": "Re-queue events for redelivery (events:replay). Provide event_id, event_ids[], or a filter; capped per request. Preserves the original event id.",
        "responses": {
          "200": {
            "description": "Replay result"
          }
        },
        "x-required-scopes": "events:replay"
      }
    },
    "/v1/events/backfill": {
      "post": {
        "summary": "Re-queue a time window of events for redelivery (events:replay). Requires from/to; window and match count are capped.",
        "responses": {
          "200": {
            "description": "Backfill result"
          }
        },
        "x-required-scopes": "events:replay"
      }
    },
    "/v1/business/profile": {
      "get": {
        "summary": "Public, unauthenticated lookup of a KaiCalls business's services, hours, pricing, service area, and FAQ by business ID or phone number. Rate-limited per source IP.",
        "parameters": [
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Business ID (UUID) or the business's public phone number."
          }
        ],
        "responses": {
          "200": {
            "description": "Business profile"
          },
          "400": {
            "description": "Missing or invalid handle"
          },
          "404": {
            "description": "No business found for that handle"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/v1/business/phone": {
      "post": {
        "summary": "Change the number a business's assistants give to callers (agents:write). Updates the business phone and, by default, redeploys each assigned assistant's stored inbound prompt unchanged so the new number reaches callers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "phone"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "description": "New caller-facing phone number."
                  },
                  "redeploy": {
                    "type": "boolean",
                    "default": true,
                    "description": "Recompile every assigned assistant's prompt with the new number."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Why the number changed (audit trail)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone updated, with per-assistant redeploy results"
          },
          "400": {
            "description": "Missing business_id or invalid phone"
          },
          "403": {
            "description": "Missing scope or no access to this business"
          },
          "404": {
            "description": "Business not found"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/business/agent-card": {
      "get": {
        "summary": "Public, unauthenticated A2A agent.json-shaped capability card for a KaiCalls business, pointing at its structured business-profile endpoint. Rate-limited per source IP.",
        "parameters": [
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Business ID (UUID) or the business's public phone number."
          }
        ],
        "responses": {
          "200": {
            "description": "Agent capability card"
          },
          "400": {
            "description": "Missing or invalid handle"
          },
          "404": {
            "description": "No business found for that handle"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/v1/directory-search": {
      "get": {
        "summary": "Public, unauthenticated search of the KaiCalls phone book (businesses that opted into the platform-wide voice directory). Paginated, filterable by category/city/state/name. Rate-limited per source IP.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of listed businesses"
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/v1/phone-numbers/available": {
      "get": {
        "summary": "List currently unassigned phone numbers from the KaiCalls registry.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "area_code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "capability",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Available phone number list"
          }
        },
        "x-required-scopes": "numbers:read"
      }
    },
    "/v1/phone-numbers": {
      "get": {
        "summary": "List assigned phone numbers or retrieve one assigned number by phone_number.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone_number",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Assigned phone number list or object"
          }
        },
        "x-required-scopes": "numbers:read"
      },
      "post": {
        "summary": "Assign an existing registry phone number to a workspace.",
        "responses": {
          "200": {
            "description": "Assigned phone number"
          }
        },
        "x-required-scopes": "numbers:write"
      },
      "delete": {
        "summary": "Release an assigned phone number without deleting historical evidence.",
        "responses": {
          "200": {
            "description": "Release result"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/phone-numbers/search": {
      "get": {
        "summary": "Search purchasable phone numbers by area code and country.",
        "parameters": [
          {
            "name": "area_code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "US"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Purchasable phone number candidates"
          }
        },
        "x-required-scopes": "numbers:read"
      }
    },
    "/v1/meetings": {
      "get": {
        "summary": "Meetings the business's agent was sent into (newest first), or one by id with live status, minutes used and the summary, plus this month's meeting-minute allowance.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Meetings and allowance"
          }
        },
        "x-required-scopes": "agents:read"
      },
      "post": {
        "summary": "Send the business's agent into a Zoom, Google Meet, Microsoft Teams or Webex meeting by link. It joins under the agent's name (the host admits it from a waiting room), takes notes, answers when addressed by name, and emails the owner a summary afterwards. Counts against the business's monthly meeting minutes (402 when none are left). Works with partner-scoped keys for the businesses the partner created. Admin role (session) or agents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "meeting_url"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "meeting_url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "agent_id": {
                    "type": "string"
                  },
                  "bot_name": {
                    "type": "string",
                    "description": "Speakable roster name override."
                  },
                  "camera_image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public https .jpg shown as the camera tile."
                  },
                  "instructions": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Context for this meeting only."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "listen",
                      "avatar"
                    ],
                    "default": "listen",
                    "description": "'listen': still camera tile, answers out loud when addressed. 'avatar': talking video face from a hosted assistant; counts as one avatar meeting (one lifetime free per business, then plan or purchased avatar meetings); 30 minutes on camera, then voice-only, ended at 60 minutes."
                  },
                  "avatar_id": {
                    "type": "string",
                    "description": "Avatar mode: stock face id (400 lists the valid ids)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already joining this meeting (same link)"
          },
          "201": {
            "description": "Joining"
          },
          "400": {
            "description": "Unsupported link or invalid override"
          },
          "402": {
            "description": "No meeting minutes (or, in avatar mode, no avatar meetings) left"
          },
          "502": {
            "description": "The meeting provider could not start the session"
          }
        },
        "x-required-scopes": "agents:write"
      },
      "delete": {
        "summary": "Have the agent leave a meeting. Notes taken so far are kept.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Left the meeting"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/phone-system": {
      "get": {
        "summary": "Phone-system status for a business: which carrier owns its numbers and, for hosted-phone-system tenants, the provisioned resource ids (never secrets).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Phone-system status"
          }
        },
        "x-required-scopes": "numbers:read"
      },
      "post": {
        "summary": "Provision (or resume provisioning) the hosted phone system for a business: carrier account, owner seat, Kai as extension 700, team ring group with the owner mobile, and the ring-team-then-Kai dial plan. Idempotent — a re-run resumes from stored ids. Pass business_address to save the street address first when the last attempt returned 409 missing_address. Owner role (session) or numbers:write. The admin key may also pass action=register_platform_webhook to register the per-environment events endpoint once (returns the signing secret exactly once).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "provision",
                      "register_platform_webhook",
                      "add_ring_group_member",
                      "get_flow",
                      "save_flow",
                      "propose_flow"
                    ],
                    "default": "provision",
                    "description": "get_flow reads the reconciled phone flow (who rings, for how long, business-hours mode) from the phone system; save_flow validates and applies a flow idempotently (loop guard on every member, schedule derived from business hours); propose_flow turns free text into a validated flow plus a one-sentence readback without applying it."
                  },
                  "flow": {
                    "type": "object",
                    "description": "save_flow: the phone flow — { version: 1, hours: { mode: always | business_hours, afterHours: kai }, ring: { members: [{ kind: cell, phone, label, requirePressOne } | { kind: desk_phone | user, userId, label }], timeoutSeconds }, overflow: kai }."
                  },
                  "text": {
                    "type": "string",
                    "description": "propose_flow: how the phones should ring, in the owner's words (e.g. \"me and Maria ring for 20 seconds, after hours straight to Kai\")."
                  },
                  "owner_mobile": {
                    "type": "string",
                    "description": "E.164 override for the ring-group member (defaults to the owner mobile on file)."
                  },
                  "business_address": {
                    "type": "object",
                    "description": "provision: the business street address to save before provisioning — { street, city, state, zip } (US). Required only when no complete address is on file (409 missing_address).",
                    "properties": {
                      "street": {
                        "type": "string"
                      },
                      "city": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      },
                      "zip": {
                        "type": "string"
                      }
                    }
                  },
                  "phone_number": {
                    "type": "string",
                    "description": "add_ring_group_member: the teammate cell (E.164) to add to the team ring group. Owners/admins are also registered on the admin roster so calling the business line from that cell reaches the secretary. The forwarded-from office line is refused (loop guard)."
                  },
                  "name": {
                    "type": "string",
                    "description": "add_ring_group_member: display name."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ],
                    "description": "add_ring_group_member: member only rings; owner/admin can call Kai."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provisioning result (ids, created/reused, warnings)"
          },
          "409": {
            "description": "Provisioning blocked (missing address / owner mobile)"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/phone-system/devices": {
      "get": {
        "summary": "Desk-phone shipping requests for a business (handset count, status, tracking) — the \"phones on the way\" state of the hosted phone system.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Device requests, newest first"
          }
        },
        "x-required-scopes": "numbers:read"
      },
      "post": {
        "summary": "Request desk phones for the hosted phone system: handset count + shipping address. Fulfilled by operations (no device API this cycle); phones arrive pre-configured. Owner role (session) or numbers:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "handset_count",
                  "address"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "handset_count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  },
                  "contact_name": {
                    "type": "string"
                  },
                  "address": {
                    "type": "object",
                    "required": [
                      "line1",
                      "city",
                      "state",
                      "postal_code"
                    ],
                    "properties": {
                      "line1": {
                        "type": "string"
                      },
                      "line2": {
                        "type": "string"
                      },
                      "city": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      },
                      "postal_code": {
                        "type": "string"
                      },
                      "country": {
                        "type": "string",
                        "default": "US"
                      }
                    }
                  },
                  "notes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved device request"
          },
          "400": {
            "description": "Missing handset count or address"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/phone-system/desk-phones": {
      "get": {
        "summary": "The desk phones plugged into a business's phone system, read live (name, MAC address, model, registration, 911 location, config-fetch and call timestamps). Ready requires current registration and a location. action=setup returns the private setup address to an owner or numbers:write key only, with no caching.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "setup"
              ]
            },
            "description": "Owner-only private setup address; requires numbers:write for API keys."
          },
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Desk phones or owner-only setup address"
          },
          "403": {
            "description": "Business access or required role/scope denied"
          }
        },
        "x-required-scopes": "numbers:read"
      },
      "post": {
        "summary": "Add a desk phone by MAC address: creates its line, registers the phone, and seats it in the team ring group so it rings with the team and unanswered calls reach Kai. Refused (409 e911_address_required) until the office address is validated. Owner role (session) or numbers:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "oneOf": [
                  {
                    "required": [
                      "mac_address",
                      "name"
                    ],
                    "not": {
                      "required": [
                        "action"
                      ]
                    }
                  },
                  {
                    "required": [
                      "action",
                      "device_id"
                    ]
                  }
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "mac_address": {
                    "type": "string",
                    "example": "00:04:13:aa:bb:cc"
                  },
                  "name": {
                    "type": "string",
                    "example": "Front Desk"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "refresh",
                      "attach_emergency_location"
                    ],
                    "description": "Send 'refresh' with device_id to reload settings. Send 'attach_emergency_location' to attach the provisioned main office location only when the phone has no location; existing locations are preserved. Both require owner role or numbers:write."
                  },
                  "device_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone added, settings reload requested, or emergency location verified by readback"
          },
          "400": {
            "description": "Invalid MAC address or missing name"
          },
          "409": {
            "description": "Duplicate phone, phone system not set up, or address not validated"
          }
        },
        "x-required-scopes": "numbers:write"
      },
      "delete": {
        "summary": "Remove a desk phone and its line from the phone system. Owner role (session) or numbers:write.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "device_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed"
          },
          "404": {
            "description": "Phone not found"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/phone-numbers/purchase": {
      "post": {
        "summary": "Purchase and assign a phone number (billed as an extra line when over the plan allowance).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone_number"
                ],
                "properties": {
                  "phone_number": {
                    "type": "string",
                    "description": "E.164 number from GET /v1/phone-numbers/search"
                  },
                  "business_id": {
                    "type": "string"
                  },
                  "agent_id": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Purchased and assigned phone number"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/communication-runs": {
      "get": {
        "summary": "List or retrieve communication runs.",
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:read",
            "calls:read",
            "sms:read"
          ]
        }
      },
      "post": {
        "summary": "Create a communication run. Requires an Idempotency-Key header; repeating a key returns the original run (200) instead of creating a new one (201).",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/communicationRunCreate"
              }
            }
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:write",
            {
              "allOf": [
                "calls:write",
                "sms:write"
              ]
            }
          ]
        }
      },
      "patch": {
        "summary": "Pause or cancel a communication run.",
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:write",
            {
              "allOf": [
                "calls:write",
                "sms:write"
              ]
            }
          ]
        }
      }
    },
    "/v1/communication-runs/validate": {
      "post": {
        "summary": "Validate a communication-run payload without scheduling work.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/communicationRunCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:write",
            {
              "allOf": [
                "calls:write",
                "sms:write"
              ]
            }
          ]
        }
      }
    },
    "/v1/communication-runs/preview": {
      "post": {
        "summary": "Preview planned run objects, touch counts, and schedule window.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/communicationRunCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preview result"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "communication-runs:write",
            {
              "allOf": [
                "calls:write",
                "sms:write"
              ]
            }
          ]
        }
      }
    },
    "/v1/campaigns": {
      "get": {
        "summary": "List campaigns (filter by status) or retrieve one by id (with enrolled-lead count).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-required-scopes": "calls:read"
      },
      "post": {
        "summary": "Create a draft outbound campaign (cadence + optional lead batch). Requires calls:write and admin role on the business. Does not place calls until launched.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "cadence_config"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "cadence_config": {
                    "type": "object",
                    "properties": {
                      "template": {
                        "type": "string"
                      },
                      "attempts": {
                        "type": "integer"
                      }
                    }
                  },
                  "lead_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-required-scopes": "calls:write"
      },
      "patch": {
        "summary": "Launch/resume (action=launch, needs agent_id) or pause (action=pause) a campaign. Requires calls:write and admin role. Launch queues jobs for the orchestrator, preserving TCPA/consent gating at dial time.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "action"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "launch",
                      "pause"
                    ]
                  },
                  "agent_id": {
                    "type": "string"
                  },
                  "business_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/agents": {
      "get": {
        "summary": "List or retrieve workspace agents. A retrieved agent includes its current voice enriched with catalog metadata (catalogVoiceId, displayName, accent, language, sampleUrl) when the voice maps to the catalog.",
        "x-required-scopes": "agents:read"
      },
      "post": {
        "summary": "Create an agent. Set the voice via voice_catalog_id (preferred — a GET /v1/voices id that resolves to the full provider/model/settings) or a raw voice object.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/agentCreate"
              }
            }
          }
        },
        "x-required-scopes": "agents:write"
      },
      "patch": {
        "summary": "Update an agent. Set the voice via voice_catalog_id (preferred — a GET /v1/voices id) or a raw voice object.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/agentUpdate"
              }
            }
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/agents/enable-multilingual": {
      "post": {
        "summary": "Safely enable automatic handling for 2-4 selected caller languages. Captures a secret-safe preimage, changes only inbound prompt, outbound prompt, and transcriber, evaluates each language plus switching and identity, and automatically restores the verified preimage if any evaluation fails or times out. Requires agents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agent_id",
                  "languages"
                ],
                "properties": {
                  "agent_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "languages": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 4,
                    "uniqueItems": true,
                    "items": {
                      "type": "string",
                      "enum": [
                        "en",
                        "es",
                        "fr",
                        "de",
                        "hi",
                        "ru",
                        "pt",
                        "ja",
                        "it",
                        "nl"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Language update committed after all required evaluations pass."
          },
          "409": {
            "description": "Preflight rejected because of invalid state or configuration drift."
          },
          "422": {
            "description": "Evaluation gate failed; the original configuration was restored and verified."
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/voices": {
      "get": {
        "summary": "List available voices (curated, credential-free catalog). Each id is the voice_catalog_id accepted by POST/PATCH /v1/agents. Requires agents:read.",
        "parameters": [
          {
            "name": "language",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "gender",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "male",
                "female"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Voice catalog list"
          }
        },
        "x-required-scopes": "agents:read"
      }
    },
    "/v1/agents/versions": {
      "get": {
        "summary": "List hashed, redacted assistant config versions for an agent (rollback lineage). Pass version for one snapshot.",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "version",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent config version list or object"
          }
        },
        "x-required-scopes": "agents:read"
      }
    },
    "/v1/agents/tools": {
      "get": {
        "summary": "List which agent tools (e.g. dispatcher_create_briefing, send_sms, smart_book_appointment) are currently enabled on an agent's assistant. Requires agents:read.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Enabled + available tool lists"
          }
        },
        "x-required-scopes": "agents:read"
      },
      "patch": {
        "summary": "Enable and/or disable agent tools on an agent's assistant. Public-API equivalent of the dashboard Settings -> Tools & Capabilities panel. Requires agents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Agent id"
                  },
                  "enable": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "disable": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated enabled/disabled tool results"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/agents/rollback": {
      "post": {
        "summary": "Restore an agent to a previously-deployed config version (greeting / voice / prompt+model only, never secrets), then record a new active version whose rolled_back_from_version_id points at the version that was live before. Scoped via agent_assignments; requires agents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agent_id"
                ],
                "properties": {
                  "agent_id": {
                    "type": "string"
                  },
                  "version_number": {
                    "type": "integer",
                    "description": "Target version number (one of version_number / version_id required)."
                  },
                  "version_id": {
                    "type": "string",
                    "description": "Target version id (one of version_number / version_id required)."
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rollback applied; returns the new active config version."
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/agents/persona": {
      "post": {
        "summary": "Load a structured mystery-shop persona into an agent (Intake Audit). Compiles the persona into a role-play system prompt + opener and applies it through the standard agent-update path (Vapi sync, change-history audit, owner-edit guard). Requires agents:write.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/agentPersonaLoad"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Persona loaded; returns the agent-update result plus persona_summary."
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/model-upgrades": {
      "get": {
        "summary": "Readiness probe for the staged voice-model upgrade executor (agents:read). Returns the model-upgrades-v1 capability contract: `available`, `mutations_enabled`, `notice_ready` and the remaining `prerequisites`. Deliberately not env-gated — every flag reports false until a trusted provider-receipt adapter is implemented, so a 200 here means \"the contract is readable\", never \"upgrades are live\". Poll this before attempting an upgrade rather than inferring readiness from a successful HTTP status.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Capability contract for the upgrade executor. `available: false` today; `reason` names the blocker."
          },
          "400": {
            "description": "business_id was not supplied."
          },
          "403": {
            "description": "Missing agents:read, or the business is not accessible."
          }
        },
        "x-required-scopes": "agents:read"
      },
      "post": {
        "summary": "Apply a staged voice-model upgrade. Platform-operator only: requires the wildcard admin key in addition to agents:write, so customer kc_live_* keys are always rejected. Fails closed today — no request body is accepted, no receipt is persisted, and no model or customer notification is changed.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "business_id was not supplied."
          },
          "403": {
            "description": "Not the operator principal, missing agents:write, or the business is not accessible."
          },
          "503": {
            "description": "upgrade_bridge_unavailable — the executor is not implemented yet. Returned alongside the same capability contract as GET. This is the only outcome at present."
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/admin-mode": {
      "get": {
        "summary": "Read the current admin/test mode state for a business (agents:write). The UI uses this to filter admin-routing rows.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mode state for the business"
          }
        },
        "x-required-scopes": "agents:write"
      },
      "patch": {
        "summary": "Enable, refresh, or disable an explicit admin-routing phone for a business (agents:write). enabled=true enables/refreshes the admin phone; enabled=false disables it (idempotent, row retained).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "phone_number",
                  "enabled"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "phone_number": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "label": {
                    "type": "string"
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated mode state"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/owner-phone": {
      "post": {
        "summary": "Save the owner's own mobile and ring it first before the secretary answers (numbers:write). Same write as the set_owner_phone MCP tool; ring_first defaults to true. declined=true records a declined signup instead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "owner_phone": {
                    "type": "string"
                  },
                  "ring_first": {
                    "type": "boolean"
                  },
                  "declined": {
                    "type": "boolean"
                  },
                  "contact_name": {
                    "type": "string"
                  },
                  "contact_email": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Owner phone saved (and ring-first applied when requested)"
          },
          "400": {
            "description": "Invalid input"
          }
        },
        "x-required-scopes": "numbers:write"
      }
    },
    "/v1/test-mode": {
      "get": {
        "summary": "Read the current test mode state for a business (agents:write).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mode state for the business"
          }
        },
        "x-required-scopes": "agents:write"
      },
      "patch": {
        "summary": "Start, refresh, or end a test-mode window for a business (agents:write). enabled=true starts/refreshes the window (default 24h); enabled=false goes live (idempotent).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "enabled"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "phone_numbers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "duration_hours": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated mode state"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/notification-settings": {
      "get": {
        "summary": "Read a business's notification settings (agents:read). Returns defaults when no row exists yet.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notification settings for the business"
          }
        },
        "x-required-scopes": "agents:read"
      },
      "patch": {
        "summary": "Upsert a business's notification settings (agents:write) — toggle alerts and set notification email/phone. Closes the gap that previously required service-role notification_settings writes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "new_lead_notification_enabled": {
                    "type": "boolean"
                  },
                  "lead_update_notification_enabled": {
                    "type": "boolean"
                  },
                  "email_notifications": {
                    "type": "boolean"
                  },
                  "in_app_notifications": {
                    "type": "boolean"
                  },
                  "sms_notifications": {
                    "type": "boolean"
                  },
                  "usage_alert_75_enabled": {
                    "type": "boolean"
                  },
                  "usage_alert_90_enabled": {
                    "type": "boolean"
                  },
                  "usage_alert_100_enabled": {
                    "type": "boolean"
                  },
                  "payment_failure_alert_enabled": {
                    "type": "boolean"
                  },
                  "notification_email": {
                    "type": "string"
                  },
                  "notification_phone": {
                    "type": "string"
                  },
                  "escalation_rules": {
                    "type": "array",
                    "description": "Post-call escalation rules: [{ id?, when, then?, enabled? }]. Alerts go to notification_phone/notification_email. null clears all rules.",
                    "items": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated notification settings"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/dispatcher-provider-config": {
      "get": {
        "summary": "Read a business's dispatcher provider-write opt-in (agents:read). Returns defaults (enabled=false, provider=logging_stub) when no row exists yet — see the WS5-P3 dispatcher provider-writes control plane.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Provider-write config for the business"
          }
        },
        "x-required-scopes": "agents:read"
      },
      "patch": {
        "summary": "Opt a business in/out of dispatcher provider-writes and set its provider (agents:write). This is the only write path for dispatcher_provider_write_config — the table has no client-writable RLS policy. A row here is only half the gate: the global DISPATCHER_PROVIDER_WRITES_ENABLED env flag must also be on for any external write to happen.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "logging_stub",
                      "gohighlevel",
                      "airtable",
                      "litify",
                      "google_calendar"
                    ]
                  },
                  "provider_config": {
                    "type": "object",
                    "description": "Non-secret ids only (locationId/baseId/calendarId/...) — never raw provider credentials."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "degraded",
                      "disabled"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated provider-write config"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/caller-memory": {
      "get": {
        "summary": "List a lead's active caller-memory facts (leads:read). Returns the remembered facts (category, confidence, provenance, status) the voice agent may reference on repeat calls.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active memory facts for the lead"
          }
        }
      },
      "post": {
        "summary": "Add an owner-stated memory fact for a lead (leads:write, admin role). Owner-stated facts carry full confidence and never decay.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "lead_id",
                  "fact"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "lead_id": {
                    "type": "string"
                  },
                  "fact": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "preference",
                      "commitment",
                      "situation",
                      "relationship",
                      "identity"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created memory fact"
          }
        }
      },
      "patch": {
        "summary": "Correct a remembered fact (leads:write, admin role). Append-only: inserts an owner-stated replacement and marks the old fact corrected — the original is never edited in place.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "lead_id",
                  "fact_id",
                  "corrected_fact"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "lead_id": {
                    "type": "string"
                  },
                  "fact_id": {
                    "type": "string"
                  },
                  "corrected_fact": {
                    "type": "string",
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Correction recorded"
          },
          "404": {
            "description": "No active fact with that id for this lead"
          }
        }
      },
      "delete": {
        "summary": "Forget a single fact (fact_id) or a lead's entire memory ledger when fact_id is omitted (leads:write, admin role). Facts are marked forgotten, never hard-deleted, preserving the audit trail.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fact_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Forgotten fact count"
          }
        }
      }
    },
    "/v1/memory-dreams": {
      "get": {
        "summary": "Owner-facing 'Kai Dreams' journal (leads:read): last nightly memory-consolidation run, facts learned/reinforced in the past day, promises coming due, and conflicts awaiting the owner. Returns enabled=false when the business hasn't opted in to caller memory.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dream journal payload for the business"
          }
        }
      }
    },
    "/v1/memory-settings": {
      "get": {
        "summary": "Read a business's caller-memory opt-in (leads:read). memory_mode defaults to 'off' — memory only runs for businesses that opted in.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current memory_mode for the business"
          }
        }
      },
      "patch": {
        "summary": "Update the caller-memory opt-in (leads:write, admin role). 'full' remembers caller details, 'contact_only' keeps only contact identity, 'off' disables memory entirely.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "memory_mode"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "memory_mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "contact_only",
                      "full"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated memory_mode"
          }
        }
      }
    },
    "/v1/follow-up-settings": {
      "get": {
        "summary": "Read a business's follow-up settings (membership): quiet hours, callback attempt cadence, lead channel preference, tone, trigger-outcome overrides, minimum call length, and the follow-up email throttle. Missing fields fall back to the platform defaults (identical to the pre-settings behavior).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Effective follow-up settings for the business"
          }
        }
      },
      "patch": {
        "summary": "Update follow-up settings (admin role). Partial upsert — only provided fields change. Also settable by voice via the admin secretary.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "quiet_hours_enabled": {
                    "type": "boolean"
                  },
                  "quiet_hours_start": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 23
                  },
                  "quiet_hours_end": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 23
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true
                  },
                  "max_attempts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5
                  },
                  "attempt_spacing_hours": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 168
                  },
                  "lead_channel_preference": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "sms",
                      "email"
                    ]
                  },
                  "tone": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 300
                  },
                  "excluded_outcomes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "nullable": true
                  },
                  "min_call_seconds": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 600
                  },
                  "email_throttle_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 168
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated follow-up settings row"
          }
        }
      }
    },
    "/v1/directory-listing": {
      "get": {
        "summary": "Read a business's KaiCalls phone book opt-in (leads:read). directory_listed defaults to false — a business is never listed unless the owner opts in.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current directory_listed / directory_category for the business"
          }
        }
      },
      "patch": {
        "summary": "Update the phone book listing opt-in (leads:write, admin role). Lists the business in the platform-wide voice directory under a controlled-vocabulary category.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "listed"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "listed": {
                    "type": "boolean"
                  },
                  "category": {
                    "type": "string",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated directory_listed / directory_category"
          }
        }
      }
    },
    "/v1/followup-actions": {
      "get": {
        "summary": "Owner pre-approval queue for high-risk actions an outbound follow-up agent proposed (calls:read). Lists queued actions (payment links, CRM writes) the outbound guardrail held for review instead of executing live.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected",
                "expired",
                "executed",
                "failed"
              ],
              "default": "pending"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Queued follow-up actions for the business"
          }
        },
        "x-required-scopes": "calls:read"
      },
      "patch": {
        "summary": "Approve or reject a queued follow-up action (calls:write). Records the owner decision; it does not auto-execute the underlying voice tool.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "id",
                  "decision"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string"
                  },
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "reject"
                    ]
                  },
                  "review_note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated follow-up action"
          },
          "404": {
            "description": "No pending action with that id"
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/update-requests": {
      "post": {
        "summary": "On-behalf update broker. Routes a typed mutation intent through scope, tenant, schema, authority, idempotency, and risk policy. Returns executed, needs_user_input, needs_approval, pending_approval, denied, or unsupported — never an unaudited side effect.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/updateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Executed (or dry-run validated) update request."
          },
          "202": {
            "description": "Accepted but needs approval/authority (needs_approval or pending_approval)."
          },
          "400": {
            "description": "needs_user_input or unsupported intent/fields."
          },
          "403": {
            "description": "denied (tenant, scope, or object authorization failed)."
          },
          "409": {
            "description": "idempotency_conflict, rejected, or expired."
          }
        }
      }
    },
    "/v1/numbers/emergency-address": {
      "get": {
        "summary": "Read the current E911 emergency address for a phone number.",
        "parameters": [
          {
            "name": "phone_number",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Emergency address record"
          }
        },
        "x-required-scopes": "numbers:read"
      },
      "post": {
        "summary": "Set the E911 emergency address for a phone number (numbers:write). Compliance-sensitive: requires a validated US service address and disclosure_acknowledged. Prefer the broker (request_kaicalls_update) for governed on-behalf changes.",
        "responses": {
          "200": {
            "description": "Emergency address provisioning result"
          }
        }
      }
    },
    "/v1/sms/messages": {
      "get": {
        "summary": "List business-scoped SMS messages, including definitive failed send attempts.",
        "description": "Requires sms:read. Successful messages and provider-rejected outbound attempts share one evidence stream. Every row includes status, to, and timestamp; failed rows have status \"failed\" and a provider code. A null code means no definitive provider failure was recorded. Compliance-blocked requests are not provider send attempts and therefore do not create failed message rows.",
        "parameters": [
          {
            "name": "conversation_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SMS message evidence, including successful and failed attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "messages",
                    "has_more"
                  ],
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "status",
                          "code",
                          "to",
                          "timestamp"
                        ],
                        "properties": {
                          "status": {
                            "type": "string"
                          },
                          "code": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Provider error code for a definitive failed send; null otherwise."
                          },
                          "to": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Destination number."
                          },
                          "timestamp": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time",
                            "description": "Failure time, send time, or creation time, in that precedence order."
                          },
                          "error_message": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "additionalProperties": true
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "x-required-scopes": "sms:read"
      }
    },
    "/v1/sms/conversations": {
      "get": {
        "summary": "List or retrieve SMS conversation threads.",
        "responses": {
          "200": {
            "description": "SMS conversation list or object"
          }
        },
        "x-required-scopes": "sms:read"
      }
    },
    "/v1/customers": {
      "get": {
        "summary": "Resolve one customer by customer_id, lead_id, phone, email or external_id, or list customers with opaque cursor pagination (leads:read).",
        "description": "A customer is the durable contact record a business has talked to. `customer_id` and `lead_id` are the same value today and both are accepted, so a consumer holding either identifier resolves the same record. Supplying any ONE lookup key switches the endpoint to lookup mode and returns a single customer object (404 when nothing in the calling workspace matches — a value that exists in another workspace is reported identically, so this cannot be used to probe the platform). Supplying two lookup keys is a 400 rather than a silent precedence rule. Without a lookup key the endpoint lists customers ordered by the immutable `created_at`; page with the opaque `next_cursor` and synchronize with `updated_since`. A cursor is bound to the tenant scope, filters and sort order that produced it and is rejected against any other request.",
        "parameters": [
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only customers changed at or after this instant. Rows whose updated_at is null fall back to created_at, so nothing is silently excluded from a sync sweep.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a previous response’s next_cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A customer object (lookup mode) or a cursor-paginated list."
          },
          "400": {
            "description": "More than one lookup key, an invalid cursor, or a bad updated_since."
          },
          "403": {
            "description": "Missing leads:read scope, or business_id outside the key."
          },
          "404": {
            "description": "No customer in the calling workspace matches the lookup."
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/conversations": {
      "get": {
        "summary": "Return a tenant-scoped normalized call and SMS timeline for one customer (calls:read AND sms:read).",
        "description": "Identify the customer with any one of customer_id, lead_id, phone, email or external_id — the same resolver `/v1/customers` uses. Items are normalized (`source_type`/`source_id`, `conversation_id`, `customer_id`/`lead_id`, `provider`/`provider_id`, `occurred_at`) while `source` preserves the original record, so a consumer can act without knowing which internal table held the artifact. Paging is an opaque keyset over the immutable `created_at` across both channels; walk it with `next_cursor`. `updated_since` matches a call on its updated_at and an SMS on its created_at — SMS rows carry no updated_at, so a delivery-status change on an already-synced message is NOT visible here; use the event/delivery endpoints for that.",
        "parameters": [
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a previous response’s next_cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Normalized call and SMS timeline"
          },
          "400": {
            "description": "No lookup key, more than one lookup key, an invalid cursor, or a bad updated_since."
          },
          "403": {
            "description": "Missing calls:read or sms:read scope"
          }
        },
        "x-required-scopes": {
          "allOf": [
            "calls:read",
            "sms:read"
          ]
        }
      }
    },
    "/v1/conversations/handoff": {
      "post": {
        "summary": "Set who answers a customer's text conversation: Kai (ai), the owner (human) or your own system (external) (sms:write).",
        "description": "owner 'ai' is the default: Kai replies. 'human' keeps Kai quiet so the owner answers. 'external' keeps Kai quiet so your system answers: it receives the sms.received webhook (which carries conversation_owner) and replies with POST /v1/messages. With external_reply_timeout_seconds (external only, 60 to 86400), Kai answers a message nothing replied to in that time, within about a minute of the deadline, unless a newer message arrived or the owner changed. Inbound messages are always stored and sms.received always fires. Pass conversation_id for one conversation, or phone for every conversation the business has with that customer's number (one per business line). Each owner change fires conversation.handoff. Re-sending the same owner is a no-op that reports changed: false. Responds 503 feature_unavailable until the feature is rolled out.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "owner"
                ],
                "properties": {
                  "owner": {
                    "type": "string",
                    "enum": [
                      "ai",
                      "human",
                      "external"
                    ]
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "One conversation. Pass this or phone."
                  },
                  "phone": {
                    "type": "string",
                    "description": "The customer's number (E.164): every conversation the business has with it."
                  },
                  "business_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Required when the API key reaches more than one business and only phone is given."
                  },
                  "external_reply_timeout_seconds": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 60,
                    "maximum": 86400,
                    "description": "owner 'external' only: Kai answers when nothing replied in this many seconds."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Owner set (or already set).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversation_id",
                    "owner",
                    "owner_set_at",
                    "conversations"
                  ],
                  "properties": {
                    "conversation_id": {
                      "type": "string"
                    },
                    "owner": {
                      "type": "string",
                      "enum": [
                        "ai",
                        "human",
                        "external"
                      ]
                    },
                    "owner_set_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "external_reply_timeout_seconds": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "conversations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "conversation_id": {
                            "type": "string"
                          },
                          "lead_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "lead_phone": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "business_phone": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "previous_owner": {
                            "type": "string",
                            "enum": [
                              "ai",
                              "human",
                              "external"
                            ]
                          },
                          "owner": {
                            "type": "string",
                            "enum": [
                              "ai",
                              "human",
                              "external"
                            ]
                          },
                          "changed": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid owner, neither or both of conversation_id and phone, a bad timeout, or business_id_required."
          },
          "403": {
            "description": "Missing sms:write scope, or business_id outside the key."
          },
          "404": {
            "description": "conversation_not_found."
          },
          "503": {
            "description": "feature_unavailable: handoff is not rolled out yet."
          }
        },
        "x-required-scopes": "sms:write"
      }
    },
    "/v1/communication-jobs": {
      "get": {
        "summary": "List per-touch communication jobs (status, queue linkage) without polling queues.",
        "responses": {
          "200": {
            "description": "Communication job list"
          }
        },
        "x-required-scopes": "communication-runs:read"
      }
    },
    "/v1/communication-attempts": {
      "get": {
        "summary": "List outbound call attempt history, scoped via the business call sequences.",
        "responses": {
          "200": {
            "description": "Call attempt list"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "calls:read",
            "communication-runs:read"
          ]
        }
      }
    },
    "/v1/recordings": {
      "get": {
        "summary": "Return a freshly-signed recording URL and transcript status for a call_id.",
        "parameters": [
          {
            "name": "call_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed recording reference"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/numbers": {
      "get": {
        "summary": "List assigned phone numbers.",
        "x-required-scopes": "numbers:read"
      }
    },
    "/v1/keys": {
      "get": {
        "summary": "List session-owned API keys.",
        "security": [
          {
            "sessionAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create an API key. With partner_id (platform operators only, session or operator key), a partner key scoped to the businesses that partner created.",
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/apiKeyCreate"
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update, expire, or revoke an API key.",
        "security": [
          {
            "sessionAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Revoke an API key.",
        "security": [
          {
            "sessionAuth": []
          }
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "summary": "List webhook subscriptions.",
        "x-required-scopes": "webhooks:read"
      },
      "post": {
        "summary": "Create or update a webhook subscription.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/webhookUpsert"
              }
            }
          }
        },
        "x-required-scopes": "webhooks:write"
      },
      "patch": {
        "summary": "Update a webhook subscription by id; fields not sent (including URL and events) are kept.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/webhookUpsert"
              }
            }
          }
        },
        "x-required-scopes": "webhooks:write"
      },
      "delete": {
        "summary": "Delete a webhook subscription.",
        "x-required-scopes": "webhooks:write"
      }
    },
    "/v1/webhooks/test": {
      "post": {
        "summary": "Send a signed test event to a webhook endpoint (built from the business's most recent lead when one exists) and return the exact body sent, the status code and the start of the endpoint's reply.",
        "responses": {
          "200": {
            "description": "Webhook test delivery result"
          }
        },
        "x-required-scopes": "webhooks:write"
      }
    },
    "/v1/webhooks/rotate-secret": {
      "post": {
        "summary": "Rotate a webhook signing secret and return the new secret once.",
        "responses": {
          "200": {
            "description": "Rotated webhook secret"
          }
        },
        "x-required-scopes": "webhooks:write"
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Return API health for authenticated clients.",
        "responses": {
          "200": {
            "description": "Health response"
          }
        }
      }
    },
    "/v1/calls": {
      "get": {
        "summary": "List business-scoped call records or retrieve one call by id.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Call list or call object"
          }
        },
        "x-required-scopes": "calls:read"
      },
      "post": {
        "summary": "Place an outbound call for a workspace agent. Pass idempotency_key (body or Idempotency-Key header) to make the request exactly-once: a replay with the same key returns the original call (200) instead of dialing again, and the same key with different arguments is rejected (409).",
        "responses": {
          "200": {
            "description": "Idempotent replay of an already-placed call"
          },
          "201": {
            "description": "Outbound call placed"
          },
          "409": {
            "description": "Idempotency conflict, in-progress duplicate, or compliance block (TCPA/DNC/caller reputation)"
          },
          "502": {
            "description": "Provider outcome unknown after submission; the call is never blind-redialed"
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/transcripts": {
      "get": {
        "summary": "List call transcripts for a workspace, filterable by agent and recency.",
        "parameters": [
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transcript list"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/leads": {
      "get": {
        "summary": "List business-scoped leads or retrieve one lead by id.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "score_gte",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "score_lte",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lead list or lead object"
          }
        },
        "x-required-scopes": "leads:read"
      },
      "post": {
        "summary": "Create or import a lead in a workspace.",
        "description": "Creates a lead (leads:write). For an imported walk-up or in-person customer, the integration may pass through a verified SMS opt-in by sending phone, sms_consent: true, and consent_language containing the exact disclosure the customer accepted. smsConsent, sms_opt_in, and text_consent are accepted aliases; consent: true asserts both SMS and voice consent. KaiCalls stores the disclosure and import source as consent provenance. Do not set an opt-in field unless the source system has verified the customer accepted that disclosure. A phone number alone remains cold and SMS is blocked, except for businesses explicitly configured for documented web-form implied consent. The 201 response includes consent ({ recorded, basis, channel }) whenever phone is supplied; send a portal invite only when consent.recorded is true.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id"
                ],
                "properties": {
                  "business_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "description": "E.164 preferred; normalized on write."
                  },
                  "source": {
                    "type": "string"
                  },
                  "sms_consent": {
                    "type": "boolean",
                    "description": "Attests that the source system verified this customer explicitly opted in to SMS. For imported walk-up clients, send true with consent_language. Never set this merely because a phone number is present."
                  },
                  "consent_language": {
                    "type": "string",
                    "description": "Exact SMS consent disclosure accepted by the customer. Stored with the capture source as proof/provenance."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead created (includes a consent object when a phone was supplied)."
          },
          "400": {
            "description": "Malformed request (missing/invalid field)."
          },
          "403": {
            "description": "Missing leads:write scope, or business_id outside the key."
          }
        },
        "x-required-scopes": "leads:write"
      },
      "patch": {
        "summary": "Update a lead (status, fields) by id.",
        "x-required-scopes": "leads:write"
      }
    },
    "/v1/knowledge": {
      "get": {
        "summary": "List a business's agent knowledge base entries (knowledge:read).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Knowledge item list"
          }
        },
        "x-required-scopes": "knowledge:read"
      },
      "post": {
        "summary": "Create or (when id is provided) update an agent knowledge base entry (knowledge:write).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/knowledgeUpsert"
              },
              "example": {
                "business_id": "00000000-0000-4000-8000-000000000001",
                "title": "Listing A-123",
                "content": "Available by appointment. Call the office to arrange a viewing.",
                "content_type": "text",
                "tags": [
                  "listing:A-123"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Knowledge item updated"
          },
          "201": {
            "description": "Knowledge item created"
          }
        },
        "x-required-scopes": "knowledge:write"
      },
      "delete": {
        "summary": "Delete a knowledge base entry by id (knowledge:write).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          }
        },
        "x-required-scopes": "knowledge:write"
      }
    },
    "/v1/products": {
      "get": {
        "summary": "List a business's agent product catalog (products:read).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "inventory",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product list"
          }
        },
        "x-required-scopes": "products:read"
      },
      "post": {
        "summary": "Create or (when id is provided) update a product row (products:write).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/productUpsert"
              },
              "example": {
                "business_id": "00000000-0000-4000-8000-000000000001",
                "name": "Listing A-123",
                "sku": "A-123",
                "description": "Available by appointment",
                "attributes": {
                  "source_listing_id": "A-123"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Product updated"
          },
          "201": {
            "description": "Product created"
          }
        },
        "x-required-scopes": "products:write"
      },
      "delete": {
        "summary": "Delete one or more products by id/ids (products:write).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          }
        },
        "x-required-scopes": "products:write"
      }
    },
    "/v1/approval-requests": {
      "post": {
        "summary": "Generic approval-channel intake for an external agent control plane (approvals:write; issued only to trusted control planes, not self-serve mintable via POST /v1/keys). Dispatches a voice approval interaction via the existing outbound-call machinery, tagged with source metadata (approval_source, source_request_id, source_intent_id, source_message_id, source_intent_hash, source_callback_url, approval_expires_at, required_factors) and treated as transactional/user-requested, not marketing outreach, for TCPA/DNC purposes. Zehrava Gate is the first documented source; the contract itself is source-neutral.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/approvalRequestCreate"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Approval interaction dispatched."
          },
          "400": {
            "description": "Malformed request (missing/invalid field)."
          },
          "403": {
            "description": "Missing approvals:write scope, or callback_url not allowlisted."
          },
          "410": {
            "description": "Request already expired."
          },
          "422": {
            "description": "Unsupported required_factors entry for this deployment."
          },
          "501": {
            "description": "channel.type \"sms\" (SMS-only delivery not yet implemented)."
          },
          "502": {
            "description": "Provider dispatch failure."
          },
          "503": {
            "description": "No approval-channel agent configured."
          }
        },
        "x-required-scopes": "approvals:write"
      }
    },
    "/v1/leads/audit": {
      "get": {
        "summary": "Return a lead-pipeline audit: new leads, follow-up activity, stalled leads, and hot-score counts over a window.",
        "parameters": [
          {
            "name": "window_hours",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "stalled_days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "hot_score",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "as_of",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lead audit summary"
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/leads/context": {
      "get": {
        "summary": "Return the full drafting context for one lead (leads:read): the lead row, its business, latest score, calls (with transcripts), lead emails, queued email approvals, SMS conversations/messages, voicemails and SDR activity. API-key-authed sibling of the session-only journey rail.",
        "parameters": [
          {
            "name": "lead_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lead drafting context"
          },
          "400": {
            "description": "lead_id missing"
          },
          "404": {
            "description": "Lead not found in an accessible business"
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/email-approvals": {
      "get": {
        "summary": "List queued lead-email drafts awaiting human approval for a business (emails:read).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email approval queue list"
          }
        },
        "x-required-scopes": "emails:read"
      },
      "post": {
        "summary": "Queue an agent-drafted email as a pending row for human approval (emails:write). Never sends — the existing approval UI owns the send. One pending draft per lead: a repeat returns 409 duplicate_pending.",
        "responses": {
          "201": {
            "description": "Draft queued for approval"
          },
          "400": {
            "description": "Malformed body, or no usable recipient email"
          },
          "403": {
            "description": "Missing emails:write scope, or lead in another business"
          },
          "404": {
            "description": "Lead not found"
          },
          "409": {
            "description": "A pending draft already exists for this lead"
          }
        },
        "x-required-scopes": "emails:write"
      }
    },
    "/v1/lead-emails": {
      "get": {
        "summary": "List lead-email delivery outcomes for a business (emails:read), including failure_reason and retry bookkeeping.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lead email delivery outcome list"
          }
        },
        "x-required-scopes": "emails:read"
      },
      "patch": {
        "summary": "Retry a failed lead-email send (emails:write). Only rows with a linked send-queue row are retryable.",
        "responses": {
          "200": {
            "description": "Retry attempted"
          }
        },
        "x-required-scopes": "emails:write"
      }
    },
    "/v1/callbacks": {
      "get": {
        "summary": "List missed-call callback delivery outcomes for a business (calls:read), including error_message and retry bookkeeping.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "call_status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lead_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Callback delivery outcome list"
          }
        },
        "x-required-scopes": "calls:read"
      },
      "patch": {
        "summary": "Retry a failed callback (calls:write). Atomically re-arms the row for the cron dispatcher to redispatch.",
        "responses": {
          "200": {
            "description": "Retry attempted"
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/messages": {
      "post": {
        "summary": "Send one text or WhatsApp message as the business (sms:write). Email goes out through POST /v1/email-campaigns.",
        "description": "One send endpoint for texts and WhatsApp. sms goes out from the agent's own line (from_agent_id required) with the same gates and error codes as POST /v1/sms/send. whatsapp goes out from the business's number connected to WhatsApp (pass from when there is more than one) and only inside the 24-hour window after the customer last messaged that number; WhatsApp consent and Do-Not-Call are checked first. Email is not sent here: channel email is 400 use_email_campaigns. API email goes out as a campaign the business owner reviews and approves, through POST /v1/email-campaigns. Send Idempotency-Key (or idempotency_key) to make a retry return the original send (replayed: true) instead of sending again; the same key with a different message is 409 idempotency_conflict. New keyed WhatsApp sends claim a durable intent before dispatch; concurrent or uncertain replays never dispatch again. A 409 message_outcome_unknown requires reconciliation, not a new key. Pre-existing lost receipts cannot be recovered. 503 idempotency_unavailable means reservation or replay evidence could not be verified. A 200 means the provider accepted the message, not that it was delivered.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "channel",
                  "to",
                  "body"
                ],
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "sms",
                      "whatsapp"
                    ],
                    "description": "email is refused with 400 use_email_campaigns."
                  },
                  "to": {
                    "type": "string",
                    "description": "E.164 phone number."
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Alias: message."
                  },
                  "business_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Required when the API key reaches more than one business."
                  },
                  "from_agent_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "sms only, required there."
                  },
                  "from": {
                    "type": "string",
                    "description": "whatsapp only: the business's connected WhatsApp number."
                  },
                  "lead_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "idempotency_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted by the provider.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "channel",
                    "status",
                    "to",
                    "from"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "channel": {
                      "type": "string",
                      "enum": [
                        "sms",
                        "whatsapp"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "sent",
                        "queued"
                      ]
                    },
                    "to": {
                      "type": "string"
                    },
                    "from": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "replayed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or a compliance block: bad_request, use_email_campaigns, business_id_required, from_required, automation_paused, number_opted_out, number_on_dnc, content_blocked, tcpa_violation, rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing sms:write scope, or no access to business_id (forbidden).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Agent or lead not found (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "whatsapp_not_connected, whatsapp_window_closed, idempotency_conflict, message_outcome_unknown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Provider failure: sms_send_failed, whatsapp_send_failed, outcome_unknown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "idempotency_unavailable: WhatsApp replay evidence cannot be verified; no new send."
          }
        },
        "x-required-scopes": "sms:write"
      }
    },
    "/v1/email-campaigns": {
      "get": {
        "summary": "A business's email campaigns, sending addresses and emailable audience (emails:read).",
        "description": "Without sender_id: the campaign view, the same one the dashboard campaign page shows: senders with their setup readiness, recent campaigns (or the one named by campaign_id) with status, the revision fingerprint the owner approves, outcome counts, and the audience that can be emailed (recorded consent, not on the do-not-email list). With sender_id: the DNS records that sending domain needs. Any member of the business can read. 503 email_campaigns_unavailable when the review cannot be built right now.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "campaign_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sender_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign view, or the sender setup records"
          },
          "400": {
            "description": "business_id is required (invalid_request)"
          },
          "403": {
            "description": "Missing emails:read scope or no access to business_id"
          },
          "503": {
            "description": "email_campaigns_unavailable"
          }
        },
        "x-required-scopes": "emails:read"
      },
      "post": {
        "summary": "Set up a sender, draft, approve, launch or stop an email campaign (emails:write).",
        "description": "The only way to send email through the API. Email goes out as a campaign from the business's own verified sending domain, only to customers with recorded email consent who are not on the do-not-email list, and only after the business owner approves the exact revision. The same actions, checks and role rules as the dashboard campaign page. action 'request_sender' (domain, from, reply_to) creates the sending domain (a subdomain the business controls) and returns the DNS records to add; it turns ready on its own once they verify. action 'check_sender' (sender_id) re-checks the records now. action 'record_consent' (lead_id and email, or emails, plus evidence_ref saying where the permission came from, optional expires_at) records email permission for existing customers; 'revoke_consent' takes it back. action 'draft' (sender_id, subject, text, lead_ids, optional daily_limit, default the most the sender and the Kai level allow) saves a campaign for review and returns id and fingerprint; nothing is sent. action 'approve' (campaign_id, fingerprint) approves that exact revision. action 'launch' (campaign_id) queues an approved campaign. action 'cancel' (campaign_id, fingerprint) stops it. Sending from a KaiCalls-managed domain also needs a Kai level that includes email, and is capped at what the level allows per day (see /dashboard/level). check_sender needs any member; request_sender, record_consent, revoke_consent and draft need an admin of the business; approve, launch and cancel need the business owner; all are checked against the API key's own user. A refusal from the campaign checks (unverified sender, missing consent, changed audience, no owner approval, Kai level, sending turned off) is 409 email_campaign_refused with the reason in message.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action",
                  "business_id"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "request_sender",
                      "check_sender",
                      "record_consent",
                      "revoke_consent",
                      "draft",
                      "approve",
                      "launch",
                      "cancel"
                    ]
                  },
                  "business_id": {
                    "type": "string"
                  },
                  "lead_id": {
                    "type": "string",
                    "description": "record_consent, revoke_consent: one customer (with email)."
                  },
                  "email": {
                    "type": "string",
                    "description": "record_consent, revoke_consent: that customer's address."
                  },
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "record_consent, revoke_consent: addresses of existing customers (up to 200)."
                  },
                  "evidence_ref": {
                    "type": "string",
                    "description": "record_consent, revoke_consent: where the permission came from, e.g. web form opt-in."
                  },
                  "expires_at": {
                    "type": "string",
                    "description": "record_consent, optional: when the permission ends."
                  },
                  "domain": {
                    "type": "string",
                    "description": "request_sender: the sending domain."
                  },
                  "from": {
                    "type": "string",
                    "description": "request_sender: the From address."
                  },
                  "reply_to": {
                    "type": "string",
                    "description": "request_sender: where replies go."
                  },
                  "sender_id": {
                    "type": "string",
                    "description": "draft: the verified sender. check_sender: the sender to re-check."
                  },
                  "subject": {
                    "type": "string",
                    "description": "draft: the subject line."
                  },
                  "text": {
                    "type": "string",
                    "description": "draft: the plain-text body."
                  },
                  "lead_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "draft: the customers to email; each needs email consent."
                  },
                  "daily_limit": {
                    "type": "integer",
                    "description": "draft: most emails sent per day (default: the most the sender and the Kai level allow)."
                  },
                  "campaign_id": {
                    "type": "string",
                    "description": "approve, launch, cancel."
                  },
                  "fingerprint": {
                    "type": "string",
                    "description": "approve, cancel: the exact revision, from draft or GET."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "request_sender: { success, senderId, status, provisioned, setupComplete, records (DNS), detail }. check_sender: { verified, status, readiness }. record_consent, revoke_consent: { changed, skipped }. draft: { id, fingerprint, packet, readiness, sendable: false }. approve, launch, cancel: the campaign result."
          },
          "400": {
            "description": "invalid_request: bad JSON, unknown action, missing business_id, or a missing campaign_id / fingerprint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing emails:write scope, no access to business_id, or the role the action needs (admin for request_sender, record_consent, revoke_consent and draft, owner for approve, launch, cancel).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "email_campaign_refused: the campaign checks refused the action.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-required-scopes": "emails:write"
      }
    },
    "/v1/business-verification": {
      "get": {
        "summary": "Read private business-verification document status and secure upload requirements (workspaces:read).",
        "description": "Tenant member only. Returns document reference, type, staff-review status and rejection reason; never storage paths, file bytes, filenames or signed URLs. Secure uploads remain on /dashboard/level (admin, up to 10 MB, PDF/JPG/PNG/WEBP/HEIC). Internal staff approval is not carrier approval; no provider submission is performed.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document metadata and secure upload guidance"
          },
          "403": {
            "description": "Scope or membership denied"
          },
          "503": {
            "description": "Document status unavailable"
          }
        },
        "x-required-scopes": "workspaces:read"
      }
    },
    "/v1/text-registration": {
      "get": {
        "summary": "Where a business stands on its own carrier registration for texting (billing:read).",
        "description": "Mirrors the get_text_registration tool: saved details (never the EIN, only ein_on_file), payment, filing stage, known details on file, the questions still missing, next_question, next_step and setup_url (the page where the owner finishes).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registration status"
          },
          "403": {
            "description": "Missing billing:read scope or no access to business_id"
          }
        },
        "x-required-scopes": "billing:read"
      },
      "post": {
        "summary": "Save registration details, start checkout or check reserved submission availability (billing:write, business owner only).",
        "description": "action 'save' mirrors save_text_registration_details: pass only the fields the owner gave; the rest is filled from what is on file, and while questions remain the response lists saved_steps, missing and next_question (code needs_answers). action 'start_checkout' mirrors start_text_registration_checkout: it returns checkout_url for the owner to pay (nothing is charged here), or included_in_plan and filed when the plan includes registration. The EIN is never accepted over the API (400 ein_not_accepted); the owner types it on setup_url. Writes need the key user to be the business owner (403 owner_required). start_checkout requires confirm_submission true; included plans file immediately. submit is disabled with 503 submission_unavailable pending shared durable reservation and reconciliation; it performs no filing. Correcting saved details does not resubmit rejected provider objects, which need operator reconciliation. Poll GET for state: submission is not approval. No document contents, credentials or legal attestations are accepted.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action",
                  "business_id"
                ],
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "save",
                      "start_checkout",
                      "submit"
                    ]
                  },
                  "business_id": {
                    "type": "string"
                  },
                  "confirm_submission": {
                    "type": "boolean",
                    "description": "Required true for start_checkout or submit after explicit owner confirmation."
                  },
                  "expected_updated_at": {
                    "type": "string",
                    "description": "submit only: registration.updatedAt from a fresh status read."
                  },
                  "legal_business_name": {
                    "type": "string"
                  },
                  "business_type": {
                    "type": "string"
                  },
                  "industry": {
                    "type": "string"
                  },
                  "website_url": {
                    "type": "string"
                  },
                  "street": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "postal_code": {
                    "type": "string"
                  },
                  "contact_first_name": {
                    "type": "string"
                  },
                  "contact_last_name": {
                    "type": "string"
                  },
                  "contact_email": {
                    "type": "string"
                  },
                  "contact_phone": {
                    "type": "string"
                  },
                  "contact_job_position": {
                    "type": "string"
                  },
                  "contact_title": {
                    "type": "string"
                  },
                  "text_types": {
                    "type": "string",
                    "enum": [
                      "service",
                      "service_and_sales"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved or checkout result (not approval)"
          },
          "400": {
            "description": "invalid_request, invalid_details or ein_not_accepted"
          },
          "403": {
            "description": "Missing billing:write scope, no access, or owner_required"
          },
          "409": {
            "description": "Not ready, confirmation required or saved revision changed"
          },
          "502": {
            "description": "checkout_failed"
          },
          "503": {
            "description": "submission_unavailable: external submission disabled; do not retry through another entry point"
          }
        },
        "x-required-scopes": "billing:write"
      }
    },
    "/v1/sms/send": {
      "post": {
        "summary": "Send an outbound SMS message from a workspace number (sms:write).",
        "description": "The text goes out on the workspace's own number through KaiCalls' carriers. The destination must already have usable SMS consent for the workspace. Imported walk-up clients become eligible only after POST /v1/leads records verified opt-in provenance; a cold phone number remains blocked. If the carrier definitively rejects an attempt, the error response includes attempt { code, to, timestamp }, and the same failed attempt is available from GET /v1/sms/messages with status \"failed\".",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "from_agent_id",
                  "message"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "Destination in E.164 format."
                  },
                  "from_agent_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1
                  },
                  "lead_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "idempotency_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS accepted by the provider."
          },
          "400": {
            "description": "Invalid or compliance-blocked request, or definitive provider rejection. A provider rejection includes attempt.code, attempt.to, and attempt.timestamp.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "object",
                      "required": [
                        "code",
                        "message"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "message": {
                          "type": "string"
                        }
                      }
                    },
                    "attempt": {
                      "type": "object",
                      "description": "Present only when the provider definitively rejected an attempted send.",
                      "required": [
                        "code",
                        "to",
                        "timestamp"
                      ],
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "to": {
                          "type": "string"
                        },
                        "timestamp": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Provider outcome unknown; safe reconciliation is required."
          }
        },
        "x-required-scopes": "sms:write"
      }
    },
    "/v1/sms/update-prompt": {
      "post": {
        "summary": "Update the SMS auto-reply prompt for a workspace's messaging.",
        "responses": {
          "200": {
            "description": "Updated SMS prompt"
          }
        },
        "x-required-scopes": "agents:write"
      }
    },
    "/v1/setup-checklist": {
      "get": {
        "summary": "Where a workspace stands on plan, phone number, texting registration, WhatsApp, alert inboxes, connected Gmail and separate managed-email campaign sender readiness, each with one next action (workspaces:read).",
        "description": "Each step reports a state (done, in_progress, needs_owner, needs_action, unavailable) and either the tool that moves it forward or the one link the owner opens for steps only they can do. Read-only; mirrors the get_setup_checklist tool.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Setup checklist"
          },
          "403": {
            "description": "Missing workspaces:read scope, or no access to this business"
          }
        },
        "x-required-scopes": "workspaces:read"
      }
    },
    "/v1/channels": {
      "get": {
        "summary": "Return every customer channel's state and next blocker for a workspace (sms:read).",
        "description": "One row per channel (voice, sms, whatsapp, imessage, rcs, messenger, instagram, webchat, email) with `availability` (live, built_off, planned), `state` (ready, needs_setup, blocked, not_available), `canSend`, `canReceive` and `nextStep` (`id`, `description`, optional `tool` naming the connector tool that takes the step). Read-only and derived from the same reads the per-channel endpoints use: sms is ready only once the business text registration is approved, and whatsapp only on a verified number. Email includes a separate managedEmail object (state, canSend, reasons, tool) for campaign sender/trust/release readiness; a connected mailbox does not prove campaign readiness. Sender readiness never proves owner approval or delivery. Mirrors the get_channel_status tool.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every channel with its state and next step"
          },
          "400": {
            "description": "business_id is missing"
          },
          "403": {
            "description": "Missing sms:read scope, or no access to this business"
          }
        },
        "x-required-scopes": "sms:read"
      }
    },
    "/v1/whatsapp-sender": {
      "get": {
        "summary": "Return the workspace's WhatsApp channel and provider-backed sender status (sms:read).",
        "description": "Returns two distinct status surfaces: channel, provisioning and sender_provisioning use the existing tenant-mapped Twilio derivation, where connected requires provider status ONLINE. telnyx.available reports server configuration; telnyx.connections contains saved tenant-scoped connection views. Each connection is connected only when its persisted status is connected, following carrier verification by POST action=sync; GET does not refresh those records. Empty connections can also mean the optional table is unavailable, so do not infer readiness or lack of setup from that alone. Neither signup-link creation nor sender status proves message delivery.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "WhatsApp channel status and provisioning state"
          },
          "403": {
            "description": "Missing sms:read scope, or no access to this business"
          }
        },
        "x-required-scopes": "sms:read"
      },
      "post": {
        "summary": "Create or verify a WhatsApp sender, or turn WhatsApp calls on/off, for a workspace (sms:write + admin).",
        "description": "`action=connect` returns the one signup link the business owner opens to put WhatsApp on one of its own KaiCalls lines (optional `phone_number`); the owner signs in to Facebook as the business and verifies the number, and the link is reused while valid. `action=sync` reads the carrier back; a number is connected only when the carrier lists it verified for WhatsApp. The GET response carries these as `telnyx.connections`. Otherwise uses a customer-owned Twilio account or KaiCalls-managed subaccount. `action=create` registers the Meta Embedded Signup WABA and assigned phone through Twilio v2; `action=verify` submits a six-digit OTP. Both actions require a durable idempotency key. Mutation responses are acknowledgements only; connected requires a following exact GET read-back of the tenant-bound sender as ONLINE. `action=voice` with `enabled` attaches (or detaches) the voice application that lets the agent answer calls placed from WhatsApp; it requires a connected sender and a Meta-verified business at the 2,000-conversation messaging tier, and is unavailable until the deployment enables WhatsApp calls.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_id",
                  "action"
                ],
                "properties": {
                  "business_id": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "connect",
                      "sync",
                      "create",
                      "verify",
                      "voice"
                    ]
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "For action=voice: true to let the agent answer WhatsApp calls"
                  },
                  "phone_number": {
                    "type": "string",
                    "description": "E.164 number for create"
                  },
                  "waba_id": {
                    "type": "string",
                    "description": "WABA returned by Embedded Signup"
                  },
                  "profile_name": {
                    "type": "string"
                  },
                  "verification_method": {
                    "type": "string",
                    "enum": [
                      "sms",
                      "voice"
                    ]
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Required for create and verify; reuse only for the same request"
                  },
                  "sender_sid": {
                    "type": "string",
                    "description": "Twilio XE SID for verify"
                  },
                  "code": {
                    "type": "string",
                    "description": "Six-digit OTP for verify"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provider-backed sender lifecycle state"
          },
          "403": {
            "description": "Missing sms:write scope, or no access to this business"
          },
          "409": {
            "description": "Business Twilio account or idempotency state requires action"
          },
          "429": {
            "description": "Verification attempt rate limit reached"
          }
        },
        "x-required-scopes": "sms:write"
      }
    },
    "/v1/usage": {
      "get": {
        "summary": "Return per-key/workspace API usage rows over a date range.",
        "parameters": [
          {
            "name": "start",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "API usage rows"
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "summary": "Return per-business plan terms, mapped subscription price, raw answered-call allowance usage, minutes, phone allowance and overage posture. The separately weighted call count is informational, not allowance consumption or right-sizing. Legacy trial records retain their historical lifetime counters; paid usage uses the billing cycle. Billing data errors fail closed; this is not activation proof.",
        "responses": {
          "200": {
            "description": "Balance response"
          }
        },
        "x-required-scopes": "billing:read"
      }
    },
    "/v1/evals": {
      "get": {
        "summary": "List seeded eval scenarios or retrieve one by id.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eval scenario list or object"
          }
        },
        "x-required-scopes": "evals:read"
      },
      "post": {
        "summary": "Create or seed an eval scenario for an agent (evals:write).",
        "x-required-scopes": "evals:write"
      },
      "patch": {
        "summary": "Update an eval scenario by id.",
        "x-required-scopes": "evals:write"
      },
      "delete": {
        "summary": "Delete an eval scenario by id.",
        "x-required-scopes": "evals:write"
      }
    },
    "/v1/evals/run": {
      "get": {
        "summary": "Retrieve eval run status/results by run_id or vapi_run_id.",
        "parameters": [
          {
            "name": "run_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vapi_run_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eval run status or results"
          }
        },
        "x-required-scopes": "evals:read"
      },
      "post": {
        "summary": "Start an eval run for an agent against seeded scenarios (evals:write).",
        "responses": {
          "200": {
            "description": "Eval run started"
          }
        },
        "x-required-scopes": "evals:write"
      }
    },
    "/v1/analytics/dashboard": {
      "get": {
        "summary": "Return aggregated dashboard analytics for a workspace.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dashboard analytics"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/analytics/calls": {
      "get": {
        "summary": "Return call-volume analytics grouped over time for a workspace.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "group_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Call analytics"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/analytics/agents": {
      "get": {
        "summary": "Return per-agent performance analytics for a workspace.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent analytics"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/analytics/businesses": {
      "get": {
        "summary": "Return per-business analytics across the calling key user accessible workspaces, including signup_attribution (utm_*, click IDs, referrer, landing_path, how_heard) for acquisition-channel reporting.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Business analytics, including signup_attribution"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/analytics/funnel": {
      "get": {
        "summary": "Return lead/call funnel analytics for a workspace.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Funnel analytics"
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/analytics/weekly": {
      "get": {
        "summary": "Return the weekly analytics rollup for a workspace.",
        "responses": {
          "200": {
            "description": "Weekly analytics"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/make/events": {
      "get": {
        "summary": "Make.com integration: poll communication events for a workspace.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event list for Make.com"
          }
        }
      }
    },
    "/v1/zapier/auth-test": {
      "get": {
        "summary": "Zapier connection test: validate the API key and return the workspace identity.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Auth test result"
          }
        }
      }
    },
    "/v1/zapier/triggers/call-completed": {
      "get": {
        "summary": "Zapier trigger: poll for completed calls (supports sample mode).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sample",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Completed-call trigger payload"
          }
        },
        "x-required-scopes": "calls:read"
      }
    },
    "/v1/zapier/triggers/lead-created": {
      "get": {
        "summary": "Zapier trigger: poll for newly created leads (supports sample mode).",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sample",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lead-created trigger payload"
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/zapier/triggers/lead-score-updated": {
      "get": {
        "summary": "Zapier trigger: poll for updated lead scores (supports sample mode).",
        "responses": {
          "200": {
            "description": "Lead-score-updated trigger payload"
          }
        },
        "x-required-scopes": "leads:read"
      }
    },
    "/v1/zapier/triggers/appointment-booked": {
      "get": {
        "summary": "Zapier trigger: poll for newly booked appointments (supports sample mode).",
        "responses": {
          "200": {
            "description": "Appointment-booked trigger payload"
          }
        },
        "x-required-scopes": "appointments:read"
      }
    },
    "/v1/zapier/actions/create-lead": {
      "post": {
        "summary": "Zapier action: create a lead in a workspace (leads:write).",
        "responses": {
          "200": {
            "description": "Created lead"
          }
        },
        "x-required-scopes": "leads:write"
      }
    },
    "/v1/zapier/actions/add-note": {
      "post": {
        "summary": "Zapier action: append a note to an existing lead (leads:write).",
        "responses": {
          "200": {
            "description": "Updated lead note"
          }
        },
        "x-required-scopes": "leads:write"
      }
    },
    "/v1/zapier/actions/request-follow-up-call": {
      "post": {
        "summary": "Zapier action: request a follow-up call (calls:write).",
        "responses": {
          "200": {
            "description": "Queued follow-up request"
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/zapier/actions/make-outbound-call": {
      "post": {
        "summary": "Zapier action: queue a single outbound call through the communication-run pipeline (calls:write). An omitted agent_id resolves to the business's one active agent assignment.",
        "responses": {
          "201": {
            "description": "Outbound call queued"
          }
        },
        "x-required-scopes": "calls:write"
      }
    },
    "/v1/wordpress/auth/verify": {
      "post": {
        "summary": "WordPress plugin: verify a connection API key and return workspace identity.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result"
          }
        }
      }
    },
    "/v1/wordpress/intake": {
      "post": {
        "summary": "WordPress plugin: submit a website intake form as a lead.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Intake lead result"
          }
        }
      }
    },
    "/v1/wordpress/dashboard-widget": {
      "get": {
        "summary": "WordPress plugin: return summary stats for the wp-admin dashboard widget.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Dashboard widget data"
          }
        }
      }
    },
    "/v1/wordpress/keys/create": {
      "post": {
        "summary": "Mint a WordPress-plugin connection API key for the authenticated dashboard user.",
        "security": [
          {
            "sessionAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Created plugin key"
          }
        }
      }
    },
    "/v1/wordpress/plugin/download": {
      "get": {
        "summary": "Download the KaiCalls WordPress plugin archive.",
        "responses": {
          "200": {
            "description": "Plugin archive (zip)"
          }
        }
      }
    },
    "/v1/appointments/availability": {
      "get": {
        "summary": "Check calendar availability for a workspace on a given date (appointments:read). Routes through the same provider-agnostic calendar lookup (GoHighLevel or Google Calendar) the voice agent uses on live calls — never a divergent availability source.",
        "parameters": [
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Required when the API key has access to more than one business."
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "duration_minutes",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability decision (success/unavailable)"
          },
          "400": {
            "description": "Missing/invalid params or business disambiguation needed"
          },
          "403": {
            "description": "API key lacks access to the business or scope"
          },
          "429": {
            "description": "Rate limited"
          },
          "502": {
            "description": "Calendar provider error (hard_failure)"
          }
        },
        "x-required-scopes": {
          "anyOf": [
            "appointments:read",
            "appointments:write"
          ]
        }
      }
    },
    "/v1/appointments/book": {
      "post": {
        "summary": "Book an appointment on a workspace calendar (appointments:write). Delegates to the exact same handler the voice agent uses (`smart_book_appointment`) — appointments booked here land in the same calendar/timeline as phone-booked ones. Requires an Idempotency-Key header; repeating a key returns the original outcome instead of attempting a second booking. `success` is only returned when the calendar provider returned a durable, confirmed booking id.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "start_time",
                  "attendee_email",
                  "attendee_name"
                ],
                "properties": {
                  "business_id": {
                    "type": "string",
                    "description": "Required when the API key has access to more than one business."
                  },
                  "start_time": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "attendee_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "attendee_name": {
                    "type": "string"
                  },
                  "attendee_phone": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay of a prior outcome for this key."
          },
          "201": {
            "description": "Booked — decision success, includes a durable booking_id."
          },
          "202": {
            "description": "queued — provider accepted the request but no durable id could be verified."
          },
          "400": {
            "description": "needs_clarification — missing/invalid input, no attempt made."
          },
          "403": {
            "description": "API key lacks access to the business or scope"
          },
          "409": {
            "description": "unavailable — the requested slot is taken."
          },
          "429": {
            "description": "Rate limited"
          },
          "502": {
            "description": "hard_failure — provider/config error."
          },
          "503": {
            "description": "retryable_delay — provider timed out, safe to retry."
          }
        },
        "x-required-scopes": "appointments:write"
      }
    },
    "/v1/concierge/approval-email-receipts": {
      "post": {
        "summary": "Receipt adapter for the configured outbound email worker (concierge:write) — records that a Concierge customer-approval email was sent/delivered for a request_id + business_id, with optional provider_message_id/provider_thread_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_id",
                  "business_id"
                ],
                "properties": {
                  "request_id": {
                    "type": "string"
                  },
                  "business_id": {
                    "type": "string"
                  },
                  "provider_message_id": {
                    "type": "string"
                  },
                  "provider_thread_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Receipt recorded"
          },
          "400": {
            "description": "Missing/invalid request_id or business_id"
          },
          "401": {
            "description": "Missing, invalid, revoked, or expired API key"
          },
          "403": {
            "description": "Missing concierge:write scope, or no access to the business"
          },
          "409": {
            "description": "Receipt rejected (e.g. unknown request_id)"
          }
        },
        "x-required-scopes": "concierge:write"
      }
    },
    "/v1/concierge/approval-replies": {
      "post": {
        "summary": "Optional inbound-email adapter (concierge:write) — records a customer reply against the exact Concierge approval packet identified by request_id + business_id. An exact \"yes\" marks the packet approved but does NOT execute it; a Concierge operator must explicitly queue an approved packet from the internal dashboard.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_id",
                  "business_id",
                  "from_email",
                  "body"
                ],
                "properties": {
                  "request_id": {
                    "type": "string"
                  },
                  "business_id": {
                    "type": "string"
                  },
                  "from_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "body": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  },
                  "message_id": {
                    "type": "string"
                  },
                  "thread_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reply recorded; decision is approve"
          },
          "202": {
            "description": "Reply recorded; decision is not a clear approval"
          },
          "400": {
            "description": "Missing/invalid request_id, business_id, from_email, or body"
          },
          "401": {
            "description": "Missing, invalid, revoked, or expired API key"
          },
          "403": {
            "description": "Missing concierge:write scope, or no access to the business"
          },
          "409": {
            "description": "Reply rejected (e.g. unknown request_id)"
          }
        },
        "x-required-scopes": "concierge:write"
      }
    },
    "/v1/signup": {
      "post": {
        "summary": "Public, unauthenticated signup entry point — no bearer key or session required. IP rate-limited (5/hour). Two account-creation paths use the body's `activation_mode` field ('otp' | 'payment'). Default (activation_mode omitted or 'otp'): a two-step, email-OTP-verified flow — step 1 (no otp_code/challenge_id) emails a 6-digit code and returns { status: 'otp_sent', challenge_id, expires_in_seconds }; step 2 (otp_code + challenge_id, same signup fields) creates the business, owner account, default API key, and browser-only assistant preview. It does not allocate a phone number or enable inbound calls, forwarding, SMS, or outbound calls. activation_mode: 'payment': creates the business/account/API key and requires a valid plan_id to select the Stripe price. It returns a Stripe Checkout URL and keeps carrier provisioning deferred until a verified webhook confirms the first subscription payment succeeded. An x402 receipt may authorize API access when configured, but does not replace the paid recurring phone plan.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/signupOtpRequest"
                  },
                  {
                    "$ref": "#/components/schemas/signupOtpVerify"
                  },
                  {
                    "$ref": "#/components/schemas/signupPayment"
                  }
                ]
              },
              "examples": {
                "requestCode": {
                  "summary": "Email a verification code for a browser preview",
                  "value": {
                    "business_name": "Acme Contractors",
                    "email": "owner@example.com",
                    "activation_mode": "otp",
                    "api_key_scopes": [
                      "agents:read",
                      "agents:write",
                      "calls:read",
                      "calls:write"
                    ]
                  }
                },
                "verifyCode": {
                  "summary": "Repeat the same scopes with the emailed code",
                  "value": {
                    "business_name": "Acme Contractors",
                    "email": "owner@example.com",
                    "activation_mode": "otp",
                    "api_key_scopes": [
                      "agents:read",
                      "agents:write",
                      "calls:read",
                      "calls:write"
                    ],
                    "challenge_id": "returned-challenge-id",
                    "otp_code": "123456"
                  }
                },
                "payment": {
                  "summary": "Create checkout for paid carrier activation",
                  "value": {
                    "business_name": "Acme Contractors",
                    "email": "owner@example.com",
                    "activation_mode": "payment"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "otp mode, step 1: { status: 'otp_sent', challenge_id, expires_in_seconds }. otp mode, step 2: status: 'preview_ready', api_key, business_id, agent_id, phone_number: null, dashboard_url, trial_ends_at: null, trial_minutes_included: 0, provisioning_deferred: true, payment_capture: 'required_for_carrier_activation'. payment mode: api_key, business_id, agent_id: null, phone_number: null, dashboard_url, trial_ends_at: null, provisioning_deferred: true, payment_capture ('stripe_checkout' | 'manual'), and a checkout_url when Stripe Checkout is available."
          },
          "400": {
            "description": "validation_error — missing/invalid business_name or email; invalid plan_id in payment mode; invalid, empty, unknown, wildcard or platform-only api_key_scopes, or api_key_scopes supplied in payment mode."
          },
          "401": {
            "description": "otp_invalid — otp mode only; the submitted otp_code was wrong, expired, or already consumed."
          },
          "402": {
            "description": "payment_required — payment mode only, when x402 is enabled and no valid payment proof was supplied; response includes the x402 payment requirements/challenge. Also payment_invalid (402) if a supplied payment proof fails verification."
          },
          "409": {
            "description": "email_exists — an account with this email already exists."
          },
          "429": {
            "description": "rate_limited — more than 5 signups from this IP in the last hour (both modes), or (otp mode only) more than 3 OTP requests/hour for the IP or email, or more than 3 completed signups/30 days from the IP."
          },
          "500": {
            "description": "internal_error — account/business/API-key creation failed; email_error (otp mode only) — failed to send the verification code."
          }
        }
      }
    }
  }
}
