{
  "openapi": "3.0.3",
  "info": {
    "title": "Zavu Unified Messaging Layer API",
    "version": "0.2.0",
    "description": "Unified multi-channel messaging API for Zavu.\n\nSupported channels:\n- **SMS**: Simple text messages\n- **WhatsApp**: Rich messaging with media, buttons, lists, CTA URL buttons, location requests, and templates\n- **Telegram**: Bot messaging with text, media, and interactive elements\n- **Email**: Transactional emails via Amazon SES\n\nDesign goals:\n- Simple `send()` entrypoint for developers\n- Project-level authentication via Bearer token\n- Support for all WhatsApp message types (text, image, video, audio, document, sticker, location, contact, buttons, list, cta_url, location_request, reaction, template)\n- If a non-text message type is sent, WhatsApp channel is used automatically\n- 24-hour WhatsApp conversation window enforcement\n- Universal `to` field accepts phone numbers (E.164), email addresses, or numeric chat IDs (Telegram/Instagram/Messenger)\n"
  },
  "servers": [
    {
      "url": "https://api.zavu.dev"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "parameters": {
      "MemoryIdParam": {
        "name": "memoryId",
        "in": "path",
        "required": true,
        "description": "Memory (fact) ID.",
        "schema": {
          "type": "string"
        }
      },
      "MemoryCollectionParam": {
        "name": "collection",
        "in": "path",
        "required": true,
        "description": "Collection name.",
        "schema": {
          "type": "string"
        }
      },
      "MemoryItemKeyParam": {
        "name": "key",
        "in": "path",
        "required": true,
        "description": "Item key. 1-255 chars from A-Z a-z 0-9 . _ : @ -",
        "schema": {
          "type": "string"
        }
      },
      "MemoryScopeParam": {
        "name": "scope",
        "in": "query",
        "required": false,
        "description": "Which scope to address: `contact:<contactId>`, `conversation:<conversationId>`, or omitted for the project scope.\n\nNote the asymmetry with `POST /v1/memory` and `POST /v1/memory/search`, which take the scope in the request BODY as `scope` plus `contactId`/`conversationId`. Every other Memory endpoint takes it here.",
        "schema": {
          "$ref": "#/components/schemas/MemoryScopeQuery"
        }
      },
      "MemoryIfMatchParam": {
        "name": "If-Match",
        "in": "header",
        "required": false,
        "description": "Make the write conditional on the item's current revision, as returned by a previous read or write (plain `3` or ETag-quoted `\"3\"`). The write is refused with `409 revision_conflict` if the item moved on in between. Without it, the write always wins and silently overwrites a concurrent one.",
        "schema": {
          "type": "string",
          "example": "3"
        }
      },
      "SenderHeader": {
        "name": "Zavu-Sender",
        "in": "header",
        "required": false,
        "description": "Optional sender profile ID. If omitted, the project's default sender will be used.",
        "schema": {
          "type": "string",
          "example": "sender_12345"
        }
      },
      "MessageIdParam": {
        "name": "messageId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "SenderIdParam": {
        "name": "senderId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "TemplateIdParam": {
        "name": "templateId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ContactIdParam": {
        "name": "contactId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ConversationIdParam": {
        "name": "conversationId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "BroadcastIdParam": {
        "name": "broadcastId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "BroadcastContactIdParam": {
        "name": "contactId",
        "in": "path",
        "required": true,
        "description": "Broadcast contact ID (not the global contact ID)",
        "schema": {
          "type": "string"
        }
      },
      "PhoneNumberIdParam": {
        "name": "phoneNumberId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "InvitationIdParam": {
        "name": "invitationId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "FlowIdParam": {
        "name": "flowId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ToolIdParam": {
        "name": "toolId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "KnowledgeBaseIdParam": {
        "name": "kbId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "DocumentIdParam": {
        "name": "docId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "AddressIdParam": {
        "name": "addressId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "RegulatoryDocumentIdParam": {
        "name": "documentId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "UrlIdParam": {
        "name": "urlId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "SubAccountIdParam": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Sub-account ID.",
        "schema": {
          "type": "string"
        }
      },
      "SubAccountApiKeyIdParam": {
        "name": "keyId",
        "in": "path",
        "required": true,
        "description": "API key ID.",
        "schema": {
          "type": "string"
        }
      },
      "TenDlcBrandIdParam": {
        "name": "brandId",
        "in": "path",
        "required": true,
        "description": "10DLC brand ID.",
        "schema": {
          "type": "string"
        }
      },
      "TenDlcCampaignIdParam": {
        "name": "campaignId",
        "in": "path",
        "required": true,
        "description": "10DLC campaign ID.",
        "schema": {
          "type": "string"
        }
      },
      "TenDlcAssignmentIdParam": {
        "name": "assignmentId",
        "in": "path",
        "required": true,
        "description": "Phone number assignment ID.",
        "schema": {
          "type": "string"
        }
      },
      "FunctionIdParam": {
        "name": "functionId",
        "in": "path",
        "required": true,
        "description": "Zavu Function ID.",
        "schema": {
          "type": "string"
        }
      },
      "FunctionDeploymentIdParam": {
        "name": "deploymentId",
        "in": "path",
        "required": true,
        "description": "Function deployment ID.",
        "schema": {
          "type": "string"
        }
      },
      "CallIdParam": {
        "name": "callId",
        "in": "path",
        "required": true,
        "description": "Voice call ID.",
        "schema": {
          "type": "string"
        }
      },
      "AgentIdParam": {
        "name": "agentId",
        "in": "path",
        "required": true,
        "description": "Agent ID.",
        "schema": {
          "type": "string"
        }
      },
      "ActivatableChannelParam": {
        "name": "channel",
        "in": "path",
        "required": true,
        "description": "The channel to switch.",
        "schema": {
          "$ref": "#/components/schemas/ActivatableChannel"
        }
      }
    },
    "schemas": {
      "MemoryScope": {
        "type": "string",
        "description": "Which namespace a memory belongs to. Scopes are isolated: a search in one never returns a memory from another.\n\n- `project`: shared by the whole project. What the agent knows in general.\n- `contact`: what the agent knows about one person, across every conversation with them.\n- `conversation`: what the agent knows about one inbox thread.\n\nDefaults to `project` everywhere. `contact` and `conversation` require the matching id.",
        "enum": [
          "project",
          "contact",
          "conversation"
        ]
      },
      "MemoryScopeQuery": {
        "type": "string",
        "description": "Scope as a query-string value: `contact:<contactId>`, `conversation:<conversationId>`, the literal `project`, or omitted for project scope.\n\nOnly the SHAPE of the id is validated, never its existence — the partition already carries your project, so an id that matches nothing simply addresses an empty scope rather than returning 404.",
        "example": "contact:jd7x2k3m4n5p6q7r8s9t0abc"
      },
      "MemoryRecord": {
        "type": "object",
        "description": "A stored fact, as returned when it is created.",
        "required": [
          "id",
          "scope",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "m7x2k3m4n5p6q7r8s9t0"
          },
          "scope": {
            "$ref": "#/components/schemas/MemoryScope"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the fact self-deletes. Present only when it was stored with `ttlSeconds`, or when the key is a test-mode key (forced to 30 days)."
          }
        }
      },
      "Memory": {
        "type": "object",
        "description": "A stored fact as returned by a read. `score` appears only on search results.",
        "required": [
          "id",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string",
            "description": "The remembered text."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "score": {
            "type": "number",
            "description": "Cosine similarity to the query, 0 to 1, higher being closer. Present on search results only — a list or a single read has no query to score against.",
            "example": 0.83
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryAddRequest": {
        "type": "object",
        "description": "Store one fact. It is embedded on write, so it becomes searchable by meaning rather than by keyword.",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "What to remember. Max 8,192 bytes of UTF-8. Write one fact per call: a paragraph holding five unrelated facts embeds as an average of all five and is retrieved well by none of them.",
            "example": "Prefers to be contacted in the morning, and by WhatsApp rather than email."
          },
          "scope": {
            "$ref": "#/components/schemas/MemoryScope"
          },
          "contactId": {
            "type": "string",
            "description": "Required when `scope` is `contact`."
          },
          "conversationId": {
            "type": "string",
            "description": "Required when `scope` is `conversation`."
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary string tags stored alongside the fact and returned with it. Keys max 64 chars, values max 512, whole object max 2,048 bytes. Not searchable — search matches `text` only.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "ttlSeconds": {
            "type": "integer",
            "description": "Delete the fact after this many seconds. 60 to 63,072,000 (2 years). Omit to keep it until deleted.",
            "minimum": 60,
            "maximum": 63072000
          }
        }
      },
      "MemorySearchRequest": {
        "type": "object",
        "description": "Retrieve facts by meaning. The query is embedded and compared against every fact in the scope.",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "What to recall. Max 8,192 bytes. Phrase it as the fact you hope to find, not as a keyword: the match is semantic.",
            "example": "when is a good time to reach them"
          },
          "scope": {
            "$ref": "#/components/schemas/MemoryScope"
          },
          "contactId": {
            "type": "string",
            "description": "Required when `scope` is `contact`."
          },
          "conversationId": {
            "type": "string",
            "description": "Required when `scope` is `conversation`."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum hits to return, closest first.",
            "default": 5,
            "minimum": 1,
            "maximum": 20
          },
          "minScore": {
            "type": "number",
            "description": "Drop hits below this similarity. Without it every scope returns its `limit` closest facts, however far away they are — an empty-handed search still answers with something. Set it (0.3 or above is a reasonable floor) when \"nothing relevant\" must be distinguishable from \"the least irrelevant thing I hold\".",
            "minimum": 0,
            "maximum": 1
          }
        }
      },
      "MemoryUsage": {
        "type": "object",
        "description": "This month's Memory consumption for the project, with the quota it is measured against.",
        "required": [
          "monthKey",
          "tier",
          "units",
          "reads",
          "writes",
          "memAdds",
          "memSearches",
          "capReached",
          "memoryBytes"
        ],
        "properties": {
          "monthKey": {
            "type": "string",
            "description": "The month these counters cover. They reset on the 1st.",
            "example": "2026-08"
          },
          "tier": {
            "type": "string",
            "description": "Plan tier the quotas come from."
          },
          "units": {
            "type": "integer",
            "description": "Billable units consumed this month. Units are metered from real capacity, and a write costs 5x a read."
          },
          "reads": {
            "type": "integer"
          },
          "writes": {
            "type": "integer"
          },
          "memAdds": {
            "type": "integer",
            "description": "Facts stored this month."
          },
          "memSearches": {
            "type": "integer",
            "description": "Semantic searches run this month."
          },
          "capReached": {
            "type": "boolean",
            "description": "Whether the plan's unit quota is exhausted. While true, writes are refused with `429`; reads and deletes keep working, so stored data is never locked away."
          },
          "quotaUnits": {
            "type": "integer",
            "nullable": true,
            "description": "Units included in the plan. Null means unlimited."
          },
          "memoryBytes": {
            "type": "integer",
            "description": "Bytes currently stored across facts and collections. Items with a TTL count as zero."
          },
          "quotaStorageGB": {
            "type": "number",
            "nullable": true,
            "description": "Storage included in the plan, in GB. Null means unlimited."
          }
        }
      },
      "MemoryCollection": {
        "type": "object",
        "description": "A namespace of JSON documents addressed by key.",
        "required": [
          "name",
          "indexes",
          "defaultTtlSeconds",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "orders"
          },
          "indexes": {
            "type": "array",
            "description": "Fields that `POST /v1/memory/collections/{collection}/query` can filter on. Immutable after creation.",
            "items": {
              "type": "object",
              "required": [
                "field"
              ],
              "properties": {
                "field": {
                  "type": "string"
                }
              }
            }
          },
          "defaultTtlSeconds": {
            "type": "integer",
            "nullable": true,
            "description": "TTL applied to items written without their own `ttlSeconds`. Null means items are kept until deleted."
          },
          "status": {
            "type": "string",
            "description": "`active`, or `deleting` while a delete drains in the background."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryCollectionCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "1-63 chars: lowercase letters, digits, `-` and `_`, starting with a letter or digit.",
            "example": "orders"
          },
          "indexes": {
            "type": "array",
            "description": "Up to 2 fields to index for `query`. **This is the only way to declare them** — a collection created implicitly by its first write has none, and querying an unindexed field returns `400 field_not_indexed`. Indexes cannot be added later, so declare them here before the first write.",
            "maxItems": 2,
            "items": {
              "type": "object",
              "required": [
                "field"
              ],
              "properties": {
                "field": {
                  "type": "string",
                  "description": "1-64 chars: letters, digits and `_`, starting with a letter. Top-level field of the stored document.",
                  "example": "status"
                }
              }
            }
          },
          "defaultTtlSeconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 63072000
          }
        }
      },
      "MemoryCollectionUpdateRequest": {
        "type": "object",
        "description": "Only the default TTL is mutable. Indexes are fixed at creation.",
        "required": [
          "defaultTtlSeconds"
        ],
        "properties": {
          "defaultTtlSeconds": {
            "type": "integer",
            "nullable": true,
            "description": "New default TTL, or null to clear it.",
            "minimum": 60,
            "maximum": 63072000
          }
        }
      },
      "MemoryItem": {
        "type": "object",
        "description": "One JSON document in a collection.",
        "required": [
          "key",
          "rev",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "key": {
            "type": "string",
            "example": "ORD-12345"
          },
          "value": {
            "description": "The stored JSON. Any JSON value.",
            "example": {
              "status": "shipped",
              "total": 42.5
            }
          },
          "rev": {
            "type": "integer",
            "description": "Revision, incremented on every write. Pass it as `If-Match` on a later write to make that write conditional.",
            "example": 3
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemoryItemPutRequest": {
        "type": "object",
        "required": [
          "value"
        ],
        "properties": {
          "value": {
            "description": "The document to store. Any JSON value, max 200KB serialized and 32 levels deep. This is a whole-document write, not a merge: it replaces whatever the key held."
          },
          "ttlSeconds": {
            "type": "integer",
            "description": "Overrides the collection's `defaultTtlSeconds` for this item.",
            "minimum": 60,
            "maximum": 63072000
          }
        }
      },
      "MemoryItemQueryRequest": {
        "type": "object",
        "description": "Look items up by an indexed field instead of by key.",
        "required": [
          "where"
        ],
        "properties": {
          "where": {
            "type": "object",
            "description": "One condition on one indexed field. `between` bounds are inclusive and must be the same type.",
            "required": [
              "field",
              "op"
            ],
            "properties": {
              "field": {
                "type": "string",
                "example": "status"
              },
              "op": {
                "type": "string",
                "enum": [
                  "eq",
                  "beginsWith",
                  "between"
                ]
              },
              "value": {
                "description": "The value to match. Used by `eq` (string, number or boolean) and `beginsWith` (string)."
              },
              "from": {
                "description": "Lower bound, for `between`."
              },
              "to": {
                "description": "Upper bound, for `between`."
              }
            }
          },
          "limit": {
            "type": "integer",
            "default": 50,
            "minimum": 1,
            "maximum": 100
          },
          "cursor": {
            "type": "string"
          }
        }
      },
      "MemoryItemBatchRequest": {
        "type": "object",
        "description": "Up to 25 writes and deletes in one call. Operations are NOT atomic: each is reported on its own, and a failure in one does not roll the others back.",
        "required": [
          "ops"
        ],
        "properties": {
          "ops": {
            "type": "array",
            "minItems": 1,
            "maxItems": 25,
            "items": {
              "type": "object",
              "required": [
                "op",
                "key"
              ],
              "properties": {
                "op": {
                  "type": "string",
                  "enum": [
                    "set",
                    "delete"
                  ]
                },
                "key": {
                  "type": "string"
                },
                "value": {
                  "description": "The document, for `set`."
                },
                "ttlSeconds": {
                  "type": "integer",
                  "minimum": 60,
                  "maximum": 63072000
                }
              }
            }
          }
        }
      },
      "MemoryItemBatchResponse": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One entry per submitted operation, in order. Check every `ok`: the call returns 200 even when some operations failed.",
            "items": {
              "type": "object",
              "required": [
                "key",
                "ok"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "ok": {
                  "type": "boolean"
                },
                "rev": {
                  "type": "integer",
                  "description": "New revision, on a successful `set`."
                },
                "error": {
                  "type": "string",
                  "description": "Why this operation failed. Present when `ok` is false."
                }
              }
            }
          }
        }
      },
      "FunctionTrigger": {
        "type": "object",
        "description": "A subscription that runs a Zavu Function when a messaging event fires.",
        "required": [
          "id",
          "functionId",
          "eventType",
          "active",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "functionId": {
            "type": "string"
          },
          "eventType": {
            "type": "string",
            "description": "Event type that fires the function. See GET /v1/functions/event-types for the supported list. The special type `cron` fires on a schedule instead of a messaging event and carries a `cron` expression."
          },
          "senderId": {
            "type": "string",
            "nullable": true,
            "description": "Restrict the trigger to a single sender. Null means all senders in the project."
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cron": {
            "type": "string",
            "nullable": true,
            "description": "5-field cron expression (minute hour day-of-month month day-of-week), evaluated in UTC. Present only on `cron` triggers.",
            "example": "*/15 * * * *"
          },
          "nextRunAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Next scheduled fire time. Present only on `cron` triggers."
          },
          "lastRunAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Last time the schedule fired. Null until the first fire."
          }
        }
      },
      "DocumentUpdateRequest": {
        "type": "object",
        "description": "Fields to update on a knowledge base document. Updating content reprocesses the document for RAG.",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "content": {
            "type": "string",
            "maxLength": 500000
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "invalid_request"
          },
          "message": {
            "type": "string",
            "example": "Phone number is invalid"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "MessageStatus": {
        "type": "string",
        "enum": [
          "queued",
          "sending",
          "sent",
          "delivered",
          "read",
          "failed",
          "received",
          "pending_url_verification"
        ]
      },
      "Channel": {
        "type": "string",
        "description": "Delivery channel. Use 'auto' for intelligent routing.",
        "enum": [
          "auto",
          "sms",
          "sms_oneway",
          "whatsapp",
          "telegram",
          "email",
          "instagram",
          "messenger",
          "voice"
        ]
      },
      "MessageType": {
        "type": "string",
        "description": "Type of message. Non-text types are supported by WhatsApp and Telegram (varies by type).\n\n`location_request` asks the recipient to share their location and is WhatsApp-only. It takes no `content` object — the prompt goes in `text` (max 1024 characters) and the button label is fixed by WhatsApp. The recipient's answer arrives as an inbound `location` message whose `content.replyToMessageId` is the ID of the request.\n\n`request_contact_info` asks the recipient to share their phone number and is WhatsApp-only. Like `location_request` it takes no `content` object — the prompt goes in `text` (max 1024 characters) and WhatsApp renders a fixed **Share Contact Info** button. The answer arrives as an inbound `contact` message. Use it to recover the phone number of a contact who adopted a WhatsApp username and is only known by their business-scoped user ID (BSUID); when they share it, Zavu automatically links the phone number to that contact.",
        "enum": [
          "text",
          "image",
          "video",
          "audio",
          "document",
          "sticker",
          "location",
          "contact",
          "buttons",
          "list",
          "cta_url",
          "request_contact_info",
          "location_request",
          "reaction",
          "template"
        ]
      },
      "MessageContent": {
        "type": "object",
        "description": "Content for non-text message types (WhatsApp and Telegram).",
        "properties": {
          "mediaUrl": {
            "type": "string",
            "description": "URL of the media file (for image, video, audio, document, sticker).",
            "example": "https://example.com/image.jpg"
          },
          "mediaId": {
            "type": "string",
            "description": "WhatsApp media ID if already uploaded."
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type of the media.",
            "example": "image/jpeg"
          },
          "filename": {
            "type": "string",
            "description": "Filename for documents.",
            "example": "invoice.pdf"
          },
          "latitude": {
            "type": "number",
            "description": "Latitude for location messages."
          },
          "longitude": {
            "type": "number",
            "description": "Longitude for location messages."
          },
          "locationName": {
            "type": "string",
            "description": "Name of the location."
          },
          "locationAddress": {
            "type": "string",
            "description": "Address of the location."
          },
          "contacts": {
            "type": "array",
            "description": "Contact cards for contact messages.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "phones": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "buttons": {
            "type": "array",
            "description": "Interactive buttons (max 3).",
            "maxItems": 3,
            "items": {
              "type": "object",
              "required": [
                "id",
                "title"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "maxLength": 256
                },
                "title": {
                  "type": "string",
                  "maxLength": 20
                }
              }
            }
          },
          "listButton": {
            "type": "string",
            "description": "Button text for list messages.",
            "maxLength": 20
          },
          "sections": {
            "type": "array",
            "description": "Sections for list messages.",
            "items": {
              "type": "object",
              "required": [
                "title",
                "rows"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "rows": {
                  "type": "array",
                  "maxItems": 10,
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "title"
                    ],
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 24
                      },
                      "description": {
                        "type": "string",
                        "maxLength": 72
                      }
                    }
                  }
                }
              }
            }
          },
          "ctaDisplayText": {
            "type": "string",
            "description": "Button label for cta_url messages.",
            "maxLength": 20,
            "example": "See Dates"
          },
          "ctaUrl": {
            "type": "string",
            "format": "uri",
            "description": "Destination URL opened in the device's default browser when the button is tapped. Used with messageType=cta_url. WhatsApp requires HTTPS in production.",
            "example": "https://example.com/schedule"
          },
          "ctaHeaderType": {
            "type": "string",
            "description": "Optional header type for cta_url messages.",
            "enum": [
              "text",
              "image",
              "video",
              "document"
            ]
          },
          "ctaHeaderText": {
            "type": "string",
            "description": "Header text when ctaHeaderType is 'text'.",
            "maxLength": 60
          },
          "ctaHeaderMediaUrl": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTPS URL of the header media when ctaHeaderType is 'image', 'video', or 'document'. WhatsApp fetches this URL — it must be publicly reachable and return the declared content type."
          },
          "footerText": {
            "type": "string",
            "description": "Optional footer text for cta_url messages.",
            "maxLength": 60,
            "example": "Dates subject to change."
          },
          "emoji": {
            "type": "string",
            "description": "Emoji for reaction messages."
          },
          "reactToMessageId": {
            "type": "string",
            "description": "Message ID to react to."
          },
          "replyToMessageId": {
            "type": "string",
            "description": "Zavu message ID of the quoted message this message replies to.\n\n**When sending:** set it to make the message a quoted reply to an earlier message in the same conversation, such as the `data.messageId` of a `message.inbound` webhook. Supported on WhatsApp (every message type except `reaction`) and Telegram. The quoted message must belong to this project (otherwise `404`), must have gone out on the same channel as this send, must belong to the conversation `to` addresses, must have been sent or received by the sender this message goes out from (pass it in `Zavu-Sender`), and must have a `providerMessageId`. A failed message and a Telegram button tap cannot be quoted. Any other case returns `400` instead of sending the message unquoted. The stored message then carries the rest of the `replyTo*` fields.\n\n**When receiving:** present on inbound messages that quote an earlier message. Omitted when the quoted message is not found in Zavu (e.g. an old or unknown message) — use replyToProviderMessageId in that case."
          },
          "replyToProviderMessageId": {
            "type": "string",
            "description": "Provider message ID (WhatsApp WAMID, or Telegram message ID) of the quoted message. Present whenever an inbound WhatsApp message is a reply, even if the quoted message is not stored in Zavu, and on outbound quoted replies. Read-only: to quote when sending, set replyToMessageId."
          },
          "replyToFrom": {
            "type": "string",
            "description": "Sender of the quoted message (phone number in E.164 format, or the Telegram chat ID). Read-only."
          },
          "replyToText": {
            "type": "string",
            "description": "Truncated snippet (up to 200 characters) of the quoted message's text, for display. Empty when the quoted message has no text (e.g. media). Read-only."
          },
          "replyToMessageType": {
            "type": "string",
            "description": "Type of the quoted message (text, image, video, etc.). Read-only."
          },
          "templateId": {
            "type": "string",
            "description": "Template ID for template messages."
          },
          "templateVariables": {
            "type": "object",
            "description": "Variables for body placeholders. Key them to match the template body: by position (`1`, `2`, ...) for positional templates, or by name (e.g. `customer_name`) for named templates. Zavu detects the template's format and sends the correct payload to Meta. Named keys also resolve a named text-header variable. Do not mix positional and named keys in the same request. On WhatsApp, a value may not contain line breaks, tabs or more than 4 consecutive spaces: Meta refuses those at delivery, so the send is rejected with `400 invalid_request` instead (`details.field` names the value).",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "1": "John",
              "2": "ORD-12345"
            }
          },
          "templateButtonVariables": {
            "type": "object",
            "description": "Variables for dynamic button placeholders (URL buttons and OTP buttons). Keys are the button index (0, 1, 2) in the template's `buttons` array — not the placeholder name. Values substitute the `{{1}}` placeholder inside that button's URL.\n\n**WhatsApp constraints:**\n- URL buttons only accept `{{1}}` — positional, numeric, no whitespace, no name. Named placeholders like `{{token}}` are stored as literal URL text by Meta and cannot be substituted.\n- At most one placeholder per URL button.\n- A template may have at most three buttons.\n- Static URL buttons (no placeholder) and `quick_reply` buttons are not included here.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "0": "abc-report-token"
            }
          },
          "templateHeaderVariables": {
            "type": "object",
            "description": "Value for a text-header variable, keyed by `1` (WhatsApp text headers allow at most one variable). Optional override. If omitted, Zavu resolves the header from `templateVariables` using the header placeholder's name (e.g. `novios`). Static text headers need no value. The same WhatsApp rule as `templateVariables` applies: no line breaks, tabs or more than 4 consecutive spaces.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "1": "Jorge y Laura"
            }
          },
          "referral": {
            "$ref": "#/components/schemas/AdReferral"
          }
        }
      },
      "AdReferral": {
        "type": "object",
        "description": "Click-to-WhatsApp (CTWA) ad attribution: where an inbound conversation came from.\n\nWhatsApp only. Present on the **first inbound message** of a conversation opened from a Meta ad or post, and on no message after it — so store it when it arrives rather than expecting it again. Organic conversations never carry it.\n\nField names are camelCased to match the rest of this API; Meta sends them as snake_case (`ctwa_clid`, `source_id`, ...). Fields that do not apply are omitted: a `post` source has no click id, and an image ad has no `videoUrl`.",
        "properties": {
          "sourceType": {
            "type": "string",
            "description": "Where the click came from.",
            "enum": [
              "ad",
              "post"
            ]
          },
          "sourceId": {
            "type": "string",
            "description": "Identifier of the ad or post that produced the click.",
            "example": "120210000000000000"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri",
            "description": "Meta permalink to the ad or post."
          },
          "headline": {
            "type": "string",
            "description": "Headline of the ad or post."
          },
          "body": {
            "type": "string",
            "description": "Body copy of the ad or post."
          },
          "mediaType": {
            "type": "string",
            "description": "Type of media on the ad, when it had any.",
            "enum": [
              "image",
              "video"
            ]
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "Image of the ad. Present when `mediaType` is `image`."
          },
          "videoUrl": {
            "type": "string",
            "format": "uri",
            "description": "Video of the ad. Present when `mediaType` is `video`."
          },
          "thumbnailUrl": {
            "type": "string",
            "format": "uri",
            "description": "Thumbnail of the ad media."
          },
          "ctwaClid": {
            "type": "string",
            "description": "Click-to-WhatsApp click identifier. This is the value Meta's Conversions API needs to credit a conversion back to the ad that produced the conversation. Present on `ad` sources; a `post` source has none.",
            "example": "ARIzZm9vYmFyY3R3YWNsaWQ"
          }
        }
      },
      "EmailAttachmentInput": {
        "type": "object",
        "description": "Email attachment. Provide either `content` (base64) or `path` (URL), not both.",
        "required": [
          "filename"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "description": "Name of the attached file.",
            "maxLength": 255,
            "example": "invoice.pdf"
          },
          "content": {
            "type": "string",
            "description": "Content of the attached file as a Base64-encoded string."
          },
          "path": {
            "type": "string",
            "format": "uri",
            "description": "URL where the attachment file is hosted. The server will fetch the file."
          },
          "content_type": {
            "type": "string",
            "description": "MIME type of the attachment. If not set, will be derived from the filename.",
            "example": "application/pdf"
          },
          "content_id": {
            "type": "string",
            "description": "Content ID for inline images. Reference in HTML as `<img src=\"cid:your_content_id\">`.",
            "maxLength": 100,
            "example": "logo"
          }
        }
      },
      "MessageRequest": {
        "type": "object",
        "description": "Request body to send a message.",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient phone number in E.164 format, email address, WhatsApp business-scoped user ID (BSUID, e.g. `US.13491208655302741918`), or numeric chat ID (for Telegram/Instagram/Messenger). A BSUID is routed to WhatsApp and sent via the `recipient` field; use it to message a contact who adopted a username and whose phone number is hidden.",
            "example": "+56912345678"
          },
          "channel": {
            "$ref": "#/components/schemas/Channel",
            "description": "Delivery channel. Use 'auto' for intelligent routing. If omitted, channel is auto-selected based on sender capabilities and recipient type. For email recipients, defaults to 'email'.",
            "default": "auto"
          },
          "messageType": {
            "$ref": "#/components/schemas/MessageType",
            "description": "Type of message. Defaults to 'text'.",
            "default": "text"
          },
          "text": {
            "type": "string",
            "description": "Text body for text messages or caption for media messages.",
            "example": "Your verification code is 123456."
          },
          "content": {
            "$ref": "#/components/schemas/MessageContent",
            "description": "Additional content for non-text message types."
          },
          "subject": {
            "type": "string",
            "description": "Email subject line. Required when channel is 'email' or recipient is an email address.",
            "maxLength": 998,
            "example": "Your order confirmation"
          },
          "htmlBody": {
            "type": "string",
            "description": "HTML body for email messages. If provided, email will be sent as multipart with both text and HTML."
          },
          "replyTo": {
            "type": "string",
            "format": "email",
            "description": "Reply-To email address for email messages.",
            "example": "support@example.com"
          },
          "idempotencyKey": {
            "type": "string",
            "description": "Optional idempotency key to avoid duplicate sends.",
            "example": "msg_01HZY4ZP7VQY2J3BRW7Z6G0QGE"
          },
          "metadata": {
            "type": "object",
            "description": "Arbitrary metadata to associate with the message.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "fallbackEnabled": {
            "type": "boolean",
            "description": "Whether a failed WhatsApp text or template message is retried over SMS, when the sender also has SMS. When omitted, it is on if `channel` is omitted or `auto`, and off if a channel is set explicitly: a send with `channel: \"whatsapp\"` does not fall back unless this is `true`. Media, buttons, lists and other WhatsApp-only types never fall back."
          },
          "voiceLanguage": {
            "type": "string",
            "description": "Language code for voice text-to-speech (e.g., 'en-US', 'es-ES', 'pt-BR'). If omitted, language is auto-detected from recipient's country code.",
            "example": "es-ES"
          },
          "attachments": {
            "type": "array",
            "description": "Email attachments. Only supported when channel is 'email'. Maximum 40MB total size.",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/EmailAttachmentInput"
            }
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "to",
          "direction",
          "status",
          "channel",
          "messageType",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "jd7x2k3m4n5p6q7r8s9t0"
          },
          "to": {
            "type": "string",
            "example": "+56912345678"
          },
          "from": {
            "type": "string",
            "example": "+13125551212"
          },
          "senderId": {
            "type": "string",
            "example": "sender_12345"
          },
          "channel": {
            "$ref": "#/components/schemas/Channel"
          },
          "direction": {
            "type": "string",
            "description": "Who sent the message. Needed to render a thread: `status` cannot tell the two apart, because an inbound message is also stored as `delivered`.",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "messageType": {
            "$ref": "#/components/schemas/MessageType"
          },
          "status": {
            "$ref": "#/components/schemas/MessageStatus"
          },
          "text": {
            "type": "string",
            "description": "Text content or caption."
          },
          "content": {
            "$ref": "#/components/schemas/MessageContent"
          },
          "conversationId": {
            "type": "string",
            "description": "ID of the conversation (inbox thread) this message belongs to. Use it to build a direct dashboard link: `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. Omitted only on legacy messages created before conversation threading.",
            "example": "js723987cyghwqxxaxcf590qd18axd95"
          },
          "providerMessageId": {
            "type": "string",
            "description": "Message ID from the delivery provider."
          },
          "errorCode": {
            "type": "string",
            "nullable": true
          },
          "errorMessage": {
            "type": "string",
            "nullable": true
          },
          "cost": {
            "type": "number",
            "nullable": true,
            "description": "Zavu platform charge in USD for this message. Messaging is billed against your plan's monthly limits plus usage-based overage."
          },
          "costProvider": {
            "type": "number",
            "nullable": true,
            "description": "Carrier and delivery cost in USD."
          },
          "costTotal": {
            "type": "number",
            "nullable": true,
            "description": "Total cost in USD (platform charge + delivery cost)."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MessageResponse": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "MessageAttachment": {
        "type": "object",
        "description": "A stored file attachment for an email message (inbound or outbound).",
        "required": [
          "id",
          "filename",
          "mimeType",
          "size",
          "contentId",
          "isInline",
          "downloadUrl",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "jd7x2k3m4n5p6q7r8s9t0"
          },
          "filename": {
            "type": "string",
            "example": "invoice.pdf"
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type of the attachment.",
            "example": "application/pdf"
          },
          "size": {
            "type": "integer",
            "description": "Size of the attachment in bytes.",
            "example": 102400
          },
          "contentId": {
            "type": "string",
            "nullable": true,
            "description": "Content-ID for inline attachments (referenced in the HTML body as `cid:<contentId>`). Null for regular attachments.",
            "example": "logo"
          },
          "isInline": {
            "type": "boolean",
            "description": "Whether the attachment is inline (embedded in the HTML body) rather than a regular attachment."
          },
          "downloadUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Short-lived signed URL to download the attachment bytes. Freshly generated on each request and expires; do not cache it. Null if the stored file is no longer available."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MessageAttachmentsResponse": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageAttachment"
            }
          }
        }
      },
      "WhatsAppCategory": {
        "type": "string",
        "description": "WhatsApp template category.",
        "enum": [
          "UTILITY",
          "MARKETING",
          "AUTHENTICATION"
        ]
      },
      "Template": {
        "type": "object",
        "required": [
          "id",
          "name",
          "language",
          "body",
          "category"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Template name. For WhatsApp, must match the approved template name in Meta.",
            "example": "order_confirmation"
          },
          "language": {
            "type": "string",
            "description": "Language code.",
            "example": "en"
          },
          "body": {
            "type": "string",
            "description": "Default template body with variables: positional ({{1}}, {{2}}) or named ({{customer_name}}, {{contact.first_name}}). Templates created in Zavu are submitted to Meta as positional; templates imported from a WhatsApp Business Account keep their original format (named or positional). Used when no channel-specific body is set.",
            "example": "Hi {{1}}, your order {{2}} has shipped."
          },
          "smsBody": {
            "type": "string",
            "description": "Channel-specific body for SMS messages. Falls back to `body` if not set."
          },
          "telegramBody": {
            "type": "string",
            "description": "Channel-specific body for Telegram messages. Falls back to `body` if not set."
          },
          "instagramBody": {
            "type": "string",
            "description": "Channel-specific body for Instagram messages. Falls back to `body` if not set."
          },
          "category": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WhatsAppCategory"
              }
            ],
            "description": "The category the template is billed under. For a template linked to WhatsApp this is the category **Meta currently assigns**, not the one it was submitted with: Meta can recategorize a template at approval or at any point afterwards, and that changes what the message costs.\n\nA recategorization reaches this field through the `template.status_changed` webhook, which carries `data.category`, and through `POST /v1/templates/sync`, which reconciles it against Meta. Read one of those rather than assuming the category you sent is the one in force.\n\nFor a template that was never submitted to WhatsApp, this is the category stored locally, defaulting to `UTILITY`."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending",
              "approved",
              "rejected"
            ],
            "default": "draft"
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of variable names for documentation."
          },
          "headerType": {
            "type": "string",
            "description": "Type of header (text, image, video, document)."
          },
          "headerContent": {
            "type": "string",
            "description": "Header content (text or media URL)."
          },
          "footer": {
            "type": "string",
            "description": "Footer text for the template."
          },
          "buttons": {
            "type": "array",
            "description": "Template buttons.",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "quick_reply",
                    "url",
                    "phone",
                    "otp",
                    "request_contact_info"
                  ]
                },
                "text": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "example": {
                  "type": "string",
                  "description": "Sample value used to substitute `{{1}}` in the URL when submitting the template to Meta for review. Only present for dynamic URL buttons."
                },
                "phoneNumber": {
                  "type": "string"
                },
                "otpType": {
                  "type": "string",
                  "enum": [
                    "COPY_CODE",
                    "ONE_TAP"
                  ],
                  "description": "OTP button type. Required when type is 'otp'."
                },
                "packageName": {
                  "type": "string",
                  "description": "Android package name. Required for ONE_TAP buttons."
                },
                "signatureHash": {
                  "type": "string",
                  "description": "Android app signature hash. Required for ONE_TAP buttons."
                }
              }
            }
          },
          "addSecurityRecommendation": {
            "type": "boolean",
            "description": "Add 'Do not share this code' disclaimer. Only for AUTHENTICATION templates."
          },
          "codeExpirationMinutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 90,
            "description": "Code expiration time in minutes. Only for AUTHENTICATION templates."
          },
          "whatsapp": {
            "type": "object",
            "description": "WhatsApp-specific template information.",
            "properties": {
              "templateName": {
                "type": "string",
                "description": "WhatsApp template name."
              },
              "namespace": {
                "type": "string",
                "description": "WhatsApp Business Account namespace."
              },
              "status": {
                "type": "string",
                "description": "WhatsApp approval status."
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TemplateCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "language",
          "body"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "language": {
            "type": "string",
            "default": "en"
          },
          "body": {
            "type": "string",
            "description": "Default template body. Used when no channel-specific body is set."
          },
          "smsBody": {
            "type": "string",
            "description": "Channel-specific body for SMS. Falls back to `body` if not set."
          },
          "telegramBody": {
            "type": "string",
            "description": "Channel-specific body for Telegram. Falls back to `body` if not set."
          },
          "instagramBody": {
            "type": "string",
            "description": "Channel-specific body for Instagram. Falls back to `body` if not set."
          },
          "whatsappCategory": {
            "$ref": "#/components/schemas/WhatsAppCategory"
          },
          "headerType": {
            "type": "string",
            "description": "Type of header for the template.",
            "enum": [
              "text",
              "image",
              "video",
              "document"
            ]
          },
          "headerContent": {
            "type": "string",
            "description": "Header content (text string or media URL)."
          },
          "footer": {
            "type": "string",
            "description": "Footer text for the template.",
            "maxLength": 60
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "buttons": {
            "type": "array",
            "description": "Template buttons (max 3).",
            "maxItems": 3,
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "quick_reply",
                    "url",
                    "phone",
                    "otp",
                    "request_contact_info"
                  ],
                  "description": "`request_contact_info` renders a fixed **Share Contact Info** button that asks the recipient to share their phone number — useful when a contact adopted a WhatsApp username and you only know their BSUID. It takes no other fields."
                },
                "text": {
                  "type": "string",
                  "maxLength": 25,
                  "description": "Button label. Required for every type except `request_contact_info`, whose label is fixed by WhatsApp."
                },
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Button destination. Use `{{1}}` exactly once for a dynamic URL (e.g. `https://example.com/orders/{{1}}`); WhatsApp only accepts the strict `{{1}}` form. Static URLs must not contain any `{{...}}` placeholder."
                },
                "example": {
                  "type": "string",
                  "description": "Sample value Meta uses to review templates with a dynamic URL button. Substituted into `{{1}}` of the URL when the template is submitted to Meta. Only meaningful when `url` contains `{{1}}`; ignored for static URLs.",
                  "example": "ORD-12345"
                },
                "phoneNumber": {
                  "type": "string"
                },
                "otpType": {
                  "type": "string",
                  "enum": [
                    "COPY_CODE",
                    "ONE_TAP"
                  ],
                  "description": "Required when type is 'otp'. COPY_CODE shows copy button, ONE_TAP enables Android autofill."
                },
                "packageName": {
                  "type": "string",
                  "description": "Android package name. Required for ONE_TAP buttons."
                },
                "signatureHash": {
                  "type": "string",
                  "description": "Android app signature hash. Required for ONE_TAP buttons."
                }
              }
            }
          },
          "addSecurityRecommendation": {
            "type": "boolean",
            "description": "Add 'Do not share this code' disclaimer. Only for AUTHENTICATION templates."
          },
          "codeExpirationMinutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 90,
            "description": "Code expiration time in minutes. Only for AUTHENTICATION templates."
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "description": "Type of event that triggers the webhook.\n\n**Message lifecycle events:**\n- `message.queued`: Message created and queued for sending. `data.status` = `queued`\n- `message.sent`: Message accepted by the provider. `data.status` = `sent`\n- `message.delivered`: Message delivered to recipient. `data.status` = `delivered`\n- `message.read`: Message was read by the recipient (WhatsApp only). `data.status` = `read`\n- `message.failed`: Message failed to send. `data.status` = `failed`\n\n**Inbound events:**\n- `message.inbound`: New message received from a contact. `data.conversationId`, the inbox thread id (deep-link with `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), is always set: the webhook is sent once the message has been filed in its thread, so the first message of a new thread carries the same id as `conversation.new`. `data.contactId` is the Zavu contact, and unlike `data.from` it does not change when a WhatsApp contact's identifier switches from a business-scoped user ID (BSUID) to a phone number, unless the switch reveals two contacts for one person; they are then merged and `contact.identity_linked` names the one that went away in `mergedContactId`. On WhatsApp, `data.whatsapp` carries the contact's `bsuid` and `username` when known. Reactions carry none of these three. Reactions are delivered as `message.inbound` with `messageType='reaction'`. When the contact replied to (quoted) an earlier message, `data.content` carries the reply context: `replyToMessageId`, `replyToProviderMessageId`, `replyToFrom`, `replyToText`, and `replyToMessageType`. `data.providerTimestamp` is the provider's original receive time in Unix milliseconds (the moment the channel received the message from the contact — WhatsApp, Telegram, Instagram, Messenger; `null` for SMS and email). Compare it against the top-level `timestamp` (when Zavu dispatched the webhook) to detect and ignore delayed deliveries. When the conversation was opened from a Click-to-WhatsApp ad or post, `data.referral` carries the ad attribution — including `ctwaClid`, the identifier Meta's Conversions API needs to credit a conversion back to that ad. WhatsApp only, and only on the first message of the thread: it is absent from every later message, so persist it when it arrives.\n- `message.unsupported`: Received a message type that is not supported\n\n**Broadcast events:**\n- `broadcast.status_changed`: Broadcast status changed (pending_review, approved, rejected, sending, completed, cancelled)\n\n**Other events:**\n- `conversation.new`: New conversation started with a contact. `data` carries `conversationId` (the inbox thread id — deep-link with `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the `phoneNumber` or `email` key, `channel`, `firstMessageId`, `firstMessageText`, and `profileName`.\n- `contact.identity_linked`: A WhatsApp contact known by BSUID was tied to a phone number, a contact known by phone received a BSUID, or a contact changed their number. `data` carries `contactId`, `bsuid`, `phoneNumber` and `reason`: `provider_reported` (WhatsApp sent the phone and the BSUID together on an inbound message), `contact_shared` (the contact shared their phone number in reply to a `request_contact_info` message), `phone_changed` (the contact changed their number; `previousPhoneNumber` carries the old one, and `bsuid` is `null` when WhatsApp never reported one for the contact), or `bsuid_changed` (WhatsApp reported a new BSUID for the same number). `contactId` is the contact that remains if two were merged, and `mergedContactId` the one folded into it. `previousBsuid` is a BSUID you may have stored that no longer identifies the contact. Sent once per link, not on every message. `data.from` on `message.inbound` is the phone only when WhatsApp includes it, so it can still be the BSUID afterwards; key on `contactId`.\n- `template.status_changed`: WhatsApp template approval status changed. `data` carries `templateId`, `name`, `previousStatus`, `currentStatus`, `rejectionReason`, and `category` — the category Meta currently bills the template under. Meta can recategorize a template (typically `UTILITY` to `MARKETING`) at approval or long afterwards, which changes what each message costs; `category` is how that reaches you. A recategorization with no status change is delivered as this same event, so compare `category` against what you hold rather than only reacting to `currentStatus`.\n\n**Partner events:**\n- `invitation.status_changed`: A partner invitation's stored status changed: to `in_progress`, `completed`, `failed`, `cancelled`, or back to `pending` when it is resent from the dashboard. A change to the same status sends nothing, and expiry is not a stored change, so no event is sent when an invitation expires. Delivered to the project webhook (`POST /v1/invitations/webhook`) of the project that created the invitation; a parent project does not receive its sub-accounts' events. `data` carries `invitationId`, `clientName`, `clientEmail`, `connectionType` (`whatsapp_waba` or `messenger`), `previousStatus`, and `currentStatus`. On `completed` it also carries `senderId`, `connectedAccount` (`channel`, `id`, `name`) — the WhatsApp number or Facebook Page that was linked — and, for WhatsApp, `wabaAccountId`. On `failed` it carries `failureReason`; the invitation link stays usable, so a client can retry it.\n\n**Voice Agent events:**\nFor every voice event, `data` carries `callId`, `direction`, `from`, `to`, `status`, `durationSeconds`, `endReason`, and `transcriptAvailable`. The terminal events (`call.completed`, `call.failed`) additionally carry `cost` — what the call was billed, in USD, combining telephony and the managed voice pipeline — and `currency`. They are dispatched after the call is charged, so `cost` is populated rather than zero. It can still grow afterwards: telephony can be settling on an outbound call, and on an agent that runs a premium model the model's tokens are charged between 30 minutes and 24 hours after the call ends. `GET /v1/calls/{callId}` holds the reconciled figure.\n- `call.initiated`: An outbound call was created and is dialing, or an inbound call was received. `data.status` = `ringing`\n- `call.answered`: The call was answered and the voice agent is connected. `data.status` = `in_progress`\n- `call.completed`: The call ended after a conversation. `data.status` = `completed`; `durationSeconds` and `endReason` describe how it ended, and `transcriptAvailable` indicates whether a transcript can be fetched.\n- `call.failed`: The call could not be completed (busy, no answer, canceled, or an error). `data.status` is the terminal status and `endReason` explains the cause.\n\n**Custom domain events:**\n- `domain.verified`: A custom email domain passed verification (DKIM, and SPF/DMARC/MAIL FROM if enhanced records are enabled)\n- `domain.failed`: A custom email domain failed verification or is partially verified",
        "enum": [
          "message.queued",
          "message.sent",
          "message.delivered",
          "message.read",
          "message.status",
          "message.failed",
          "message.inbound",
          "message.unsupported",
          "broadcast.status_changed",
          "conversation.new",
          "contact.identity_linked",
          "template.status_changed",
          "invitation.status_changed",
          "call.initiated",
          "call.answered",
          "call.completed",
          "call.failed",
          "domain.verified",
          "domain.failed"
        ]
      },
      "WebhookPayload": {
        "type": "object",
        "description": "Webhook payload structure sent to your endpoint.",
        "required": [
          "id",
          "type",
          "timestamp",
          "senderId",
          "projectId",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique event identifier.",
            "example": "evt_1736850000000_abc123"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "timestamp": {
            "type": "integer",
            "description": "Unix timestamp in milliseconds when the event was created.",
            "example": 1736850000000
          },
          "senderId": {
            "type": "string",
            "description": "ID of the sender associated with this event.",
            "example": "jx7abc123def456"
          },
          "projectId": {
            "type": "string",
            "description": "ID of the project.",
            "example": "jx7xyz789ghi012"
          },
          "data": {
            "type": "object",
            "description": "Event-specific data. For message events, contains messageId, to, channel, status, errorCode, and errorMessage."
          }
        }
      },
      "WebhookMessageData": {
        "type": "object",
        "description": "Data payload for message webhook events (queued, sent, delivered, failed, inbound).",
        "required": [
          "messageId",
          "to",
          "channel",
          "status"
        ],
        "properties": {
          "messageId": {
            "type": "string",
            "description": "ID of the message.",
            "example": "jx7744x3wckc9cwy0j9c8g6v0h7z7shs"
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number or email. On `message.inbound` this is your own number (the message's destination).",
            "example": "+56912345678"
          },
          "from": {
            "type": "string",
            "description": "Sender of the message. Present on `message.inbound`: the contact who sent the message.",
            "example": "+14155551234"
          },
          "channel": {
            "$ref": "#/components/schemas/Channel"
          },
          "status": {
            "$ref": "#/components/schemas/MessageStatus"
          },
          "messageType": {
            "$ref": "#/components/schemas/MessageType",
            "description": "Type of the message. Present on `message.inbound`."
          },
          "conversationId": {
            "type": "string",
            "nullable": true,
            "description": "ID of the conversation (inbox thread) this message belongs to. Sent on `message.inbound` on every channel, except for reactions. Use it to deep-link to the dashboard inbox: `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`. The webhook is sent once the message has been filed in its thread, so this is always the thread the message is in: on the first message of a new thread it is the id `conversation.new` carries, and on the WhatsApp message that ties a BSUID to a phone number it is the thread that remains once the two are joined.",
            "example": "js723987cyghwqxxaxcf590qd18axd95"
          },
          "contactId": {
            "type": "string",
            "nullable": true,
            "description": "ID of the Zavu contact that sent the message. Sent on `message.inbound` on every channel, except for reactions. It does not change when a WhatsApp contact's `from` switches from a business-scoped user ID (BSUID) to a phone number, so use it, not `from`, as your key for the person. `null` only when no contact could be resolved.",
            "example": "jd7x2k3m4n5p6q7r8s9t0abc"
          },
          "whatsapp": {
            "type": "object",
            "description": "WhatsApp identity of the sender. Present on WhatsApp `message.inbound` when Zavu knows the sender's business-scoped user ID (BSUID), and absent otherwise. Same shape as `whatsapp` on a contact or a conversation.",
            "required": [
              "bsuid"
            ],
            "properties": {
              "bsuid": {
                "type": "string",
                "description": "Business-scoped user ID. Stable for the person on your WhatsApp Business Account, whether or not `from` is a phone number.",
                "example": "CO.13491208655302741918"
              },
              "username": {
                "type": "string",
                "description": "The WhatsApp username, when the sender has one and Meta reported it."
              }
            }
          },
          "text": {
            "type": "string",
            "description": "Text body or media caption. Present on `message.inbound` when the message has text."
          },
          "errorCode": {
            "type": "string",
            "nullable": true,
            "description": "Error code if the message failed."
          },
          "errorMessage": {
            "type": "string",
            "nullable": true,
            "description": "Error message if the message failed."
          },
          "providerTimestamp": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "description": "The provider's original receive time in Unix milliseconds (when the channel received the message from the contact). Present on `message.inbound` for channels that report it (WhatsApp, Telegram, Instagram, Messenger); null for SMS and email."
          },
          "referral": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdReferral"
              }
            ],
            "description": "Click-to-WhatsApp ad attribution. Present on `message.inbound` only when this is the first message of a conversation opened from a Meta ad or post — absent on every later message in that same thread, and on organic conversations. Read `referral.ctwaClid` here to attribute the conversation, and persist it on your side: this webhook is the only delivery. `GET /v1/messages/{messageId}` also returns it, under `content.referral`."
          }
        }
      },
      "SenderWebhook": {
        "type": "object",
        "description": "Webhook configuration for the sender.",
        "required": [
          "url",
          "events",
          "active",
          "signatureVersion"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL that will receive webhook events.",
            "example": "https://api.example.com/webhooks/zavu"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "List of events the webhook is subscribed to."
          },
          "secret": {
            "type": "string",
            "description": "Webhook secret for signature verification. Only returned on create or regenerate.",
            "example": "whsec_abc123..."
          },
          "active": {
            "type": "boolean",
            "description": "Whether the webhook is active.",
            "default": true
          },
          "signatureVersion": {
            "$ref": "#/components/schemas/WebhookSignatureVersion"
          }
        }
      },
      "Sender": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "sender_12345"
          },
          "name": {
            "type": "string",
            "example": "Primary sender"
          },
          "phoneNumber": {
            "type": "string",
            "description": "Phone number in E.164 format. Absent on a sender that has none: an email-only sender, or one created with only `enableSmsOneway`.",
            "example": "+13125551212"
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Channels this sender can actually send on right now: configured AND activated. Empty means the sender cannot send or receive anything yet: a phoneNumber alone does not enable SMS or voice, and a connected account that is not activated is left out, because every send on it is refused. Check this rather than inferring capability from phoneNumber or emailAddress, and turn a connected channel on with `POST /v1/senders/{senderId}/channels/{channel}/activate`.",
            "example": [
              "sms",
              "voice"
            ]
          },
          "isDefault": {
            "type": "boolean",
            "description": "Whether this sender is the project's default.",
            "default": false
          },
          "webhook": {
            "$ref": "#/components/schemas/SenderWebhook"
          },
          "whatsapp": {
            "type": "object",
            "description": "WhatsApp Business Account information. Only present if a WABA is connected.",
            "properties": {
              "phoneNumberId": {
                "type": "string",
                "description": "WhatsApp phone number ID from Meta."
              },
              "displayPhoneNumber": {
                "type": "string",
                "description": "Display phone number.",
                "example": "+14155551234"
              },
              "paymentStatus": {
                "type": "object",
                "description": "Payment configuration status from Meta.",
                "properties": {
                  "setupStatus": {
                    "type": "string",
                    "description": "Payment setup status (COMPLETE, NOT_STARTED, etc.).",
                    "example": "COMPLETE"
                  },
                  "methodStatus": {
                    "type": "string",
                    "description": "Payment method status (VALID, NONE, etc.).",
                    "example": "VALID"
                  },
                  "canSendTemplates": {
                    "type": "boolean",
                    "description": "Whether template messages can be sent. Requires setupStatus=COMPLETE and methodStatus=VALID."
                  }
                }
              }
            }
          },
          "emailAddress": {
            "type": "string",
            "description": "From-address for the email channel, if configured.",
            "example": "noreply@yourdomain.com"
          },
          "emailReceivingEnabled": {
            "type": "boolean",
            "description": "Whether inbound email receiving is enabled for this sender.",
            "default": false
          },
          "emailCatchAllEnabled": {
            "type": "boolean",
            "description": "Whether catch-all receiving is enabled. When true (and emailReceivingEnabled is true), this sender receives email addressed to any local part at its domain, not just its own address. The original recipient is delivered in the message.inbound webhook's data.to.",
            "default": false
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SenderCreateRequest": {
        "type": "object",
        "description": "Create a sender. Provide `phoneNumber` for an SMS/WhatsApp sender, `emailAddress` (with a verified email domain) for an email sender, or `enableSmsOneway: true` for a zero-setup one-way SMS sender — at least one is required.",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "phoneNumber": {
            "type": "string",
            "description": "Phone number in E.164 format, and it must be a number your project already owns (see `GET /v1/phone-numbers`). The number is routed to the sender as part of this call, which is what turns the SMS channel on. Passing a number the project does not own, one already attached to another sender, or one rejected in regulatory review returns 400 rather than creating a sender that cannot send. A number still under review is attached and starts carrying messages when it is approved. Omit for an email-only sender."
          },
          "enableVoice": {
            "type": "boolean",
            "description": "Let this sender place and answer phone calls. Requires `phoneNumber`; enabling it without one returns 400. Check the `channels` array on the response to confirm `voice` is on.",
            "default": false
          },
          "enableSmsOneway": {
            "type": "boolean",
            "description": "Enable the one-way SMS channel (`sms_oneway`). No phone number and no credential, so it is the fastest way to get a sender that can send. Recipients cannot reply. Sending to a US or Canadian (+1) number still needs an approved 10DLC campaign, and is refused with `403 ten_dlc_required` until then. Confirm with `sms_oneway` in the `channels` array on the response.",
            "default": false
          },
          "setAsDefault": {
            "type": "boolean",
            "default": false
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL for webhook events."
          },
          "webhookEvents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Events to subscribe to."
          },
          "emailAddress": {
            "type": "string",
            "format": "email",
            "description": "From-address for the email channel (e.g. noreply@yourdomain.com). The address's domain must be a verified email domain in your project. Setting this attaches the email channel to the sender.",
            "example": "noreply@yourdomain.com"
          },
          "emailDomainId": {
            "type": "string",
            "description": "ID of the verified email domain to attach. Optional — resolved from `emailAddress`'s domain when omitted."
          },
          "emailFromName": {
            "type": "string",
            "maxLength": 100,
            "description": "Display name shown in the recipient's inbox for the email channel."
          },
          "emailReceivingEnabled": {
            "type": "boolean",
            "description": "Enable inbound email receiving on this sender. Requires a verified inbound MX record on the domain; the request is ignored otherwise. Read `emailReceivingEnabled` back off the response to see whether it was applied — it comes back `false` when the MX has not verified."
          },
          "webhookSignatureVersion": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookSignatureVersion"
              }
            ],
            "description": "Signature scheme for this sender's webhook. Defaults to `v2`, which is correct for a new receiver. Pass `v1+v2` when this sender points at an endpoint that already serves an older sender still verifying `v1`."
          }
        }
      },
      "SenderUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "enableVoice": {
            "type": "boolean",
            "description": "Turn the voice channel on or off. The sender must already have a phone number provisioned for calls; enabling it otherwise returns 400 instead of storing a flag that changes nothing. Confirm with the `channels` array on the response."
          },
          "enableSmsOneway": {
            "type": "boolean",
            "description": "Turn the one-way SMS channel on or off. Enabling needs no number or credential and takes effect immediately (US and Canadian destinations still need an approved 10DLC campaign); disabling removes the channel from the sender. Confirm with the `channels` array on the response."
          },
          "setAsDefault": {
            "type": "boolean"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "HTTPS URL for webhook events. Set to null to remove webhook."
          },
          "webhookEvents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Events to subscribe to."
          },
          "webhookActive": {
            "type": "boolean",
            "description": "Whether the webhook is active."
          },
          "emailReceivingEnabled": {
            "type": "boolean",
            "description": "Enable or disable inbound email receiving for this sender. Enabling requires a verified inbound MX record on the domain; the request is ignored otherwise, and `emailReceivingEnabled` comes back `false` on the response. Disabling always applies."
          },
          "emailCatchAllEnabled": {
            "type": "boolean",
            "description": "Enable or disable domain catch-all. When enabled (with emailReceivingEnabled true), this sender receives email for any address at its domain. Ignored (treated as false) if receiving is not enabled."
          },
          "emailAddress": {
            "type": "string",
            "format": "email",
            "description": "Attach or change the sender's email from-address (e.g. noreply@yourdomain.com). The domain must be a verified email domain in your project.",
            "example": "noreply@yourdomain.com"
          },
          "emailDomainId": {
            "type": "string",
            "description": "ID of the verified email domain to attach. Optional — resolved from `emailAddress`'s domain when omitted."
          },
          "emailFromName": {
            "type": "string",
            "maxLength": 100,
            "description": "Display name shown in the recipient's inbox for the email channel."
          },
          "webhookSignatureVersion": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookSignatureVersion"
              }
            ],
            "description": "Move this webhook between signature schemes. `v1` -> `v2` directly returns `400`; go through `v1+v2` first. See https://docs.zavu.dev/guides/receiving-messages/signature-migration"
          }
        }
      },
      "WebhookSecretResponse": {
        "type": "object",
        "required": [
          "secret"
        ],
        "properties": {
          "secret": {
            "type": "string",
            "description": "The new webhook secret.",
            "example": "whsec_abc123..."
          }
        }
      },
      "ContactChannel": {
        "type": "object",
        "description": "A communication channel for a contact.",
        "required": [
          "id",
          "channel",
          "identifier",
          "isPrimary",
          "verified",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "email",
              "telegram",
              "instagram",
              "messenger",
              "voice"
            ],
            "description": "Channel type."
          },
          "identifier": {
            "type": "string",
            "description": "Channel identifier (phone number or email address).",
            "example": "+14155551234"
          },
          "countryCode": {
            "type": "string",
            "description": "ISO country code for phone numbers.",
            "example": "US"
          },
          "label": {
            "type": "string",
            "description": "Optional label for the channel.",
            "example": "work"
          },
          "isPrimary": {
            "type": "boolean",
            "description": "Whether this is the primary channel for its type."
          },
          "verified": {
            "type": "boolean",
            "description": "Whether this channel has been verified."
          },
          "metrics": {
            "type": "object",
            "description": "Delivery metrics for this channel.",
            "properties": {
              "successCount": {
                "type": "integer"
              },
              "failureCount": {
                "type": "integer"
              },
              "totalAttempts": {
                "type": "integer"
              },
              "avgDeliveryTimeMs": {
                "type": "number"
              },
              "lastSuccessAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "lastInboundAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last time a message was received on this channel."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "id",
          "availableChannels",
          "verified",
          "metadata",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "displayName": {
            "type": "string",
            "description": "Display name for the contact.",
            "example": "John Doe"
          },
          "phoneNumber": {
            "type": "string",
            "description": "DEPRECATED: Use primaryPhone instead. Primary phone number in E.164 format.",
            "example": "+56912345678"
          },
          "primaryPhone": {
            "type": "string",
            "description": "Primary phone number in E.164 format.",
            "example": "+56912345678"
          },
          "primaryEmail": {
            "type": "string",
            "format": "email",
            "description": "Primary email address.",
            "example": "john@example.com"
          },
          "countryCode": {
            "type": "string",
            "example": "CL"
          },
          "profileName": {
            "type": "string",
            "nullable": true,
            "description": "Contact's WhatsApp profile name. Only available for WhatsApp contacts.",
            "example": "John Doe"
          },
          "whatsapp": {
            "type": "object",
            "description": "WhatsApp identity. Present when Zavu has a business-scoped user ID (BSUID) for the contact, and absent otherwise. A contact with a username can be reached under its BSUID, its phone number, or both; they are the same contact. Look one up by BSUID with the `bsuid` query parameter of the list endpoint.",
            "required": [
              "bsuid"
            ],
            "properties": {
              "bsuid": {
                "type": "string",
                "description": "Business-scoped user ID. Can be used as `to` when sending.",
                "example": "CO.13491208655302741918"
              },
              "username": {
                "type": "string",
                "description": "The WhatsApp username, when Meta reported one."
              }
            }
          },
          "availableChannels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of available messaging channels for this contact."
          },
          "defaultChannel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "telegram",
              "email",
              "instagram",
              "messenger",
              "voice"
            ],
            "description": "Preferred channel for this contact."
          },
          "verified": {
            "type": "boolean",
            "description": "Whether this contact has been verified."
          },
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactChannel"
            },
            "description": "All communication channels for this contact."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContactUpdateRequest": {
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string",
            "maxLength": 200,
            "nullable": true,
            "description": "Human-readable name for this contact. Set to null to clear it and fall back to the contact's identifier. Contacts created automatically from an inbound message have no display name until you set one.",
            "example": "John Doe"
          },
          "defaultChannel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "telegram",
              "email",
              "instagram",
              "messenger",
              "voice"
            ],
            "nullable": true,
            "description": "Preferred channel for this contact. Set to null to clear."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "ContactChannelInput": {
        "type": "object",
        "description": "Input for creating a contact channel.",
        "required": [
          "channel",
          "identifier"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "email",
              "telegram",
              "instagram",
              "messenger",
              "voice"
            ],
            "description": "Channel type."
          },
          "identifier": {
            "type": "string",
            "description": "Channel identifier (phone number in E.164 format or email address).",
            "example": "+14155551234"
          },
          "countryCode": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO country code for phone numbers.",
            "example": "US"
          },
          "label": {
            "type": "string",
            "maxLength": 50,
            "description": "Optional label for the channel.",
            "example": "work"
          },
          "isPrimary": {
            "type": "boolean",
            "description": "Whether this should be the primary channel for its type.",
            "default": false
          }
        }
      },
      "ContactCreateRequest": {
        "type": "object",
        "description": "Request body to create a contact with channels.",
        "required": [
          "channels"
        ],
        "properties": {
          "displayName": {
            "type": "string",
            "maxLength": 200,
            "description": "Display name for the contact.",
            "example": "John Doe"
          },
          "channels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactChannelInput"
            },
            "minItems": 1,
            "maxItems": 20,
            "description": "Communication channels for the contact."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Arbitrary metadata to associate with the contact."
          }
        }
      },
      "AddChannelRequest": {
        "type": "object",
        "description": "Request body to add a channel to a contact.",
        "required": [
          "channel",
          "identifier"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "sms",
              "whatsapp",
              "email",
              "telegram",
              "instagram",
              "messenger",
              "voice"
            ],
            "description": "Channel type."
          },
          "identifier": {
            "type": "string",
            "description": "Channel identifier (phone number in E.164 format or email address).",
            "example": "+14155551234"
          },
          "countryCode": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO country code for phone numbers.",
            "example": "US"
          },
          "label": {
            "type": "string",
            "maxLength": 50,
            "description": "Optional label for the channel.",
            "example": "work"
          },
          "isPrimary": {
            "type": "boolean",
            "description": "Whether this should be the primary channel for its type.",
            "default": false
          }
        }
      },
      "UpdateChannelRequest": {
        "type": "object",
        "description": "Request body to update a channel.",
        "properties": {
          "label": {
            "type": "string",
            "maxLength": 50,
            "nullable": true,
            "description": "Optional label for the channel. Set to null to clear."
          },
          "verified": {
            "type": "boolean",
            "description": "Whether the channel is verified."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "MergeContactsRequest": {
        "type": "object",
        "description": "Request body to merge contacts.",
        "required": [
          "sourceContactId"
        ],
        "properties": {
          "sourceContactId": {
            "type": "string",
            "description": "ID of the contact to merge into the target contact. The source contact will be marked as merged."
          }
        }
      },
      "ContactChannelResponse": {
        "type": "object",
        "required": [
          "channel"
        ],
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/ContactChannel"
          }
        }
      },
      "Conversation": {
        "type": "object",
        "description": "An inbox thread with one contact. A conversation groups every message exchanged with that contact across channels, so a contact who writes on WhatsApp and later by email stays in one thread.",
        "required": [
          "id",
          "contactIdentifier",
          "channels",
          "lastMessage",
          "messageCount",
          "unreadCount",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "js723987cyghwqxxaxcf590qd18axd95"
          },
          "contactId": {
            "type": "string",
            "description": "ID of the contact this thread belongs to. Absent on group threads and on threads whose contact has not been resolved yet."
          },
          "contactIdentifier": {
            "type": "string",
            "description": "The key this thread is filed under: a phone number in E.164, a WhatsApp business-scoped user ID (BSUID), or a numeric chat ID (Telegram/Instagram/Messenger). It is not always a phone number, so do not parse it as one.",
            "example": "+56912345678"
          },
          "email": {
            "type": "string",
            "description": "Email address of the thread, when the contact was reached by email."
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every channel this thread has carried messages on.",
            "example": [
              "whatsapp",
              "sms"
            ]
          },
          "lastMessage": {
            "type": "object",
            "description": "Denormalized preview of the most recent message, so a thread list needs no extra fetch.",
            "required": [
              "id",
              "text",
              "channel",
              "direction",
              "at"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "text": {
                "type": "string",
                "description": "Text or caption. Empty when the last message carried no text (e.g. media)."
              },
              "channel": {
                "$ref": "#/components/schemas/Channel"
              },
              "direction": {
                "type": "string",
                "enum": [
                  "inbound",
                  "outbound"
                ]
              },
              "at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "senderId": {
            "type": "string",
            "description": "Sender that last handled this thread. Use it as the `Zavu-Sender` header when replying so the answer leaves from the same number the contact knows."
          },
          "messageCount": {
            "type": "integer"
          },
          "unreadCount": {
            "type": "integer",
            "description": "Inbound messages not yet marked read. Reset with POST /v1/conversations/{conversationId}/read."
          },
          "whatsapp": {
            "type": "object",
            "description": "WhatsApp identity, present when the contact adopted a username.",
            "properties": {
              "bsuid": {
                "type": "string",
                "description": "Business-scoped user ID. Can be used as `to` when sending."
              },
              "username": {
                "type": "string"
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ConversationResponse": {
        "type": "object",
        "required": [
          "conversation"
        ],
        "properties": {
          "conversation": {
            "$ref": "#/components/schemas/Conversation"
          }
        }
      },
      "PhoneIntrospectionRequest": {
        "type": "object",
        "required": [
          "phoneNumber"
        ],
        "properties": {
          "phoneNumber": {
            "type": "string",
            "example": "+56912345678"
          }
        }
      },
      "LineType": {
        "type": "string",
        "description": "Type of phone line.",
        "enum": [
          "mobile",
          "landline",
          "voip",
          "toll_free",
          "unknown"
        ]
      },
      "Carrier": {
        "type": "object",
        "description": "Carrier information for the phone number.",
        "properties": {
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Carrier name.",
            "example": "Verizon Wireless"
          },
          "type": {
            "$ref": "#/components/schemas/LineType"
          }
        }
      },
      "PhoneIntrospectionResponse": {
        "type": "object",
        "required": [
          "phoneNumber",
          "countryCode",
          "validNumber"
        ],
        "properties": {
          "phoneNumber": {
            "type": "string"
          },
          "countryCode": {
            "type": "string",
            "example": "CL"
          },
          "validNumber": {
            "type": "boolean"
          },
          "nationalFormat": {
            "type": "string",
            "description": "Phone number in national format.",
            "example": "(312) 945-7420"
          },
          "lineType": {
            "$ref": "#/components/schemas/LineType"
          },
          "carrier": {
            "$ref": "#/components/schemas/Carrier"
          },
          "availableChannels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of available messaging channels for this phone number."
          }
        }
      },
      "EmailIntrospectionRequest": {
        "type": "object",
        "description": "Validate one email (`email`) or a batch (`emails`). At most 100 addresses per request across both fields.",
        "properties": {
          "email": {
            "type": "string",
            "description": "Single email address to validate.",
            "example": "maria@example.com"
          },
          "emails": {
            "type": "array",
            "description": "Batch of email addresses to validate (max 100).",
            "maxItems": 100,
            "items": {
              "type": "string"
            },
            "example": [
              "maria@example.com",
              "info@deaddomain.example"
            ]
          }
        }
      },
      "EmailValidationResult": {
        "type": "object",
        "required": [
          "email",
          "normalized",
          "domain",
          "verdict",
          "reasons"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "The address exactly as submitted."
          },
          "normalized": {
            "type": "string",
            "nullable": true,
            "description": "Lowercased, trimmed form of the address. Null when the syntax is invalid."
          },
          "domain": {
            "type": "string",
            "nullable": true,
            "description": "Domain part of the address. Null when the syntax is invalid."
          },
          "verdict": {
            "type": "string",
            "description": "Validation verdict.\n- `deliverable`: nothing suggests the address will bounce.\n- `risky`: sendable, but a signal predicts elevated bounce/complaint odds (role address, disposable domain, MX-less domain, prior soft bounce).\n- `undeliverable`: will bounce or is blocked (invalid syntax, dead domain, or the address is on your suppression list after a hard bounce/complaint).",
            "enum": [
              "deliverable",
              "risky",
              "undeliverable"
            ]
          },
          "reasons": {
            "type": "array",
            "description": "Signals behind the verdict. Empty for a clean `deliverable` address.",
            "items": {
              "type": "string",
              "enum": [
                "invalid_syntax",
                "domain_not_found",
                "domain_no_mx",
                "disposable_domain",
                "role_address",
                "suppressed_hard_bounce",
                "suppressed_soft_bounce",
                "suppressed_complaint",
                "suppressed_manual",
                "suppressed_unsubscribe"
              ]
            }
          }
        }
      },
      "EmailIntrospectionResponse": {
        "type": "object",
        "required": [
          "results",
          "summary"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One result per submitted address, in the same order.",
            "items": {
              "$ref": "#/components/schemas/EmailValidationResult"
            }
          },
          "summary": {
            "type": "object",
            "required": [
              "total",
              "deliverable",
              "risky",
              "undeliverable"
            ],
            "properties": {
              "total": {
                "type": "integer"
              },
              "deliverable": {
                "type": "integer"
              },
              "risky": {
                "type": "integer"
              },
              "undeliverable": {
                "type": "integer"
              }
            }
          }
        }
      },
      "InteractiveButton": {
        "type": "object",
        "required": [
          "id",
          "title"
        ],
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 256,
            "description": "Unique button identifier returned in webhook when clicked."
          },
          "title": {
            "type": "string",
            "maxLength": 20,
            "description": "Button text displayed to user."
          }
        }
      },
      "ListSection": {
        "type": "object",
        "required": [
          "title",
          "rows"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Section title."
          },
          "rows": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ListRow"
            }
          }
        }
      },
      "ListRow": {
        "type": "object",
        "required": [
          "id",
          "title"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique row identifier."
          },
          "title": {
            "type": "string",
            "maxLength": 24,
            "description": "Row title."
          },
          "description": {
            "type": "string",
            "maxLength": 72,
            "description": "Optional row description."
          }
        }
      },
      "ContactCard": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "object",
            "required": [
              "formatted_name"
            ],
            "properties": {
              "formatted_name": {
                "type": "string"
              },
              "first_name": {
                "type": "string"
              },
              "last_name": {
                "type": "string"
              }
            }
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "phone": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "MOBILE",
                    "HOME",
                    "WORK"
                  ]
                }
              }
            }
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "HOME",
                    "WORK"
                  ]
                }
              }
            }
          }
        }
      },
      "ReactionRequest": {
        "type": "object",
        "required": [
          "emoji"
        ],
        "properties": {
          "emoji": {
            "type": "string",
            "description": "Single emoji character to react with.",
            "example": "👍"
          }
        }
      },
      "BroadcastChannel": {
        "type": "string",
        "description": "Broadcast delivery channel. Use 'smart' for per-contact intelligent routing.",
        "enum": [
          "smart",
          "sms",
          "sms_oneway",
          "whatsapp",
          "telegram",
          "email"
        ]
      },
      "BroadcastStatus": {
        "type": "string",
        "description": "Current status of the broadcast. `pending_review`: in the automated content review. `pending_admin_review`: the automated review passed and it waits for a person, which is where every channel but a WhatsApp broadcast on an approved template goes. `rejected`: the review refused it; edit it and retry the review, or escalate. `scheduled` and `sending` follow approval.",
        "enum": [
          "draft",
          "pending_review",
          "pending_admin_review",
          "approved",
          "rejected",
          "escalated",
          "rejected_final",
          "scheduled",
          "sending",
          "paused",
          "completed",
          "cancelled",
          "failed"
        ]
      },
      "BroadcastContactStatus": {
        "type": "string",
        "description": "Status of a contact within a broadcast.\n\n- `pending`, `queued`, `sending`: not handed to the provider yet.\n- `sent`: accepted by the provider; delivery is not confirmed yet. Channels that never report delivery leave the recipient here.\n- `delivered`: the channel confirmed delivery to the device. A WhatsApp read receipt also counts as delivered.\n- `failed`: not delivered. A recipient can move from `sent` or `delivered` to `failed` when the provider reports a failure late.\n- `skipped`: not sent, because the recipient opted out of the channel or the broadcast was cancelled before reaching it.",
        "enum": [
          "pending",
          "queued",
          "sending",
          "sent",
          "delivered",
          "failed",
          "skipped"
        ]
      },
      "BroadcastMessageType": {
        "type": "string",
        "description": "Type of message for broadcast.",
        "enum": [
          "text",
          "image",
          "video",
          "audio",
          "document",
          "template"
        ]
      },
      "BroadcastContent": {
        "type": "object",
        "description": "Content for non-text broadcast message types.",
        "properties": {
          "mediaUrl": {
            "type": "string",
            "description": "URL of the media file."
          },
          "mediaId": {
            "type": "string",
            "description": "Media ID if already uploaded."
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type of the media."
          },
          "filename": {
            "type": "string",
            "description": "Filename for documents."
          },
          "templateId": {
            "type": "string",
            "description": "Template ID for template messages."
          },
          "templateVariables": {
            "type": "object",
            "description": "Default body variables (can be overridden per contact). Key them to match the template body: by position (`1`, `2`, ...) for positional templates, or by name (e.g. `customer_name`) for named templates. Zavu detects the template's format and sends the correct payload to Meta. Do not mix positional and named keys.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "templateButtonVariables": {
            "type": "object",
            "description": "Default button variables for dynamic URL/OTP buttons. Keys are the button index (0, 1, 2). Per-contact values override these.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "templateHeaderVariables": {
            "type": "object",
            "description": "Default value for a text-header variable, keyed by `1` (can be overridden per contact). If omitted, Zavu resolves the header from `templateVariables` by the header placeholder's name.",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "BroadcastCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "channel"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Name of the broadcast campaign.",
            "example": "Black Friday Sale"
          },
          "channel": {
            "$ref": "#/components/schemas/BroadcastChannel"
          },
          "messageType": {
            "$ref": "#/components/schemas/BroadcastMessageType",
            "default": "text"
          },
          "text": {
            "type": "string",
            "description": "Text content or caption. Supports template variables: {{name}}, {{1}}, etc.",
            "example": "Hi {{name}}, check out our Black Friday deals!"
          },
          "content": {
            "$ref": "#/components/schemas/BroadcastContent"
          },
          "senderId": {
            "type": "string",
            "description": "Sender profile ID. Uses default sender if omitted."
          },
          "emailSubject": {
            "type": "string",
            "description": "Email subject line. Required for email broadcasts."
          },
          "emailHtmlBody": {
            "type": "string",
            "description": "HTML body for email broadcasts."
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "Schedule the broadcast for future delivery."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "idempotencyKey": {
            "type": "string",
            "description": "Idempotency key to prevent duplicate broadcasts."
          }
        }
      },
      "BroadcastUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "content": {
            "$ref": "#/components/schemas/BroadcastContent"
          },
          "emailSubject": {
            "type": "string"
          },
          "emailHtmlBody": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "BroadcastContactInput": {
        "type": "object",
        "required": [
          "recipient"
        ],
        "properties": {
          "recipient": {
            "type": "string",
            "description": "Phone number (E.164) or email address. On a `telegram` broadcast, the numeric chat ID of someone who has already written to your bot (`838360395`, or `telegram:838360395`); a phone number is rejected there, because a bot cannot start a chat.",
            "example": "+14155551234"
          },
          "templateVariables": {
            "type": "object",
            "description": "Per-contact body variables. Key them to match the template body: by position (`1`, `2`, ...) for positional templates, or by name (e.g. `customer_name`) for named templates. Zavu detects the template's format and sends the correct payload to Meta. Do not mix positional and named keys.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "1": "John",
              "2": "ORD-12345"
            }
          },
          "templateButtonVariables": {
            "type": "object",
            "description": "Per-contact button variables for dynamic URL/OTP buttons. Keys are the button index (0, 1, 2).",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "0": "abc-report-token"
            }
          },
          "templateHeaderVariables": {
            "type": "object",
            "description": "Per-contact value for a text-header variable, keyed by `1`. If omitted, Zavu resolves the header from `templateVariables` by the header placeholder's name.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "1": "Jorge y Laura"
            }
          }
        }
      },
      "BroadcastContactsAddRequest": {
        "type": "object",
        "required": [
          "contacts"
        ],
        "properties": {
          "contacts": {
            "type": "array",
            "description": "List of contacts to add (max 1000 per request).",
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/BroadcastContactInput"
            }
          }
        }
      },
      "BroadcastContactsAddResponse": {
        "type": "object",
        "required": [
          "added",
          "duplicates",
          "invalid"
        ],
        "properties": {
          "added": {
            "type": "integer",
            "description": "Number of contacts successfully added."
          },
          "duplicates": {
            "type": "integer",
            "description": "Number of duplicate contacts skipped."
          },
          "invalid": {
            "type": "integer",
            "description": "Number of invalid contacts rejected."
          },
          "errors": {
            "type": "array",
            "description": "Details about invalid contacts.",
            "items": {
              "type": "object",
              "properties": {
                "recipient": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BroadcastSendRequest": {
        "type": "object",
        "properties": {
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "Schedule for future delivery. Omit to send immediately."
          }
        }
      },
      "BroadcastRescheduleRequest": {
        "type": "object",
        "required": [
          "scheduledAt"
        ],
        "properties": {
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "New scheduled time for the broadcast."
          }
        }
      },
      "Broadcast": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "channel",
          "messageType",
          "totalContacts",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "brd_abc123"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/BroadcastStatus"
          },
          "channel": {
            "$ref": "#/components/schemas/BroadcastChannel"
          },
          "messageType": {
            "$ref": "#/components/schemas/BroadcastMessageType"
          },
          "text": {
            "type": "string"
          },
          "content": {
            "$ref": "#/components/schemas/BroadcastContent"
          },
          "senderId": {
            "type": "string"
          },
          "emailSubject": {
            "type": "string"
          },
          "totalContacts": {
            "type": "integer",
            "description": "Total number of contacts in the broadcast."
          },
          "pendingCount": {
            "type": "integer"
          },
          "sendingCount": {
            "type": "integer"
          },
          "sentCount": {
            "type": "integer",
            "description": "Recipients whose message the provider accepted, without a confirmed delivery yet. Channels that never report delivery keep their recipients here."
          },
          "deliveredCount": {
            "type": "integer",
            "description": "Recipients with confirmed delivery to the device."
          },
          "failedCount": {
            "type": "integer"
          },
          "estimatedCost": {
            "type": "number",
            "nullable": true,
            "description": "Estimated total cost in USD."
          },
          "reservedAmount": {
            "type": "number",
            "nullable": true,
            "description": "Amount reserved from balance in USD."
          },
          "actualCost": {
            "type": "number",
            "nullable": true,
            "description": "Actual cost so far in USD."
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "reviewResult": {
            "type": "object",
            "nullable": true,
            "description": "AI content review result.",
            "properties": {
              "score": {
                "type": "number",
                "description": "Content safety score from 0.0 to 1.0, where 1.0 is completely safe."
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Policy categories violated, if any."
              },
              "reasoning": {
                "type": "string",
                "description": "Explanation of the review decision."
              },
              "reviewedAt": {
                "type": "string",
                "format": "date-time"
              },
              "flaggedContent": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "nullable": true,
                "description": "Problematic text fragments, if any."
              }
            }
          },
          "reviewAttempts": {
            "type": "integer",
            "nullable": true,
            "description": "Number of review attempts (max 3)."
          }
        }
      },
      "BroadcastResponse": {
        "type": "object",
        "required": [
          "broadcast"
        ],
        "properties": {
          "broadcast": {
            "$ref": "#/components/schemas/Broadcast"
          }
        }
      },
      "BroadcastContact": {
        "type": "object",
        "required": [
          "id",
          "recipient",
          "recipientType",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "recipient": {
            "type": "string"
          },
          "recipientType": {
            "type": "string",
            "enum": [
              "phone",
              "email"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/BroadcastContactStatus"
          },
          "templateVariables": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "templateButtonVariables": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "templateHeaderVariables": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "messageId": {
            "type": "string",
            "description": "Associated message ID after processing."
          },
          "errorCode": {
            "type": "string"
          },
          "errorMessage": {
            "type": "string"
          },
          "cost": {
            "type": "number",
            "nullable": true
          },
          "processedAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BroadcastProgress": {
        "type": "object",
        "required": [
          "broadcastId",
          "status",
          "total",
          "pending",
          "sending",
          "delivered",
          "failed",
          "skipped",
          "percentComplete"
        ],
        "properties": {
          "broadcastId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/BroadcastStatus"
          },
          "total": {
            "type": "integer",
            "description": "Total contacts in broadcast."
          },
          "pending": {
            "type": "integer",
            "description": "Not yet queued for sending."
          },
          "sending": {
            "type": "integer",
            "description": "Currently being sent."
          },
          "sent": {
            "type": "integer",
            "description": "Accepted by the provider, delivery not confirmed yet."
          },
          "delivered": {
            "type": "integer",
            "description": "Confirmed delivered to the device."
          },
          "failed": {
            "type": "integer",
            "description": "Failed to deliver."
          },
          "skipped": {
            "type": "integer",
            "description": "Skipped (broadcast cancelled)."
          },
          "percentComplete": {
            "type": "number",
            "description": "Percentage complete (0-100)."
          },
          "estimatedCost": {
            "type": "number",
            "nullable": true,
            "description": "Estimated total cost in USD."
          },
          "reservedAmount": {
            "type": "number",
            "nullable": true,
            "description": "Amount reserved from balance in USD."
          },
          "actualCost": {
            "type": "number",
            "nullable": true,
            "description": "Actual cost so far in USD."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "estimatedCompletionAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PhoneNumberStatus": {
        "type": "string",
        "description": "Billing state of an owned number, separate from `regulatoryStatus`. `pending` is legacy and is not written to numbers today. The SDKs carry `active`, `suspended` and `pending` only; `releasing` and `released` are returned by the REST API until their next release.",
        "enum": [
          "active",
          "suspended",
          "pending",
          "releasing",
          "released"
        ]
      },
      "PhoneNumberType": {
        "type": "string",
        "enum": [
          "local",
          "national",
          "tollFree",
          "mobile"
        ],
        "description": "Type of phone number. `mobile` is stocked in countries where no geographic (`local`) or non-geographic (`national`) inventory exists, and in several markets it is the only type that can receive SMS."
      },
      "PhoneNumberCapabilities": {
        "type": "object",
        "properties": {
          "sms": {
            "type": "boolean"
          },
          "voice": {
            "type": "boolean"
          },
          "mms": {
            "type": "boolean"
          }
        }
      },
      "PhoneNumberPricing": {
        "type": "object",
        "properties": {
          "monthlyPrice": {
            "type": "number",
            "description": "Monthly price in USD."
          },
          "upfrontPrice": {
            "type": "number",
            "description": "One-time purchase price in USD."
          },
          "isFreeEligible": {
            "type": "boolean",
            "description": "Whether this number qualifies as the plan-included number: a US or Canadian number (a +1 number) costing $20 a month or less. The benefit is one per account: it is never offered again once claimed, not even after the number is released."
          }
        }
      },
      "AvailablePhoneNumber": {
        "type": "object",
        "required": [
          "phoneNumber",
          "capabilities",
          "pricing"
        ],
        "properties": {
          "phoneNumber": {
            "type": "string",
            "example": "+15551234567"
          },
          "friendlyName": {
            "type": "string",
            "example": "(555) 123-4567"
          },
          "locality": {
            "type": "string",
            "example": "San Francisco"
          },
          "region": {
            "type": "string",
            "example": "CA"
          },
          "capabilities": {
            "$ref": "#/components/schemas/PhoneNumberCapabilities"
          },
          "pricing": {
            "$ref": "#/components/schemas/PhoneNumberPricing"
          }
        }
      },
      "OwnedPhoneNumberPricing": {
        "type": "object",
        "properties": {
          "monthlyPrice": {
            "type": "number",
            "description": "Monthly price in USD."
          },
          "upfrontCost": {
            "type": "number",
            "description": "One-time purchase cost in cents."
          },
          "monthlyCost": {
            "type": "number",
            "description": "Monthly cost in cents."
          },
          "isFreeNumber": {
            "type": "boolean",
            "description": "Whether this is a free number."
          }
        }
      },
      "OwnedPhoneNumber": {
        "type": "object",
        "required": [
          "id",
          "phoneNumber",
          "capabilities",
          "status",
          "pricing",
          "createdAt",
          "regulatoryStatus"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "phoneNumber": {
            "type": "string",
            "example": "+15551234567"
          },
          "name": {
            "type": "string",
            "description": "Optional custom name for the phone number."
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "sms",
              "voice"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/PhoneNumberStatus"
          },
          "senderId": {
            "type": "string",
            "description": "Sender ID if the phone number is assigned to a sender."
          },
          "pricing": {
            "$ref": "#/components/schemas/OwnedPhoneNumberPricing"
          },
          "regulatoryStatus": {
            "type": "string",
            "enum": [
              "approved",
              "pending_review",
              "rejected"
            ],
            "description": "Regulatory review state. Numbers that need no review are `approved` immediately. A number bought with regulatory information is owned and billed from purchase and starts `pending_review`; it cannot send messages or place calls until this is `approved`. The state is re-checked every 6 hours: poll `GET /v1/phone-numbers/{phoneNumberId}` to follow it.\n\nAssign it to a sender with `PATCH /v1/phone-numbers/{phoneNumberId}` (`senderId`) before or after approval. A number assigned while under review is recorded and connected to that sender when it is approved; the connection is retried until it succeeds. A sender created over the API is set up for SMS as part of the assignment. `rejected` means review refused the information: the number cannot be assigned to a sender. A number that stays `pending_review` may be waiting on information the API cannot supply; contact support."
          },
          "nextRenewalDate": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PhoneNumberPurchaseRequest": {
        "type": "object",
        "required": [
          "phoneNumber"
        ],
        "properties": {
          "phoneNumber": {
            "type": "string",
            "description": "Phone number in E.164 format.",
            "example": "+15551234567"
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Optional custom name for the phone number."
          },
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PhoneNumberType"
              }
            ],
            "description": "Type the number was found under in `GET /v1/phone-numbers/available`. Used to look the number up and to keep regulatory information for reuse. Required when `regulatoryRequirements` is sent. `toll_free` is accepted as an alias of `tollFree`. A number not listed under this type is refused with `400 number_unavailable` saying so."
          },
          "regulatoryRequirements": {
            "type": "array",
            "maxItems": 50,
            "description": "Regulatory information, for numbers whose requirements list is not empty. Get the list with `GET /v1/phone-numbers/requirements?phoneNumber=...` and send one entry per requirement id, except `action` requirements, which take no value. Every required id must be present, once, and no unknown id may be sent; otherwise the purchase is refused with `400 invalid_request` before anything is charged.\n\nThe information is kept for your project under the number's country and `type`. A later purchase there may omit this field if what is kept still covers that number's requirements. Omit it for numbers without requirements.",
            "items": {
              "type": "object",
              "required": [
                "requirementType",
                "fieldValue"
              ],
              "properties": {
                "requirementType": {
                  "type": "string",
                  "description": "A `requirementTypes[].id` from `GET /v1/phone-numbers/requirements`. Each id may appear only once.",
                  "example": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
                  "minLength": 1,
                  "maxLength": 200
                },
                "fieldValue": {
                  "type": "string",
                  "maxLength": 1000,
                  "description": "Depends on the requirement's `type`: the text itself for `textual`; for `address`, the `id` of an address created in this project with `POST /v1/addresses`; for `document`, the `id` of a document created with `POST /v1/documents`. An address or document from another project, or one rejected in review, is refused.",
                  "example": "jd7x2k3m4n5p6q7r8s9t0abc",
                  "minLength": 1
                }
              }
            }
          }
        }
      },
      "PhoneNumberUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "nullable": true,
            "description": "Custom name for the phone number. Set to null to clear."
          },
          "senderId": {
            "type": "string",
            "nullable": true,
            "description": "Sender ID to assign the phone number to. Set to null to unassign. A number under regulatory review is recorded now and connected to the sender when approved; a rejected number is refused."
          }
        }
      },
      "PhoneNumberResponse": {
        "type": "object",
        "required": [
          "phoneNumber"
        ],
        "properties": {
          "phoneNumber": {
            "$ref": "#/components/schemas/OwnedPhoneNumber"
          }
        }
      },
      "InvitationStatus": {
        "type": "string",
        "description": "Current status of the partner invitation.\n\n`failed` means the client started the connection and it did not finish (they cancelled Meta's dialog, denied a permission, or abandoned the tab). A failed invitation is still usable: the same link can be retried, and it moves back to `in_progress` when the client tries again.",
        "enum": [
          "pending",
          "in_progress",
          "completed",
          "expired",
          "cancelled",
          "failed"
        ]
      },
      "InvitationCreateRequest": {
        "type": "object",
        "properties": {
          "clientName": {
            "type": "string",
            "maxLength": 100,
            "description": "Name of the client being invited.",
            "example": "Acme Corp"
          },
          "clientEmail": {
            "type": "string",
            "format": "email",
            "description": "Email of the client being invited.",
            "example": "contact@acme.com"
          },
          "clientPhone": {
            "type": "string",
            "description": "Phone number of the client in E.164 format.",
            "example": "+14155551234"
          },
          "phoneNumberId": {
            "type": "string",
            "description": "ID of a Zavu phone number to pre-assign for WhatsApp registration. If provided, the client will use this number instead of their own. Only valid when `connectionType` is `whatsapp_waba` — sending it with `messenger` returns 400, since a Facebook Page has no phone number.",
            "example": "pn_abc123"
          },
          "expiresInDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 7,
            "description": "Number of days until the invitation expires."
          },
          "allowedPhoneCountries": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "description": "ISO country codes for allowed phone numbers. Only valid when `connectionType` is `whatsapp_waba` — sending it with `messenger` returns 400.",
            "example": [
              "US",
              "MX"
            ]
          },
          "connectionType": {
            "type": "string",
            "enum": [
              "whatsapp_waba",
              "messenger"
            ],
            "default": "whatsapp_waba",
            "description": "Which Meta channel the client connects, and how.\n- `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp Business Account. Accepts `phoneNumberId` and `allowedPhoneCountries`.\n- `messenger`: the client authorizes with Facebook and picks a Facebook Page they administer. The Page's Messenger inbox — including Marketplace chats — is routed to Zavu. They must be an admin of at least one Page. A Page can only be connected to one Zavu project at a time: if the client picks a Page that another project already connected, the newer connection wins and the older one is disconnected.\n\nOne invitation connects one channel. To onboard a client on several channels, create one invitation per channel; each completes into its own sender."
          }
        }
      },
      "Invitation": {
        "type": "object",
        "required": [
          "id",
          "url",
          "token",
          "status",
          "expiresAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "inv_abc123"
          },
          "url": {
            "type": "string",
            "description": "Full URL to share with the client.",
            "example": "https://dashboard.zavu.dev/invite/abc123xyz"
          },
          "token": {
            "type": "string",
            "description": "Unique invitation token."
          },
          "clientName": {
            "type": "string",
            "nullable": true
          },
          "clientEmail": {
            "type": "string",
            "nullable": true
          },
          "clientPhone": {
            "type": "string",
            "nullable": true
          },
          "phoneNumberId": {
            "type": "string",
            "nullable": true,
            "description": "ID of a pre-assigned Zavu phone number for WhatsApp registration. Always null for `messenger` invitations."
          },
          "connectionType": {
            "type": "string",
            "enum": [
              "whatsapp_waba",
              "messenger"
            ],
            "description": "Which Meta channel the client connects: `whatsapp_waba` (official WhatsApp Cloud API via embedded signup) or `messenger` (a Facebook Page's Messenger inbox, including Marketplace chats)."
          },
          "status": {
            "$ref": "#/components/schemas/InvitationStatus"
          },
          "senderId": {
            "type": "string",
            "nullable": true,
            "description": "ID of the sender created when invitation is completed."
          },
          "connectedAccount": {
            "type": "object",
            "nullable": true,
            "description": "The account the client linked, populated once the invitation is `completed`. Null before that. Use it to show the partner what was connected without fetching the sender.",
            "required": [
              "channel",
              "id"
            ],
            "properties": {
              "channel": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "messenger"
                ]
              },
              "id": {
                "type": "string",
                "description": "Provider-side identifier: the WhatsApp phone number ID, or the Facebook Page ID."
              },
              "name": {
                "type": "string",
                "nullable": true,
                "description": "Display name of the connected account: the WhatsApp verified name, or the Facebook Page name."
              }
            }
          },
          "failureReason": {
            "type": "string",
            "nullable": true,
            "description": "Stable code for why the last attempt failed, present when `status` is `failed`. Values include `fb_cancelled` (client closed Meta's dialog), `fb_not_authorized` (permission denied), `signup_abandoned` (started but never finished), `meta_no_pages` (the client administers no Facebook Page), and `internal_error`. Treat unknown codes as a generic failure."
          },
          "failedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "viewedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvitationResponse": {
        "type": "object",
        "required": [
          "invitation"
        ],
        "properties": {
          "invitation": {
            "$ref": "#/components/schemas/Invitation"
          }
        }
      },
      "WhatsAppBusinessProfileVertical": {
        "type": "string",
        "description": "Business category for WhatsApp Business profile.",
        "enum": [
          "UNDEFINED",
          "OTHER",
          "AUTO",
          "BEAUTY",
          "APPAREL",
          "EDU",
          "ENTERTAIN",
          "EVENT_PLAN",
          "FINANCE",
          "GROCERY",
          "GOVT",
          "HOTEL",
          "HEALTH",
          "NONPROFIT",
          "PROF_SERVICES",
          "RETAIL",
          "TRAVEL",
          "RESTAURANT",
          "NOT_A_BIZ"
        ]
      },
      "WhatsAppBusinessProfile": {
        "type": "object",
        "description": "WhatsApp Business profile information.",
        "properties": {
          "about": {
            "type": "string",
            "description": "Short description of the business (max 139 characters).",
            "maxLength": 139,
            "example": "Succulent specialists!"
          },
          "address": {
            "type": "string",
            "description": "Physical address of the business (max 256 characters).",
            "maxLength": 256,
            "example": "1 Hacker Way, Menlo Park, CA 94025"
          },
          "description": {
            "type": "string",
            "description": "Extended description of the business (max 512 characters).",
            "maxLength": 512,
            "example": "At Lucky Shrub, we specialize in providing a diverse range of high-quality succulents."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Business email address.",
            "example": "contact@example.com"
          },
          "profilePictureUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL of the business profile picture.",
            "example": "https://pps.whatsapp.net/v/t61.24..."
          },
          "websites": {
            "type": "array",
            "description": "Business website URLs (maximum 2).",
            "maxItems": 2,
            "items": {
              "type": "string",
              "format": "uri"
            },
            "example": [
              "https://www.example.com/"
            ]
          },
          "vertical": {
            "$ref": "#/components/schemas/WhatsAppBusinessProfileVertical"
          }
        }
      },
      "WhatsAppBusinessProfileUpdateRequest": {
        "type": "object",
        "description": "Request body to update WhatsApp Business profile.",
        "properties": {
          "about": {
            "type": "string",
            "description": "Short description of the business (max 139 characters).",
            "maxLength": 139
          },
          "address": {
            "type": "string",
            "description": "Physical address of the business (max 256 characters).",
            "maxLength": 256
          },
          "description": {
            "type": "string",
            "description": "Extended description of the business (max 512 characters).",
            "maxLength": 512
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Business email address."
          },
          "websites": {
            "type": "array",
            "description": "Business website URLs (maximum 2).",
            "maxItems": 2,
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "vertical": {
            "$ref": "#/components/schemas/WhatsAppBusinessProfileVertical"
          }
        }
      },
      "WhatsAppBusinessProfileResponse": {
        "type": "object",
        "required": [
          "profile"
        ],
        "properties": {
          "profile": {
            "$ref": "#/components/schemas/WhatsAppBusinessProfile"
          }
        }
      },
      "WhatsAppBusinessProfilePictureRequest": {
        "type": "object",
        "description": "Request body to upload a profile picture.",
        "required": [
          "imageUrl",
          "mimeType"
        ],
        "properties": {
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL of the image to upload."
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type of the image.",
            "enum": [
              "image/jpeg",
              "image/png"
            ]
          }
        }
      },
      "WhatsAppSyncHistoryStatus": {
        "type": "string",
        "description": "Status of WhatsApp message history sync.",
        "enum": [
          "not_requested",
          "pending",
          "syncing",
          "completed",
          "rejected"
        ]
      },
      "WhatsAppSyncContactsStatus": {
        "type": "string",
        "description": "Status of WhatsApp contacts sync.",
        "enum": [
          "not_requested",
          "pending",
          "syncing",
          "completed"
        ]
      },
      "WhatsAppSyncHistory": {
        "type": "object",
        "description": "History sync status details.",
        "required": [
          "status",
          "canSync"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/WhatsAppSyncHistoryStatus"
          },
          "canSync": {
            "type": "boolean",
            "description": "Whether history sync can be initiated."
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the sync was last requested."
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the sync was completed."
          }
        }
      },
      "WhatsAppSyncContacts": {
        "type": "object",
        "description": "Contacts sync status details.",
        "required": [
          "status",
          "canSync"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/WhatsAppSyncContactsStatus"
          },
          "canSync": {
            "type": "boolean",
            "description": "Whether contacts sync can be initiated."
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the sync was last requested."
          }
        }
      },
      "WhatsAppSyncStatus": {
        "type": "object",
        "description": "WhatsApp coexistence sync status.",
        "required": [
          "isCoexistence",
          "status",
          "history",
          "contacts"
        ],
        "properties": {
          "isCoexistence": {
            "type": "boolean",
            "description": "Whether the account is in coexistence mode."
          },
          "status": {
            "type": "string",
            "description": "WhatsApp account status.",
            "enum": [
              "pending_verification",
              "pending_registration",
              "active",
              "disconnected",
              "error"
            ]
          },
          "history": {
            "$ref": "#/components/schemas/WhatsAppSyncHistory"
          },
          "contacts": {
            "$ref": "#/components/schemas/WhatsAppSyncContacts"
          }
        }
      },
      "WhatsAppSyncStatusResponse": {
        "type": "object",
        "required": [
          "sync"
        ],
        "properties": {
          "sync": {
            "$ref": "#/components/schemas/WhatsAppSyncStatus"
          }
        }
      },
      "WhatsAppSyncStartResponse": {
        "type": "object",
        "required": [
          "message",
          "sync"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Success message.",
            "example": "History sync initiated"
          },
          "sync": {
            "$ref": "#/components/schemas/WhatsAppSyncStatus"
          }
        }
      },
      "AgentProvider": {
        "type": "string",
        "description": "LLM provider for the AI agent.",
        "enum": [
          "openai",
          "anthropic",
          "google",
          "mistral",
          "zavu"
        ]
      },
      "AgentExecutionStatus": {
        "type": "string",
        "description": "Status of an agent execution.",
        "enum": [
          "success",
          "error",
          "filtered",
          "rate_limited",
          "balance_insufficient"
        ]
      },
      "Agent": {
        "type": "object",
        "description": "AI Agent configuration for a sender.",
        "required": [
          "id",
          "name",
          "enabled",
          "provider",
          "model",
          "systemPrompt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "agent_abc123"
          },
          "senderId": {
            "type": "string",
            "description": "The sender the agent was created on, its primary. Absent on an agent created with `POST /v1/agents`, which has no primary even after senders are connected to it. `senderIds` lists every sender the agent answers on.",
            "example": "sender_12345"
          },
          "name": {
            "type": "string",
            "example": "Customer Support Agent"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether the agent is active."
          },
          "provider": {
            "$ref": "#/components/schemas/AgentProvider"
          },
          "model": {
            "type": "string",
            "description": "Model ID (e.g., gpt-4o-mini, claude-3-5-sonnet).",
            "example": "gpt-4o-mini"
          },
          "systemPrompt": {
            "type": "string",
            "description": "System prompt for the agent."
          },
          "contextWindowMessages": {
            "type": "integer",
            "description": "Number of previous messages to include as context.",
            "default": 10
          },
          "includeContactMetadata": {
            "type": "boolean",
            "description": "Whether to include contact metadata in context.",
            "default": true
          },
          "maxTokens": {
            "type": "integer",
            "description": "Maximum tokens for LLM response.",
            "nullable": true
          },
          "temperature": {
            "type": "number",
            "description": "LLM temperature (0-2).",
            "nullable": true
          },
          "triggerOnChannels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Channels that trigger the agent.",
            "example": [
              "sms",
              "whatsapp"
            ]
          },
          "triggerOnMessageTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Message types that trigger the agent.",
            "example": [
              "text"
            ]
          },
          "stats": {
            "type": "object",
            "properties": {
              "totalInvocations": {
                "type": "integer"
              },
              "totalTokensUsed": {
                "type": "integer"
              },
              "totalCost": {
                "type": "number",
                "description": "Total cost in USD."
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "voice": {
            "$ref": "#/components/schemas/AgentVoiceConfig",
            "description": "Voice Agent configuration. When present and enabled, the agent can answer inbound phone calls and place outbound calls with Zavu's managed voice pipeline."
          },
          "senderIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Senders this agent answers on. An agent can serve several; `senderId` remains the primary one, for compatibility."
          }
        }
      },
      "AgentCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "provider",
          "model",
          "systemPrompt"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "provider": {
            "$ref": "#/components/schemas/AgentProvider"
          },
          "model": {
            "type": "string"
          },
          "systemPrompt": {
            "type": "string",
            "maxLength": 10000
          },
          "apiKey": {
            "type": "string",
            "description": "API key for the LLM provider. Required unless provider is 'zavu'."
          },
          "contextWindowMessages": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 10
          },
          "includeContactMetadata": {
            "type": "boolean",
            "default": true
          },
          "maxTokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4096
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "triggerOnChannels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "default": [
              "*"
            ]
          },
          "triggerOnMessageTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "default": [
              "text"
            ]
          },
          "voice": {
            "$ref": "#/components/schemas/AgentVoiceConfig",
            "description": "Voice Agent configuration. Enable this to let the agent answer and place phone calls with Zavu's managed voice pipeline."
          }
        }
      },
      "AgentUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "enabled": {
            "type": "boolean"
          },
          "provider": {
            "$ref": "#/components/schemas/AgentProvider"
          },
          "model": {
            "type": "string"
          },
          "systemPrompt": {
            "type": "string",
            "maxLength": 10000
          },
          "apiKey": {
            "type": "string",
            "description": "API key for the LLM provider, stored and attached to the agent. Send it with `provider` when the agent does not already hold a key for that provider: an update that would leave the agent on a key for a different provider is refused with `400 invalid_request`, and one that leaves it with no key at all is refused too. Moving to `zavu` needs no key."
          },
          "contextWindowMessages": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "includeContactMetadata": {
            "type": "boolean"
          },
          "maxTokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4096,
            "nullable": true
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2,
            "nullable": true
          },
          "triggerOnChannels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "triggerOnMessageTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "voice": {
            "$ref": "#/components/schemas/AgentVoiceConfig",
            "description": "Voice Agent configuration. Patch this object to enable voice, change the greeting, or adjust call limits."
          }
        }
      },
      "AgentResponse": {
        "type": "object",
        "required": [
          "agent"
        ],
        "properties": {
          "agent": {
            "$ref": "#/components/schemas/Agent"
          }
        }
      },
      "AgentStats": {
        "type": "object",
        "required": [
          "totalInvocations",
          "totalTokensUsed",
          "totalCost",
          "successCount",
          "errorCount"
        ],
        "properties": {
          "totalInvocations": {
            "type": "integer"
          },
          "totalTokensUsed": {
            "type": "integer"
          },
          "totalCost": {
            "type": "number",
            "description": "Total cost in USD."
          },
          "successCount": {
            "type": "integer",
            "description": "Successful executions among the agent's 100 most recent. Executions with status `filtered` (messages the agent never ran on) are not counted here, in `errorCount` or in `avgLatencyMs`."
          },
          "errorCount": {
            "type": "integer",
            "description": "Failed executions among the agent's 100 most recent."
          },
          "avgLatencyMs": {
            "type": "number",
            "nullable": true,
            "description": "Average latency of the agent's 100 most recent executions, `filtered` excluded. Null when there are none."
          }
        }
      },
      "AgentExecution": {
        "type": "object",
        "required": [
          "id",
          "agentId",
          "status",
          "inputTokens",
          "outputTokens",
          "latencyMs",
          "cost",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "agentId": {
            "type": "string"
          },
          "inboundMessageId": {
            "type": "string"
          },
          "responseMessageId": {
            "type": "string",
            "nullable": true
          },
          "responseText": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/AgentExecutionStatus"
          },
          "errorMessage": {
            "type": "string",
            "nullable": true
          },
          "inputTokens": {
            "type": "integer"
          },
          "outputTokens": {
            "type": "integer"
          },
          "latencyMs": {
            "type": "integer"
          },
          "cost": {
            "type": "number",
            "description": "Cost in USD."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "knowledgeChunksUsed": {
            "type": "integer",
            "nullable": true,
            "description": "Knowledge-base chunks retrieved for this answer. Zero on an agent that has documents attached means the reply was not grounded in them, which is otherwise indistinguishable from a correct answer in this record. Absent on executions recorded before this field existed, which is not the same as zero."
          },
          "toolCalls": {
            "type": "integer",
            "nullable": true,
            "description": "Tools the agent called while producing this reply. Zero on an agent that has tools configured means it answered without calling any — the case where a reply says it will look something up and nothing ever reaches your endpoint. Absent on executions recorded before this field existed, which is not the same as zero."
          }
        }
      },
      "FlowTriggerType": {
        "type": "string",
        "description": "What starts a flow.\n\n- `keyword`: the message contains one of the words listed in `keywords`. Plain substring matching, so a word inside another word still counts.\n- `intent`: the message MEANS what `intent` describes, whatever words it uses.\n- `always`: any message starts it.\n- `manual`: reserved. Nothing starts a `manual` flow today — it is accepted and stored, and no message or endpoint runs it.",
        "enum": [
          "keyword",
          "intent",
          "always",
          "manual"
        ]
      },
      "FlowStepType": {
        "type": "string",
        "description": "Type of flow step.",
        "enum": [
          "message",
          "collect",
          "condition",
          "tool",
          "llm",
          "transfer"
        ]
      },
      "FlowTrigger": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/FlowTriggerType"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Words that start the flow, for `keyword` triggers. Matched as substrings, case-insensitively, against the whole message: a flow on `info` also starts on \"no quiero info\". Use `intent` when that matters."
          },
          "intent": {
            "type": "string",
            "description": "One plain sentence describing what the contact wants, for `intent` triggers. Any language.\n\nThe message is judged for meaning, not for words, so \"kiero saber el presio\" starts a flow whose intent is \"quiere saber precios o cotizar\", and \"no quiero info de precios\" starts nothing.\n\nA `keyword` or `always` flow with a higher `priority` is matched first and wins. At most 12 intent flows are considered per message, highest priority first. When the classification is unavailable or uncertain, the message is handled as if no intent matched, so a flow never starts on a guess.",
            "example": "quiere saber precios o cotizar"
          }
        }
      },
      "FlowStep": {
        "type": "object",
        "required": [
          "id",
          "type",
          "config"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique step identifier."
          },
          "type": {
            "$ref": "#/components/schemas/FlowStepType"
          },
          "config": {
            "type": "object",
            "description": "Step configuration (varies by type).",
            "additionalProperties": true
          },
          "nextStepId": {
            "type": "string",
            "description": "ID of the next step to execute.",
            "nullable": true
          }
        }
      },
      "AgentFlow": {
        "type": "object",
        "required": [
          "id",
          "agentId",
          "name",
          "trigger",
          "steps",
          "enabled",
          "priority",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "agentId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "trigger": {
            "$ref": "#/components/schemas/FlowTrigger"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            }
          },
          "enabled": {
            "type": "boolean"
          },
          "priority": {
            "type": "integer",
            "description": "Priority when multiple flows match (higher = more priority)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FlowCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "trigger",
          "steps"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string"
          },
          "trigger": {
            "$ref": "#/components/schemas/FlowTrigger"
          },
          "steps": {
            "type": "array",
            "description": "The first step runs on the message that started the flow. It cannot be a `condition` without a `defaultGoto`: nothing has been collected yet, so its conditions evaluate the same way on every message, and the request is refused with `400 invalid_request`.\n\nA flow that ends on that first message without replying hands the message to the agent, which answers it. A `transfer` step stays silent.",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "minItems": 1
          },
          "enabled": {
            "type": "boolean",
            "default": false
          },
          "priority": {
            "type": "integer",
            "default": 0
          }
        }
      },
      "FlowUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string"
          },
          "trigger": {
            "$ref": "#/components/schemas/FlowTrigger"
          },
          "steps": {
            "type": "array",
            "description": "The first step runs on the message that started the flow. It cannot be a `condition` without a `defaultGoto`: nothing has been collected yet, so its conditions evaluate the same way on every message, and the request is refused with `400 invalid_request`.\n\nA flow that ends on that first message without replying hands the message to the agent, which answers it. A `transfer` step stays silent.",
            "items": {
              "$ref": "#/components/schemas/FlowStep"
            },
            "minItems": 1
          },
          "enabled": {
            "type": "boolean"
          },
          "priority": {
            "type": "integer"
          }
        }
      },
      "FlowResponse": {
        "type": "object",
        "required": [
          "flow"
        ],
        "properties": {
          "flow": {
            "$ref": "#/components/schemas/AgentFlow"
          }
        }
      },
      "FlowDuplicateRequest": {
        "type": "object",
        "required": [
          "newName"
        ],
        "properties": {
          "newName": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "ToolParameters": {
        "type": "object",
        "required": [
          "type",
          "properties",
          "required"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "object"
            ]
          },
          "properties": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "required": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AgentTool": {
        "type": "object",
        "required": [
          "id",
          "agentId",
          "name",
          "description",
          "parameters",
          "webhookUrl",
          "enabled",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "agentId": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "example": "get_order_status"
          },
          "description": {
            "type": "string",
            "description": "Description for the LLM to understand when to use this tool."
          },
          "parameters": {
            "$ref": "#/components/schemas/ToolParameters"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL to call when the tool is executed."
          },
          "enabled": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "webhookSecret": {
            "type": "string",
            "description": "Signing secret for this tool's webhook. **Returned only when the tool is created**, never on a later read.\n\nZavu generates one if you do not supply it, and signs every call to this tool with it: `X-Zavu-Signature: <hex>`, the HMAC-SHA256 of the request body. Verify it before trusting the call. Lost it? Rotate with `POST /v1/senders/{senderId}/agent/tools/{toolId}/webhook/secret`.",
            "example": "whsec_abc123..."
          }
        }
      },
      "ToolCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "description",
          "parameters",
          "webhookUrl"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "parameters": {
            "$ref": "#/components/schemas/ToolParameters"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "Must be HTTPS."
          },
          "webhookSecret": {
            "type": "string",
            "description": "Signing secret for the webhook. Optional: Zavu generates one when omitted and returns it on this response only. Supply your own if you already have a secret you want reused."
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "ToolUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "parameters": {
            "$ref": "#/components/schemas/ToolParameters"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri"
          },
          "webhookSecret": {
            "type": "string",
            "nullable": true
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "ToolResponse": {
        "type": "object",
        "required": [
          "tool"
        ],
        "properties": {
          "tool": {
            "$ref": "#/components/schemas/AgentTool"
          }
        }
      },
      "ToolTestRequest": {
        "type": "object",
        "required": [
          "testParams"
        ],
        "properties": {
          "testParams": {
            "type": "object",
            "description": "Parameters to pass to the tool for testing.",
            "additionalProperties": true
          }
        }
      },
      "AgentToolTestRun": {
        "type": "object",
        "description": "One run of a tool triggered from the test endpoint. Recorded so a test is verifiable after the fact rather than only visible in the response.",
        "required": [
          "id",
          "toolId",
          "success",
          "durationMs",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "toolId": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": true,
            "description": "The parameters the tool was called with."
          },
          "success": {
            "type": "boolean",
            "description": "Whether the tool returned without error. A tool that answered with a non-2xx status is a failed run, not an error of this endpoint."
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "description": "HTTP status the tool's webhook returned. Absent for tools that do not go over HTTP."
          },
          "response": {
            "type": "string",
            "nullable": true,
            "description": "The tool's response body, truncated."
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Why the run failed, when it did."
          },
          "durationMs": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ToolTestResponse": {
        "type": "object",
        "required": [
          "run"
        ],
        "properties": {
          "run": {
            "$ref": "#/components/schemas/AgentToolTestRun"
          }
        }
      },
      "AgentKnowledgeBase": {
        "type": "object",
        "required": [
          "id",
          "agentId",
          "name",
          "documentCount",
          "totalChunks",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "agentId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "documentCount": {
            "type": "integer"
          },
          "totalChunks": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "KnowledgeBaseCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string"
          }
        }
      },
      "KnowledgeBaseUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "KnowledgeBaseResponse": {
        "type": "object",
        "required": [
          "knowledgeBase"
        ],
        "properties": {
          "knowledgeBase": {
            "$ref": "#/components/schemas/AgentKnowledgeBase"
          }
        }
      },
      "AgentDocument": {
        "type": "object",
        "required": [
          "id",
          "knowledgeBaseId",
          "title",
          "contentLength",
          "chunkCount",
          "isProcessed",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "knowledgeBaseId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "contentLength": {
            "type": "integer",
            "description": "Length of the document content in characters."
          },
          "chunkCount": {
            "type": "integer",
            "description": "Number of chunks created from this document."
          },
          "isProcessed": {
            "type": "boolean",
            "description": "Whether the document has been processed for RAG."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DocumentCreateRequest": {
        "type": "object",
        "required": [
          "title",
          "content"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "content": {
            "type": "string",
            "maxLength": 100000
          }
        }
      },
      "DocumentResponse": {
        "type": "object",
        "required": [
          "document"
        ],
        "properties": {
          "document": {
            "$ref": "#/components/schemas/AgentDocument"
          }
        }
      },
      "RequirementFieldType": {
        "type": "string",
        "description": "Type of requirement field.",
        "enum": [
          "textual",
          "address",
          "document",
          "action"
        ]
      },
      "RequirementAcceptanceCriteria": {
        "type": "object",
        "description": "Acceptance criteria for a requirement.",
        "properties": {
          "minLength": {
            "type": "integer",
            "nullable": true
          },
          "maxLength": {
            "type": "integer",
            "nullable": true
          },
          "allowedValues": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "regexPattern": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "RequirementType": {
        "type": "object",
        "description": "A specific requirement type within a requirement group.",
        "required": [
          "id",
          "name",
          "description",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Send this as `requirementType` in `regulatoryRequirements` when purchasing."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/RequirementFieldType"
          },
          "example": {
            "type": "string",
            "nullable": true
          },
          "acceptanceCriteria": {
            "$ref": "#/components/schemas/RequirementAcceptanceCriteria"
          }
        }
      },
      "Requirement": {
        "type": "object",
        "description": "The requirements for ordering a number: for a country and number type, or for one specific number when requested with `phoneNumber` (then `id` is that phone number and `countryCode` is taken from it).",
        "required": [
          "id",
          "countryCode",
          "phoneNumberType",
          "action",
          "requirementTypes"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "countryCode": {
            "type": "string",
            "example": "DE"
          },
          "phoneNumberType": {
            "type": "string",
            "example": "local"
          },
          "action": {
            "type": "string",
            "example": "ordering"
          },
          "requirementTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequirementType"
            }
          }
        }
      },
      "AddressStatus": {
        "type": "string",
        "enum": [
          "pending",
          "verified",
          "rejected"
        ]
      },
      "Address": {
        "type": "object",
        "description": "A regulatory address for phone number requirements.",
        "required": [
          "id",
          "streetAddress",
          "locality",
          "postalCode",
          "countryCode",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "businessName": {
            "type": "string",
            "nullable": true
          },
          "streetAddress": {
            "type": "string"
          },
          "extendedAddress": {
            "type": "string",
            "nullable": true
          },
          "locality": {
            "type": "string"
          },
          "administrativeArea": {
            "type": "string",
            "nullable": true
          },
          "postalCode": {
            "type": "string"
          },
          "countryCode": {
            "type": "string",
            "example": "DE"
          },
          "status": {
            "$ref": "#/components/schemas/AddressStatus"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AddressCreateRequest": {
        "type": "object",
        "description": "A regulatory address. `firstName` and `lastName` are required: the carrier will not register an address without a named person. Requests that omit them are refused with `400 invalid_request`, and the SDKs still type them as optional until their next release.",
        "required": [
          "firstName",
          "lastName",
          "streetAddress",
          "locality",
          "postalCode",
          "countryCode"
        ],
        "properties": {
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "description": "First name of the person the address is registered to."
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "description": "Last name of the person the address is registered to."
          },
          "businessName": {
            "type": "string",
            "maxLength": 200,
            "description": "Business name, when the address belongs to a business. Defaults to the person's full name."
          },
          "streetAddress": {
            "type": "string",
            "maxLength": 200
          },
          "extendedAddress": {
            "type": "string",
            "maxLength": 100
          },
          "locality": {
            "type": "string",
            "maxLength": 100
          },
          "administrativeArea": {
            "type": "string",
            "maxLength": 100
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20
          },
          "countryCode": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "example": "DE"
          }
        }
      },
      "AddressResponse": {
        "type": "object",
        "required": [
          "address"
        ],
        "properties": {
          "address": {
            "$ref": "#/components/schemas/Address"
          }
        }
      },
      "RegulatoryDocumentStatus": {
        "type": "string",
        "enum": [
          "pending",
          "uploaded",
          "verified",
          "rejected"
        ]
      },
      "RegulatoryDocumentType": {
        "type": "string",
        "enum": [
          "passport",
          "national_id",
          "drivers_license",
          "utility_bill",
          "tax_id",
          "business_registration",
          "proof_of_address",
          "other"
        ]
      },
      "RegulatoryDocument": {
        "type": "object",
        "description": "A regulatory document for phone number requirements.",
        "required": [
          "id",
          "name",
          "documentType",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "documentType": {
            "$ref": "#/components/schemas/RegulatoryDocumentType"
          },
          "mimeType": {
            "type": "string"
          },
          "fileSize": {
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/RegulatoryDocumentStatus"
          },
          "rejectionReason": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RegulatoryDocumentCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "documentType",
          "storageId",
          "mimeType",
          "fileSize"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "documentType": {
            "$ref": "#/components/schemas/RegulatoryDocumentType"
          },
          "storageId": {
            "type": "string",
            "description": "Storage ID from the upload-url endpoint."
          },
          "mimeType": {
            "type": "string"
          },
          "fileSize": {
            "type": "integer"
          }
        }
      },
      "RegulatoryDocumentResponse": {
        "type": "object",
        "required": [
          "document"
        ],
        "properties": {
          "document": {
            "$ref": "#/components/schemas/RegulatoryDocument"
          }
        }
      },
      "UploadUrlResponse": {
        "type": "object",
        "required": [
          "uploadUrl"
        ],
        "properties": {
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "description": "Pre-signed URL for uploading the file."
          }
        }
      },
      "VerifiedUrlStatus": {
        "type": "string",
        "description": "Status of a verified URL.",
        "enum": [
          "pending",
          "approved",
          "rejected",
          "escalated",
          "malicious"
        ]
      },
      "VerifiedUrlApprovalType": {
        "type": "string",
        "description": "How the URL was approved or rejected.",
        "enum": [
          "manual",
          "auto_web_risk"
        ]
      },
      "VerifiedUrl": {
        "type": "object",
        "required": [
          "id",
          "url",
          "domain",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "url_abc123"
          },
          "url": {
            "type": "string",
            "description": "The verified URL.",
            "example": "https://example.com/page"
          },
          "domain": {
            "type": "string",
            "description": "Domain extracted from the URL.",
            "example": "example.com"
          },
          "status": {
            "$ref": "#/components/schemas/VerifiedUrlStatus"
          },
          "approvalType": {
            "$ref": "#/components/schemas/VerifiedUrlApprovalType"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VerifiedUrlSubmitRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "The URL to submit for verification.",
            "example": "https://example.com/page"
          }
        }
      },
      "VerifiedUrlResponse": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "$ref": "#/components/schemas/VerifiedUrl"
          }
        }
      },
      "SubAccountStatus": {
        "type": "string",
        "enum": [
          "active",
          "inactive",
          "pending_deletion"
        ],
        "description": "`active` and `inactive` are set with PATCH. `pending_deletion` is read-only: the sub-account was deactivated with DELETE, its API keys are revoked, and it is removed after 14 days."
      },
      "SubAccount": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "totalSpent",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "example": "Client ABC"
          },
          "externalId": {
            "type": "string",
            "nullable": true,
            "description": "External reference ID set by the parent account."
          },
          "status": {
            "$ref": "#/components/schemas/SubAccountStatus"
          },
          "totalSpent": {
            "type": "integer",
            "description": "Total amount spent by this sub-account in cents."
          },
          "creditLimit": {
            "type": "integer",
            "nullable": true,
            "description": "Spending cap in cents. When reached, messages from this sub-account will be blocked."
          },
          "metadata": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true
          },
          "apiKey": {
            "type": "string",
            "description": "API key for the sub-account. Only returned on creation."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubAccountCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "Name of the sub-account.",
            "example": "Client ABC"
          },
          "externalId": {
            "type": "string",
            "description": "External reference ID for your own tracking."
          },
          "creditLimit": {
            "type": "integer",
            "minimum": 0,
            "description": "Spending cap in cents. When reached, messages from this sub-account will be blocked. Omit or set to 0 for no limit."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "SubAccountUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "externalId": {
            "type": "string"
          },
          "creditLimit": {
            "type": "integer",
            "nullable": true,
            "minimum": 0
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "status": {
            "$ref": "#/components/schemas/SubAccountStatus"
          }
        }
      },
      "SubAccountResponse": {
        "type": "object",
        "required": [
          "subAccount"
        ],
        "properties": {
          "subAccount": {
            "$ref": "#/components/schemas/SubAccount"
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "balance",
          "currency"
        ],
        "properties": {
          "balance": {
            "type": "integer",
            "description": "Team balance in cents. All charges are billed to the parent team."
          },
          "currency": {
            "type": "string",
            "example": "usd"
          },
          "creditLimit": {
            "type": "integer",
            "nullable": true,
            "description": "Spending cap in cents (only for sub-accounts)."
          },
          "totalSpent": {
            "type": "integer",
            "nullable": true,
            "description": "Total amount spent by this sub-account in cents (only for sub-accounts)."
          },
          "isSubAccount": {
            "type": "boolean",
            "description": "Whether this API key belongs to a sub-account."
          }
        }
      },
      "SubAccountApiKey": {
        "type": "object",
        "required": [
          "id",
          "name",
          "keyPrefix",
          "environment",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "description": "Full API key. Only returned on creation."
          },
          "keyPrefix": {
            "type": "string",
            "description": "First characters of the key for identification."
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lastUsedAt": {
            "type": "number",
            "nullable": true
          },
          "revokedAt": {
            "type": "number",
            "nullable": true
          },
          "createdAt": {
            "type": "number"
          }
        }
      },
      "SubAccountApiKeyCreateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "default": "live"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "TenDlcEntityType": {
        "type": "string",
        "description": "Business entity type for 10DLC brand registration.",
        "enum": [
          "PRIVATE_PROFIT",
          "PUBLIC_PROFIT",
          "NON_PROFIT",
          "GOVERNMENT",
          "SOLE_PROPRIETOR"
        ]
      },
      "TenDlcBrandStatus": {
        "type": "string",
        "description": "Status of a 10DLC brand registration.\n\n- `draft`: created, not yet submitted to the carrier.\n- `pending`: submitted, awaiting the carrier's answer.\n- `verified`: the carrier registered the brand AND verified the business behind it.\n- `unverified`: the carrier registered the brand but did not verify the business — the registration exists, the identity check did not pass or has not been resolved. Campaigns are allowed, with lower daily limits. Read `identityStatus` for the carrier's own wording.\n- `rejected`: refused by the carrier.\n- `failed`: the registration never reached the carrier; the fee is refunded.",
        "enum": [
          "draft",
          "pending",
          "verified",
          "unverified",
          "rejected",
          "failed"
        ]
      },
      "TenDlcBrand": {
        "type": "object",
        "required": [
          "id",
          "entityType",
          "displayName",
          "email",
          "phone",
          "street",
          "city",
          "state",
          "postalCode",
          "country",
          "vertical",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "entityType": {
            "$ref": "#/components/schemas/TenDlcEntityType"
          },
          "displayName": {
            "type": "string",
            "description": "Display name of the brand.",
            "example": "Acme Corp"
          },
          "companyName": {
            "type": "string",
            "nullable": true,
            "description": "Legal company name."
          },
          "ein": {
            "type": "string",
            "nullable": true,
            "description": "Employer Identification Number (EIN).",
            "example": "12-3456789"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "description": "Contact phone number in E.164 format.",
            "example": "+14155551234"
          },
          "street": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "postalCode": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "Two-letter ISO country code.",
            "example": "US"
          },
          "website": {
            "type": "string",
            "nullable": true,
            "format": "uri"
          },
          "vertical": {
            "type": "string",
            "description": "Industry vertical.",
            "example": "Technology"
          },
          "stockSymbol": {
            "type": "string",
            "nullable": true
          },
          "stockExchange": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/TenDlcBrandStatus"
          },
          "identityStatus": {
            "type": "string",
            "nullable": true,
            "description": "The carrier's raw identity verdict on the business, as the carrier spells it (`VERIFIED`, `VETTED_VERIFIED`, `SELF_DECLARED`, `UNVERIFIED`). Null while the identity has not been resolved — which is not the same as verified, and is why such a brand reports `status: unverified`.",
            "example": "VERIFIED"
          },
          "brandScore": {
            "type": "integer",
            "nullable": true,
            "description": "Trust score assigned by TCR after vetting."
          },
          "brandRelationship": {
            "type": "string",
            "nullable": true
          },
          "failureReason": {
            "type": "string",
            "nullable": true,
            "description": "Reason for rejection, if applicable."
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TenDlcBrandCreateRequest": {
        "type": "object",
        "required": [
          "entityType",
          "displayName",
          "email",
          "phone",
          "street",
          "city",
          "state",
          "postalCode",
          "country",
          "vertical"
        ],
        "properties": {
          "entityType": {
            "$ref": "#/components/schemas/TenDlcEntityType"
          },
          "displayName": {
            "type": "string",
            "maxLength": 100,
            "description": "Display name of the brand.",
            "example": "Acme Corp"
          },
          "companyName": {
            "type": "string",
            "maxLength": 200,
            "description": "Legal company name."
          },
          "ein": {
            "type": "string",
            "description": "Employer Identification Number (format: XX-XXXXXXX). Required to submit every entity type except `SOLE_PROPRIETOR`: a brand without it can be created as a draft, and `POST /v1/10dlc/brands/{brandId}/submit` refuses it with `400`.",
            "example": "12-3456789"
          },
          "firstName": {
            "type": "string",
            "maxLength": 50
          },
          "lastName": {
            "type": "string",
            "maxLength": 50
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "description": "Contact phone in E.164 format. For a `SOLE_PROPRIETOR`, a mobile number: the carrier texts a verification code to it.",
            "example": "+14155551234"
          },
          "street": {
            "type": "string",
            "maxLength": 200
          },
          "city": {
            "type": "string",
            "maxLength": 100
          },
          "state": {
            "type": "string",
            "maxLength": 50
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "Two-letter ISO country code.",
            "example": "US"
          },
          "website": {
            "type": "string",
            "format": "uri"
          },
          "vertical": {
            "type": "string",
            "maxLength": 50,
            "description": "Industry vertical.",
            "example": "Technology"
          },
          "stockSymbol": {
            "type": "string",
            "maxLength": 10
          },
          "stockExchange": {
            "type": "string",
            "maxLength": 50
          }
        }
      },
      "TenDlcBrandUpdateRequest": {
        "type": "object",
        "properties": {
          "entityType": {
            "$ref": "#/components/schemas/TenDlcEntityType"
          },
          "displayName": {
            "type": "string",
            "maxLength": 100
          },
          "companyName": {
            "type": "string",
            "maxLength": 200
          },
          "ein": {
            "type": "string"
          },
          "firstName": {
            "type": "string",
            "maxLength": 50
          },
          "lastName": {
            "type": "string",
            "maxLength": 50
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "street": {
            "type": "string",
            "maxLength": 200
          },
          "city": {
            "type": "string",
            "maxLength": 100
          },
          "state": {
            "type": "string",
            "maxLength": 50
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "website": {
            "type": "string",
            "format": "uri"
          },
          "vertical": {
            "type": "string",
            "maxLength": 50
          },
          "stockSymbol": {
            "type": "string",
            "maxLength": 10
          },
          "stockExchange": {
            "type": "string",
            "maxLength": 50
          }
        }
      },
      "TenDlcBrandResponse": {
        "type": "object",
        "required": [
          "brand"
        ],
        "properties": {
          "brand": {
            "$ref": "#/components/schemas/TenDlcBrand"
          }
        }
      },
      "TenDlcCampaignStatus": {
        "type": "string",
        "description": "Status of a 10DLC campaign registration.\n\n- `draft`: created, not yet submitted.\n- `pending`: submitted, awaiting the carriers. Re-read from the carrier every 30 minutes, so it moves on its own.\n- `approved`: SMS to +1 numbers can go out from numbers assigned to it. The monthly fee is charged while it is approved.\n- `rejected`: refused by the carriers. The one-time registration fee is refunded.\n- `failed`: the registration did not reach the carriers. The fee is refunded.\n- `expired`: was approved and has lapsed.",
        "enum": [
          "draft",
          "pending",
          "approved",
          "rejected",
          "expired",
          "failed"
        ]
      },
      "TenDlcCampaign": {
        "type": "object",
        "required": [
          "id",
          "brandId",
          "name",
          "useCase",
          "description",
          "sampleMessages",
          "subscriberOptIn",
          "subscriberOptOut",
          "subscriberHelp",
          "numberPooling",
          "directLending",
          "embeddedLink",
          "embeddedPhone",
          "affiliateMarketing",
          "ageGated",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "brandId": {
            "type": "string",
            "description": "ID of the brand this campaign belongs to."
          },
          "name": {
            "type": "string",
            "example": "Order Notifications"
          },
          "useCase": {
            "type": "string",
            "description": "Campaign use case type.",
            "example": "ACCOUNT_NOTIFICATION"
          },
          "subUseCases": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Description of the messaging campaign."
          },
          "sampleMessages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Sample messages representative of campaign content."
          },
          "subscriberOptIn": {
            "type": "boolean"
          },
          "subscriberOptOut": {
            "type": "boolean"
          },
          "subscriberHelp": {
            "type": "boolean"
          },
          "numberPooling": {
            "type": "boolean"
          },
          "directLending": {
            "type": "boolean"
          },
          "embeddedLink": {
            "type": "boolean"
          },
          "embeddedPhone": {
            "type": "boolean"
          },
          "affiliateMarketing": {
            "type": "boolean"
          },
          "ageGated": {
            "type": "boolean"
          },
          "messageFlow": {
            "type": "string",
            "nullable": true
          },
          "helpMessage": {
            "type": "string",
            "nullable": true
          },
          "optInKeywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "optOutKeywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "status": {
            "$ref": "#/components/schemas/TenDlcCampaignStatus"
          },
          "dailyLimit": {
            "type": "integer",
            "nullable": true,
            "description": "Daily message limit based on brand trust score."
          },
          "failureReason": {
            "type": "string",
            "nullable": true
          },
          "registrationCostCents": {
            "type": "integer",
            "nullable": true,
            "description": "One-time registration cost in cents."
          },
          "monthlyFeeCents": {
            "type": "integer",
            "nullable": true,
            "description": "Recurring monthly fee in cents."
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "approvedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TenDlcCampaignCreateRequest": {
        "type": "object",
        "required": [
          "brandId",
          "name",
          "useCase",
          "description",
          "sampleMessages",
          "subscriberOptIn",
          "subscriberOptOut",
          "subscriberHelp",
          "numberPooling",
          "directLending",
          "embeddedLink",
          "embeddedPhone",
          "affiliateMarketing",
          "ageGated"
        ],
        "properties": {
          "brandId": {
            "type": "string",
            "description": "ID of the brand to create this campaign under."
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "useCase": {
            "type": "string",
            "maxLength": 50,
            "description": "Campaign use case (e.g., ACCOUNT_NOTIFICATION, MARKETING, 2FA)."
          },
          "subUseCases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "description": {
            "type": "string",
            "maxLength": 4096
          },
          "sampleMessages": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 1024
            },
            "minItems": 1,
            "maxItems": 5
          },
          "subscriberOptIn": {
            "type": "boolean"
          },
          "subscriberOptOut": {
            "type": "boolean"
          },
          "subscriberHelp": {
            "type": "boolean"
          },
          "numberPooling": {
            "type": "boolean"
          },
          "directLending": {
            "type": "boolean"
          },
          "embeddedLink": {
            "type": "boolean"
          },
          "embeddedPhone": {
            "type": "boolean"
          },
          "affiliateMarketing": {
            "type": "boolean"
          },
          "ageGated": {
            "type": "boolean"
          },
          "messageFlow": {
            "type": "string",
            "maxLength": 2048
          },
          "helpMessage": {
            "type": "string",
            "maxLength": 500
          },
          "optInKeywords": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            }
          },
          "optOutKeywords": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            }
          }
        }
      },
      "TenDlcCampaignUpdateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "maxLength": 4096
          },
          "sampleMessages": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 1024
            },
            "minItems": 1,
            "maxItems": 5
          },
          "messageFlow": {
            "type": "string",
            "maxLength": 2048
          },
          "helpMessage": {
            "type": "string",
            "maxLength": 500
          },
          "optInKeywords": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            }
          },
          "optOutKeywords": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 50
            }
          }
        }
      },
      "TenDlcCampaignResponse": {
        "type": "object",
        "required": [
          "campaign"
        ],
        "properties": {
          "campaign": {
            "$ref": "#/components/schemas/TenDlcCampaign"
          }
        }
      },
      "TenDlcPhoneNumberAssignment": {
        "type": "object",
        "required": [
          "id",
          "campaignId",
          "phoneNumberId",
          "status",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "campaignId": {
            "type": "string"
          },
          "phoneNumberId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Assignment status. `pending`: sent to the carrier, not confirmed yet. `active`: the number is on the campaign and can send. `failed`: the carrier refused it; see `failureReason`. The number is free to be assigned again.",
            "enum": [
              "pending",
              "active",
              "failed"
            ]
          },
          "failureReason": {
            "type": "string",
            "nullable": true
          },
          "assignedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TenDlcPhoneNumberAssignRequest": {
        "type": "object",
        "required": [
          "phoneNumberId"
        ],
        "properties": {
          "phoneNumberId": {
            "type": "string",
            "description": "ID of the phone number to assign."
          }
        }
      },
      "TenDlcAssignmentResponse": {
        "type": "object",
        "required": [
          "assignment"
        ],
        "properties": {
          "assignment": {
            "$ref": "#/components/schemas/TenDlcPhoneNumberAssignment"
          }
        }
      },
      "ZavuFunctionStatus": {
        "type": "string",
        "description": "Lifecycle status of a Zavu Function.",
        "enum": [
          "draft",
          "bundling",
          "deploying",
          "active",
          "failed",
          "disabled"
        ]
      },
      "ZavuFunctionRuntime": {
        "type": "string",
        "description": "Runtime the function is deployed on.",
        "enum": [
          "nodejs24"
        ]
      },
      "ZavuFunction": {
        "type": "object",
        "description": "A Zavu Function — user-supplied TypeScript that runs in Zavu Cloud and reacts to messaging events or HTTP requests.",
        "required": [
          "id",
          "slug",
          "name",
          "runtime",
          "status",
          "timeoutSec",
          "memoryMb",
          "httpEnabled",
          "dependencies",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "fn_abc123"
          },
          "slug": {
            "type": "string",
            "description": "URL-safe identifier, unique per project.",
            "example": "order-bot"
          },
          "name": {
            "type": "string",
            "example": "Order Bot"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "runtime": {
            "$ref": "#/components/schemas/ZavuFunctionRuntime"
          },
          "status": {
            "$ref": "#/components/schemas/ZavuFunctionStatus"
          },
          "timeoutSec": {
            "type": "integer",
            "description": "Per-invocation timeout in seconds.",
            "example": 10
          },
          "memoryMb": {
            "type": "integer",
            "description": "Memory allocation in MB.",
            "example": 256
          },
          "httpEnabled": {
            "type": "boolean",
            "description": "Whether the function can be invoked over HTTPS via its public URL."
          },
          "publicUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "HTTPS endpoint, present only while httpEnabled is true. Null otherwise, including for a function that was previously exposed — the stored URL stops serving the moment HTTP is turned off, so it is never returned."
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "npm dependencies installed in the function bundle. Keys are package names, values are semver ranges.",
            "example": {
              "openai": "^4.20.0"
            }
          },
          "activeDeploymentId": {
            "type": "string",
            "nullable": true,
            "description": "ID of the deployment currently serving traffic."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ZavuFunctionResponse": {
        "type": "object",
        "required": [
          "function"
        ],
        "properties": {
          "function": {
            "$ref": "#/components/schemas/ZavuFunction"
          }
        }
      },
      "FunctionCreateRequest": {
        "type": "object",
        "required": [
          "slug",
          "name"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "URL-safe identifier (lowercase, digits, hyphens). Must be unique per project.",
            "minLength": 1,
            "maxLength": 50,
            "example": "order-bot"
          },
          "name": {
            "type": "string",
            "maxLength": 80,
            "example": "Order Bot"
          },
          "description": {
            "type": "string",
            "maxLength": 280
          },
          "runtime": {
            "$ref": "#/components/schemas/ZavuFunctionRuntime"
          },
          "timeoutSec": {
            "type": "integer",
            "minimum": 1,
            "maximum": 180,
            "default": 30,
            "description": "Per-invocation timeout in seconds. Event and cron invocations are asynchronous, so a long timeout only bounds cost; a tool called during a live conversation holds up the reply, and a function exposed over HTTP is additionally bounded by the platform's HTTP response limit."
          },
          "memoryMb": {
            "type": "integer",
            "enum": [
              128,
              256,
              512,
              1024
            ],
            "default": 256
          },
          "httpEnabled": {
            "type": "boolean",
            "default": false,
            "description": "Whether to expose a public HTTPS URL for this function."
          },
          "files": {
            "type": "object",
            "description": "The project's source files, keyed by path relative to the project root (e.g. `index.ts`, `lib/orders.ts`). Imports between them are resolved when the function is built, so a function can be split across as many files as it needs.\n\nPaths must be relative and use forward slashes; `..`, `node_modules/` and `package.json` are rejected. npm packages are not uploaded here — declare them under `dependencies` and Zavu installs them. Limits: 200 files and 900,000 bytes for the whole tree.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "index.ts": "import { formatOrder } from './lib/orders';\n\nexport default async function handler(event) {\n  return { statusCode: 200, body: formatOrder(event) };\n}\n",
              "lib/orders.ts": "export function formatOrder(event) {\n  return JSON.stringify(event);\n}\n"
            }
          },
          "entrypoint": {
            "type": "string",
            "description": "Which file in `files` is the entry point. Defaults to `index.ts`.",
            "default": "index.ts",
            "example": "index.ts"
          },
          "sourceCode": {
            "type": "string",
            "description": "Shortcut for a single-file function: exactly equivalent to sending `files` with one entry named after `entrypoint` (`index.ts` by default). Fully supported — use whichever fits. If both are sent, `files` wins.",
            "maxLength": 900000
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "npm dependencies. Keys are package names, values are semver ranges.",
            "example": {
              "openai": "^4.20.0"
            }
          }
        }
      },
      "FunctionDeployRequest": {
        "type": "object",
        "description": "Optional source/dependencies update applied before deploying. Omit every field to redeploy the current draft as-is.",
        "properties": {
          "files": {
            "type": "object",
            "description": "The project's source files, keyed by path relative to the project root (e.g. `index.ts`, `lib/orders.ts`). Imports between them are resolved when the function is built, so a function can be split across as many files as it needs.\n\nPaths must be relative and use forward slashes; `..`, `node_modules/` and `package.json` are rejected. npm packages are not uploaded here — declare them under `dependencies` and Zavu installs them. Limits: 200 files and 900,000 bytes for the whole tree.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "index.ts": "import { formatOrder } from './lib/orders';\n\nexport default async function handler(event) {\n  return { statusCode: 200, body: formatOrder(event) };\n}\n",
              "lib/orders.ts": "export function formatOrder(event) {\n  return JSON.stringify(event);\n}\n"
            }
          },
          "entrypoint": {
            "type": "string",
            "description": "Which file in `files` is the entry point. Defaults to `index.ts`.",
            "default": "index.ts",
            "example": "index.ts"
          },
          "sourceCode": {
            "type": "string",
            "description": "Shortcut for a single-file function: exactly equivalent to sending `files` with one entry named after `entrypoint` (`index.ts` by default). Fully supported — use whichever fits. If both are sent, `files` wins.",
            "maxLength": 900000
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "New dependency map (replaces existing dependencies)."
          }
        }
      },
      "FunctionDeploymentStatus": {
        "type": "string",
        "description": "Stage of a function deployment.",
        "enum": [
          "pending",
          "bundling",
          "uploading",
          "publishing",
          "active",
          "failed",
          "superseded"
        ]
      },
      "FunctionDeployment": {
        "type": "object",
        "required": [
          "id",
          "functionId",
          "version",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "fnd_abc123"
          },
          "functionId": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "description": "Monotonically increasing deployment version, starting at 1."
          },
          "status": {
            "$ref": "#/components/schemas/FunctionDeploymentStatus"
          },
          "sourceCodeBytes": {
            "type": "integer",
            "nullable": true,
            "description": "Total size of the deployed source tree in bytes."
          },
          "bundleBytes": {
            "type": "integer",
            "nullable": true,
            "description": "Size of the built bundle in bytes. Null until the build finishes."
          },
          "errorMessage": {
            "type": "string",
            "nullable": true,
            "description": "Failure reason when status is 'failed'."
          },
          "buildLogs": {
            "type": "string",
            "nullable": true,
            "description": "What the build printed: dependency installation, the bundler's output, and the compiler's message when it failed. Returned when fetching a single deployment, omitted from the list. Read this first when a deploy fails — `errorMessage` is often the outer wrapper's summary, and the line that names the broken import or the syntax error is here."
          },
          "deployedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FunctionDeploymentResponse": {
        "type": "object",
        "required": [
          "deployment"
        ],
        "properties": {
          "deployment": {
            "$ref": "#/components/schemas/FunctionDeployment"
          }
        }
      },
      "FunctionLogEvent": {
        "type": "object",
        "required": [
          "timestamp",
          "message"
        ],
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "FunctionLogsResponse": {
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FunctionLogEvent"
            }
          },
          "nextToken": {
            "type": "string",
            "nullable": true,
            "description": "Pass to the next request to fetch the following page of logs."
          }
        }
      },
      "GitRepoLink": {
        "type": "object",
        "description": "A GitHub repository bound to a function. A push to `branch` deploys the function. A function holds at most one link.",
        "required": [
          "id",
          "functionId",
          "provider",
          "owner",
          "repo",
          "branch",
          "autoDeploy",
          "connection",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "functionId": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "github"
            ]
          },
          "owner": {
            "type": "string",
            "example": "acme"
          },
          "repo": {
            "type": "string",
            "example": "order-bot"
          },
          "branch": {
            "type": "string",
            "description": "Only pushes to this branch deploy.",
            "example": "main"
          },
          "rootDir": {
            "type": "string",
            "nullable": true,
            "description": "Subdirectory holding the project, for monorepos. Null when the project is at the repository root."
          },
          "autoDeploy": {
            "type": "boolean",
            "description": "When false the link is kept and pushes are ignored."
          },
          "connection": {
            "type": "string",
            "description": "How this link authenticates, decided by the server rather than by the caller.\n- `app`: the Zavu GitHub App is installed on the account. Pushes arrive on the app's webhook and private repositories work. Nothing to configure in the repository.\n- `manual`: no installation. The link carries its own secret and you add the webhook to the repository yourself.",
            "enum": [
              "app",
              "manual"
            ]
          },
          "lastStatus": {
            "type": "string",
            "nullable": true,
            "enum": [
              "deploying",
              "deployed",
              "failed",
              null
            ]
          },
          "lastCommitSha": {
            "type": "string",
            "nullable": true
          },
          "lastCommitMessage": {
            "type": "string",
            "nullable": true
          },
          "lastError": {
            "type": "string",
            "nullable": true,
            "description": "Why the last deploy failed. Null otherwise."
          },
          "lastDeployAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GitRepoLinkResponse": {
        "type": "object",
        "required": [
          "link",
          "webhookUrl"
        ],
        "properties": {
          "link": {
            "$ref": "#/components/schemas/GitRepoLink"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "Endpoint that receives GitHub's push deliveries. Only needed on a `manual` link, where you add it to the repository yourself."
          },
          "webhookSecret": {
            "type": "string",
            "description": "Shared secret for the repository's webhook. **Returned only when creating a `manual` link, and only there** — every later read strips it, and re-linking mints a new one. Absent entirely on an `app` link, which needs no secret of its own.",
            "example": "ghs_a1b2c3..."
          }
        }
      },
      "AgentVoiceConfig": {
        "type": "object",
        "description": "Voice Agent configuration on a sender's AI agent. Controls how the agent behaves on inbound and outbound phone calls through Zavu's managed voice pipeline (speech recognition, the agent's LLM, and speech synthesis, with real-time interruption handling).",
        "required": [
          "enabled"
        ],
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether the agent handles voice calls. When false, the sender's number is not answered by the voice agent and outbound calls are rejected."
          },
          "greeting": {
            "type": "string",
            "description": "Opening line the agent speaks when the call connects. If omitted, the agent waits for the caller to speak first.",
            "maxLength": 1000,
            "example": "Hi, thanks for calling Acme. How can I help you today?"
          },
          "greetings": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Greeting per language, keyed by language code. Used when the caller's language differs from the one `greeting` is written in.",
            "example": {
              "es": "Hola, soy Atlas. Preguntame lo que quieras."
            }
          },
          "language": {
            "type": "string",
            "description": "BCP-47 language code used for both speech recognition and speech synthesis (e.g. `en`, `es`, `pt-BR`). Auto-detected from the recipient when omitted.",
            "example": "en"
          },
          "model": {
            "type": "string",
            "description": "Model that runs the conversation, co-located in the voice network for lowest latency. Independent of the model used for text messaging. Derived from the agent's text model when omitted.",
            "example": "openai/gpt-4o"
          },
          "sttProvider": {
            "type": "string",
            "description": "Speech-recognition provider. Uses the default when omitted."
          },
          "sttModel": {
            "type": "string",
            "description": "Speech-recognition model. Uses the default when omitted."
          },
          "ttsProvider": {
            "type": "string",
            "description": "Speech-synthesis provider. Uses the default when omitted."
          },
          "ttsVoiceId": {
            "type": "string",
            "description": "Identifier of the synthesized voice that speaks. Choose from the voices available in the dashboard. Uses a neutral default when omitted.",
            "example": "aria"
          },
          "voiceSpeed": {
            "type": "number",
            "description": "Speech rate. 1.0 is natural. Only honoured by voices that support rate control; ignored by the others.",
            "minimum": 0.5,
            "maximum": 1.5,
            "default": 1.0
          },
          "interruptible": {
            "type": "boolean",
            "description": "Whether the caller can interrupt the agent while it is speaking (barge-in). When true, the agent stops talking as soon as the caller starts.",
            "default": true
          },
          "maxCallDurationMinutes": {
            "type": "integer",
            "description": "Hard limit on call length in minutes. The call ends automatically when reached.",
            "minimum": 1,
            "maximum": 120,
            "default": 15
          },
          "maxIdleSeconds": {
            "type": "integer",
            "description": "How long the agent waits during silence before ending the call.",
            "minimum": 5,
            "maximum": 300,
            "default": 30
          },
          "recordCalls": {
            "type": "boolean",
            "description": "Whether the call audio is recorded."
          },
          "voicemailAction": {
            "type": "string",
            "description": "What the agent does when an answering machine or voicemail is detected on an outbound call.",
            "enum": [
              "hangup",
              "leave_message"
            ],
            "default": "hangup"
          },
          "voicemailMessage": {
            "type": "string",
            "description": "Message spoken when `voicemailAction` is `leave_message`. Falls back to `greeting` when omitted.",
            "maxLength": 1000
          },
          "transferPhoneNumber": {
            "type": "string",
            "description": "E.164 phone number the agent can transfer the call to. When set, the agent is given a transfer tool it can use to hand the call to a human.",
            "example": "+14155551234"
          }
        }
      },
      "VoiceCall": {
        "type": "object",
        "required": [
          "id",
          "direction",
          "from",
          "to",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "call_abc123"
          },
          "direction": {
            "$ref": "#/components/schemas/VoiceCallDirection"
          },
          "from": {
            "type": "string",
            "description": "Caller phone number in E.164 format. Your sender's number for outbound calls; the caller's number for inbound calls.",
            "example": "+13125551212"
          },
          "to": {
            "type": "string",
            "description": "Callee phone number in E.164 format.",
            "example": "+56912345678"
          },
          "status": {
            "$ref": "#/components/schemas/VoiceCallStatus"
          },
          "endReason": {
            "type": "string",
            "nullable": true,
            "description": "Why the call ended (e.g. `agent_ended`, `max_duration`, `transfer`, `hangup`). Present once the call is no longer active."
          },
          "answeredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the call was answered."
          },
          "endedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the call ended."
          },
          "durationSeconds": {
            "type": "integer",
            "nullable": true,
            "description": "Billable talk time in seconds, measured from answer to hangup."
          },
          "turnCount": {
            "type": "integer",
            "nullable": true,
            "description": "Number of conversation turns exchanged during the call."
          },
          "transcript": {
            "type": "array",
            "description": "Ordered transcript of the call. Included when retrieving a single call; omitted from list responses.",
            "items": {
              "$ref": "#/components/schemas/VoiceCallTurn"
            }
          },
          "cost": {
            "type": "number",
            "nullable": true,
            "description": "Total cost of the call in USD, combining the managed voice pipeline per-minute charge, telephony, and, on an agent that runs a premium model, that model's tokens. Available once the call has ended. Tokens are charged between 30 minutes and 24 hours after the call ends, so the figure can grow until then."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Arbitrary metadata you attached when creating the call."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VoiceCallCreateRequest": {
        "type": "object",
        "description": "Request body to place an outbound voice call. The call is answered by the voice agent configured on the sender.",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient phone number in E.164 format.",
            "example": "+56912345678"
          },
          "senderId": {
            "type": "string",
            "description": "Sender profile that places the call. Uses the project's default sender if omitted. The sender's agent must have voice enabled.",
            "example": "sender_12345"
          },
          "greeting": {
            "type": "string",
            "description": "Overrides the agent's configured greeting for this call only.",
            "maxLength": 1000
          },
          "language": {
            "type": "string",
            "description": "Language the agent speaks on this call only, as a BCP-47 tag (`en`, `es`, `es-ES`, `pt-BR`), or `auto` to detect the caller's language and follow it. Overrides the agent's configured language for speech recognition, the agent's replies, and the synthesized voice. If the agent uses a custom voice you supplied, that voice is kept and only the language changes. When omitted, the agent's configured language is used.",
            "maxLength": 15,
            "example": "es-ES"
          },
          "maxDurationMinutes": {
            "type": "integer",
            "description": "Overrides the agent's maximum call duration for this call only.",
            "minimum": 1,
            "maximum": 120
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Arbitrary metadata to associate with the call. Returned on the call object and included in voice webhooks."
          }
        }
      },
      "VoiceCallDirection": {
        "type": "string",
        "description": "Whether the call was placed by Zavu (outbound) or received from a caller (inbound).",
        "enum": [
          "inbound",
          "outbound"
        ]
      },
      "VoiceCallResponse": {
        "type": "object",
        "required": [
          "call"
        ],
        "properties": {
          "call": {
            "$ref": "#/components/schemas/VoiceCall"
          }
        }
      },
      "VoiceCallStatus": {
        "type": "string",
        "description": "Lifecycle status of a voice call.\n- `queued`: outbound call created, not yet dialing.\n- `ringing`: dialing (outbound) or received and ringing (inbound).\n- `in_progress`: answered, the agent is connected.\n- `completed`: ended after a conversation.\n- `failed`: could not be completed.\n- `busy`: the line was busy.\n- `no_answer`: rang but was not answered.\n- `canceled`: canceled before it was answered.",
        "enum": [
          "queued",
          "ringing",
          "in_progress",
          "completed",
          "failed",
          "busy",
          "no_answer",
          "canceled"
        ]
      },
      "VoiceCallTurn": {
        "type": "object",
        "description": "A single turn in a voice call transcript.",
        "required": [
          "seq",
          "role",
          "text"
        ],
        "properties": {
          "seq": {
            "type": "integer",
            "description": "Ordinal position of the turn within the call, starting at 0."
          },
          "role": {
            "type": "string",
            "description": "Who produced the turn. `tool` records a tool call the agent made during the conversation.",
            "enum": [
              "user",
              "assistant",
              "tool"
            ]
          },
          "text": {
            "type": "string",
            "description": "Transcribed speech for `user` and `assistant` turns, or a JSON summary of the tool call for `tool` turns."
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the turn started."
          },
          "endedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the turn ended."
          }
        }
      },
      "AgentTemplate": {
        "type": "object",
        "description": "A fully rendered factory agent: the function files to scaffold plus the secrets it needs. Returned by GET /v1/agent-templates/{templateId} and consumed by `npx zavudev agents pull`.",
        "required": [
          "id",
          "name",
          "summary",
          "category",
          "voice",
          "defaultSlug",
          "files",
          "dependencies",
          "requiredSecrets"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "fermi"
          },
          "name": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "sales",
              "support",
              "frontDesk",
              "ops"
            ]
          },
          "voice": {
            "type": "boolean"
          },
          "defaultSlug": {
            "type": "string",
            "example": "fermi"
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentTemplateFile"
            }
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "npm dependencies for the scaffolded function."
          },
          "requiredSecrets": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "hint"
              ],
              "properties": {
                "key": {
                  "type": "string",
                  "example": "SENDER_ID"
                },
                "hint": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AgentTemplateFile": {
        "type": "object",
        "required": [
          "path",
          "content"
        ],
        "properties": {
          "path": {
            "type": "string",
            "example": "index.ts"
          },
          "content": {
            "type": "string",
            "description": "File contents to write verbatim."
          }
        }
      },
      "AgentTemplateSummary": {
        "type": "object",
        "description": "Compact catalog entry for a factory agent.",
        "required": [
          "id",
          "name",
          "summary",
          "category",
          "voice",
          "toolCount"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "fermi"
          },
          "name": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "sales",
              "support",
              "frontDesk",
              "ops"
            ]
          },
          "voice": {
            "type": "boolean",
            "description": "Whether this agent answers phone calls."
          },
          "toolCount": {
            "type": "integer"
          }
        }
      },
      "EmailDomain": {
        "type": "object",
        "required": [
          "id",
          "domain",
          "status",
          "dkimStatus"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "emd_abc123"
          },
          "domain": {
            "type": "string",
            "example": "example.com"
          },
          "status": {
            "type": "string",
            "description": "Overall verification status.",
            "example": "pending"
          },
          "dkimStatus": {
            "type": "string",
            "example": "not_started"
          },
          "dnsRecords": {
            "type": "array",
            "description": "DNS records to publish. Present when fetching a single domain or after adding one.",
            "items": {
              "$ref": "#/components/schemas/EmailDomainDnsRecord"
            }
          }
        }
      },
      "EmailDomainDnsRecord": {
        "type": "object",
        "required": [
          "type",
          "name",
          "value",
          "purpose",
          "required"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "DNS record type.",
            "example": "CNAME"
          },
          "name": {
            "type": "string",
            "description": "Record host/name to create."
          },
          "value": {
            "type": "string",
            "description": "Record value."
          },
          "priority": {
            "type": "integer",
            "description": "Priority (MX records only)."
          },
          "purpose": {
            "type": "string",
            "description": "What the record is for. `ownership` is a TXT at `_zavu-verification.<domain>`, returned only when another Zavu account also added this domain and neither has verified it: DKIM records are the same for everyone, so the domain verifies only once this account's own token is published.",
            "enum": [
              "dkim",
              "spf",
              "dmarc",
              "mail_from",
              "ownership"
            ]
          },
          "required": {
            "type": "boolean",
            "description": "Whether the record is required to verify + send (DKIM) or recommended for deliverability."
          }
        }
      },
      "AgentTestRequest": {
        "type": "object",
        "description": "Run the agent and return what it would say. Nothing is delivered to anyone and no execution is recorded.\n\nA run is **billed like a live reply**: on an agent with the `zavu` provider it is charged to your balance at the same price per token, and refused with `402 insufficient_balance` at a balance of zero. On a bring-your-own-key agent it costs your provider, not Zavu. `cost` on the response is what this run charged.",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "What to say to the agent.",
            "example": "Where is order ORD-12345?"
          },
          "history": {
            "type": "array",
            "description": "Prior turns, oldest first, to exercise multi-turn behaviour without persisting a thread. Trimmed to the agent's context window.",
            "items": {
              "type": "object",
              "required": [
                "role",
                "content"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "user",
                    "assistant"
                  ]
                },
                "content": {
                  "type": "string"
                }
              }
            }
          },
          "useKnowledgeBase": {
            "type": "boolean",
            "default": true,
            "description": "Set false to skip retrieval and isolate prompt behaviour from the knowledge base."
          },
          "executeTools": {
            "type": "boolean",
            "default": false,
            "description": "Run the tools the agent calls instead of reporting the choice and stopping.\n\nOff by default because a tool handler talks to the outside world: a rehearsal that charges a card is not a rehearsal. Turn it on to exercise the loop that actually matters — the model picks a tool, the handler answers, the model replies with the result — without sending a message to anyone. What ran comes back in `executedToolCalls`."
          }
        }
      },
      "AgentTestResponse": {
        "type": "object",
        "required": [
          "cost",
          "error",
          "inputTokens",
          "knowledgeChunksUsed",
          "latencyMs",
          "outputTokens",
          "plannedToolCalls",
          "success",
          "text",
          "warnings"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "text": {
            "type": "string",
            "nullable": true,
            "description": "What the agent would reply."
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "inputTokens": {
            "type": "integer"
          },
          "outputTokens": {
            "type": "integer"
          },
          "latencyMs": {
            "type": "integer"
          },
          "knowledgeChunksUsed": {
            "type": "integer",
            "description": "Knowledge-base chunks retrieved for this message. Zero means the answer was not grounded in your documents."
          },
          "executedToolCalls": {
            "type": "array",
            "description": "Tools that actually ran, in order, when the request set `executeTools`. Empty on a normal dry run, where nothing is executed. An entry with `ok: false` means the agent saw an error and answered around it, which is what a customer would have received.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "ok"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "ok": {
                  "type": "boolean"
                },
                "error": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Things that are true of this agent but that a dry run cannot prove. Surfaced so a passing dry run is never mistaken for proof that the agent works live.\n\n- The agent being disabled.\n- Enabled tools that were offered to the model but not run: if the agent picks one, the reply stops at that choice (see `plannedToolCalls`). Pass `executeTools` to run them.\n- Tools that ran for real, when `executeTools` was set, and any that failed.\n- An agent whose sender has none of the channels it triggers on, which answers every dry run and no real message.\n- Contact metadata that exists on a real conversation but not here, and memory tools (`remember`, `recall`, `save_record`), which have no contact to write about on a test run."
          },
          "cost": {
            "type": "number",
            "description": "USD charged to your balance for this run. 0 on a bring-your-own-key agent. When the balance covers only part of a run, that part is charged and the next run is refused with `402`."
          },
          "plannedToolCalls": {
            "type": "array",
            "description": "Tools the model chose to call, with the arguments it chose, on a run without `executeTools`. Nothing was executed: the reply stops at the choice. Empty when it answered without reaching for a tool, and on a run that executed tools (see `executedToolCalls`).",
            "items": {
              "type": "object",
              "required": [
                "name",
                "arguments"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "arguments": {
                  "type": "string",
                  "description": "The arguments as a JSON string."
                }
              }
            }
          }
        }
      },
      "AgentCreateStandaloneRequest": {
        "type": "object",
        "description": "Create an agent that exists on its own. Connect senders afterwards; an agent answers on any number of them.\n\n**Sub-resources.** An agent's tools, flows and knowledge bases are reachable at `/v1/agents/{agentId}/tools`, `/v1/agents/{agentId}/flows` and `/v1/agents/{agentId}/knowledge-bases`, mirroring the sender-scoped routes documented under `/v1/senders/{senderId}/agent/...` exactly. Use the agent-scoped form while the agent has no sender: the sender-scoped one cannot address it.",
        "required": [
          "name",
          "provider",
          "model",
          "systemPrompt"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "provider": {
            "$ref": "#/components/schemas/AgentProvider"
          },
          "model": {
            "type": "string"
          },
          "systemPrompt": {
            "type": "string",
            "maxLength": 10000
          },
          "apiKey": {
            "type": "string",
            "description": "API key for the LLM provider. Required unless provider is 'zavu'. Stored encrypted and never returned."
          },
          "contextWindowMessages": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 10
          },
          "includeContactMetadata": {
            "type": "boolean",
            "default": true
          },
          "triggerOnChannels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "default": [
              "*"
            ]
          },
          "triggerOnMessageTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "default": [
              "text"
            ]
          },
          "maxTokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4096
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2
          },
          "voice": {
            "$ref": "#/components/schemas/AgentVoiceConfig"
          }
        }
      },
      "FunctionUpdateRequest": {
        "type": "object",
        "description": "Fields to update on an existing function. Provide at least one. `files` / `sourceCode` and `dependencies` edit the draft and take effect on the next deploy; `httpEnabled` applies immediately to the deployed function.",
        "properties": {
          "files": {
            "type": "object",
            "description": "The project's source files, keyed by path relative to the project root (e.g. `index.ts`, `lib/orders.ts`). Imports between them are resolved when the function is built, so a function can be split across as many files as it needs.\n\nPaths must be relative and use forward slashes; `..`, `node_modules/` and `package.json` are rejected. npm packages are not uploaded here — declare them under `dependencies` and Zavu installs them. Limits: 200 files and 900,000 bytes for the whole tree.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "index.ts": "import { formatOrder } from './lib/orders';\n\nexport default async function handler(event) {\n  return { statusCode: 200, body: formatOrder(event) };\n}\n",
              "lib/orders.ts": "export function formatOrder(event) {\n  return JSON.stringify(event);\n}\n"
            }
          },
          "entrypoint": {
            "type": "string",
            "description": "Which file in `files` is the entry point. Defaults to `index.ts`.",
            "default": "index.ts",
            "example": "index.ts"
          },
          "sourceCode": {
            "type": "string",
            "description": "Shortcut for a single-file function: exactly equivalent to sending `files` with one entry named after `entrypoint` (`index.ts` by default). Fully supported — use whichever fits. If both are sent, `files` wins.",
            "maxLength": 900000
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "New dependency map (replaces existing dependencies)."
          },
          "httpEnabled": {
            "type": "boolean",
            "description": "Expose the function on its public HTTPS URL, or take it down. Applies to the already-deployed function without redeploying; the URL is returned as `publicUrl`."
          }
        }
      },
      "WebhookSignatureVersion": {
        "type": "string",
        "description": "Which `X-Zavu-Signature` scheme this receiver is sent.\n\n- `v1`: `v1=HMAC_SHA256(secret, body)`. The scheme used before this was configurable. Existing webhooks stay on it until you move them.\n- `v2`: `v2=HMAC_SHA256(secret, \"{t}.{body}\")`. The current scheme, and the default for new senders. It signs the timestamp together with the body.\n- `v1+v2`: both signatures, sharing one `t`. The migration setting: a receiver reading either one works, so you can deploy and confirm your new verifier before switching over.\n\nMoving from `v1` straight to `v2` returns `400`. Set `v1+v2` first. See https://docs.zavu.dev/guides/receiving-messages/signature-migration",
        "enum": [
          "v1",
          "v1+v2",
          "v2"
        ],
        "example": "v2"
      },
      "ProjectWebhook": {
        "type": "object",
        "description": "The project-level webhook. It receives project events such as `invitation.status_changed` for this project only: a parent project does not receive its sub-accounts' events, so configure one per sub-account with that sub-account's API key.",
        "required": [
          "url",
          "events",
          "active"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://your-app.com/webhooks/zavu"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectWebhookEvent"
            }
          },
          "secret": {
            "type": "string",
            "description": "Signing secret for `X-Zavu-Signature`. Returned only by `POST /v1/invitations/webhook`.",
            "example": "whsec_abc123..."
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "ProjectWebhookEvent": {
        "type": "string",
        "description": "Events a project webhook can subscribe to. Any other value is refused with `400`.",
        "enum": [
          "invitation.status_changed",
          "broadcast.status_changed",
          "domain.verified",
          "domain.failed"
        ]
      },
      "ProjectWebhookCreateRequest": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://your-app.com/webhooks/zavu"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ProjectWebhookEvent"
            },
            "example": [
              "invitation.status_changed"
            ]
          }
        }
      },
      "ProjectWebhookUpdateRequest": {
        "type": "object",
        "description": "Only valid on a webhook created with `POST /v1/invitations/webhook`; updating one that does not exist returns `400`.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ProjectWebhookEvent"
            }
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "ActivatableChannel": {
        "type": "string",
        "description": "A channel whose connection has an on/off switch. `sms`, `sms_oneway` and `voice` are billed per message and per phone number, are always on, and have nothing to activate.",
        "enum": [
          "whatsapp",
          "telegram",
          "instagram",
          "messenger",
          "email"
        ]
      },
      "SenderChannelActivationResponse": {
        "type": "object",
        "required": [
          "sender",
          "channel",
          "activated",
          "chargedCents",
          "monthlyCents"
        ],
        "properties": {
          "sender": {
            "$ref": "#/components/schemas/Sender",
            "description": "The sender after the change. `channels` includes the channel once it is active."
          },
          "channel": {
            "$ref": "#/components/schemas/ActivatableChannel"
          },
          "activated": {
            "type": "boolean",
            "description": "True when this call switched the channel on. False when it was already active; nothing was charged."
          },
          "chargedCents": {
            "type": "integer",
            "description": "Cents taken from the balance by this call. Zero when the channel was already active, its month is already paid, or the plan includes it."
          },
          "monthlyCents": {
            "type": "integer",
            "description": "Cents this connection adds to each month while it stays active. Zero when the plan includes it."
          }
        }
      },
      "SenderChannelDeactivationResponse": {
        "type": "object",
        "required": [
          "sender",
          "channel",
          "deactivated"
        ],
        "properties": {
          "sender": {
            "$ref": "#/components/schemas/Sender"
          },
          "channel": {
            "$ref": "#/components/schemas/ActivatableChannel"
          },
          "deactivated": {
            "type": "boolean",
            "description": "True when this call switched the channel off. False when it was already inactive."
          }
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/memory/collections": {
      "post": {
        "summary": "Create a collection",
        "description": "Declare a collection of JSON documents addressed by key. This is the counterpart to facts: `POST /v1/memory` recalls by meaning, a collection recalls by key.\n\n**Create it explicitly when you need `query`.** Writing to an unknown collection creates it implicitly, but with no indexes — and indexes cannot be added afterwards. If you will ever look items up by a field rather than by key, declare that field here, before the first write.\n\nA collection is defined once for the project. Its ITEMS are per-scope, so the same collection holds a separate set of documents for the project, for each contact, and for each conversation.",
        "operationId": "createMemoryCollection",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryCollectionCreateRequest"
              },
              "example": {
                "name": "orders",
                "indexes": [
                  {
                    "field": "status"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Collection created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "collection"
                  ],
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/MemoryCollection"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid name, index field, or TTL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A collection with that name already exists (`already_exists`), or one is still being deleted (`collection_deleting`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The plan's collection cap is reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List collections",
        "operationId": "listMemoryCollections",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Collections in the project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoryCollection"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/collections/{collection}": {
      "get": {
        "summary": "Get a collection",
        "description": "The collection's definition: its indexes, default TTL and status. Collections are project-level, so this takes no scope.",
        "operationId": "getMemoryCollection",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Collection details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "collection"
                  ],
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/MemoryCollection"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid collection name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update a collection",
        "description": "Only `defaultTtlSeconds` can change. Indexes are fixed at creation: changing them would require rewriting every item in the collection.",
        "operationId": "updateMemoryCollection",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryCollectionUpdateRequest"
              },
              "example": {
                "defaultTtlSeconds": 2592000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Collection updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "collection"
                  ],
                  "properties": {
                    "collection": {
                      "$ref": "#/components/schemas/MemoryCollection"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid TTL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete a collection",
        "description": "Delete the collection and every item it holds, **in every scope** — the per-contact and per-conversation copies included. Returns immediately; the purge drains in batches and writes are refused while `status` is `deleting`.",
        "operationId": "deleteMemoryCollection",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          }
        ],
        "responses": {
          "202": {
            "description": "Purge scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleting"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid collection name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/collections/{collection}/items": {
      "get": {
        "summary": "List items",
        "description": "Items in one scope of a collection, by key in lexicographic order. Narrow with `prefix` — key your documents so a prefix is meaningful (`order:2026-08:...`) and this becomes a range scan rather than a filter.",
        "operationId": "listMemoryItems",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          },
          {
            "name": "prefix",
            "in": "query",
            "description": "Only keys starting with this string.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Items in the scope.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoryItem"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope or query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`collection_not_found`: no collection with that name. A collection exists once it is created or written to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/collections/{collection}/items/batch": {
      "post": {
        "summary": "Write items in bulk",
        "description": "Up to 25 sets and deletes in one call, in one scope.\n\n**Not a transaction.** Each operation succeeds or fails on its own and is reported separately; the call returns `200` even when some failed. Check every entry's `ok` — treating a 200 as \"all 25 landed\" is the mistake this endpoint invites.",
        "operationId": "batchMemoryItems",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryItemBatchRequest"
              },
              "example": {
                "ops": [
                  {
                    "op": "set",
                    "key": "ORD-1",
                    "value": {
                      "status": "shipped"
                    }
                  },
                  {
                    "op": "delete",
                    "key": "ORD-0"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every operation was attempted. Inspect each result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemoryItemBatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, scope, or more than 25 operations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The plan's monthly unit quota is spent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/collections/{collection}/items/{key}": {
      "put": {
        "summary": "Write an item",
        "description": "Store a JSON document under a key. Creates the collection if it does not exist — with **no indexes**, which `query` then cannot use. Declare indexes with `POST /v1/memory/collections` first if you need them.\n\nThis replaces the whole document; it does not merge. Pass `If-Match` with the revision from a previous read to make the write conditional.",
        "operationId": "putMemoryItem",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryItemKeyParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          },
          {
            "$ref": "#/components/parameters/MemoryIfMatchParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryItemPutRequest"
              },
              "example": {
                "value": {
                  "status": "shipped",
                  "total": 42.5
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing item replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "item"
                  ],
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/MemoryItem"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Item created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "item"
                  ],
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/MemoryItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid key, scope, TTL, or `If-Match`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`revision_conflict`: the item changed since the revision in `If-Match`. Re-read it and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "`document_too_large`: the serialized value exceeds 200KB. Memory is not a blob store; put media in file storage and keep the URL here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The plan's monthly unit quota is spent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "Get an item",
        "operationId": "getMemoryItem",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryItemKeyParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "item"
                  ],
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/MemoryItem"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid collection name, key, or `scope`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`collection_not_found` or `item_not_found`. An item written in one scope is not found in another — pass the same `?scope=`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete an item",
        "description": "Idempotent: deleting a key that is not there, or a key of a collection that does not exist, still answers `204`. Deletes are never refused for quota reasons.",
        "operationId": "deleteMemoryItem",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryItemKeyParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          },
          {
            "$ref": "#/components/parameters/MemoryIfMatchParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Item deleted."
          },
          "400": {
            "description": "Invalid collection name, key, `scope`, or `If-Match`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`revision_conflict`: the item changed since the revision in `If-Match`. Re-read it and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/collections/{collection}/query": {
      "post": {
        "summary": "Query items by field",
        "description": "Find items by an indexed field instead of by key, within one scope.\n\nThe field must have been declared in `indexes` when the collection was created. Querying any other field returns `400 field_not_indexed` — including every field of a collection that was auto-created by its first write, which has no indexes at all.",
        "operationId": "queryMemoryItems",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryCollectionParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryItemQueryRequest"
              },
              "examples": {
                "eq": {
                  "summary": "Exact match",
                  "value": {
                    "where": {
                      "field": "status",
                      "op": "eq",
                      "value": "shipped"
                    }
                  }
                },
                "between": {
                  "summary": "Inclusive range",
                  "value": {
                    "where": {
                      "field": "total",
                      "op": "between",
                      "from": 10,
                      "to": 100
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoryItem"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or scope, or `field_not_indexed` when the field carries no index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "field_not_indexed": {
                    "summary": "Field has no index",
                    "value": {
                      "code": "field_not_indexed",
                      "message": "Field 'status' is not indexed on this collection. Indexes are declared when the collection is created and cannot be added later."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Collection not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory": {
      "post": {
        "summary": "Remember a fact",
        "description": "Store one fact so the agent can recall it later by meaning. The text is embedded on write; retrieval is semantic, not keyword.\n\n**Scope goes in the body here.** Pass `scope` plus the matching `contactId` / `conversationId`. Omit it for the project scope. Every read endpoint takes the scope as a `?scope=` query parameter instead — see the `scope` parameter on `GET /v1/memory`.\n\n**One fact per call.** A paragraph carrying five unrelated facts is embedded as an average of all five and retrieved well by none of them.",
        "operationId": "addMemory",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoryAddRequest"
              },
              "examples": {
                "project": {
                  "summary": "A fact the whole project shares",
                  "value": {
                    "text": "Orders placed before 2pm ship the same day."
                  }
                },
                "contact": {
                  "summary": "A fact about one person",
                  "value": {
                    "text": "Prefers WhatsApp over email.",
                    "scope": "contact",
                    "contactId": "jd7x2k3m4n5p6q7r8s9t0abc",
                    "metadata": {
                      "source": "onboarding"
                    }
                  }
                },
                "expiring": {
                  "summary": "A fact that expires",
                  "value": {
                    "text": "Currently travelling, back on the 14th.",
                    "scope": "contact",
                    "contactId": "jd7x2k3m4n5p6q7r8s9t0abc",
                    "ttlSeconds": 1209600
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Fact stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "memory"
                  ],
                  "properties": {
                    "memory": {
                      "$ref": "#/components/schemas/MemoryRecord"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, oversized text, or a scope without its id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A quota is exhausted. `memory_scope_full` means this scope is at its cap (10,000 facts for project, 2,000 per contact, 1,000 per conversation); `memory_limit_exceeded` means the plan's monthly unit quota is spent. Reads and deletes keep working in both cases.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "scope_full": {
                    "summary": "Scope at capacity",
                    "value": {
                      "code": "memory_scope_full",
                      "message": "This memory scope is full. Delete facts you no longer need, or store them under a narrower scope."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Memory is not available for this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List facts",
        "description": "Every fact in a scope, newest first. This is the bookkeeping view — to actually recall something, use `POST /v1/memory/search`, which ranks by meaning.",
        "operationId": "listMemories",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`. Do not construct it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Facts in the scope.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Memory"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope or query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Forget an entire scope",
        "description": "Delete every fact in one scope. Returns immediately; the purge runs in batches in the background, so a list right afterwards can still show rows.\n\nThis does not touch collections — `DELETE /v1/memory/collections/{collection}` does that.",
        "operationId": "wipeMemoryScope",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "responses": {
          "202": {
            "description": "Purge scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "deleting"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/search": {
      "post": {
        "summary": "Recall facts by meaning",
        "description": "Embed the query and return the closest facts in the scope, ranked by cosine similarity.\n\n**Scope goes in the body**, like `POST /v1/memory`: `scope` plus the matching id.\n\nWithout `minScore`, a scope always answers with its `limit` closest facts however distant they are — a search that found nothing relevant is indistinguishable from one that found the right thing. Set `minScore` when your code branches on \"did I know this?\".",
        "operationId": "searchMemories",
        "tags": [
          "Memory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemorySearchRequest"
              },
              "examples": {
                "contact": {
                  "summary": "What do I know about this person?",
                  "value": {
                    "query": "how do they prefer to be contacted",
                    "scope": "contact",
                    "contactId": "jd7x2k3m4n5p6q7r8s9t0abc",
                    "limit": 5,
                    "minScore": 0.3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching facts, closest first. An empty `items` means nothing cleared `minScore`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Memory"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or a scope without its id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/usage": {
      "get": {
        "summary": "Get Memory usage",
        "description": "This month's Memory consumption for the project, with the plan quotas it is measured against. Read `capReached` to know whether writes are currently being refused.",
        "operationId": "getMemoryUsage",
        "tags": [
          "Memory"
        ],
        "responses": {
          "200": {
            "description": "Usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "usage"
                  ],
                  "properties": {
                    "usage": {
                      "$ref": "#/components/schemas/MemoryUsage"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/memory/{memoryId}": {
      "get": {
        "summary": "Get a fact",
        "operationId": "getMemory",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryIdParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The fact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "memory"
                  ],
                  "properties": {
                    "memory": {
                      "$ref": "#/components/schemas/Memory"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed memory id or `scope`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such fact in that scope. A fact stored under `contact` is not found by a project-scoped read — pass the same `?scope=` it was written with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Forget a fact",
        "description": "Delete one fact. Idempotent: deleting a fact that is not there, or no longer there, still answers `204`. Deletes are never refused for quota reasons.",
        "operationId": "deleteMemory",
        "tags": [
          "Memory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemoryIdParam"
          },
          {
            "$ref": "#/components/parameters/MemoryScopeParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Fact deleted."
          },
          "400": {
            "description": "Malformed memory id or `scope`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/event-types": {
      "get": {
        "summary": "List function event types",
        "description": "List the event types a function trigger can subscribe to. Includes the special type `cron`, which fires on a schedule (see POST /v1/functions/{functionId}/triggers) rather than on a messaging event.",
        "operationId": "listFunctionEventTypes",
        "tags": [
          "Functions"
        ],
        "responses": {
          "200": {
            "description": "Supported event types.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "message.inbound",
                        "message.delivered",
                        "cron"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/triggers": {
      "get": {
        "summary": "List function triggers",
        "operationId": "listFunctionTriggers",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Triggers for the function.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "triggers"
                  ],
                  "properties": {
                    "triggers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FunctionTrigger"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create function triggers",
        "description": "Subscribe a function to one or more event types, optionally scoped to specific senders. Provide eventTypes and senderIds (use null in senderIds for all senders); a trigger is created for each event type and sender combination.\n\nThe special event type `cron` runs the function on a schedule instead of a messaging event: include a `cron` field with a 5-field UTC cron expression (minimum granularity one minute). A cron trigger ignores the sender axis, and a function may hold several cron triggers with different expressions. The function receives an event with `type: \"cron\"` and `data.cron`.",
        "operationId": "createFunctionTriggers",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "eventTypes",
                  "senderIds"
                ],
                "properties": {
                  "eventTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event types to subscribe to."
                  },
                  "senderIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "nullable": true
                    },
                    "description": "Senders to scope the triggers to. Use null for all senders."
                  },
                  "cron": {
                    "type": "string",
                    "description": "Required when eventTypes includes `cron`: a 5-field cron expression (minute hour day-of-month month day-of-week), evaluated in UTC.",
                    "example": "0 9 * * 1-5"
                  }
                }
              },
              "example": {
                "eventTypes": [
                  "message.inbound"
                ],
                "senderIds": [
                  null
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Triggers created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "added",
                    "skipped",
                    "triggers"
                  ],
                  "properties": {
                    "added": {
                      "type": "integer"
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Number of triggers that already existed."
                    },
                    "triggers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FunctionTrigger"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/triggers/{triggerId}": {
      "patch": {
        "summary": "Enable or disable a trigger",
        "operationId": "updateFunctionTrigger",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "name": "triggerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "active"
                ],
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Trigger updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "active"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "active": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Trigger not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete a trigger",
        "operationId": "deleteFunctionTrigger",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "name": "triggerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Trigger deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Trigger not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/deployments": {
      "get": {
        "summary": "List function deployments",
        "description": "List a function's deployment history, newest first. Source code is omitted; fetch a single deployment via GET /v1/functions/deployments/{deploymentId} for full details.",
        "operationId": "listFunctionDeployments",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deployment history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deployments"
                  ],
                  "properties": {
                    "deployments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "version": {
                            "type": "integer"
                          },
                          "status": {
                            "$ref": "#/components/schemas/FunctionDeploymentStatus"
                          },
                          "bundleSizeBytes": {
                            "type": "integer",
                            "nullable": true
                          },
                          "errorMessage": {
                            "type": "string",
                            "nullable": true
                          },
                          "isActive": {
                            "type": "boolean"
                          },
                          "deployedAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "description": "When the deployment went live. Null for a deployment that failed or is still building."
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/rollback": {
      "post": {
        "summary": "Roll back to a previous deployment",
        "description": "Re-deploy a previous version by copying its source, dependencies, and runtime pin onto the function's draft, then deploying. Returns immediately with a deployment ID — poll GET /v1/functions/deployments/{deploymentId} until status is active or failed. Secrets are not rolled back.",
        "operationId": "rollbackFunction",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "deploymentId"
                ],
                "properties": {
                  "deploymentId": {
                    "type": "string",
                    "description": "ID of the deployment to roll back to."
                  }
                }
              },
              "example": {
                "deploymentId": "fnd_abc123"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Rollback deployment queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deployment"
                  ],
                  "properties": {
                    "deployment": {
                      "$ref": "#/components/schemas/FunctionDeployment"
                    },
                    "rolledBackToVersion": {
                      "type": "integer"
                    },
                    "previousDraft": {
                      "type": "object",
                      "nullable": true,
                      "description": "The draft that was replaced, so a UI can offer to restore it."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or function in a non-deployable state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function or deployment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/urls/{urlId}/escalate": {
      "post": {
        "summary": "Escalate a rejected URL",
        "description": "Request manual review of a rejected URL. Only URLs in 'rejected' status can be escalated; the status then moves to 'escalated'.",
        "operationId": "escalateUrl",
        "tags": [
          "URL Verification"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/UrlIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 1000,
                    "description": "Why the URL should be reviewed manually."
                  }
                }
              },
              "example": {
                "reason": "This is our official landing page and was rejected in error."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "URL escalated for manual review.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url",
                    "message"
                  ],
                  "properties": {
                    "url": {
                      "$ref": "#/components/schemas/VerifiedUrl"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Only rejected URLs can be escalated, or the URL is already escalated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "URL not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/messages": {
      "post": {
        "summary": "Send a message",
        "description": "Send a message to a recipient via SMS or WhatsApp.\n\n**Channel selection:**\n- If `channel` is omitted and `messageType` is `text`, defaults to SMS\n- If `messageType` is anything other than `text`, WhatsApp is used automatically\n\n**WhatsApp 24-hour window:**\n- Free-form messages (non-template) require an open 24h window\n- Window opens when the user messages you first\n- Use template messages to initiate conversations outside the window\n\n**Plan allowances and email billing:**\n- WhatsApp, Telegram, Instagram and Messenger share an allowance of 2,000 messages per month on Free. **It counts messages in both directions**: a message a contact sends you consumes one unit exactly as a message you send them does, so a project that has sent 300 and received 1,700 has used the whole allowance. Messages you send from the WhatsApp Business App on your own phone under coexistence are mirrored into your inbox but never counted, and neither are failed sends. Over the allowance, sends return 429 with code `a2p_limit_exceeded` and upgrade details, **and inbound messages on those channels are refused as well**: not stored, not shown in the inbox, and no `message.inbound` webhook, and not delivered later when the month resets. The counter resets on the 1st of each month. Paid plans have no message caps\n- Email is billed from your prepaid balance in 1,000-message blocks: $0.40 per 1,000 transactional emails, $0.80 per 1,000 marketing (broadcast) emails. A block is charged when your monthly count crosses each 1,000 boundary, and at zero balance email sends return 402 with code `insufficient_balance`. Free teams start with $2 of credit and additionally cap at 3,000 emails/month and 100/day. Email broadcasts (marketing email) need a paid plan: Free cannot send them, see `POST /v1/broadcasts/{broadcastId}/send`. Teams on earlier plans keep their original email quotas instead\n- SMS and voice are billed per message from your balance on every plan\n\n**Daily limits:**\n- An account sends on every channel from its first minute, to any destination. Verification is not a permission to send: identity verification and business verification (KYB) raise the ceilings below. That includes `sms_oneway`: it sends without verification, within the SMS ceiling, and every message passes the risk review below. KYB is still required to register a 10DLC brand and campaign, which every US and Canadian (+1) SMS destination needs — a carrier rule, answered separately with `403 ten_dlc_required`\n- Daily ceilings apply per channel group and rise with verification. An account that has verified nothing: 25/day across `sms` + `sms_oneway`, 5/day for `voice`, 100/day across WhatsApp, Telegram, Instagram and Messenger combined. Past that floor: 200/day for SMS, or 10,000/day once identity or business verification is approved (or a higher limit agreed for your account); 50/day voice and 250/day conversational on Free. **Paid plans have no voice or conversational daily ceiling.** Over a ceiling, sends return `429` with code `daily_limit_exceeded` and `details.limit`; the count resets at 00:00 UTC\n- The daily ceiling never reduces the monthly allowance: 100/day on the conversational group still reaches the 2,000 monthly A2P messages Free includes\n- Email: a sender with a verified domain sends from day one, within the plan quota (100/day and 3,000/month on Free). Over the daily quota it returns `429` with code `daily_limit_exceeded`\n- Every plan, paid ones included, also has a rolling email ceiling by account age: 200 per hour and 1,000 per 24 hours for the first 7 days, 1,000 per hour and 10,000 per 24 hours until day 30, then 5,000 per hour and 50,000 per 24 hours. Past it the request is accepted and the message then fails with `errorCode` `EMAIL_RATE_LIMIT_HOURLY` or `EMAIL_RATE_LIMIT_DAILY`, visible on `GET /v1/messages/{messageId}` and the `message.failed` webhook. It does not reset at 00:00 UTC: it counts the last hour and the last 24 hours\n- Full reference: https://docs.zavu.dev/concepts/sending-limits\n\n**Risk review:**\nEvery outbound `sms`, `sms_oneway`, `email` and `voice` message is read before it is sent — the content, and how this account has been sending. What is checked is the message, not who you are.\n- A message can be **held** for a short review. It stays `queued` while it waits: no new status exists for this, and `MessageStatus` is unchanged. When it is approved it sends normally.\n- A message that is not approved moves to `failed` and fires `message.failed`. `errorCode` says which happened: `RISK_REJECTED` (a reviewer refused it), `RISK_REVIEW_EXPIRED` (the review window closed first — it is a couple of hours, because a code that arrives late is worse than one that does not arrive), or `RISK_BLOCKED` (refused outright, without a hold). An SMS that fails this way is not charged; the prepaid amount is returned.\n- A call is never held. `POST /v1/calls` fails a call the review stops rather than placing it hours late.\n- A message whose content cannot be read — the check is briefly unavailable — is held rather than sent. An account with an approved business verification is unaffected, and so is one that has verified something, already sends real traffic, and has a clean recent record.\n- Repeated refusals suspend an account's sending. While it is suspended every send is refused with `403` and code `sending_suspended`, `details.dashboardUrl` points at support, and a message already queued fails with `errorCode` `SENDING_SUSPENDED`.\n- A broadcast is read once, on the broadcast itself, rather than per recipient — see `POST /v1/broadcasts/{broadcastId}/send`.\n\n**Email recipient pre-flight:**\nEmail messages are validated automatically before dispatch. Sends that would be a guaranteed hard bounce are failed instead of sent, protecting your bounce rate: the message transitions to `failed` (visible via `GET /v1/messages/{messageId}` and the `message.failed` webhook) with `errorCode` set to `EMAIL_INVALID_RECIPIENT` (malformed address), `EMAIL_DOMAIN_NOT_FOUND` (recipient domain has no MX or A records), or `EMAIL_RECIPIENT_SUPPRESSED` (address is on your suppression list after a previous bounce or complaint). Advisory signals (role addresses, disposable domains) do not block sends — check them beforehand with `POST /v1/introspect/email`.",
        "operationId": "sendMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              },
              "examples": {
                "sms": {
                  "summary": "Simple SMS",
                  "value": {
                    "to": "+56912345678",
                    "text": "Your verification code is 123456"
                  }
                },
                "whatsapp_text": {
                  "summary": "WhatsApp text message",
                  "value": {
                    "to": "+56912345678",
                    "channel": "whatsapp",
                    "text": "Hello from Zavu!"
                  }
                },
                "whatsapp_image": {
                  "summary": "WhatsApp image",
                  "value": {
                    "to": "+56912345678",
                    "messageType": "image",
                    "text": "Check out this product!",
                    "content": {
                      "mediaUrl": "https://example.com/product.jpg"
                    }
                  }
                },
                "whatsapp_buttons": {
                  "summary": "WhatsApp interactive buttons",
                  "value": {
                    "to": "+56912345678",
                    "messageType": "buttons",
                    "text": "How would you rate your experience?",
                    "content": {
                      "buttons": [
                        {
                          "id": "great",
                          "title": "Great!"
                        },
                        {
                          "id": "okay",
                          "title": "It was okay"
                        },
                        {
                          "id": "poor",
                          "title": "Not good"
                        }
                      ]
                    }
                  }
                },
                "whatsapp_template": {
                  "summary": "WhatsApp template message",
                  "value": {
                    "to": "+56912345678",
                    "messageType": "template",
                    "content": {
                      "templateId": "tmpl_abc123",
                      "templateVariables": {
                        "1": "John",
                        "2": "ORD-12345"
                      }
                    }
                  }
                },
                "whatsapp_template_with_url_button": {
                  "summary": "WhatsApp template with dynamic URL button",
                  "value": {
                    "to": "+56912345678",
                    "messageType": "template",
                    "content": {
                      "templateId": "tmpl_abc123",
                      "templateVariables": {
                        "1": "ORDAZ BRAVO MARCO",
                        "2": "REPORTE: Conducta inadecuada"
                      },
                      "templateButtonVariables": {
                        "0": "abc-report-token"
                      }
                    }
                  }
                },
                "whatsapp_cta_url": {
                  "summary": "WhatsApp CTA URL button",
                  "value": {
                    "to": "+56912345678",
                    "channel": "whatsapp",
                    "messageType": "cta_url",
                    "text": "Tap the button below to see available dates.",
                    "content": {
                      "ctaDisplayText": "See Dates",
                      "ctaUrl": "https://example.com/schedule?ref=zavu",
                      "ctaHeaderType": "image",
                      "ctaHeaderMediaUrl": "https://example.com/banner.png",
                      "footerText": "Dates subject to change."
                    }
                  }
                },
                "whatsapp_quoted_reply": {
                  "summary": "WhatsApp quoted reply",
                  "value": {
                    "to": "+56912345678",
                    "channel": "whatsapp",
                    "text": "Your order ships tomorrow.",
                    "content": {
                      "replyToMessageId": "jd7x2k3m4n5p6q7r8s9t0"
                    }
                  }
                },
                "whatsapp_location_request": {
                  "summary": "WhatsApp location request",
                  "value": {
                    "to": "+56912345678",
                    "channel": "whatsapp",
                    "messageType": "location_request",
                    "text": "To finish your order, share the delivery address."
                  }
                },
                "whatsapp_request_contact_info": {
                  "summary": "WhatsApp contact info request (ask for phone number)",
                  "value": {
                    "to": "US.13491208655302741918",
                    "channel": "whatsapp",
                    "messageType": "request_contact_info",
                    "text": "Share your phone number so our team can call you back."
                  }
                },
                "whatsapp_to_bsuid": {
                  "summary": "WhatsApp message to a BSUID (username contact)",
                  "value": {
                    "to": "US.13491208655302741918",
                    "channel": "whatsapp",
                    "text": "Thanks for reaching out! How can we help?"
                  }
                },
                "telegram_text": {
                  "summary": "Telegram text message",
                  "value": {
                    "to": "123456789",
                    "channel": "telegram",
                    "text": "Hello from Zavu via Telegram!"
                  }
                },
                "telegram_quoted_reply": {
                  "summary": "Telegram quoted reply",
                  "value": {
                    "to": "123456789",
                    "channel": "telegram",
                    "text": "Got it, checking now.",
                    "content": {
                      "replyToMessageId": "jd7x2k3m4n5p6q7r8s9t0"
                    }
                  }
                },
                "telegram_template": {
                  "summary": "Telegram template message",
                  "value": {
                    "to": "123456789",
                    "channel": "telegram",
                    "messageType": "template",
                    "content": {
                      "templateId": "tmpl_abc123",
                      "templateVariables": {
                        "1": "John",
                        "2": "ORD-12345"
                      }
                    }
                  }
                },
                "sms_template": {
                  "summary": "SMS template message",
                  "value": {
                    "to": "+56912345678",
                    "channel": "sms",
                    "messageType": "template",
                    "content": {
                      "templateId": "tmpl_abc123",
                      "templateVariables": {
                        "1": "John",
                        "2": "ORD-12345"
                      }
                    }
                  }
                },
                "instagram_text": {
                  "summary": "Instagram text message",
                  "value": {
                    "to": "17841400000000000",
                    "channel": "instagram",
                    "text": "Hello from Zavu via Instagram!"
                  }
                },
                "messenger_text": {
                  "summary": "Messenger text message (Facebook Page / Marketplace chat)",
                  "value": {
                    "to": "24025631120151183",
                    "channel": "messenger",
                    "text": "Hello from Zavu via Messenger!"
                  }
                },
                "voice_tts": {
                  "summary": "Voice text-to-speech message",
                  "value": {
                    "to": "+56912345678",
                    "channel": "voice",
                    "text": "Your verification code is 1 2 3 4 5 6"
                  }
                },
                "voice_tts_spanish": {
                  "summary": "Voice TTS in Spanish",
                  "value": {
                    "to": "+56912345678",
                    "channel": "voice",
                    "text": "Su codigo de verificacion es 1 2 3 4 5 6",
                    "voiceLanguage": "es-ES"
                  }
                },
                "email_simple": {
                  "summary": "Simple email",
                  "value": {
                    "to": "user@example.com",
                    "channel": "email",
                    "subject": "Your order has shipped",
                    "text": "Hi John, your order #12345 has shipped and will arrive in 2-3 business days."
                  }
                },
                "email_html": {
                  "summary": "Email with HTML body",
                  "value": {
                    "to": "user@example.com",
                    "channel": "email",
                    "subject": "Welcome to Zavu",
                    "text": "Welcome to Zavu! We're excited to have you.",
                    "htmlBody": "<h1>Welcome to Zavu!</h1><p>We're excited to have you on board.</p>",
                    "replyTo": "support@example.com"
                  }
                },
                "email_attachment": {
                  "summary": "Email with attachments",
                  "value": {
                    "to": "user@example.com",
                    "channel": "email",
                    "subject": "Your invoice",
                    "text": "Please find your invoice attached.",
                    "attachments": [
                      {
                        "filename": "invoice.pdf",
                        "content": "JVBERi0xLjQK...",
                        "content_type": "application/pdf"
                      }
                    ]
                  }
                },
                "email_inline_image": {
                  "summary": "Email with inline image",
                  "value": {
                    "to": "user@example.com",
                    "channel": "email",
                    "subject": "Your report",
                    "text": "See your report below.",
                    "htmlBody": "<h1>Report</h1><img src=\"cid:chart\"><p>Details above.</p>",
                    "attachments": [
                      {
                        "filename": "chart.png",
                        "path": "https://example.com/chart.png",
                        "content_type": "image/png",
                        "content_id": "chart"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Message accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, WhatsApp 24h window not open, a WhatsApp template parameter Meta would refuse (line break, tab or more than 4 consecutive spaces in `content.templateVariables` or `content.templateHeaderVariables`), or a quoted reply (`content.replyToMessageId`) that cannot be delivered as one: wrong channel, a reaction, a button tap, a failed message, a message from another conversation or sender, or one with no provider message ID yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "window_closed": {
                    "summary": "WhatsApp window closed",
                    "value": {
                      "code": "whatsapp_window_closed",
                      "message": "WhatsApp 24-hour window is not open. Use a template message or wait for user to message first."
                    }
                  },
                  "template_param_line_break": {
                    "summary": "WhatsApp template parameter with a line break",
                    "value": {
                      "code": "invalid_request",
                      "message": "content.templateVariables[\"2\"] contains a line break. WhatsApp refuses template parameters with line breaks, tabs or more than 4 consecutive spaces. Replace them (for example with \" · \") before sending.",
                      "details": {
                        "field": "content.templateVariables.2",
                        "reason": "line_break"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance. Email is billed from your prepaid balance in 1,000-message blocks; SMS and voice are billed per message. When the balance cannot cover the send, the message is rejected until you add funds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "insufficient_balance",
                  "message": "Insufficient balance to send email. Add funds from the dashboard to resume."
                }
              }
            }
          },
          "403": {
            "description": "Message blocked. `sending_suspended`: this account's sending is suspended after Zavu's automated review refused several of its messages, and `details.dashboardUrl` points at support. `url_not_verified` / `url_shortener_blocked`: the message contains URLs that are not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "sending_suspended": {
                    "summary": "Sending suspended after repeated refusals",
                    "value": {
                      "code": "sending_suspended",
                      "message": "Sending from this account is suspended after Zavu's automated risk review refused several messages. Contact support to have it reviewed.",
                      "details": {
                        "dashboardUrl": "/support"
                      }
                    }
                  },
                  "url_not_verified": {
                    "summary": "URL not verified",
                    "value": {
                      "code": "url_not_verified",
                      "message": "Message blocked: Contains unverified URLs. Submit URLs for verification via the /v1/urls endpoint.",
                      "details": {
                        "urls": [
                          "https://example.com"
                        ],
                        "submissionEndpoint": "/v1/urls"
                      }
                    }
                  },
                  "url_shortener_blocked": {
                    "summary": "URL shortener blocked",
                    "value": {
                      "code": "url_shortener_blocked",
                      "message": "Message blocked: URL shorteners are not allowed. Please use the full destination URL.",
                      "details": {
                        "blockedUrls": [
                          "https://bit.ly/abc123"
                        ],
                        "reason": "url_shorteners_hidden_destination"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Template, sender, or quoted message (`content.replyToMessageId`) not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The `idempotencyKey` was already used in this project. Nothing is sent again: the body replays the message the first request created, in its current state, so a retry can tell the first attempt landed and read its `id` and `status`. The replay is answered before any rate, daily or balance limit, since the original already consumed them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "422": {
            "description": "`sms_compliance_blocked`: an SMS or one-way SMS was refused by the content check, or the check could not run (then the message says so, nothing was charged, and a retry in a few minutes goes through). `details` carries `riskScore`, `messageType`, `complianceFailures`, `requiredFixes` and, when there is one, `suggestedCompliantVersion`. One-time codes (OTP) always pass.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "sms_compliance_blocked",
                  "message": "SMS blocked: The message asks for a payment through a link without identifying the sender.",
                  "details": {
                    "riskScore": 85,
                    "messageType": "marketing",
                    "complianceFailures": [
                      {
                        "code": "MISSING_SENDER_ID",
                        "severity": "high",
                        "title": "Sender not identified"
                      }
                    ],
                    "requiredFixes": [
                      "Name your business in the message."
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "A sending limit was reached. `daily_limit_exceeded` is the channel group's daily ceiling described above: `details.limit` is the ceiling that applied, and the count resets at 00:00 UTC. Verifying your identity or adding a payment method raises it; for email, upgrading the plan does. Two other limits also answer 429 on this endpoint, both wrapped in an `error` object rather than at the top level: `a2p_limit_exceeded`, the Free plan's monthly allowance on WhatsApp, Telegram, Instagram and Messenger, and `rate_limit_exceeded`, one SMS per recipient per minute.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "`daily_limit_exceeded` is a top-level Error. `a2p_limit_exceeded` and `rate_limit_exceeded` wrap the same fields in `error`.",
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "details": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "error": {
                      "$ref": "#/components/schemas/Error"
                    }
                  }
                },
                "example": {
                  "code": "daily_limit_exceeded",
                  "message": "Daily SMS limit reached (25/day). Verify your identity or add a payment method to raise it.",
                  "details": {
                    "limit": 25,
                    "remaining": 0,
                    "dashboardUrl": "/kyc"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List messages",
        "description": "List messages previously sent by this project.",
        "operationId": "listMessages",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Filter by status. Not all stored statuses are filterable. `received` returns inbound messages only (messages your contacts sent you). Inbound messages report `status: delivered`, so `delivered` returns them together with delivered outbound messages; use `received` to list inbound messages alone.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "sending",
                "sent",
                "delivered",
                "failed",
                "received"
              ]
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "description": "Filter by delivery channel.",
            "schema": {
              "type": "string",
              "enum": [
                "sms",
                "sms_oneway",
                "whatsapp",
                "email",
                "telegram",
                "instagram",
                "messenger",
                "voice"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of messages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): a `status` or `channel` outside the documented values, or a `limit` that is not an integer from 1 to 100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/messages/{messageId}": {
      "get": {
        "summary": "Get message by ID",
        "operationId": "getMessage",
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Message details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Message not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/messages/{messageId}/reactions": {
      "post": {
        "summary": "Send reaction to message",
        "description": "Send an emoji reaction to an existing WhatsApp message. Reactions are only supported for WhatsApp messages.",
        "operationId": "sendReaction",
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageIdParam"
          },
          {
            "$ref": "#/components/parameters/SenderHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReactionRequest"
              },
              "example": {
                "emoji": "👍"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Reaction accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or message is not from WhatsApp.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_whatsapp": {
                    "summary": "Not a WhatsApp message",
                    "value": {
                      "code": "bad_request",
                      "message": "Reactions are only supported for WhatsApp messages"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Message not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/messages/{messageId}/typing": {
      "post": {
        "summary": "Show typing indicator",
        "description": "Mark an inbound WhatsApp message as read and display a typing indicator to the user while you prepare a response. The indicator is automatically dismissed when you send a reply, or after 25 seconds — whichever comes first. Only valid for inbound WhatsApp messages. Use this when a reply will take more than a couple of seconds (LLM agent, tool call, lookup) to improve the recipient's experience.",
        "operationId": "showTypingIndicator",
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageIdParam"
          },
          {
            "$ref": "#/components/parameters/SenderHeader"
          }
        ],
        "responses": {
          "202": {
            "description": "Typing indicator displayed and message marked as read.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — message is not WhatsApp, not inbound, or has no provider message ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_whatsapp": {
                    "summary": "Not a WhatsApp message",
                    "value": {
                      "code": "bad_request",
                      "message": "Typing indicators are only supported for WhatsApp messages"
                    }
                  },
                  "not_inbound": {
                    "summary": "Message is not inbound",
                    "value": {
                      "code": "bad_request",
                      "message": "Typing indicators can only be sent in response to inbound messages"
                    }
                  },
                  "no_provider_id": {
                    "summary": "Missing provider message ID",
                    "value": {
                      "code": "bad_request",
                      "message": "Cannot send typing indicator: no provider message ID available"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Message not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/messages/{messageId}/attachments": {
      "get": {
        "summary": "List message attachments",
        "description": "List the stored file attachments for an email message and get a short-lived signed `downloadUrl` for each. Works for both inbound emails (received via `message.inbound`) and outbound emails you sent with attachments. Messages without stored attachments (including SMS, WhatsApp, and other channels) return an empty list. Each `downloadUrl` is generated fresh per request and expires — fetch the file promptly and do not cache the URL.",
        "operationId": "listMessageAttachments",
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "List of attachments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageAttachmentsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Message not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/templates": {
      "get": {
        "summary": "List templates",
        "description": "List WhatsApp message templates for this project.",
        "operationId": "listTemplates",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Template"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): `limit` outside 1-100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create template",
        "description": "Create a WhatsApp message template. Note: Templates must be approved by Meta before use.",
        "operationId": "createTemplate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateCreateRequest"
              },
              "example": {
                "name": "order_confirmation",
                "language": "en",
                "body": "Hi {{1}}, your order {{2}} has been confirmed and will ship within 24 hours.",
                "whatsappCategory": "UTILITY",
                "variables": [
                  "customer_name",
                  "order_id"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Template created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "description": "Invalid template.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A template with this name already exists in the project (`conflict`). Names are unique per project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/templates/{templateId}": {
      "get": {
        "summary": "Get template",
        "operationId": "getTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Template details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Template not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete template",
        "operationId": "deleteTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Template deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Template not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/templates/sync": {
      "post": {
        "summary": "Sync templates from WhatsApp",
        "description": "Reconcile this project's templates against WhatsApp. Three things happen per connected WhatsApp Business Account: templates that exist on Meta but not in Zavu are imported (or linked to an existing template with the same name), the approval status of the templates Zavu already knows about is refreshed from Meta, and their **category** is refreshed from Meta.\n\nThe category matters because it is what each message is billed under, and Meta reassigns it on its own — commonly `UTILITY` to `MARKETING`, on a template that is already approved and whose status therefore never moves. A template whose category changed but whose status did not is still counted in `updated`. This is the way to repair templates whose category drifted before you started listening for `template.status_changed`.\n\nThis is what to call when a template was created outside Zavu — in Meta Business Manager, or by another tool — or when a `template.status_changed` webhook was missed and a template is stuck in `pending`. Status changes normally arrive by webhook; this endpoint is the recovery path and the only path for a template Zavu never created.\n\nTemplates that Meta reports as rejected or disabled are not imported; they are counted in `skipped`. Existing local templates are matched first by Meta template ID, then by name.\n\nBy default every sender in the project with a WhatsApp Business Account is synced. Pass `senderId` to sync only that sender's account. The call is synchronous — it waits for Meta and returns what changed — so it can take a few seconds per account. A failure on one account does not fail the request: it is reported in `errors` and the remaining accounts are still synced.",
        "operationId": "syncTemplates",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "senderId": {
                    "type": "string",
                    "description": "Sync only the WhatsApp Business Account attached to this sender. If omitted, every WhatsApp sender in the project is synced.",
                    "example": "sender_12345"
                  }
                }
              },
              "examples": {
                "all": {
                  "summary": "Sync every WhatsApp account in the project",
                  "value": {}
                },
                "one_sender": {
                  "summary": "Sync one sender's account",
                  "value": {
                    "senderId": "sender_12345"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync completed. Counts are totals across every account synced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "accountsSynced",
                    "imported",
                    "linked",
                    "updated",
                    "skipped",
                    "errors"
                  ],
                  "properties": {
                    "accountsSynced": {
                      "type": "integer",
                      "description": "WhatsApp Business Accounts reconciled in this call."
                    },
                    "imported": {
                      "type": "integer",
                      "description": "Templates that existed on Meta and were created in Zavu by this call."
                    },
                    "linked": {
                      "type": "integer",
                      "description": "Existing Zavu templates that were matched to a Meta template by name and bound to its Meta ID."
                    },
                    "updated": {
                      "type": "integer",
                      "description": "Templates brought back in line with Meta — approval status, category, or both. A template whose status and category both moved is counted once."
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Meta templates left alone: already linked to a Zavu template, or rejected/disabled on Meta."
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Problems hit while syncing. Non-empty with a 200 means part of the sync did not complete — the rest still did."
                    }
                  }
                },
                "example": {
                  "accountsSynced": 1,
                  "imported": 2,
                  "linked": 1,
                  "updated": 3,
                  "skipped": 12,
                  "errors": []
                }
              }
            }
          },
          "400": {
            "description": "No sender in the project has a WhatsApp Business Account connected, or the given sender has none.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "No sender in this project has a WhatsApp Business Account connected"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/templates/{templateId}/submit": {
      "post": {
        "summary": "Submit template for approval",
        "description": "Submit a WhatsApp template to Meta for approval. The template must be in draft status and associated with a sender that has a WhatsApp Business Account configured.",
        "operationId": "submitTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "senderId"
                ],
                "properties": {
                  "senderId": {
                    "type": "string",
                    "description": "The sender ID with the WhatsApp Business Account to submit the template to."
                  },
                  "category": {
                    "$ref": "#/components/schemas/WhatsAppCategory",
                    "description": "Template category. If not provided, uses the category set on the template."
                  },
                  "variableExamples": {
                    "type": "object",
                    "description": "Sample value for each body variable, sent to Meta as the example it reviews the template with. Key a named variable (`{{customer_name}}`) by its name, and a positional one (`{{1}}`, `{{2}}`) as `var1`, `var2`.\n\nAny variable left out gets a generic placeholder: a stock value for a few common names (`name`, `order_id`, `code`, ...) and `Sample <variable>` for the rest. Meta reviews those less favourably, so send a realistic value for every variable.",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "example": {
                      "var1": "Maria",
                      "var2": "ORD-12345"
                    }
                  }
                }
              },
              "example": {
                "senderId": "sender_abc123",
                "category": "UTILITY",
                "variableExamples": {
                  "var1": "Maria",
                  "var2": "ORD-12345"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Template submitted for approval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Template"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or template not in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_draft": {
                    "summary": "Template not in draft status",
                    "value": {
                      "code": "bad_request",
                      "message": "Template is already pending approval or approved"
                    }
                  },
                  "no_waba": {
                    "summary": "Sender has no WABA",
                    "value": {
                      "code": "bad_request",
                      "message": "Sender does not have a WhatsApp Business Account configured"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Template or sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders": {
      "get": {
        "summary": "List senders",
        "operationId": "listSenders",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of sender profiles.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sender"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): a `limit` that is not an integer from 1 to 100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create sender",
        "operationId": "createSender",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SenderCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sender created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sender"
                }
              }
            }
          },
          "400": {
            "description": "Invalid sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}": {
      "get": {
        "summary": "Get sender",
        "operationId": "getSender",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Sender details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sender"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update sender",
        "operationId": "updateSender",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SenderUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sender updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sender"
                }
              }
            }
          },
          "400": {
            "description": "Invalid update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete sender",
        "operationId": "deleteSender",
        "description": "Delete a sender. The project's default sender cannot be deleted while other senders exist: set another as default first. The default can be deleted when it is the project's only sender, and then the project has no default: every send that names no sender (no `Zavu-Sender` header) fails until another sender exists and is set as default.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Sender deleted."
          },
          "400": {
            "description": "The sender is the project's default and other senders exist. Set another as default first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/webhook/secret": {
      "post": {
        "summary": "Regenerate webhook secret",
        "description": "Regenerate the webhook secret for a sender. The old secret will be invalidated immediately.",
        "operationId": "regenerateSenderWebhookSecret",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "New webhook secret generated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretResponse"
                }
              }
            }
          },
          "400": {
            "description": "Sender has no webhook configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/channels/{channel}/activate": {
      "post": {
        "summary": "Activate a sender channel",
        "description": "Switch on the account connected to this sender for one channel, so it sends and receives. This is the same switch as **Activate** on the dashboard's Accounts screen, with the same price and the same limits.\n\nA newly connected account that your plan already includes (nothing charged now, nothing added to the monthly bill) is switched on by itself as soon as it can carry messages. One that would cost money starts inactive: every send on it is refused and it is not listed in the sender's `channels` until you activate it here or from Accounts, after seeing the price. A connection you switched off stays off.\n\n**Billing.** Activating is billed as a channel connection. On a paid plan that is one full month charged from the balance at once, and the same amount every month while the connection stays active; the response states both as `chargedCents` and `monthlyCents`. The first connections a plan includes cost nothing, and so does re-activating a connection whose current month is already paid. A WhatsApp Business Account is never charged from the balance for its month: one beyond the ones the plan includes is billed on the subscription instead. The exception is a WhatsApp account switched off for non-payment, which settles its past-due fee from the balance when re-activated.\n\nCalling this on a channel that is already active changes nothing and charges nothing. Not available with test-mode keys.",
        "operationId": "activateSenderChannel",
        "tags": [
          "Senders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ActivatableChannelParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The channel is active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SenderChannelActivationResponse"
                }
              }
            }
          },
          "400": {
            "description": "The channel has no activation (`sms`, `sms_oneway`, `voice`), or the key is a test-mode key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The balance cannot cover what activating charges now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "insufficient_balance",
                  "message": "Not enough balance to activate this connection. Add funds and try again."
                }
              }
            }
          },
          "403": {
            "description": "The plan has no room for another active connection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "plan_limit_reached",
                  "message": "Your plan has no room for another active connection. Deactivate one or upgrade."
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`channel_not_connected`: the sender has no account for this channel. `connection_not_ready`: the account is connected but cannot carry messages yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "connection_not_ready",
                  "message": "The telegram connection is not ready to carry messages yet. Finish connecting it before activating."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/channels/{channel}/deactivate": {
      "post": {
        "summary": "Deactivate a sender channel",
        "description": "Switch off the account connected to this sender for one channel. Sends on it are refused and it leaves the sender's `channels`. The account stays connected, so activating it again needs no reconnection.\n\nThe month already paid is not refunded, and re-activating before it ends charges nothing. Calling this on a channel that is already inactive changes nothing. Not available with test-mode keys.",
        "operationId": "deactivateSenderChannel",
        "tags": [
          "Senders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ActivatableChannelParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The channel is inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SenderChannelDeactivationResponse"
                }
              }
            }
          },
          "400": {
            "description": "The channel has no activation (`sms`, `sms_oneway`, `voice`), or the key is a test-mode key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`channel_not_connected`: the sender has no account for this channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/profile": {
      "get": {
        "summary": "Get WhatsApp Business profile",
        "description": "Get the WhatsApp Business profile for a sender. The sender must have a WhatsApp Business Account connected.",
        "operationId": "getSenderProfile",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "WhatsApp Business profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsAppBusinessProfileResponse"
                }
              }
            }
          },
          "400": {
            "description": "Sender does not have a WhatsApp Business Account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "WhatsApp (Meta) refused or failed the call, for example an expired access token or a field Meta rejected. `message` carries Meta's reason. Nothing was changed on Zavu's side; fix the cause and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "whatsapp_api_error",
                  "message": "Invalid OAuth access token"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update WhatsApp Business profile",
        "description": "Update the WhatsApp Business profile for a sender. The sender must have a WhatsApp Business Account connected.",
        "operationId": "updateSenderProfile",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WhatsAppBusinessProfileUpdateRequest"
              },
              "example": {
                "about": "Succulent specialists!",
                "description": "We specialize in providing high-quality succulents.",
                "email": "contact@example.com",
                "websites": [
                  "https://www.example.com"
                ],
                "vertical": "RETAIL"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile updated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "profile"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "profile": {
                      "$ref": "#/components/schemas/WhatsAppBusinessProfile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or sender does not have a WhatsApp Business Account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "WhatsApp (Meta) refused or failed the call, for example an expired access token or a field Meta rejected. `message` carries Meta's reason. Nothing was changed on Zavu's side; fix the cause and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "whatsapp_api_error",
                  "message": "Invalid OAuth access token"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/profile/picture": {
      "post": {
        "summary": "Upload WhatsApp Business profile picture",
        "description": "Upload a new profile picture for the WhatsApp Business profile. The image will be uploaded to Meta and set as the profile picture.",
        "operationId": "uploadSenderProfilePicture",
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WhatsAppBusinessProfilePictureRequest"
              },
              "example": {
                "imageUrl": "https://example.com/profile.jpg",
                "mimeType": "image/jpeg"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Profile picture uploaded successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "profile"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "profile": {
                      "$ref": "#/components/schemas/WhatsAppBusinessProfile"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or sender does not have a WhatsApp Business Account connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "WhatsApp (Meta) refused or failed the call, for example an expired access token or a field Meta rejected. `message` carries Meta's reason. Nothing was changed on Zavu's side; fix the cause and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "whatsapp_api_error",
                  "message": "Invalid OAuth access token"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/whatsapp-sync": {
      "get": {
        "summary": "Get WhatsApp sync status",
        "description": "Get the current sync status for a sender's WhatsApp coexistence account. Only available for senders connected in coexistence mode (WhatsApp Business App + Cloud API).",
        "operationId": "getWhatsAppSyncStatus",
        "tags": [
          "WhatsApp Sync"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Sync status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsAppSyncStatusResponse"
                },
                "example": {
                  "sync": {
                    "isCoexistence": true,
                    "status": "active",
                    "history": {
                      "status": "completed",
                      "canSync": false,
                      "requestedAt": "2024-01-15T10:00:00Z",
                      "completedAt": "2024-01-15T10:05:00Z"
                    },
                    "contacts": {
                      "status": "not_requested",
                      "canSync": true,
                      "requestedAt": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Sender does not have a WhatsApp Business Account connected or is not in coexistence mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "no_waba": {
                    "summary": "No WABA connected",
                    "value": {
                      "code": "bad_request",
                      "message": "Sender does not have a WhatsApp Business Account connected"
                    }
                  },
                  "not_coexistence": {
                    "summary": "Not coexistence mode",
                    "value": {
                      "code": "bad_request",
                      "message": "Sync is only available for coexistence accounts"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/whatsapp-sync/history": {
      "post": {
        "summary": "Start history sync",
        "description": "Initiate message history sync from the WhatsApp Business App. This sends a request to the account owner to approve sharing their conversation history. Only available for coexistence accounts with active status.",
        "operationId": "startWhatsAppHistorySync",
        "tags": [
          "WhatsApp Sync"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "202": {
            "description": "History sync initiated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsAppSyncStartResponse"
                },
                "example": {
                  "message": "History sync initiated",
                  "sync": {
                    "isCoexistence": true,
                    "status": "active",
                    "history": {
                      "status": "pending",
                      "canSync": false,
                      "requestedAt": "2024-01-15T10:00:00Z",
                      "completedAt": null
                    },
                    "contacts": {
                      "status": "not_requested",
                      "canSync": true,
                      "requestedAt": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "no_waba": {
                    "summary": "No WABA connected",
                    "value": {
                      "code": "bad_request",
                      "message": "Sender does not have a WhatsApp Business Account connected"
                    }
                  },
                  "not_coexistence": {
                    "summary": "Not coexistence mode",
                    "value": {
                      "code": "bad_request",
                      "message": "History sync is only available for coexistence accounts"
                    }
                  },
                  "not_active": {
                    "summary": "Account not active",
                    "value": {
                      "code": "bad_request",
                      "message": "WhatsApp account is not active"
                    }
                  },
                  "in_progress": {
                    "summary": "Sync in progress",
                    "value": {
                      "code": "bad_request",
                      "message": "History sync is already in progress"
                    }
                  },
                  "rate_limit": {
                    "summary": "Rate limit",
                    "value": {
                      "code": "bad_request",
                      "message": "Sync limit reached. Please wait 24 hours before trying again."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/whatsapp-sync/contacts": {
      "post": {
        "summary": "Start contacts sync",
        "description": "Initiate contact names sync from the WhatsApp Business App. This imports contact names stored in the app to Zavu. Only available for coexistence accounts with active status.",
        "operationId": "startWhatsAppContactsSync",
        "tags": [
          "WhatsApp Sync"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "202": {
            "description": "Contacts sync initiated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhatsAppSyncStartResponse"
                },
                "example": {
                  "message": "Contacts sync initiated",
                  "sync": {
                    "isCoexistence": true,
                    "status": "active",
                    "history": {
                      "status": "completed",
                      "canSync": false,
                      "requestedAt": "2024-01-15T10:00:00Z",
                      "completedAt": "2024-01-15T10:05:00Z"
                    },
                    "contacts": {
                      "status": "pending",
                      "canSync": false,
                      "requestedAt": "2024-01-15T11:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "no_waba": {
                    "summary": "No WABA connected",
                    "value": {
                      "code": "bad_request",
                      "message": "Sender does not have a WhatsApp Business Account connected"
                    }
                  },
                  "not_coexistence": {
                    "summary": "Not coexistence mode",
                    "value": {
                      "code": "bad_request",
                      "message": "Contacts sync is only available for coexistence accounts"
                    }
                  },
                  "not_active": {
                    "summary": "Account not active",
                    "value": {
                      "code": "bad_request",
                      "message": "WhatsApp account is not active"
                    }
                  },
                  "in_progress": {
                    "summary": "Sync in progress",
                    "value": {
                      "code": "bad_request",
                      "message": "Contacts sync is already in progress"
                    }
                  },
                  "rate_limit": {
                    "summary": "Rate limit",
                    "value": {
                      "code": "bad_request",
                      "message": "Sync limit reached. Please wait 24 hours before trying again."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts": {
      "get": {
        "summary": "List contacts",
        "description": "List contacts with their communication channels.",
        "operationId": "listContacts",
        "parameters": [
          {
            "name": "phoneNumber",
            "in": "query",
            "description": "Exact match on the contact's primary phone number, in E.164.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Free-text match over the contact's name (`displayName` and the WhatsApp profile name), phone numbers and email addresses. Case- and accent-insensitive. A phone number matches on a trailing fragment too, so `5551234` finds `+14155551234`.\n\nContacts created automatically from an inbound message have no `displayName` — they are matched by their identifier until you set one with `PATCH /v1/contacts/{contactId}`.\n\nResults come back in relevance order rather than newest-first. `cursor` is opaque in both modes; pass back exactly what the previous response returned, and start a new pagination run when the search term changes.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "marta"
          },
          {
            "name": "bsuid",
            "in": "query",
            "description": "Exact WhatsApp business-scoped user ID (e.g. `CO.13491208655302741918`). Returns the contact that holds it, or an empty list. Combines with `phoneNumber` and `tag` as a filter, and cannot be combined with `search` (400). A value that is not shaped like a BSUID returns 400.",
            "schema": {
              "type": "string"
            },
            "example": "CO.13491208655302741918"
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Tag name. Repeatable: `?tag=vip&tag=chile` returns contacts carrying **every** tag given, not any of them — the same rule the dashboard filter applies.\n\nTags are matched by name, case-insensitively. An unknown tag returns 400 rather than being ignored, because a typo that silently matched every contact would be a worse answer than an error.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true,
            "example": [
              "vip"
            ]
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`. Do not construct it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): `limit` outside 1-100, a malformed cursor, or a `tag` that names no tag in this project — `details.unknownTags` lists them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create contact",
        "description": "Create a new contact with one or more communication channels.",
        "operationId": "createContact",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCreateRequest"
              },
              "examples": {
                "phone_only": {
                  "summary": "Contact with phone only",
                  "value": {
                    "displayName": "John Doe",
                    "channels": [
                      {
                        "channel": "sms",
                        "identifier": "+14155551234",
                        "isPrimary": true
                      }
                    ]
                  }
                },
                "multi_channel": {
                  "summary": "Contact with multiple channels",
                  "value": {
                    "displayName": "John Doe",
                    "channels": [
                      {
                        "channel": "sms",
                        "identifier": "+14155551234",
                        "isPrimary": true
                      },
                      {
                        "channel": "whatsapp",
                        "identifier": "+14155551234",
                        "isPrimary": true
                      },
                      {
                        "channel": "email",
                        "identifier": "john@example.com",
                        "isPrimary": true
                      }
                    ],
                    "metadata": {
                      "source": "import"
                    }
                  }
                },
                "email_only": {
                  "summary": "Contact with email only",
                  "value": {
                    "displayName": "Jane Smith",
                    "channels": [
                      {
                        "channel": "email",
                        "identifier": "jane@example.com",
                        "isPrimary": true
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "duplicate_identifier": {
                    "summary": "Duplicate identifier",
                    "value": {
                      "code": "bad_request",
                      "message": "This sms identifier is already associated with another contact"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/{contactId}": {
      "get": {
        "summary": "Get contact",
        "operationId": "getContact",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update contact",
        "operationId": "updateContact",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Invalid update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete contact",
        "description": "Permanently delete a contact and its communication channels. Implements right-to-erasure obligations under GDPR Art. 17, Ley 19.628 (Chile) Art. 12, CCPA § 1798.105, and LGPD Art. 18.VI. The contact, its channels and any associated agent flow sessions are removed. Conversation threads, message history and broadcast delivery logs are kept and no longer reference the deleted contact.",
        "operationId": "deleteContact",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/phone/{phoneNumber}": {
      "get": {
        "summary": "Get contact by phone number",
        "operationId": "getContactByPhone",
        "parameters": [
          {
            "name": "phoneNumber",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "E.164 phone number."
          }
        ],
        "responses": {
          "200": {
            "description": "Contact details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/{contactId}/channels": {
      "post": {
        "summary": "Add channel to contact",
        "description": "Add a new communication channel to an existing contact.",
        "operationId": "addContactChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddChannelRequest"
              },
              "example": {
                "channel": "email",
                "identifier": "john.work@company.com",
                "label": "work"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactChannelResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or duplicate identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/{contactId}/channels/{channelId}": {
      "patch": {
        "summary": "Update channel",
        "description": "Update a contact's channel properties.",
        "operationId": "updateContactChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          },
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChannelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Channel updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactChannelResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact or channel not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Remove channel from contact",
        "description": "Remove a communication channel from a contact. Cannot remove the last channel.",
        "operationId": "removeContactChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          },
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel ID."
          }
        ],
        "responses": {
          "204": {
            "description": "Channel removed."
          },
          "400": {
            "description": "Cannot remove the last channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact or channel not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/{contactId}/channels/{channelId}/primary": {
      "post": {
        "summary": "Set channel as primary",
        "description": "Set a channel as the primary channel for its type.",
        "operationId": "setPrimaryChannel",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          },
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Channel set as primary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactChannelResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Contact or channel not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/contacts/{contactId}/merge": {
      "post": {
        "summary": "Merge contacts",
        "description": "Merge a source contact into this contact. All channels from the source contact will be moved to the target contact, and the source contact will be marked as merged.",
        "operationId": "mergeContacts",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContactIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MergeContactsRequest"
              },
              "example": {
                "sourceContactId": "jx7xyz789"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contacts merged. Returns the updated target contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g., cannot merge contact with itself).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Target or source contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/conversations": {
      "get": {
        "summary": "List conversations",
        "description": "List inbox threads, most recently active first. A conversation groups every message with one contact across channels, which is what you need to build an inbox: `GET /v1/messages` returns a flat log with no thread to hang it on.\n\nUse `senderId` to scope the list to a single number, and `channel` to keep only threads that have carried that channel.",
        "operationId": "listConversations",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "description": "Keep only threads that have carried this channel.",
            "schema": {
              "type": "string",
              "enum": [
                "sms",
                "sms_oneway",
                "whatsapp",
                "email",
                "telegram",
                "instagram",
                "messenger",
                "voice"
              ]
            }
          },
          {
            "name": "senderId",
            "in": "query",
            "description": "Keep only threads last handled by this sender.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Search threads by identity: phone number (any format — `+1 (555) 123-4567` and `15551234567` both match), email address (full or local part), WhatsApp username, or BSUID. Matching is by whole word, with prefix matching on the last term, so `mar` finds `maria@example.com` and `+1555` finds `+15551234567`; a fragment from the middle or end of a number (`4567`) does not match.\n\nIt does **not** search message bodies — only who the thread is with.\n\nResults come back ranked by relevance rather than by recency, so the usual \"most recently active first\" ordering does not apply while `q` is set. `senderId` and `channel` still narrow the results, and `cursor` paginates them as usual. An empty or whitespace-only `q` returns no items rather than the full list.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "example": "+56912345678"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`. Do not construct it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of conversations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/conversations/{conversationId}": {
      "get": {
        "summary": "Get conversation",
        "operationId": "getConversation",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/conversations/{conversationId}/messages": {
      "get": {
        "summary": "List conversation messages",
        "description": "Messages in this thread, newest first, across every channel it has carried. Reply with `POST /v1/messages`, passing the conversation's `senderId` as the `Zavu-Sender` header so the answer leaves from the number the contact already knows.",
        "operationId": "listConversationMessages",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of messages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/conversations/{conversationId}/read": {
      "post": {
        "summary": "Mark conversation as read",
        "description": "Reset the thread's `unreadCount` to zero. Marks the thread read in your own inbox only: it does not send a read receipt to the contact.",
        "operationId": "markConversationRead",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation marked as read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Conversation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/introspect/phone": {
      "post": {
        "summary": "Introspect phone number",
        "description": "Validate a phone number and check if a WhatsApp conversation window is open.",
        "operationId": "introspectPhone",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneIntrospectionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number introspection result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneIntrospectionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid phone number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/introspect/email": {
      "post": {
        "summary": "Validate email addresses",
        "description": "Heuristic email validation to run before sending: catches invalid syntax, dead domains (no MX/A records), disposable inboxes, role-based addresses (info@, contacto@, sales@), and addresses already on your project's suppression list. Use it to clean a list before a broadcast and keep your bounce rate low.\n\nNo mailbox-level (SMTP) probe is performed, so a `deliverable` verdict is not a delivery guarantee — it means no negative signal was found. Treat `risky` addresses with care and drop `undeliverable` ones.\n\nAccepts a single `email` or an `emails` batch (max 100 per request).",
        "operationId": "introspectEmail",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailIntrospectionRequest"
              },
              "examples": {
                "single": {
                  "summary": "Validate one address",
                  "value": {
                    "email": "maria@example.com"
                  }
                },
                "batch": {
                  "summary": "Validate a list",
                  "value": {
                    "emails": [
                      "maria@example.com",
                      "info@deaddomain.example",
                      "not-an-email"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation results, one per submitted address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailIntrospectionResponse"
                },
                "example": {
                  "results": [
                    {
                      "email": "maria@example.com",
                      "normalized": "maria@example.com",
                      "domain": "example.com",
                      "verdict": "deliverable",
                      "reasons": []
                    },
                    {
                      "email": "info@deaddomain.example",
                      "normalized": "info@deaddomain.example",
                      "domain": "deaddomain.example",
                      "verdict": "undeliverable",
                      "reasons": [
                        "domain_not_found",
                        "role_address"
                      ]
                    },
                    {
                      "email": "not-an-email",
                      "normalized": null,
                      "domain": null,
                      "verdict": "undeliverable",
                      "reasons": [
                        "invalid_syntax"
                      ]
                    }
                  ],
                  "summary": {
                    "total": 3,
                    "deliverable": 1,
                    "risky": 0,
                    "undeliverable": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body (no addresses, or more than 100).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts": {
      "post": {
        "summary": "Create broadcast",
        "description": "Create a new broadcast campaign. Add contacts after creation, then send.",
        "operationId": "createBroadcast",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastCreateRequest"
              },
              "examples": {
                "sms": {
                  "summary": "SMS broadcast",
                  "value": {
                    "name": "Black Friday Sale",
                    "channel": "sms",
                    "text": "Hi {{name}}, check out our Black Friday deals! Use code FRIDAY20 for 20% off."
                  }
                },
                "whatsapp_template": {
                  "summary": "WhatsApp template broadcast",
                  "value": {
                    "name": "Order Confirmation Campaign",
                    "channel": "whatsapp",
                    "messageType": "template",
                    "content": {
                      "templateId": "tmpl_abc123"
                    }
                  }
                },
                "telegram": {
                  "summary": "Telegram broadcast",
                  "value": {
                    "name": "Product Announcement",
                    "channel": "telegram",
                    "text": "Hi {{name}}, check out our new product launch!"
                  }
                },
                "email": {
                  "summary": "Email broadcast",
                  "value": {
                    "name": "Newsletter",
                    "channel": "email",
                    "emailSubject": "Your Weekly Update",
                    "text": "Hi {{name}}, here's what's new this week..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Broadcast created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The `senderId` or `content.templateId` names no sender or template in this project (`not_found`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency conflict (broadcast already exists).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List broadcasts",
        "description": "List broadcasts for this project.",
        "operationId": "listBroadcasts",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/BroadcastStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of broadcasts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Broadcast"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): an unknown `status`, or `limit` outside 1-100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}": {
      "get": {
        "summary": "Get broadcast",
        "operationId": "getBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Broadcast details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update broadcast",
        "description": "Update a broadcast in draft status.",
        "operationId": "updateBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Broadcast updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or broadcast not in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete broadcast",
        "description": "Delete a broadcast in draft status.",
        "operationId": "deleteBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Broadcast deleted."
          },
          "400": {
            "description": "Broadcast not in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/contacts": {
      "post": {
        "summary": "Add contacts to broadcast",
        "description": "Add contacts to a broadcast in batch. Maximum 1000 contacts per request.",
        "operationId": "addBroadcastContacts",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastContactsAddRequest"
              },
              "example": {
                "contacts": [
                  {
                    "recipient": "+14155551234",
                    "templateVariables": {
                      "name": "John",
                      "order_id": "ORD-001"
                    }
                  },
                  {
                    "recipient": "+14155555678",
                    "templateVariables": {
                      "name": "Jane",
                      "order_id": "ORD-002"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contacts added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastContactsAddResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or broadcast not in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List broadcast contacts",
        "description": "List contacts in a broadcast with optional status filter.",
        "operationId": "listBroadcastContacts",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/BroadcastContactStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of broadcast contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BroadcastContact"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): an unknown `status`, `limit` outside 1-100, or a malformed `cursor`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/contacts/{contactId}": {
      "delete": {
        "summary": "Remove contact from broadcast",
        "description": "Remove a contact from a broadcast in draft status.",
        "operationId": "removeBroadcastContact",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          },
          {
            "$ref": "#/components/parameters/BroadcastContactIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Contact removed."
          },
          "400": {
            "description": "Broadcast not in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast or contact not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/send": {
      "post": {
        "summary": "Send broadcast",
        "description": "Start sending the broadcast immediately or schedule for later.\n\n**Sending a broadcast needs no account verification.** Identity and business verification raise daily ceilings; neither is a permission to broadcast, on any channel. What stands in front of a broadcast is the content review below, and it applies to every send path — dashboard, API and CLI. Drafts can be created, edited and kept freely.\n\n**Email broadcasts need a paid plan.** On Free, sending or scheduling a broadcast on the `email` channel is refused with `402` and code `paid_plan_required`; drafts can still be created and edited. A `smart` broadcast is not refused: its email recipients fail individually with `errorCode` `PAID_PLAN_REQUIRED` and the others are sent. Teams created before August 1, 2026 keep their earlier terms.\n\n**Daily ceilings apply per recipient.** Each message a broadcast sends counts against the channel's daily ceiling (see `POST /v1/messages`). Once the ceiling is reached, the remaining recipients are marked `failed` with `errorCode` `DAILY_LIMIT_EXCEEDED`; they are not retried the next day.\n\n**Review depends on the channel, and cannot be bypassed.** A draft is submitted to automated content review here; it does not go straight out. A WhatsApp broadcast built on a Meta-approved template skips review (Meta already vetted the content) and begins sending. An email broadcast sends as soon as the review passes it, unless the review asks for a person. Every other channel moves to `pending_admin_review` and waits for a person. A broadcast the review refuses lands on `rejected`: use PATCH to edit the content then call POST /retry-review, or escalate it for a manual review.\n\nA broadcast is read once, on its own text, rather than per recipient. While an account's sending is suspended its recipients fail individually with `errorCode` `SENDING_SUSPENDED`; see `POST /v1/messages`.\n\nCalling this on a broadcast that is already `approved` or `scheduled` sends or reschedules it directly, since it has already been reviewed. Reserves the estimated cost from your balance.",
        "operationId": "sendBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastSendRequest"
              },
              "examples": {
                "immediate": {
                  "summary": "Send immediately",
                  "value": {}
                },
                "scheduled": {
                  "summary": "Schedule for later",
                  "value": {
                    "scheduledAt": "2024-01-15T10:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Broadcast started or scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, no contacts, or broadcast not in valid status.\n\nA broadcast whose sender was deleted after it was created is refused here with code `sender_not_found`, before any balance is reserved. Create a new broadcast with another sender.\n\nA WhatsApp broadcast whose template Meta has not approved yet is refused here with code `template_not_approved`. Because a WhatsApp broadcast needs no account verification, this is the only review it passes through: retry once the template is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "template_not_approved": {
                    "summary": "WhatsApp template awaiting Meta approval",
                    "value": {
                      "code": "template_not_approved",
                      "message": "This broadcast can't be sent yet. Its WhatsApp template is still awaiting Meta approval."
                    }
                  },
                  "sender_not_found": {
                    "summary": "The broadcast's sender was deleted",
                    "value": {
                      "code": "sender_not_found",
                      "message": "The sender this broadcast was set to send from no longer exists. Create a new broadcast with another sender."
                    }
                  },
                  "no_contacts": {
                    "summary": "Broadcast has no recipients",
                    "value": {
                      "code": "invalid_request",
                      "message": "This broadcast has no recipients yet. Add recipients before sending."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance (`insufficient_balance`), or an email broadcast on a plan without email broadcasts (`paid_plan_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_balance": {
                    "summary": "Balance cannot cover the broadcast",
                    "value": {
                      "code": "insufficient_balance",
                      "message": "Insufficient balance. Available: $50.00",
                      "details": {
                        "totalBalance": 50,
                        "reservedBalance": 25,
                        "availableBalance": 25
                      }
                    }
                  },
                  "paid_plan_required": {
                    "summary": "Email broadcast on Free",
                    "value": {
                      "code": "paid_plan_required",
                      "message": "Email broadcasts need a paid plan. Upgrade to send this one, or send it on another channel.",
                      "details": {
                        "dashboardUrl": "/upgrade"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/schedule": {
      "patch": {
        "summary": "Reschedule broadcast",
        "description": "Update the scheduled time for a broadcast. The broadcast must be in scheduled status.",
        "operationId": "rescheduleBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastRescheduleRequest"
              },
              "example": {
                "scheduledAt": "2024-01-15T14:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Broadcast rescheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or broadcast not in scheduled status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/cancel": {
      "post": {
        "summary": "Cancel broadcast",
        "description": "Cancel a broadcast. Pending contacts will be skipped, but already queued messages may still be delivered.\n\nA broadcast can be cancelled from `draft`, `pending_review`, `pending_admin_review`, `approved`, `rejected`, `escalated`, `scheduled` and `sending`. Any balance reserved for it is released. A review still running when it is cancelled cannot bring it back.",
        "operationId": "cancelBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Broadcast cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Broadcast cannot be cancelled in its current status: it already finished (`completed`, `cancelled`, `failed`), was refused for good (`rejected_final`), or is `paused`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/retry-review": {
      "post": {
        "summary": "Retry broadcast review",
        "description": "Resubmit a rejected broadcast for AI review after editing content. Maximum 3 review attempts allowed per broadcast.",
        "operationId": "retryBroadcastReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Review resubmitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Broadcast not in rejected status or max attempts reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_rejected": {
                    "summary": "Broadcast not rejected",
                    "value": {
                      "code": "invalid_request",
                      "message": "Only rejected broadcasts can retry review"
                    }
                  },
                  "max_attempts": {
                    "summary": "Max attempts reached",
                    "value": {
                      "code": "invalid_request",
                      "message": "Maximum review attempts reached. Request manual review instead."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/escalate": {
      "post": {
        "summary": "Escalate to manual review",
        "description": "Request manual review by the Zavu team for a rejected broadcast. Use this after automated review rejection if you believe the content is legitimate.",
        "operationId": "escalateBroadcast",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Escalation submitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResponse"
                }
              }
            }
          },
          "400": {
            "description": "Broadcast not in rejected status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "Only rejected broadcasts can be escalated"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/broadcasts/{broadcastId}/progress": {
      "get": {
        "summary": "Get broadcast progress",
        "description": "Get real-time progress of a broadcast including delivery counts and estimated completion time.",
        "operationId": "getBroadcastProgress",
        "parameters": [
          {
            "$ref": "#/components/parameters/BroadcastIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Broadcast progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastProgress"
                },
                "example": {
                  "broadcastId": "brd_abc123",
                  "status": "sending",
                  "total": 5000,
                  "pending": 2500,
                  "sending": 100,
                  "delivered": 2350,
                  "failed": 50,
                  "skipped": 0,
                  "percentComplete": 48,
                  "estimatedCost": 75,
                  "reservedAmount": 75,
                  "actualCost": 35.25,
                  "startedAt": "2024-01-15T10:30:00.000Z",
                  "estimatedCompletionAt": "2024-01-15T10:45:00.000Z"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Broadcast not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/phone-numbers/requirements": {
      "get": {
        "summary": "Get regulatory requirements",
        "description": "Get the regulatory information needed to buy a phone number, for one specific number or for a country and number type. Prefer `phoneNumber`: the response is then exactly the list the purchase of that number validates against. Pass each `requirementTypes[].id` back as `requirementType` in `regulatoryRequirements` on `POST /v1/phone-numbers`.\n\nFor `phoneNumber`, the requirements of that exact number are returned. When they cannot be resolved for the number itself, the list for its country and `type` is returned instead, and the purchase uses the same list. An empty `items` array means the number needs no regulatory information. If the requirements cannot be retrieved at all, the response is `502 requirements_unavailable`, never an empty list.\n\nURL-encode the `+` of `phoneNumber` as `%2B`. An unencoded `+` is also accepted.",
        "operationId": "getPhoneNumberRequirements",
        "parameters": [
          {
            "name": "countryCode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "example": "DE",
            "description": "Two-letter ISO country code. Required unless `phoneNumber` is given."
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PhoneNumberType"
            },
            "description": "Type of phone number (local, national, mobile, tollFree). Defaults to `local`. With `phoneNumber`, used only when the number's own requirements cannot be resolved and the country list is returned."
          },
          {
            "name": "phoneNumber",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "+4930123456",
            "description": "E.164 number from `GET /v1/phone-numbers/available`, with `+` encoded as `%2B`. Returns the requirements the purchase of that number checks. Takes precedence over `countryCode`."
          }
        ],
        "responses": {
          "200": {
            "description": "Requirements for the number, or for the country and type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Requirement"
                      }
                    }
                  }
                },
                "example": {
                  "items": [
                    {
                      "id": "+4930123456",
                      "countryCode": "DE",
                      "phoneNumberType": "local",
                      "action": "ordering",
                      "requirementTypes": [
                        {
                          "id": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
                          "name": "Address",
                          "description": "An address in Germany",
                          "type": "address"
                        },
                        {
                          "id": "3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f",
                          "name": "Identity document",
                          "description": "A copy of an identity document",
                          "type": "document"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Neither `countryCode` nor `phoneNumber` was given, one is malformed, or the requirements cannot be looked up for this input (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The requirements could not be retrieved right now. Retry; do not treat this as \"no requirements\".",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "requirements_unavailable",
                  "message": "Regulatory requirements could not be retrieved right now. Retry shortly; do not treat this as no requirements."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/phone-numbers/available": {
      "get": {
        "summary": "Search available phone numbers",
        "description": "Search for available phone numbers to purchase by country and type.",
        "operationId": "searchAvailablePhoneNumbers",
        "parameters": [
          {
            "name": "countryCode",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            },
            "example": "US",
            "description": "Two-letter ISO country code."
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PhoneNumberType"
            },
            "description": "Type of phone number to search for."
          },
          {
            "name": "contains",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 20
            },
            "description": "Search for numbers containing this string: digits, or letters for vanity numbers. Spaces, dashes, dots and parentheses are ignored, so `1-800` searches `1800`."
          },
          {
            "name": "capabilities",
            "in": "query",
            "description": "Comma-separated capabilities the number must have: `sms`, `voice`, `mms`. Numbers missing any of them are dropped.",
            "schema": {
              "type": "string",
              "example": "voice,sms"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50
            },
            "description": "Maximum number of results to return."
          }
        ],
        "responses": {
          "200": {
            "description": "List of available phone numbers. An empty `items` means the country is served but nothing matched these filters, including a number type the country does not sell (retry with another `type`) and a `contains` pattern no number matches.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AvailablePhoneNumber"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters, or `country_restricted`: phone numbers in this country are not sold self-service and need a minimum commitment. The message names who to contact. A `contains` pattern the inventory refused is answered `invalid_request` with `details.field` set to `contains`; one that only matched nothing is a `200` with empty `items`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "country_restricted": {
                    "summary": "Country needs an enterprise agreement",
                    "value": {
                      "code": "country_restricted",
                      "message": "Phone numbers in this country require a 12-month minimum commitment. Contact sales@zavu.dev for enterprise plans."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`country_unavailable`: we do not currently stock numbers in this country. Distinct from an empty `items` array, which means the country is served but nothing matched your filters — retry with another `type`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "country_unavailable",
                  "message": "Phone numbers are not available in this country."
                }
              }
            }
          },
          "502": {
            "description": "The number inventory could not be reached, or refused a search it should accept. Retry; do not treat this as no stock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "inventory_unavailable",
                  "message": "Available numbers could not be retrieved right now. Retry shortly; do not treat this as no stock."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/phone-numbers": {
      "post": {
        "summary": "Purchase a phone number",
        "description": "Purchase an available phone number. Requires a paid plan: the Free plan cannot purchase phone numbers and receives `402` with code `paid_plan_required`.\n\n**The included number.** A paid plan includes one number at no charge, once per account: it must be a US or Canadian number (a +1 number) costing $20 a month or less. `isFreeEligible` in `GET /v1/phone-numbers/available` marks the numbers that qualify. Claiming it spends the benefit for good, across every team the account owner owns, so releasing that number does not make another one free.\n\n**Numbers with regulatory requirements.** Which numbers need regulatory information is decided per number, not by a fixed country list. The purchase looks the requirements up for the exact number before charging anything:\n\n1. `GET /v1/phone-numbers/requirements?phoneNumber=...`. If `items` is empty, buy normally.\n2. Create what it asks for: addresses with `POST /v1/addresses`, documents with `POST /v1/documents`.\n3. Purchase with `type` and `regulatoryRequirements`. The number is bought and billed at once with `regulatoryStatus: pending_review`.\n4. Poll `GET /v1/phone-numbers/{phoneNumberId}` until `regulatoryStatus` is `approved`. Assign it to a sender before or after approval; it starts carrying messages once approved.\n\n**Reuse.** Information you submitted is kept for your project, per country and `type`, and a later purchase there may omit `regulatoryRequirements`. Reuse only happens when what is kept still covers every requirement of the new number and every address and document in it belongs to the project. Otherwise, or when nothing is kept, the purchase returns `400 regulatory_compliance_required` with the missing requirements in `details`.\n\nInvalid values (a missing, unknown or repeated requirement id, an address or document from another project, or one rejected in review) return `400 invalid_request`. If an address or document cannot be registered for review, the purchase returns `400 invalid_request` naming the requirement. If the requirements cannot be looked up, the purchase returns `502 requirements_unavailable`, except for US and Canadian numbers, which are sold as numbers without requirements. None of these errors charge anything.",
        "operationId": "purchasePhoneNumber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneNumberPurchaseRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Number without regulatory requirements",
                  "value": {
                    "phoneNumber": "+15551234567",
                    "name": "Primary Line"
                  }
                },
                "regulated": {
                  "summary": "Number in a regulated country",
                  "value": {
                    "phoneNumber": "+4930123456",
                    "type": "local",
                    "regulatoryRequirements": [
                      {
                        "requirementType": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
                        "fieldValue": "jd7x2k3m4n5p6q7r8s9t0abc"
                      },
                      {
                        "requirementType": "3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f",
                        "fieldValue": "kg2abc123def456ghi789"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Phone number purchased successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneNumberResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, the number is no longer available or not listed under the `type` sent (`number_unavailable`), the country is restricted, regulatory information is missing (`regulatory_compliance_required`), or the regulatory information sent is invalid (`invalid_request`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "regulatory_compliance_required": {
                    "summary": "Regulatory information missing",
                    "value": {
                      "code": "regulatory_compliance_required",
                      "message": "This number requires regulatory information. Fetch GET /v1/phone-numbers/requirements?phoneNumber=%2B4930123456 and pass the values as regulatoryRequirements, with the number's type.",
                      "details": {
                        "countryCode": "DE",
                        "phoneNumberType": "local",
                        "missingRequirements": [
                          {
                            "id": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
                            "name": "Address",
                            "type": "address"
                          }
                        ]
                      }
                    }
                  },
                  "invalid_regulatory_value": {
                    "summary": "Address from another project",
                    "value": {
                      "code": "invalid_request",
                      "message": "Requirement \"Address\" (8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10) must be the id of an address created in this project.",
                      "details": {
                        "requirementId": "8c5b1a2e-0f3d-4f5b-9a61-2c7e4d9b1f10",
                        "expected": "address",
                        "reason": "not_owned"
                      }
                    }
                  },
                  "missing_regulatory_value": {
                    "summary": "A required value was not sent",
                    "value": {
                      "code": "invalid_request",
                      "message": "Missing a value for: Identity document (3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f).",
                      "details": {
                        "missingRequirements": [
                          {
                            "id": "3f9e2d41-7b6a-4c1e-8d52-9a0b1c2d3e4f",
                            "name": "Identity document",
                            "type": "document"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Payment required. Either the balance cannot cover the purchase (code `insufficient_balance`) or the team is on the Free plan (code `paid_plan_required`): buying phone numbers requires a paid plan, which includes one US number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient_balance": {
                    "summary": "Balance cannot cover the purchase",
                    "value": {
                      "code": "insufficient_balance",
                      "message": "Insufficient balance. Required: $1.25. Please add funds to continue."
                    }
                  },
                  "paid_plan_required": {
                    "summary": "Free plan cannot purchase numbers",
                    "value": {
                      "code": "paid_plan_required",
                      "message": "Buying phone numbers requires a paid plan. Upgrade to Pro to get one US number included."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The number's regulatory requirements could not be looked up, so nothing was charged. Retry. Not returned for US and Canadian numbers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "requirements_unavailable",
                  "message": "Regulatory requirements could not be retrieved right now, so nothing was charged. Retry shortly."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List phone numbers",
        "description": "List all phone numbers owned by this project.",
        "operationId": "listPhoneNumbers",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PhoneNumberStatus"
            },
            "description": "Filter by phone number status."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Pagination cursor."
          }
        ],
        "responses": {
          "200": {
            "description": "List of phone numbers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OwnedPhoneNumber"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/phone-numbers/{phoneNumberId}": {
      "get": {
        "summary": "Get phone number",
        "description": "Get details of a specific phone number.",
        "operationId": "getPhoneNumber",
        "parameters": [
          {
            "$ref": "#/components/parameters/PhoneNumberIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Phone number details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneNumberResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update phone number",
        "description": "Update a phone number's name or sender assignment.",
        "operationId": "updatePhoneNumber",
        "parameters": [
          {
            "$ref": "#/components/parameters/PhoneNumberIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneNumberUpdateRequest"
              },
              "examples": {
                "rename": {
                  "summary": "Rename phone number",
                  "value": {
                    "name": "Support Line"
                  }
                },
                "assign": {
                  "summary": "Assign to sender",
                  "value": {
                    "senderId": "sender_abc123"
                  }
                },
                "unassign": {
                  "summary": "Unassign from sender",
                  "value": {
                    "senderId": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneNumberResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, or the number was rejected in regulatory review and cannot be assigned to a sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Phone number or sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The sender could not be set up for SMS right now. Nothing was assigned; retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Release phone number",
        "description": "Release a phone number. The phone number must not be assigned to a sender.",
        "operationId": "releasePhoneNumber",
        "parameters": [
          {
            "$ref": "#/components/parameters/PhoneNumberIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Phone number released."
          },
          "400": {
            "description": "Cannot release phone number assigned to a sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Phone number not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/invitations": {
      "post": {
        "summary": "Create invitation",
        "description": "Create a partner invitation link for a client to connect a Meta channel. The client opens the returned `url` and authorizes with Meta; the resulting sender is created in your project when they finish, and the invitation transitions to `completed`.\n\n`connectionType` picks the channel:\n- `whatsapp_waba` (default): Meta's embedded signup links an official WhatsApp Business Account.\n- `messenger`: the client picks a Facebook Page they administer; its Messenger inbox (including Marketplace chats) is routed to Zavu.\n\nOne invitation connects one channel — create one per channel to onboard a client on several. `phoneNumberId` and `allowedPhoneCountries` apply to `whatsapp_waba` only.",
        "operationId": "createInvitation",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvitationCreateRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Basic invitation",
                  "value": {
                    "clientName": "Acme Corp"
                  }
                },
                "full": {
                  "summary": "Full invitation with all options",
                  "value": {
                    "clientName": "Acme Corp",
                    "clientEmail": "contact@acme.com",
                    "clientPhone": "+14155551234",
                    "expiresInDays": 14,
                    "allowedPhoneCountries": [
                      "US",
                      "MX"
                    ]
                  }
                },
                "messenger": {
                  "summary": "Messenger invitation (Facebook Page)",
                  "value": {
                    "clientName": "Acme Corp",
                    "clientEmail": "contact@acme.com",
                    "connectionType": "messenger"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List invitations",
        "description": "List partner invitations for this project.",
        "operationId": "listInvitations",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/InvitationStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invitation"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters (`invalid_request`): an unknown `status`, or `limit` outside 1-100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/invitations/webhook": {
      "get": {
        "summary": "Get project webhook",
        "description": "The project webhook for the project behind the API key. `webhook` is null when none is configured. The secret is never returned here.",
        "operationId": "getProjectWebhook",
        "responses": {
          "200": {
            "description": "The project webhook, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhook"
                  ],
                  "properties": {
                    "webhook": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ProjectWebhook"
                        }
                      ],
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Configure project webhook",
        "description": "Create or replace the project webhook for the project behind the API key, and activate it. The response carries the signing `secret`; an existing secret is kept.\n\nThis is where `invitation.status_changed` is delivered. Events are scoped to this project: when you onboard clients in sub-accounts, call this with each sub-account's API key.",
        "operationId": "configureProjectWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectWebhookCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhook"
                  ],
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/ProjectWebhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL or an event a project webhook cannot subscribe to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update project webhook",
        "description": "Change the URL, the events, or whether the webhook is active. The webhook must already exist.",
        "operationId": "updateProjectWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectWebhookUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated. The secret is not returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhook"
                  ],
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/ProjectWebhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No project webhook is configured, or an invalid event was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Remove project webhook",
        "description": "Remove the project webhook and its secret. Project events stop being delivered.",
        "operationId": "deleteProjectWebhook",
        "responses": {
          "204": {
            "description": "Webhook removed."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/invitations/{invitationId}": {
      "get": {
        "summary": "Get invitation",
        "operationId": "getInvitation",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvitationIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invitation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/invitations/{invitationId}/cancel": {
      "post": {
        "summary": "Cancel invitation",
        "description": "Cancel an active invitation. The client will no longer be able to use the invitation link.",
        "operationId": "cancelInvitation",
        "parameters": [
          {
            "$ref": "#/components/parameters/InvitationIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Cannot cancel this invitation (already completed or cancelled).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invitation not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/addresses": {
      "post": {
        "summary": "Create address",
        "description": "Create a regulatory address, to use as the value of an `address` requirement when buying a phone number. It is registered for review when it is created, with status `pending`.",
        "operationId": "createAddress",
        "tags": [
          "Regulatory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddressCreateRequest"
              },
              "example": {
                "firstName": "John",
                "lastName": "Doe",
                "streetAddress": "123 Main St",
                "locality": "Berlin",
                "postalCode": "10115",
                "countryCode": "DE"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Address created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (first name and last name are required), or the address could not be registered for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List addresses",
        "description": "List regulatory addresses for this project.",
        "operationId": "listAddresses",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of addresses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Address"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/addresses/{addressId}": {
      "get": {
        "summary": "Get address",
        "description": "Get a specific regulatory address.",
        "operationId": "getAddress",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AddressIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Address details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Address not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete address",
        "description": "Delete a regulatory address from this project. Any address can be deleted, whatever its status. Phone numbers already purchased with it are not affected, and neither is information already submitted for later purchases in its country.",
        "operationId": "deleteAddress",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AddressIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Address deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Address not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/documents": {
      "post": {
        "summary": "Create document",
        "description": "Create a regulatory document record after uploading the file. Use the upload-url endpoint first to get an upload URL.",
        "operationId": "createRegulatoryDocument",
        "tags": [
          "Regulatory"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegulatoryDocumentCreateRequest"
              },
              "example": {
                "name": "Passport Scan",
                "documentType": "passport",
                "storageId": "kg2abc123...",
                "mimeType": "image/jpeg",
                "fileSize": 102400
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document created and submitted for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegulatoryDocumentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or upload failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List documents",
        "description": "List regulatory documents for this project.",
        "operationId": "listRegulatoryDocuments",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RegulatoryDocument"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/documents/upload-url": {
      "post": {
        "summary": "Get document upload URL",
        "description": "Get a presigned URL to upload a document file. After uploading, use the storageId to create the document record.",
        "operationId": "getRegulatoryDocumentUploadUrl",
        "tags": [
          "Regulatory"
        ],
        "responses": {
          "200": {
            "description": "Upload URL generated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadUrlResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/documents/{documentId}": {
      "get": {
        "summary": "Get document",
        "description": "Get a specific regulatory document.",
        "operationId": "getRegulatoryDocument",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RegulatoryDocumentIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Document details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegulatoryDocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Document not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete document",
        "description": "Delete a regulatory document. Cannot delete verified documents.",
        "operationId": "deleteRegulatoryDocument",
        "tags": [
          "Regulatory"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RegulatoryDocumentIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Document deleted."
          },
          "400": {
            "description": "Cannot delete a verified document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Document not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent": {
      "get": {
        "summary": "Get agent",
        "description": "Get the AI agent configuration for a sender.",
        "operationId": "getAgent",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender or agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create agent",
        "description": "Create an AI agent for a sender. Each sender can have at most one agent.",
        "operationId": "createAgent",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentCreateRequest"
              },
              "example": {
                "name": "Customer Support",
                "provider": "openai",
                "model": "gpt-4o-mini",
                "systemPrompt": "You are a helpful customer support agent. Be friendly and concise.",
                "apiKey": "sk-..."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Agent created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: the sender already answers with an agent, either one created on it or one connected to it with `POST /v1/agents/{agentId}/senders`. Update that agent instead, or disconnect it first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "This sender already has an AI agent configured"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update agent",
        "description": "Update an AI agent's configuration.",
        "operationId": "updateAgent",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender or agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete agent",
        "description": "Delete the agent created on this sender. A sender that is only connected to an agent through `POST /v1/agents/{agentId}/senders` is refused with `409`: deleting there would remove the agent from every sender it answers on. Disconnect it with `DELETE /v1/agents/{agentId}/senders/{senderId}`, or delete the agent with `DELETE /v1/agents/{agentId}`.",
        "operationId": "deleteAgent",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Agent deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender or agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The sender is connected to an agent it does not own.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "This sender is connected to agent agent_abc123, which it does not own. Disconnect it with DELETE /v1/agents/agent_abc123/senders/sender_12345, or delete the agent with DELETE /v1/agents/agent_abc123."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/stats": {
      "get": {
        "summary": "Get agent statistics",
        "description": "Get statistics for an AI agent including invocations, tokens, and costs.\n\nCovers the messaging channels only. Voice calls are not counted here: a call is a multi-turn conversation rather than one inbound message and one reply, so it is recorded as a call, not an execution. An agent that only answers phone calls reports zeros on every field. Use `GET /v1/calls` for voice activity, duration, and cost.",
        "operationId": "getAgentStats",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentStats"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/executions": {
      "get": {
        "summary": "List agent executions",
        "description": "List recent agent executions with pagination.\n\nAn execution is one inbound message answered by the agent, so this covers the messaging channels only. Voice calls are never listed here regardless of how many the agent handled. Use `GET /v1/calls` (and `GET /v1/calls/{callId}` for the transcript) for voice.",
        "operationId": "listAgentExecutions",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/AgentExecutionStatus"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of executions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentExecution"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters, such as a `status` that is not an execution status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/executions/{executionId}": {
      "get": {
        "summary": "Get a single agent execution",
        "description": "Fetch full details for one execution — including `errorMessage`, `errorCode`, and `responseText`. Use this to debug failures surfaced by the list endpoint.",
        "operationId": "getAgentExecution",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "name": "executionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Execution details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "execution"
                  ],
                  "properties": {
                    "execution": {
                      "$ref": "#/components/schemas/AgentExecution"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent or execution not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/flows": {
      "get": {
        "summary": "List flows",
        "description": "List flows for an agent.",
        "operationId": "listAgentFlows",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "name": "enabled",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of flows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentFlow"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters. `enabled` must be `true` or `false`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create flow",
        "description": "Create a new flow for an agent.",
        "operationId": "createAgentFlow",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowCreateRequest"
              },
              "example": {
                "name": "Lead Capture",
                "description": "Capture lead information",
                "trigger": {
                  "type": "keyword",
                  "keywords": [
                    "info",
                    "pricing",
                    "demo"
                  ]
                },
                "steps": [
                  {
                    "id": "welcome",
                    "type": "message",
                    "config": {
                      "text": "Thanks for your interest! Let me get some info."
                    },
                    "nextStepId": "ask_name"
                  },
                  {
                    "id": "ask_name",
                    "type": "collect",
                    "config": {
                      "variable": "name",
                      "prompt": "What's your name?"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Flow created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/flows/{flowId}": {
      "get": {
        "summary": "Get flow",
        "description": "Get a specific flow.",
        "operationId": "getAgentFlow",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/FlowIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Flow details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Flow not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update flow",
        "description": "Update a flow.",
        "operationId": "updateAgentFlow",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/FlowIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Flow updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Flow not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete flow",
        "description": "Delete a flow. Cannot delete flows with active sessions.",
        "operationId": "deleteAgentFlow",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/FlowIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Flow deleted."
          },
          "400": {
            "description": "Cannot delete flow with active sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Flow not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/flows/{flowId}/duplicate": {
      "post": {
        "summary": "Duplicate flow",
        "description": "Create a copy of an existing flow with a new name.",
        "operationId": "duplicateAgentFlow",
        "tags": [
          "Agent Flows"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/FlowIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlowDuplicateRequest"
              },
              "example": {
                "newName": "Lead Capture (Copy)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Flow duplicated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlowResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Flow not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/tools": {
      "get": {
        "summary": "List tools",
        "description": "List tools for an agent.",
        "operationId": "listAgentTools",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "name": "enabled",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of tools.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentTool"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters. `enabled` must be `true` or `false`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create tool",
        "description": "Create a new tool for an agent. Tools allow the agent to call external webhooks.",
        "operationId": "createAgentTool",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ToolCreateRequest"
              },
              "example": {
                "name": "get_order_status",
                "description": "Get the status of a customer order",
                "webhookUrl": "https://api.example.com/webhooks/order-status",
                "webhookSecret": "whsec_...",
                "parameters": {
                  "type": "object",
                  "properties": {
                    "order_id": {
                      "type": "string",
                      "description": "The order ID to look up"
                    }
                  },
                  "required": [
                    "order_id"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tool created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`conflict`: the agent already has a tool with this name. Tool names are unique per agent, because a flow's `tool` step and the model both call a tool by name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "A tool with this name already exists for this agent"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/tools/{toolId}": {
      "get": {
        "summary": "Get tool",
        "description": "Get a specific tool.",
        "operationId": "getAgentTool",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Tool details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update tool",
        "description": "Update a tool.",
        "operationId": "updateAgentTool",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ToolUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete tool",
        "description": "Delete a tool.",
        "operationId": "deleteAgentTool",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Tool deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/tools/{toolId}/test": {
      "post": {
        "summary": "Test tool",
        "description": "Run a tool with the parameters you supply and return what it answered.\n\nThe call is synchronous: the response carries the tool's status, body, and duration, so a green result is evidence the tool ran rather than evidence it was accepted. Each run is also recorded and readable afterwards via `GET /v1/senders/{senderId}/agent/tools/{toolId}/test-runs`.\n\nA tool that answers with an error is reported as a run with `success: false` — the endpoint itself still returns 200. This fires the tool's real webhook, so a test has whatever side effects the tool has.",
        "operationId": "testAgentTool",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ToolTestRequest"
              },
              "example": {
                "testParams": {
                  "order_id": "ORD-12345"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran. Check `run.success` for what it answered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolTestResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/tools/{toolId}/test-runs": {
      "get": {
        "summary": "List tool test runs",
        "description": "Recent runs of this tool triggered from the test endpoint, newest first. Covers manual tests only: a tool called by an agent during a real conversation is not recorded here.",
        "operationId": "listAgentToolTestRuns",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent test runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentToolTestRun"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/knowledge-bases": {
      "get": {
        "summary": "List knowledge bases",
        "description": "List knowledge bases for an agent.",
        "operationId": "listAgentKnowledgeBases",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of knowledge bases.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentKnowledgeBase"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create knowledge base",
        "description": "Create a new knowledge base for an agent.",
        "operationId": "createAgentKnowledgeBase",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBaseCreateRequest"
              },
              "example": {
                "name": "Product FAQ",
                "description": "Frequently asked questions about our products"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Knowledge base created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeBaseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/knowledge-bases/{kbId}": {
      "get": {
        "summary": "Get knowledge base",
        "description": "Get a specific knowledge base.",
        "operationId": "getAgentKnowledgeBase",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Knowledge base details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeBaseResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Knowledge base not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update knowledge base",
        "description": "Update a knowledge base.",
        "operationId": "updateAgentKnowledgeBase",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBaseUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Knowledge base updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnowledgeBaseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Knowledge base not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete knowledge base",
        "description": "Delete a knowledge base and all its documents.",
        "operationId": "deleteAgentKnowledgeBase",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Knowledge base deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Knowledge base not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/knowledge-bases/{kbId}/documents": {
      "get": {
        "summary": "List documents",
        "description": "List documents in a knowledge base.",
        "operationId": "listAgentDocuments",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentDocument"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Knowledge base not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create document",
        "description": "Add a document to a knowledge base. The document will be automatically processed for RAG.",
        "operationId": "createAgentDocument",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentCreateRequest"
              },
              "example": {
                "title": "Return Policy",
                "content": "Our return policy allows returns within 30 days of purchase..."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Document created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Knowledge base not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/knowledge-bases/{kbId}/documents/{docId}": {
      "get": {
        "summary": "Get document",
        "description": "Get a single document from a knowledge base.",
        "operationId": "getAgentDocument",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          },
          {
            "$ref": "#/components/parameters/DocumentIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Document details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Document not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update document",
        "description": "Update a document's title or content. Updating content reprocesses the document for RAG.",
        "operationId": "updateAgentDocument",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          },
          {
            "$ref": "#/components/parameters/DocumentIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Document not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete document",
        "description": "Delete a document from a knowledge base.",
        "operationId": "deleteAgentDocument",
        "tags": [
          "Agent Knowledge Bases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/KnowledgeBaseIdParam"
          },
          {
            "$ref": "#/components/parameters/DocumentIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Document deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Document not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/urls": {
      "post": {
        "summary": "Submit URL for verification",
        "description": "Submit a URL for verification. URLs are automatically checked against Google Web Risk API. Safe URLs are auto-approved, malicious URLs are blocked. URL shorteners (bit.ly, t.co, etc.) are always blocked.\n\n**Important:** SMS, one-way SMS and voice messages containing URLs require those URLs to be verified before the message can be sent; a URL that is not yet known is checked when the message is sent. Email, WhatsApp, Telegram, Instagram and Messenger messages are not checked. This endpoint allows pre-verification of URLs.",
        "operationId": "submitUrl",
        "tags": [
          "URL Verification"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifiedUrlSubmitRequest"
              },
              "example": {
                "url": "https://example.com/page"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "URL already verified.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedUrlResponse"
                }
              }
            }
          },
          "201": {
            "description": "URL verified and approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedUrlResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "URL blocked (malicious, rejected, or shortener).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "malicious": {
                    "summary": "Malicious URL",
                    "value": {
                      "code": "url_malicious",
                      "message": "URL has been flagged as potentially harmful by security systems. This incident has been logged.",
                      "details": {
                        "urls": [
                          "https://malicious-site.com"
                        ],
                        "threatTypes": [
                          "MALWARE",
                          "SOCIAL_ENGINEERING"
                        ],
                        "warning": "fraud_prevention"
                      }
                    }
                  },
                  "shortener": {
                    "summary": "URL shortener blocked",
                    "value": {
                      "code": "url_shortener_blocked",
                      "message": "URL shorteners are not allowed. Please use the full destination URL.",
                      "details": {
                        "urls": [
                          "https://bit.ly/abc123"
                        ],
                        "reason": "url_shorteners_hidden_destination"
                      }
                    }
                  },
                  "rejected": {
                    "summary": "URL rejected",
                    "value": {
                      "code": "url_rejected",
                      "message": "URL has been rejected by our security team.",
                      "details": {
                        "urls": [
                          "https://rejected-site.com"
                        ],
                        "warning": "fraud_prevention"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List verified URLs",
        "description": "List URLs that have been verified for this project.",
        "operationId": "listUrls",
        "tags": [
          "URL Verification"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/VerifiedUrlStatus"
            },
            "description": "Filter by verification status."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of verified URLs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VerifiedUrl"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/urls/{urlId}": {
      "get": {
        "summary": "Get URL details",
        "description": "Get details of a specific verified URL.",
        "operationId": "getUrl",
        "tags": [
          "URL Verification"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/UrlIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "URL details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedUrlResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "URL not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/me": {
      "get": {
        "summary": "Get current API key context",
        "description": "Returns the project, team, and API key metadata bound to the current Bearer token. Used by CLIs and SDKs to confirm which project they will operate on.",
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Context for the API key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "project",
                    "team",
                    "apiKey",
                    "isTestMode"
                  ],
                  "properties": {
                    "project": {
                      "type": "object",
                      "required": [
                        "id",
                        "name",
                        "isSubAccount"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "isSubAccount": {
                          "type": "boolean"
                        }
                      }
                    },
                    "team": {
                      "type": "object",
                      "required": [
                        "id",
                        "name"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "apiKey": {
                      "type": "object",
                      "required": [
                        "id"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        }
                      }
                    },
                    "isTestMode": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/balance": {
      "get": {
        "summary": "Get balance",
        "description": "Get balance for the API key's team. If the API key belongs to a sub-account, also includes the sub-account's total spending and credit limit.",
        "operationId": "getBalance",
        "tags": [
          "Balance"
        ],
        "responses": {
          "200": {
            "description": "Balance details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                },
                "example": {
                  "balance": 150000,
                  "currency": "usd",
                  "creditLimit": 100000,
                  "totalSpent": 35000,
                  "isSubAccount": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/sub-accounts": {
      "post": {
        "summary": "Create sub-account",
        "description": "Create a new sub-account (project) with its own API key. All charges are billed to the parent team's balance. Use creditLimit to set a spending cap. The sub-account's API key is returned only in the creation response. Requires a parent project API key; sub-account API keys receive HTTP 403.",
        "operationId": "createSubAccount",
        "tags": [
          "Sub-Accounts"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubAccountCreateRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Basic sub-account",
                  "value": {
                    "name": "Client ABC"
                  }
                },
                "with_limit": {
                  "summary": "Sub-account with spending cap",
                  "value": {
                    "name": "Client ABC",
                    "externalId": "client_123",
                    "creditLimit": 100000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sub-account created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubAccountResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A sub-account with this `externalId` already exists in the team.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "A sub-account with this externalId already exists"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List sub-accounts",
        "description": "List sub-accounts for this team. Requires a parent project API key; sub-account API keys receive HTTP 403.",
        "operationId": "listSubAccounts",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SubAccount"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/sub-accounts/{id}": {
      "get": {
        "summary": "Get sub-account",
        "operationId": "getSubAccount",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Sub-account details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubAccountResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sub-account not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Get sub-account. Requires a parent project API key; sub-account API keys receive HTTP 403."
      },
      "patch": {
        "summary": "Update sub-account",
        "operationId": "updateSubAccount",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubAccountUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sub-account updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubAccountResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sub-account not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Another sub-account in the team already has this `externalId`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Update sub-account. Requires a parent project API key; sub-account API keys receive HTTP 403."
      },
      "delete": {
        "summary": "Deactivate sub-account",
        "description": "Deactivate a sub-account. Remaining balance is returned to the parent team and all API keys are revoked. Requires a parent project API key; sub-account API keys receive HTTP 403.",
        "operationId": "deactivateSubAccount",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Sub-account deactivated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "keysRevoked"
                  ],
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "Sub-account deactivated"
                    },
                    "keysRevoked": {
                      "type": "integer",
                      "description": "Number of API keys revoked."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sub-account not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/sub-accounts/{id}/balance": {
      "get": {
        "summary": "Get sub-account spending",
        "description": "Get spending information for a sub-account. Returns the parent team's balance, the sub-account's total spending, and its credit limit (spending cap). Requires a parent project API key; sub-account API keys receive HTTP 403.",
        "operationId": "getSubAccountBalance",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Sub-account balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not a sub-account of this team. The parent project's own id is not a sub-account and answers 404.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/sub-accounts/{id}/api-keys": {
      "get": {
        "summary": "List sub-account API keys",
        "operationId": "listSubAccountApiKeys",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "List of API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SubAccountApiKey"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not a sub-account of this team. The parent project's own id is not a sub-account and answers 404.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "List sub-account API keys. Requires a parent project API key; sub-account API keys receive HTTP 403."
      },
      "post": {
        "summary": "Create sub-account API key",
        "operationId": "createSubAccountApiKey",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubAccountApiKeyCreateRequest"
              },
              "example": {
                "name": "Production Key",
                "environment": "live"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created. The full key is only returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "apiKey"
                  ],
                  "properties": {
                    "apiKey": {
                      "type": "object",
                      "required": [
                        "id",
                        "key",
                        "name",
                        "environment"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "environment": {
                          "type": "string",
                          "enum": [
                            "live",
                            "test"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not a sub-account of this team. The parent project's own id is not a sub-account and answers 404.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Create sub-account API key. Requires a parent project API key; sub-account API keys receive HTTP 403."
      }
    },
    "/v1/sub-accounts/{id}/api-keys/{keyId}": {
      "delete": {
        "summary": "Revoke sub-account API key",
        "operationId": "revokeSubAccountApiKey",
        "tags": [
          "Sub-Accounts"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubAccountIdParam"
          },
          {
            "$ref": "#/components/parameters/SubAccountApiKeyIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "API key revoked."
          },
          "400": {
            "description": "The API key is already revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_request",
                  "message": "API key is already revoked"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden. Sub-account API keys cannot manage sub-accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sub-account or API key not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Revoke sub-account API key. Requires a parent project API key; sub-account API keys receive HTTP 403."
      }
    },
    "/v1/10dlc/brands": {
      "get": {
        "summary": "List 10DLC brands",
        "description": "List 10DLC brand registrations for this project.",
        "operationId": "listTenDlcBrands",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of brands.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TenDlcBrand"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create 10DLC brand",
        "description": "Create a 10DLC brand registration. The brand starts in draft status. Submit it for review using the submit endpoint.",
        "operationId": "createTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenDlcBrandCreateRequest"
              },
              "example": {
                "entityType": "PRIVATE_PROFIT",
                "displayName": "Acme Corp",
                "companyName": "Acme Corporation",
                "ein": "12-3456789",
                "email": "compliance@acme.com",
                "phone": "+14155551234",
                "street": "123 Main St",
                "city": "San Francisco",
                "state": "CA",
                "postalCode": "94102",
                "country": "US",
                "website": "https://acme.com",
                "vertical": "Technology"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Brand created in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcBrandResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/brands/use-cases": {
      "get": {
        "summary": "List 10DLC use cases",
        "description": "List available use cases for 10DLC campaign registration.",
        "operationId": "listTenDlcUseCases",
        "tags": [
          "10DLC"
        ],
        "responses": {
          "200": {
            "description": "List of available use cases.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "useCases"
                  ],
                  "properties": {
                    "useCases": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "Use case identifier. Pass it as `useCase` when creating a campaign.",
                            "example": "ACCOUNT_NOTIFICATION"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/brands/{brandId}": {
      "get": {
        "summary": "Get 10DLC brand",
        "operationId": "getTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcBrandIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcBrandResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update 10DLC brand",
        "description": "Update a 10DLC brand in draft status. Cannot update after submission.",
        "operationId": "updateTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcBrandIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenDlcBrandUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Brand updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcBrandResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or brand already submitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete 10DLC brand",
        "operationId": "deleteTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcBrandIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Brand deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/brands/{brandId}/submit": {
      "post": {
        "summary": "Submit 10DLC brand for review",
        "description": "Submit a draft brand to The Campaign Registry (TCR) for vetting. The brand must be in draft status, and the team must have an approved Business Verification (KYB) — carriers register a brand against a vetted legal entity, so submitting without one returns `403` with code `kyb_required`.\n\nTCR's one-time $4 brand registration fee is charged from your balance at submission (passed through at cost) and refunded if the carrier rejects the registration. The fee is per BRAND: a team registering a second legal entity pays it again. A brand already paid for through the compliance flow is not charged twice. Campaign registration is billed separately when a campaign is submitted.",
        "operationId": "submitTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcBrandIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand submitted for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcBrandResponse"
                }
              }
            }
          },
          "400": {
            "description": "Brand already submitted or invalid status, or it lacks an EIN, which every entity type except `SOLE_PROPRIETOR` needs to register (`invalid_request`, nothing charged). Or the carrier refused the registration (`invalid_request`, with the carrier's reason in `message`). On a refusal the fee is refunded and the brand stays `draft`, so it can be corrected and submitted again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Business verification (KYB) is not approved. Carriers register 10DLC against a vetted legal entity, so registration is blocked until it is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "kyb_required",
                  "message": "Complete Business Verification before registering a 10DLC brand or campaign.",
                  "details": {
                    "dashboardUrl": "/kyb"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/brands/{brandId}/sync": {
      "post": {
        "summary": "Sync 10DLC brand status",
        "description": "Sync the brand status with the registration provider. Use this to check for approval updates after submission.",
        "operationId": "syncTenDlcBrand",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcBrandIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Brand status synced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcBrandResponse"
                }
              }
            }
          },
          "400": {
            "description": "Brand has not been submitted yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns": {
      "get": {
        "summary": "List 10DLC campaigns",
        "description": "List 10DLC campaign registrations for this project.",
        "operationId": "listTenDlcCampaigns",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "name": "brandId",
            "in": "query",
            "description": "Filter campaigns by brand ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of campaigns.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TenDlcCampaign"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create 10DLC campaign",
        "description": "Create a 10DLC campaign under an existing brand. The campaign starts in draft status. Submit it for carrier review using the submit endpoint.",
        "operationId": "createTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenDlcCampaignCreateRequest"
              },
              "example": {
                "brandId": "brand_abc123",
                "name": "Order Notifications",
                "useCase": "ACCOUNT_NOTIFICATION",
                "description": "Send order status updates and shipping notifications to customers who opted in.",
                "sampleMessages": [
                  "Hi {{name}}, your order #{{order_id}} has shipped! Track it at {{url}}",
                  "Your order #{{order_id}} has been delivered. Thank you for your purchase!"
                ],
                "subscriberOptIn": true,
                "subscriberOptOut": true,
                "subscriberHelp": true,
                "numberPooling": false,
                "directLending": false,
                "embeddedLink": true,
                "embeddedPhone": false,
                "affiliateMarketing": false,
                "ageGated": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Campaign created in draft status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcCampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns/{campaignId}": {
      "get": {
        "summary": "Get 10DLC campaign",
        "operationId": "getTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcCampaignResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update 10DLC campaign",
        "description": "Update a 10DLC campaign in draft status. Cannot update after submission.",
        "operationId": "updateTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenDlcCampaignUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campaign updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcCampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or campaign already submitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete 10DLC campaign",
        "operationId": "deleteTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Campaign deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns/{campaignId}/submit": {
      "post": {
        "summary": "Submit 10DLC campaign for review",
        "description": "The team must have an approved Business Verification (KYB): submitting without one returns `403` with code `kyb_required`. Submit a draft campaign for carrier review. The campaign must be in draft status and its brand must be verified. TCR's one-time registration fee is charged from your balance at submission ($15 for standard use cases, $2 for LOW_VOLUME), passed through at cost and refunded if the carrier rejects it. Once approved, the campaign's monthly TCR fee ($10 standard, $2 LOW_VOLUME) is charged from your balance while the campaign is active — see registrationCostCents and monthlyFeeCents on the campaign object.",
        "operationId": "submitTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign submitted for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcCampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "Campaign already submitted or invalid status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Business verification (KYB) is not approved. Carriers register 10DLC against a vetted legal entity, so registration is blocked until it is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "kyb_required",
                  "message": "Complete Business Verification before registering a 10DLC brand or campaign.",
                  "details": {
                    "dashboardUrl": "/kyb"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns/{campaignId}/sync": {
      "post": {
        "summary": "Sync 10DLC campaign status",
        "description": "Sync the campaign status with the registration provider. Use this to check for approval updates after submission.",
        "operationId": "syncTenDlcCampaign",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign status synced.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcCampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "Campaign has not been submitted yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns/{campaignId}/phone-numbers": {
      "get": {
        "summary": "List campaign phone number assignments",
        "description": "List phone numbers assigned to a 10DLC campaign. A number that was unassigned is no longer listed.",
        "operationId": "listTenDlcCampaignPhoneNumbers",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of phone number assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TenDlcPhoneNumberAssignment"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Assign phone number to campaign",
        "description": "Assign a US phone number to an approved 10DLC campaign. The campaign must be in approved status.",
        "operationId": "assignTenDlcPhoneNumber",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenDlcPhoneNumberAssignRequest"
              },
              "example": {
                "phoneNumberId": "pn_abc123"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Phone number assigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenDlcAssignmentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Campaign not approved or invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign or phone number not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/10dlc/campaigns/{campaignId}/phone-numbers/{assignmentId}": {
      "delete": {
        "summary": "Unassign phone number from campaign",
        "description": "Remove a phone number assignment from a 10DLC campaign.",
        "operationId": "unassignTenDlcPhoneNumber",
        "tags": [
          "10DLC"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TenDlcCampaignIdParam"
          },
          {
            "$ref": "#/components/parameters/TenDlcAssignmentIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Phone number unassigned."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Campaign or assignment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions": {
      "post": {
        "summary": "Create function",
        "description": "Create a new Zavu Function. The function starts in `draft` status. A dedicated API key is auto-provisioned and injected as the `ZAVU_API_KEY` secret so the function can call back into the Zavu API without manual setup.\n\nProvide `sourceCode` to seed the draft. Call `POST /v1/functions/{functionId}/deploy` afterwards to publish.",
        "operationId": "createFunction",
        "tags": [
          "Functions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FunctionCreateRequest"
              },
              "example": {
                "slug": "order-bot",
                "name": "Order Bot",
                "description": "Replies to order status questions on WhatsApp.",
                "sourceCode": "import { defineFunction } from '@zavudev/functions';\n\nexport default defineFunction(async (event, ctx) => {\n  ctx.log('received', event.type);\n});\n",
                "dependencies": {
                  "openai": "^4.20.0"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Function created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZavuFunctionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g. duplicate slug, invalid dependencies).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A function with this `slug` already exists in the project. Slugs are unique per project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "A function with slug \"order-bot\" already exists"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}": {
      "get": {
        "summary": "Get function",
        "operationId": "getFunction",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Function details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZavuFunctionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update function draft",
        "description": "Update an existing function. `sourceCode` / `dependencies` edit the draft without triggering a build — they go live on the next `POST /v1/functions/{functionId}/deploy`. `httpEnabled` is applied to the deployed function immediately, so turning the public endpoint on or off does not require a redeploy.",
        "operationId": "updateFunctionDraft",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FunctionUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZavuFunctionResponse"
                }
              }
            }
          },
          "400": {
            "description": "No fields provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete function",
        "description": "Permanently delete a function and cascade: triggers, secrets, deployment history, managed agents+tools, and revoke the auto-provisioned API key. The AWS Lambda + log group are torn down asynchronously.",
        "operationId": "deleteFunction",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Function deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Cannot delete while a deployment is in progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/deploy": {
      "post": {
        "summary": "Deploy function",
        "description": "Publish the function. If `sourceCode` or `dependencies` are provided in the body, they replace the current draft before deployment. Returns immediately with a deployment ID — poll `GET /v1/functions/deployments/{deploymentId}` until status is `active` or `failed`.",
        "operationId": "deployFunction",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FunctionDeployRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Deployment queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FunctionDeploymentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or function in a non-deployable state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/deployments/{deploymentId}": {
      "get": {
        "summary": "Get deployment",
        "description": "Fetch a deployment to poll its status during a deploy.",
        "operationId": "getFunctionDeployment",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionDeploymentIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Deployment details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FunctionDeploymentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Deployment not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/secrets": {
      "get": {
        "summary": "List function secrets",
        "description": "Lists every secret key set on the function. Plaintext is NEVER returned — only the last 4 characters of each value, for visual confirmation.",
        "operationId": "listFunctionSecrets",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Secrets list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "secrets"
                  ],
                  "properties": {
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "key",
                          "valueLast4"
                        ],
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "key": {
                            "type": "string"
                          },
                          "valueLast4": {
                            "type": "string"
                          },
                          "syncedToAws": {
                            "type": "boolean"
                          },
                          "createdAt": {
                            "type": "number"
                          },
                          "updatedAt": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/secrets/{key}": {
      "put": {
        "summary": "Set function secret",
        "description": "Create or update a secret on a function. Marks the function out-of-sync; the next `POST /deploy` re-publishes the Lambda with the new env. Keys must match `[A-Z_][A-Z0-9_]*` (uppercase env-var style) and cannot start with reserved prefixes (AWS_, LAMBDA_, etc).",
        "operationId": "setFunctionSecret",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "value"
                ],
                "properties": {
                  "value": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Secret updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "201": {
            "description": "Secret created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid key or value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Unset function secret",
        "description": "Remove a secret from a function. Doesn't take effect on the running Lambda until the next deploy.",
        "operationId": "unsetFunctionSecret",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Secret removed."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Secret not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/git-link": {
      "post": {
        "summary": "Link a GitHub repository",
        "description": "Bind a repository to this function so every push to `branch` deploys it. A function holds at most one link; linking again returns 400.\n\n**The server decides how the link authenticates.** If the project has the Zavu GitHub App installed, the link uses that installation: private repositories work and there is nothing to configure in the repository. Otherwise it falls back to a manual link and the response carries a `webhookSecret` you add to the repository yourself. `connection` says which one you got.\n\nThe repository is not checked against GitHub here, because it cannot be: an owner/repo that does not exist, or that the installation cannot see, is accepted and fails on the first deploy with a fetch error.",
        "operationId": "createFunctionGitLink",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "owner",
                  "repo"
                ],
                "properties": {
                  "owner": {
                    "type": "string",
                    "example": "acme"
                  },
                  "repo": {
                    "type": "string",
                    "example": "order-bot"
                  },
                  "branch": {
                    "type": "string",
                    "default": "main"
                  },
                  "rootDir": {
                    "type": "string",
                    "description": "Subdirectory holding the project, for monorepos.",
                    "example": "apps/bot"
                  },
                  "autoDeploy": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "owner": "acme",
                "repo": "order-bot",
                "branch": "main"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Link created. The only response that ever carries `webhookSecret`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitRepoLinkResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid owner, repo or branch, or the function is already linked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "Get the linked repository",
        "description": "The link and its last deploy. Never returns the webhook secret.",
        "operationId": "getFunctionGitLink",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitRepoLinkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No repository is linked to this function.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update the linked repository",
        "description": "Change the branch, the root directory, or whether pushes deploy. Pass at least one field. `rootDir: null` clears the subdirectory.",
        "operationId": "updateFunctionGitLink",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "branch": {
                    "type": "string"
                  },
                  "rootDir": {
                    "type": "string",
                    "nullable": true
                  },
                  "autoDeploy": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "autoDeploy": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GitRepoLinkResponse"
                }
              }
            }
          },
          "400": {
            "description": "No fields to update, or invalid values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No repository is linked to this function.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Unlink the repository",
        "description": "Remove the link. The function and its deployments stay. A manual webhook left in the repository stops being accepted, so remove it there too.",
        "operationId": "deleteFunctionGitLink",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Link removed."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No repository is linked to this function.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/git-link/deploy": {
      "post": {
        "summary": "Deploy the linked branch now",
        "description": "Fetch the linked branch and deploy it without waiting for a push. Returns immediately; follow the outcome with `GET /v1/functions/{functionId}/git-link`, whose `lastStatus` and `lastError` describe the run.",
        "operationId": "deployFunctionGitLink",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          }
        ],
        "responses": {
          "202": {
            "description": "Deploy scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "scheduled"
                  ],
                  "properties": {
                    "scheduled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No repository is linked to this function.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A deploy for this link is already running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/functions/{functionId}/logs": {
      "get": {
        "summary": "Tail function logs",
        "description": "Fetch invocation logs for a function. Logs are paginated via `nextToken`. Pass `startTime` / `endTime` (Unix epoch milliseconds) to bound the window, or `filterPattern` to filter messages.",
        "operationId": "tailFunctionLogs",
        "tags": [
          "Functions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/FunctionIdParam"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 1000
            }
          },
          {
            "name": "nextToken",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startTime",
            "in": "query",
            "description": "Start of the log window in Unix epoch milliseconds.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "endTime",
            "in": "query",
            "description": "End of the log window in Unix epoch milliseconds.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "filterPattern",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Log events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FunctionLogsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Function not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/calls": {
      "post": {
        "summary": "Place a voice call",
        "description": "Place an outbound voice call answered by the voice agent configured on the sender. Zavu dials the recipient and runs the conversation through its managed voice pipeline (speech recognition, the agent's LLM, and speech synthesis, with real-time interruption handling).\n\n**Requirements:**\n- An account may call any destination from its first minute, within the daily ceiling: 5 calls a day for an account that has verified nothing, 50 a day on Free once it has verified its identity, added a payment method, settled a deposit or subscribed. Paid plans have no daily call ceiling. Over it, `429` with code `daily_limit_exceeded`. Full reference: https://docs.zavu.dev/concepts/sending-limits\n- The call is read by Zavu's automated risk review before it is dialed, the way a message is (see `POST /v1/messages`). A call is never held for a person: one the review stops is failed rather than placed late. Repeated refusals suspend the account's calling, answered with `403` and code `sending_suspended`.\n- The sender's agent must have `voice.enabled` set to `true`.\n- Not available with test-mode API keys.\n\n**Billing:** Voice calls are billed per minute of connected time plus telephony, deducted from your prepaid balance. A short-duration estimate is reserved when the call is placed; you are charged for the actual duration when the call ends.",
        "operationId": "createCall",
        "tags": [
          "Voice Agents"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceCallCreateRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Outbound call with the default sender",
                  "value": {
                    "to": "+56912345678"
                  }
                },
                "language": {
                  "summary": "Outbound call in a different language",
                  "value": {
                    "to": "+56912345678",
                    "language": "es-ES"
                  }
                },
                "full": {
                  "summary": "Outbound call with overrides",
                  "value": {
                    "to": "+56912345678",
                    "senderId": "sender_12345",
                    "greeting": "Hi, this is Acme calling about your appointment.",
                    "language": "en-US",
                    "maxDurationMinutes": 10,
                    "metadata": {
                      "campaign": "appointment_reminders"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Call accepted and dialing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceCallResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, the sender's agent does not have voice enabled, or voice calls are not available in test mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "voice_not_enabled": {
                    "summary": "Agent voice not enabled",
                    "value": {
                      "code": "invalid_request",
                      "message": "The sender's agent does not have voice enabled"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "insufficient_balance",
                  "message": "Insufficient balance to place this call. Please add funds to continue."
                }
              }
            }
          },
          "403": {
            "description": "This account's calling is suspended after Zavu's automated review refused several of its sends (`sending_suspended`), or the destination is not one this account may call yet (`destination_not_verified`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "sending_suspended": {
                    "summary": "Sending suspended after repeated refusals",
                    "value": {
                      "code": "sending_suspended",
                      "message": "Sending from this account is suspended after Zavu's automated risk review refused several messages. Contact support to have it reviewed.",
                      "details": {
                        "dashboardUrl": "/support"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Sender or agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily call limit reached. An account that has verified nothing may place 5 calls a day, and 50 on a Free plan once it has verified its identity, added a payment method, settled a deposit or subscribed. Paid plans have no daily call ceiling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "daily_limit_exceeded",
                  "message": "Daily VOICE limit reached (5/day). Verify your identity or add a payment method to raise it.",
                  "details": {
                    "limit": 5,
                    "remaining": 0,
                    "dashboardUrl": "/kyc"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List voice calls",
        "description": "List voice calls for this project, most recent first. Transcripts are omitted from the list; fetch a single call to get its transcript.",
        "operationId": "listCalls",
        "tags": [
          "Voice Agents"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/VoiceCallStatus"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/VoiceCallDirection"
            }
          },
          {
            "name": "senderId",
            "in": "query",
            "description": "Keep only calls placed or received by this sender.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of voice calls.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VoiceCall"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters: an unknown `status` or `direction`, or a `limit` outside 1-100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/calls/{callId}": {
      "get": {
        "summary": "Get voice call",
        "description": "Retrieve a single voice call, including its full transcript once the conversation has produced turns.",
        "operationId": "getCall",
        "tags": [
          "Voice Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CallIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice call details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceCallResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/calls/{callId}/hangup": {
      "post": {
        "summary": "Hang up a voice call",
        "description": "End an active voice call. The call must still be queued, ringing or in progress; a call that already ended answers `409`. Not available with test-mode API keys.",
        "operationId": "hangupCall",
        "tags": [
          "Voice Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CallIdParam"
          }
        ],
        "responses": {
          "202": {
            "description": "Hang up accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceCallResponse"
                }
              }
            }
          },
          "400": {
            "description": "Hang up is not available with test-mode API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Call not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The call is not active and cannot be hung up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "conflict",
                  "message": "Call is not active and cannot be hung up."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agent-templates": {
      "get": {
        "summary": "List factory agents",
        "description": "List the factory agents available to scaffold with `npx zavudev agents pull`. Each entry is a ready-made voice or text agent (system prompt, skills, and — for voice agents — a co-located voice config).",
        "operationId": "listAgentTemplates",
        "tags": [
          "Agents"
        ],
        "responses": {
          "200": {
            "description": "List of factory agents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgentTemplateSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agent-templates/{templateId}": {
      "get": {
        "summary": "Get a factory agent",
        "description": "Fetch a single factory agent fully rendered: the function files to scaffold (an `index.ts` that declares the agent with `defineAgent` and its skills with `defineTool`) plus the secrets it needs. This is what `npx zavudev agents pull <id>` writes to disk before `npx zavudev deploy`.",
        "operationId": "getAgentTemplate",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "fermi"
          }
        ],
        "responses": {
          "200": {
            "description": "Rendered factory agent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "template"
                  ],
                  "properties": {
                    "template": {
                      "$ref": "#/components/schemas/AgentTemplate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Template not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/email-domains": {
      "post": {
        "summary": "Add an email domain",
        "description": "Add a domain to send email from. Returns the DNS records to publish (DKIM CNAMEs are required; SPF, DMARC, and MAIL FROM are recommended). Publish them at your DNS provider, then verify.",
        "operationId": "addEmailDomain",
        "tags": [
          "Email Domains"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "domain"
                ],
                "properties": {
                  "domain": {
                    "type": "string",
                    "description": "Bare domain, e.g. example.com.",
                    "example": "example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Domain added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "domain"
                  ],
                  "properties": {
                    "domain": {
                      "$ref": "#/components/schemas/EmailDomain"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid domain or setup failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`domain_taken`: another Zavu account has already verified this domain. A sending domain belongs to one account; if it is yours, contact support.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "domain_taken",
                  "message": "This domain is already verified by another Zavu account. If it is yours, contact support."
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "get": {
        "summary": "List email domains",
        "operationId": "listEmailDomains",
        "tags": [
          "Email Domains"
        ],
        "responses": {
          "200": {
            "description": "Email domains.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmailDomain"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/email-domains/{domainId}": {
      "get": {
        "summary": "Get an email domain",
        "description": "Fetch a domain with its DNS records and current status.",
        "operationId": "getEmailDomain",
        "tags": [
          "Email Domains"
        ],
        "parameters": [
          {
            "name": "domainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Domain details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "domain"
                  ],
                  "properties": {
                    "domain": {
                      "$ref": "#/components/schemas/EmailDomain"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Domain not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Remove an email domain",
        "operationId": "deleteEmailDomain",
        "tags": [
          "Email Domains"
        ],
        "parameters": [
          {
            "name": "domainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Domain removed."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Domain not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/email-domains/{domainId}/verify": {
      "post": {
        "summary": "Verify an email domain",
        "description": "Re-check the domain's published DNS records and refresh its status.",
        "operationId": "verifyEmailDomain",
        "tags": [
          "Email Domains"
        ],
        "parameters": [
          {
            "name": "domainId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification re-checked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "domain"
                  ],
                  "properties": {
                    "domain": {
                      "$ref": "#/components/schemas/EmailDomain"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Domain not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The DNS check could not run right now. Nothing about the domain changed; retry shortly. Distinct from `404`, which means the domain is not in this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "verification_unavailable",
                  "message": "The domain's DNS records could not be checked right now. Nothing changed; retry shortly."
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/telegram": {
      "post": {
        "summary": "Connect Telegram",
        "description": "Connect a Telegram bot to a sender. Provide the bot token from @BotFather; Zavu validates it, registers the webhook, and routes the sender's Telegram messages through it.",
        "operationId": "connectSenderTelegram",
        "tags": [
          "Senders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "botToken"
                ],
                "properties": {
                  "botToken": {
                    "type": "string",
                    "description": "Bot token from @BotFather."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Telegram connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "telegram"
                  ],
                  "properties": {
                    "telegram": {
                      "type": "object",
                      "required": [
                        "connected"
                      ],
                      "properties": {
                        "connected": {
                          "type": "boolean"
                        },
                        "botUsername": {
                          "type": "string"
                        },
                        "botId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid bot token or request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Disconnect Telegram",
        "description": "Disconnect Telegram from a sender and remove the webhook.",
        "operationId": "disconnectSenderTelegram",
        "tags": [
          "Senders"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Telegram disconnected."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents": {
      "get": {
        "summary": "List agents",
        "description": "Every agent in the project, newest first — including agents that are not connected to any sender yet, which the sender-scoped routes cannot reach. Each item carries `senderIds`, the senders the agent answers on.",
        "operationId": "listAgents",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of agents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Agent"
                      }
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "summary": "Create a standalone agent",
        "description": "Create an agent without a sender. It is created disabled; connect a sender and enable it when you are ready for it to answer.\n\n**Sub-resources.** An agent's tools, flows and knowledge bases are reachable at `/v1/agents/{agentId}/tools`, `/v1/agents/{agentId}/flows` and `/v1/agents/{agentId}/knowledge-bases`, mirroring the sender-scoped routes documented under `/v1/senders/{senderId}/agent/...` exactly. Use the agent-scoped form while the agent has no sender: the sender-scoped one cannot address it.",
        "operationId": "createStandaloneAgent",
        "tags": [
          "Agents"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentCreateStandaloneRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Agent created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "agent"
                  ],
                  "properties": {
                    "agent": {
                      "$ref": "#/components/schemas/Agent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents/{agentId}": {
      "get": {
        "summary": "Get an agent",
        "operationId": "getAgentById",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "agent"
                  ],
                  "properties": {
                    "agent": {
                      "$ref": "#/components/schemas/Agent"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "summary": "Update an agent",
        "operationId": "updateAgentById",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Agent updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "agent"
                  ],
                  "properties": {
                    "agent": {
                      "$ref": "#/components/schemas/Agent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "delete": {
        "summary": "Delete an agent",
        "operationId": "deleteAgentById",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Agent deleted."
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents/{agentId}/test": {
      "post": {
        "summary": "Test an agent",
        "description": "Run the agent's prompt, model and knowledge base against a message and return the reply instead of delivering it. Nothing is delivered and no execution is recorded.\n\n**Billed like a live reply.** On an agent with the `zavu` provider the run is charged to your balance at the same price per token, and refused with `402 insufficient_balance` at a balance of zero; on a bring-your-own-key agent it costs your provider. `cost` on the response is what it charged. Runs are rate limited per project (`429`).\n\n**Tools.** By default the model is offered the agent's enabled tools but none is run: if it picks one, the reply stops there and `plannedToolCalls` says which, with what arguments. Set `executeTools` to run them — that reaches your real handlers, with whatever they cost and change — and read `executedToolCalls`.\n\n**Test-mode keys** are billed the same way, and `executeTools` with one still reaches your real handlers and writes real memory: a test run is not sandboxed by the key's mode.",
        "operationId": "testAgent",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentTestRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The agent's reply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentTestResponse"
                }
              }
            }
          },
          "400": {
            "description": "message is required, or the message and history exceed 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Sender or agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The agent runs on a Zavu model and the balance is at or below zero.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "insufficient_balance",
                  "message": "Insufficient balance to test on a Zavu model. Add funds, or test with your own provider key."
                }
              }
            }
          },
          "429": {
            "description": "Too many test runs for this project. Retry after the number of seconds in the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents/{agentId}/senders": {
      "post": {
        "summary": "Connect a sender to an agent",
        "description": "Make the agent answer on this sender. An agent can serve several senders; a sender answers with at most one agent, so connecting one that is already in use returns `400` naming the agent that holds it.",
        "operationId": "attachAgentSender",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "senderId"
                ],
                "properties": {
                  "senderId": {
                    "type": "string",
                    "description": "Sender to connect."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sender connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "agent"
                  ],
                  "properties": {
                    "agent": {
                      "$ref": "#/components/schemas/Agent"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "senderId missing, or the sender already answers with another agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent or sender not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents/{agentId}/senders/{senderId}": {
      "delete": {
        "summary": "Disconnect a sender from an agent",
        "description": "Stop the agent answering on this sender. The agent's primary sender is part of the agent itself and cannot be disconnected here.",
        "operationId": "detachAgentSender",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AgentIdParam"
          },
          {
            "$ref": "#/components/parameters/SenderIdParam"
          }
        ],
        "responses": {
          "204": {
            "description": "Sender disconnected."
          },
          "400": {
            "description": "That sender is the agent's primary, or is not connected to it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Agent not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/agents/voices": {
      "get": {
        "summary": "List agent voices",
        "description": "The voices an agent can speak with, for `voice.ttsVoiceId`. Filter by `language` to get the ones that speak it; a voice can still be used with `language: auto`, where the agent follows the caller and keeps the chosen voice.",
        "operationId": "listAgentVoices",
        "tags": [
          "Agents"
        ],
        "parameters": [
          {
            "name": "language",
            "in": "query",
            "description": "BCP-47 tag (`en`, `es`, `pt-BR`). Omit, or pass `auto`, for every voice.",
            "schema": {
              "type": "string",
              "example": "es"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Available voices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "languages"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "name",
                          "language"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Value for `voice.ttsVoiceId`.",
                            "example": "aura-2-celeste-es"
                          },
                          "name": {
                            "type": "string",
                            "example": "celeste"
                          },
                          "language": {
                            "type": "string",
                            "example": "es"
                          }
                        }
                      }
                    },
                    "languages": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Languages an agent can be pinned to. `auto` follows the caller."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Voices in the catalog, before filtering."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/senders/{senderId}/agent/tools/{toolId}/webhook/secret": {
      "post": {
        "summary": "Rotate a tool's webhook secret",
        "description": "Generate a new signing secret for this tool. The previous one stops working on the next call, with no overlap, so update your endpoint first. The tool keeps its id, so flows that reference it by name are unaffected.",
        "operationId": "rotateAgentToolWebhookSecret",
        "tags": [
          "Agent Tools"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SenderIdParam"
          },
          {
            "$ref": "#/components/parameters/ToolIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "New secret generated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tool not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    }
  }
}
