{
  "openapi": "3.1.0",
  "info": {
    "title": "Miss Blue API",
    "version": "1.0.0",
    "summary": "Send and receive iMessage from a dedicated business number.",
    "description": "The HTTP API behind Miss Blue. Every request carries a bearer key; a key beginning `mb_test_` is currently only a label and can still act on live data.\n\nThe same reference in prose: https://missblue.dev/api-reference.md",
    "contact": {
      "name": "Miss Blue",
      "url": "https://missblue.dev/contact"
    },
    "termsOfService": "https://missblue.dev/terms"
  },
  "servers": [
    {
      "url": "https://api.missblue.dev",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Sandbox API",
      "description": "Virtual numbers, simulated outcomes and isolated webhook events."
    },
    {
      "name": "Identity",
      "description": "What this key is, and which project it holds."
    },
    {
      "name": "Messages",
      "description": "Send, read, react, unsend, and see what failed."
    },
    {
      "name": "Conversations",
      "description": "Threads, history sync, read receipts, and typing indicators."
    },
    {
      "name": "Group conversations",
      "description": "Create groups, pull metadata, and manage names, photos, and people."
    },
    {
      "name": "Location sharing",
      "description": "Request Find My sharing, pull its state, or watch live fixes."
    },
    {
      "name": "Calls",
      "description": "FaceTime signaling, shared phone and FaceTime history, recordings."
    },
    {
      "name": "Lookup",
      "description": "Which services have reached a handle."
    },
    {
      "name": "Contacts",
      "description": "Names for handles, shared by the project."
    },
    {
      "name": "Attachments",
      "description": "Upload a file, then send it."
    },
    {
      "name": "Numbers",
      "description": "Pull lines, label them, forward calls, manage sender identity, and check recipients."
    },
    {
      "name": "Campaigns",
      "description": "Announcements, scheduled messages, lists, tags, and automations created entirely through the API, including multi-step sequences with waits, reply conditions and messages personalized from each contact's name and custom fields."
    },
    {
      "name": "Delivery",
      "description": "Webhooks and per-message callbacks."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key from the Miss Blue console. `Authorization: Bearer <key>`. mb_sandbox_ selects isolated simulation; mb_live_ and legacy mb_test_ access real traffic."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every failure has this shape.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "A stable, machine-readable reason."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, in prose."
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  },
  "paths": {
    "/v1/sandbox": {
      "get": {
        "operationId": "getV1Sandbox",
        "summary": "Inspect this project's sandbox.",
        "description": "Inspect this project's sandbox.\n\nReturns the virtual number, retained fixtures, endpoints without secrets, delivery attempts, expiry and limits. Requires a sandbox key.",
        "tags": [
          "Sandbox API"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Inspect this project's sandbox."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A live or legacy test key was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1Sandbox",
        "summary": "Reset fixtures and sandbox webhook endpoints.",
        "description": "Reset fixtures and sandbox webhook endpoints.\n\nThe virtual number ID and API keys stay valid. Returns the reset sandbox state.",
        "tags": [
          "Sandbox API"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Reset fixtures and sandbox webhook endpoints."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sandbox/events": {
      "get": {
        "operationId": "getV1SandboxEvents",
        "summary": "List retained simulated events.",
        "description": "List retained simulated events.",
        "tags": [
          "Sandbox API"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "List retained simulated events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [],
                  "total": 0
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1SandboxEvents",
        "summary": "Emit a documented webhook type with test data.",
        "description": "Emit a documented webhook type with test data.",
        "tags": [
          "Sandbox API"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "One of the documented webhook event types."
                  },
                  "data": {
                    "type": "object",
                    "description": "Fixture payload, sent as given. Use the event's live data shape. It is marked sandbox and sent only to sandbox endpoints."
                  }
                },
                "required": [
                  "type",
                  "data"
                ]
              },
              "example": {
                "type": "call.ended",
                "data": {
                  "project_id": "b3c2a1d0-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                  "number": {
                    "id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                    "handle": "+12025550100"
                  },
                  "resource": {
                    "call_id": "fixture-call",
                    "direction": "outbound",
                    "outcome": "answered",
                    "talk_seconds": 42
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Emit a documented webhook type with test data."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sandbox/messages/{id}/simulate": {
      "post": {
        "operationId": "postV1SandboxMessagesByIdSimulate",
        "summary": "Advance a simulated outbound message.",
        "description": "Advance a simulated outbound message.\n\nReturns the message. Completed receipts cannot move backwards; each reply adds an inbound fixture.",
        "tags": [
          "Sandbox API"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scenario": {
                    "type": "string",
                    "description": "sent, delivered, read, reply, failed, offline or timeout."
                  },
                  "reply_text": {
                    "type": "string",
                    "description": "Optional text for the reply scenario, up to 4096 bytes."
                  }
                },
                "required": [
                  "scenario"
                ]
              },
              "example": {
                "scenario": "reply",
                "reply_text": "Thanks, I received it."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Advance a simulated outbound message."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The message already passed the requested status or the fixture limit was reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getV1Me",
        "summary": "What this credential is.",
        "description": "What this credential is.\n\nFor a key: its id, its project, and the organization above it. `live` is false for a sandbox or legacy test key. `sandbox: true` identifies an isolated mb_sandbox_ key; legacy mb_test_ keys still access real traffic. There is no person in the answer, because there is no person behind a key.",
        "tags": [
          "Identity"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "What this credential is.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "kind": "key",
                  "key_id": "2aea3032-1631-419a-ba45-05cb965d157c",
                  "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                  "project_name": "Client Support",
                  "workspace_id": "3480fdd8-2ef3-4edd-9949-bf78b4cd3e35",
                  "workspace_name": "Acme Agency",
                  "live": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "operationId": "postV1Messages",
        "summary": "Send an iMessage.",
        "description": "Send an iMessage.\n\nText, files, or a reply quoting an earlier message. Sending to an existing group is the same call with the group's chat_id as the recipient. A send the number cannot take right now is still accepted, not refused: 201 with status pending, queued: true, and a notice — { \"code\": \"number_unavailable\", \"message\": \"This number is temporarily unavailable. Your message is queued and will be sent automatically as soon as it's back.\" }. It goes out by itself when the number is back, so do not send it again; until then POST /v1/messages/{id}/cancel calls it off. A notice code of send_retrying means the send was turned down for now and will be tried again automatically. A notice code of daily_new_contact_limit means the number has messaged as many new people as it may today: the first message waits and goes out by itself at send_after, as room opens — do not send it again. Only a first message that could not go within five days is refused, with 429 and a Retry-After of when a try would be taken. GET /v1/projects/{id}/sending-stats shows each number's allowance: new_contacts_used, new_contacts_limit, new_contacts_waiting, and next_new_contact_at, when the next new person can be messaged if not now; messages_used and messages_limit, every message the number sent in the last 24 hours against its daily total (150 unless raised); and warmup, its week and limit while it climbs back after sending as texts. A paced send — or an automated one past the daily total — is queued with no notice: the message's send_after says when; one past the daily total that could not go within five days is refused with 429 daily_message_limit and a Retry-After. Pass manual: true for one message a person asked to send now: like a send typed in the inbox, it goes past the daily total and still counts toward it; opt-outs, blocks and the new-people limit still apply. Not for bulk. See /docs/rate-limits. notice.message is written to be shown to your own users as it is; switch on notice.code. link_code and url are the conversation's short link, the same one GET /v1/threads and its webhooks give it; they are absent while a first message to somebody new is still waiting for its number, and message.sent carries them once it has gone.\n\nSend an uploaded audio file as a native voice note.\n\nUpload one audio/* file first. An attachment-only audio send defaults to Apple's native voice-note bubble for every number-scoped customer call. Pass native_voice_note: false only when you deliberately want a normal file. If native delivery is unavailable before sending, Miss Blue automatically sends the original audio as a regular playable attachment. An uncertain delivery is kept pending to avoid duplicate messages.\n\nSend two through twenty images as one native carousel.\n\nUpload every image first and preserve the order in attachment_ids. All files must be images. A native carousel cannot carry a caption or quote another message; send text separately if you need it.",
        "tags": [
          "Messages"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Which of your numbers to send from. GET /v1/numbers lists them."
                  },
                  "recipient": {
                    "type": "string",
                    "description": "A phone number, an Apple ID email, or a chat_id to send into an existing conversation."
                  },
                  "text": {
                    "type": "string",
                    "description": "Optional only if you are sending attachments — a message must carry one or the other."
                  },
                  "attachment_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "From POST /v1/attachments. This call accepts text or attachments, not both; send a caption separately."
                  },
                  "native_voice_note": {
                    "type": "boolean",
                    "description": "Audio-only sends default to a native voice note. Set false to send an ordinary file."
                  },
                  "reply_to_message_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Our id for the message being quoted, not Apple's."
                  },
                  "status_callback": {
                    "type": "string",
                    "format": "uri",
                    "description": "Where to POST this one message's outcome. See Delivery."
                  },
                  "allow_duplicate": {
                    "type": "boolean",
                    "description": "Send anyway, though the same text went to the same person moments ago."
                  },
                  "manual": {
                    "type": "boolean",
                    "description": "One message a person asked to send now: not held by the number's daily total, as a send from the inbox is not. It still counts toward it; opt-outs, blocks and the new-people limit still apply. Not for bulk."
                  }
                },
                "required": [
                  "number_id",
                  "recipient"
                ]
              },
              "example": {
                "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                "recipient": "+15555550100",
                "text": "Your table is confirmed for 7pm."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Send an iMessage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                  "request_id": "d0cc8f6e-8d57-4bd2-bb27-a3c86ca810e8",
                  "agent_id": "3e814ce3-d92b-4d93-9baf-33459eacb264",
                  "status": "sent",
                  "external_message_id": "p:0/4AEE9B2A-7E3C-4E7C-88EA-5F071974624B",
                  "queued": false,
                  "chat_id": "iMessage;-;+15555550100",
                  "link_code": "k7Qm3xT9pa",
                  "url": "https://missblue.dev/c/k7Qm3xT9pa"
                }
              }
            }
          },
          "202": {
            "description": "Send an iMessage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                  "request_id": "d0cc8f6e-8d57-4bd2-bb27-a3c86ca810e8",
                  "agent_id": "3e814ce3-d92b-4d93-9baf-33459eacb264",
                  "status": "sent",
                  "external_message_id": "p:0/4AEE9B2A-7E3C-4E7C-88EA-5F071974624B",
                  "queued": false,
                  "chat_id": "iMessage;-;+15555550100",
                  "link_code": "k7Qm3xT9pa",
                  "url": "https://missblue.dev/c/k7Qm3xT9pa"
                }
              }
            }
          },
          "400": {
            "description": "invalid_recipient: a phone number nobody can have — a +1 number without ten digits, an area code or exchange starting with 0 or 1 or an N11 code, a fictional 555-0100–0199, or not 8 to 15 digits after the +. Nothing is written or sent. error.reason is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "A number_id your key does not hold. Not 403 — whether it exists is not your business.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A native voice note while the number is temporarily unavailable (agent_offline). Voice notes are sent live and never queued; ordinary sends are queued instead — see queued and notice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "recipient_undeliverable: the number cannot get texts — a landline, not in service, or not a valid number, as its carrier, Twilio or the check before a first text on a Twilio line said. error.reason is landline, not_in_service, invalid or other.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-miss-blue-variants": [
          {
            "id": "post-v1-messages-native-voice-note",
            "summary": "Send an uploaded audio file as a native voice note.",
            "description": "Upload one audio/* file first. An attachment-only audio send defaults to Apple's native voice-note bubble for every number-scoped customer call. Pass native_voice_note: false only when you deliberately want a normal file. If native delivery is unavailable before sending, Miss Blue automatically sends the original audio as a regular playable attachment. An uncertain delivery is kept pending to avoid duplicate messages.",
            "request": {
              "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
              "recipient": "+15555550100",
              "attachment_ids": [
                "4c9103dd-ae62-4888-9a17-c762efe8ddcb"
              ]
            },
            "response": {
              "id": "f30fd81d-79a4-4795-9794-b58a49cbb825",
              "request_id": "2ebf14c9-2952-4931-adc1-2e80852ce239",
              "agent_id": "3e814ce3-d92b-4d93-9baf-33459eacb264",
              "status": "sent",
              "external_message_id": "p:0/2A3E52BC-6922-4608-94E9-F120285AE812",
              "chat_id": "iMessage;-;+15555550100",
              "queued": false
            }
          },
          {
            "id": "post-v1-messages-carousel",
            "summary": "Send two through twenty images as one native carousel.",
            "description": "Upload every image first and preserve the order in attachment_ids. All files must be images. A native carousel cannot carry a caption or quote another message; send text separately if you need it.",
            "request": {
              "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
              "recipient": "+15555550100",
              "attachment_ids": [
                "88de9745-30e0-4b93-bfcb-dbb7f3325dde",
                "45793a35-0a1c-431d-8806-38f71ce658ff",
                "fe74c17c-f3cd-4547-a32b-cf271845d95f"
              ]
            },
            "response": {
              "id": "9396bdad-9e14-4caa-8f01-20188aedbb23",
              "request_id": "3cd8cab6-1e17-4169-866d-eac13765ef58",
              "agent_id": "3e814ce3-d92b-4d93-9baf-33459eacb264",
              "status": "sent",
              "external_message_id": "p:0/F4AF7E62-FC00-4805-A9A9-86557DB11137",
              "chat_id": "iMessage;-;+15555550100",
              "queued": false
            }
          }
        ]
      },
      "get": {
        "operationId": "getV1Messages",
        "summary": "List messages across your project's numbers, newest first.",
        "description": "List messages across your project's numbers, newest first.",
        "tags": [
          "Messages"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "List messages across your project's numbers, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                      "chat_id": "iMessage;-;+15555550100",
                      "direction": "outbound",
                      "recipient": "+15555550100",
                      "text": "Your table is confirmed for 7pm.",
                      "status": "delivered",
                      "service": "imessage",
                      "message_at": "2026-08-28T10:04:00Z",
                      "created_at": "2026-08-28T10:04:00Z",
                      "updated_at": "2026-08-28T10:04:02Z",
                      "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}": {
      "get": {
        "operationId": "getV1MessagesById",
        "summary": "One message, including its current delivery state.",
        "description": "One message, including its current delivery state.\n\nPolling this works, but a webhook or a status_callback tells you sooner and costs you nothing. `agent_version` and `macos_version` say which Mac build handled it, stamped when the message was written rather than looked up now — a Mac that is updated must not rewrite what it was running last week, which is exactly the week you are trying to explain. `read` means the person has seen it: an iMessage read receipt reads the conversation up to that message, so earlier delivered iMessages to the same person turn `read` with it, as they do on the sender's iPhone — Apple itself stamps only some of them. A failed message carries `reason` (landline, not_in_service, unreachable, invalid, opted_out, blocked, spam_filtered or other) and, when a carrier gave one, `error_code` (Twilio's 30006, say), as message.failed webhooks do.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          }
        ],
        "responses": {
          "200": {
            "description": "One message, including its current delivery state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                  "chat_id": "iMessage;-;+15555550100",
                  "direction": "outbound",
                  "recipient": "+15555550100",
                  "text": "Your table is confirmed for 7pm.",
                  "status": "delivered",
                  "service": "imessage",
                  "message_at": "2026-08-28T10:04:00Z",
                  "created_at": "2026-08-28T10:04:00Z",
                  "updated_at": "2026-08-28T10:04:02Z",
                  "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such message, or one on a number your key does not hold.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/thread/{chat_id}": {
      "get": {
        "operationId": "getV1MessagesThreadByChatId",
        "summary": "Every message in one conversation, oldest first.",
        "description": "Every message in one conversation, oldest first.\n\nOrdered by the platform's own sort key, so a message the Mac picked up late still lands where it belongs rather than at the end. message_at is the original Messages.app time; created_at is the later control-plane ingestion time. link_code and url are the conversation's short link, the same one GET /v1/threads gives it, made from the messages you are shown; absent when you are shown none.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;-;+15555550100"
          }
        ],
        "responses": {
          "200": {
            "description": "Every message in one conversation, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 14,
                  "link_code": "k7Qm3xT9pa",
                  "url": "https://missblue.dev/c/k7Qm3xT9pa",
                  "data": [
                    {
                      "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                      "chat_id": "iMessage;-;+15555550100",
                      "direction": "inbound",
                      "sender_handle": "+15555550100",
                      "sender_name": "Ari Chen",
                      "text": "Perfect, see you then",
                      "status": "received",
                      "service": "imessage",
                      "message_at": "2026-08-28T10:04:00Z",
                      "created_at": "2026-08-28T10:04:00Z",
                      "updated_at": "2026-08-28T10:04:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/react": {
      "post": {
        "operationId": "postV1MessagesByIdReact",
        "summary": "Add or remove a tapback.",
        "description": "Add or remove a tapback.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reaction_key": {
                    "type": "string",
                    "description": "heart, like, dislike, laugh, emphasize, or question."
                  },
                  "remove": {
                    "type": "boolean",
                    "description": "Take it back rather than add it."
                  }
                },
                "required": [
                  "reaction_key"
                ]
              },
              "example": {
                "reaction_key": "heart"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Add or remove a tapback."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The number is temporarily unavailable (agent_offline). Nothing was changed; retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/problems": {
      "get": {
        "operationId": "getV1MessagesProblems",
        "summary": "Messages that failed, or are still queued.",
        "description": "Messages that failed, or are still queued.\n\nThe queue and the failures in one list, because from a caller's side they are the same question: what have I sent that has not arrived. Worth polling on a schedule even if you take webhooks — a webhook that never fired is exactly the case a webhook cannot tell you about.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "only",
            "in": "query",
            "required": false,
            "description": "queued or failed. Omit for both.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages that failed, or are still queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                      "status": "failed",
                      "recipient": "+15555550100",
                      "error": "This message could not be sent. Try again or contact support.",
                      "message_at": "2026-08-28T10:04:00Z",
                      "created_at": "2026-08-28T10:04:00Z",
                      "updated_at": "2026-08-28T10:04:30Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/cancel": {
      "post": {
        "operationId": "postV1MessagesByIdCancel",
        "summary": "Cancel a message that is still queued.",
        "description": "Cancel a message that is still queued.\n\nFor a send that has not gone yet — held while its number is temporarily unavailable, paced, or waiting to be tried again. Only an outbound message that is pending, queued, and has no external_message_id can be cancelled. It ends failed, with the error \"Cancelled before it was sent.\", and the usual message.failed webhook and status_callback fire: an integration learns this outcome the way it learns every other. Once a message has been handed over for sending it may already be on somebody's phone, so it can no longer be cancelled.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancel a message that is still queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77",
                  "chat_id": "iMessage;-;+15555550100",
                  "direction": "outbound",
                  "recipient": "+15555550100",
                  "text": "Your table is confirmed for 7pm.",
                  "status": "failed",
                  "error": "Cancelled before it was sent.",
                  "service": "imessage",
                  "message_at": "2026-08-28T10:04:00Z",
                  "created_at": "2026-08-28T10:04:00Z",
                  "updated_at": "2026-08-28T10:06:12Z",
                  "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such message on a number your key holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It is already on its way and can't be cancelled — or it was never queued: sent, failed, or inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/unsend": {
      "post": {
        "operationId": "postV1MessagesByIdUnsend",
        "summary": "Unsend a message.",
        "description": "Unsend a message.\n\nApple's two-minute window and its own rules apply. We do not re-check them against our clock — the Mac reports the platform's answer, so a refusal here is Apple's refusal, not ours. The recipient is left with \"You unsent a message\" where the bubble was; unsending is not the same as them never having seen it. Afterwards the message reads is_deleted: true with no text, and the Mac writes a separate action row carrying the notice.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          }
        ],
        "responses": {
          "204": {
            "description": "Unsend a message."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The number is temporarily unavailable (agent_offline). Nothing was changed; retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/edit": {
      "post": {
        "operationId": "postV1MessagesByIdEdit",
        "summary": "Change what a message already sent says.",
        "description": "Change what a message already sent says.\n\n202, not 204, and the difference matters: Messages.app accepts an edit and reports nothing. The change arrives back through ordinary sync moments later, exactly as one typed on the Mac would, so \"accepted\" is the honest answer and the transcript is what confirms it. Poll GET /v1/messages/{id} and watch for edited_at — until it is set, the edit has not landed. Apple allows this for roughly fifteen minutes after sending and shows the recipient that the message was edited; there is no silent correction.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "The replacement. An empty or whitespace-only edit is a 400 rather than an unsend — the two are different enough that guessing which was meant would be the wrong kind of helpful."
                  }
                },
                "required": [
                  "text"
                ]
              },
              "example": {
                "text": "Tuesday at 10, not Thursday."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Change what a message already sent says."
          },
          "400": {
            "description": "The text is empty, or the Mac refused the edit — most often the window has closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such message on a number your key holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The number that sent it is temporarily unavailable (agent_offline). Nothing was changed; retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/conversations/sync": {
      "get": {
        "operationId": "getV1ConversationsSync",
        "summary": "Read conversation sync availability and the latest result.",
        "description": "Read conversation sync availability and the latest result.\n\nLimited to Macs behind numbers this credential can access. The cooldown is shared per Mac, not per caller. Optional last_started_at, last_completed_at, last_count, and last_error describe the latest observed work.",
        "tags": [
          "Conversations"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Read conversation sync availability and the latest result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "limit_per_agent": 100,
                  "cooldown_seconds": 300,
                  "connected_agents": 1,
                  "eligible_agents": 1,
                  "syncing_agents": 0
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ConversationsSync",
        "summary": "Queue a fresh conversation history sync from eligible Macs.",
        "description": "Queue a fresh conversation history sync from eligible Macs.\n\nRequests up to 100 recent conversations per eligible Mac. A 202 means queued, not complete; poll GET /v1/conversations/sync for progress. Offline Macs, active syncs, and Macs in the five-minute cooldown may be skipped.",
        "tags": [
          "Conversations"
        ],
        "parameters": [],
        "responses": {
          "202": {
            "description": "Queue a fresh conversation history sync from eligible Macs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "queued_agents": 1,
                  "skipped_agents": 0
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/threads": {
      "get": {
        "operationId": "getV1Threads",
        "summary": "Every conversation on your project's numbers, most recent first.",
        "description": "Every conversation on your project's numbers, most recent first.\n\ncontact_is_typing is transient presence reported by the Mac and expires automatically. unread is always 0 for a key. A key is not a person and has no place in a conversation it has read up to, so reporting every inbound message ever would have read as a backlog nobody has. number_id and search are applied in the query, before the page is cut — filtering the returned page client-side would search the newest few hundred conversations and report that as the whole answer. link_code and url are each conversation's short link (url is https://missblue.dev/c/ and the code). It opens only for somebody who may read the conversation, so it is safe to pass on.",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "newest (default), oldest, or unreplied: conversations whose last message came in, newest first, then the rest. A blocked contact's conversation sorts with the rest, since nobody can answer it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "number_id",
            "in": "query",
            "required": false,
            "description": "One of your lines, to see only what arrived on it. Use GET /v1/threads/lines for the ids and what each is carrying.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "A handle, part of one, or a saved contact's name. Matches both, so somebody found by name does not have to be found again by number.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unreplied",
            "in": "query",
            "required": false,
            "description": "true: only conversations waiting on a reply (their latest message came in, from somebody not blocked), by latest activity alone. Exactly the ones sort=unreplied puts first.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "RFC 3339. Only conversations whose latest activity is at or after it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "With unreplied=true: the previous page's next_before, which is opaque, to read on from right after its last conversation; or an RFC 3339 time. Nothing is missed or repeated, however many conversations share an instant.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "pending: each conversation also has pending (what they said since our last message, oldest first, at most 50), pending_truncated, and last_reply (our last message, or null).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every conversation on your project's numbers, most recent first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 2,
                  "data": [
                    {
                      "chat_id": "iMessage;-;+15555550100",
                      "handle": "+15555550100",
                      "is_group": false,
                      "last_text": "Perfect, see you then",
                      "last_at": "2026-08-23T09:12:04Z",
                      "last_direction": "inbound",
                      "message_count": 14,
                      "unread": 0,
                      "contact_is_typing": true,
                      "last_replied_by": "Sam Okafor",
                      "link_code": "k7Qm3xT9pa",
                      "url": "https://missblue.dev/c/k7Qm3xT9pa"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/threads/{chat_id}/pin": {
      "put": {
        "operationId": "putV1ThreadsByChatIdPin",
        "summary": "Pin or unpin a conversation for the signed-in member.",
        "description": "Pin or unpin a conversation for the signed-in member.\n\nMember sessions only. Pins stay ahead of unpinned conversations under every sort and do not affect other members. Current number and organization access is required; a pin never grants access.",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;-;+15555550100"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agent_id": {
                    "type": "string",
                    "description": "The agent_id returned by the conversation list."
                  },
                  "pinned": {
                    "type": "boolean",
                    "description": "true to pin; false to unpin."
                  }
                },
                "required": [
                  "agent_id",
                  "pinned"
                ]
              },
              "example": {
                "agent_id": "d8f25b47-dda6-41ec-a303-4bdfb9d6953e",
                "pinned": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Pin or unpin a conversation for the signed-in member."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/threads/lines": {
      "get": {
        "operationId": "getV1ThreadsLines",
        "summary": "Each of your numbers, and what it is carrying right now.",
        "description": "Each of your numbers, and what it is carrying right now.\n\nThe counts behind a by-number filter, asked as its own question rather than derived from the conversation list. The list is a page: counting what came back would report the lines present in the newest few hundred conversations and call it the total. unread is 0 for a key, for the same reason it is 0 in the conversation list.",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Narrow to one project. Omit for every project this credential reaches.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Each of your numbers, and what it is carrying right now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 2,
                  "data": [
                    {
                      "number_id": "9c1e4f0a-3d7b-4a21-9f38-7c2d5e6b8a14",
                      "unread": 0,
                      "conversations": 214
                    },
                    {
                      "number_id": "b47d2a90-5e13-4c8f-a6b2-19d3f7c04e85",
                      "unread": 0,
                      "conversations": 37
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/threads/unread": {
      "get": {
        "operationId": "getV1ThreadsUnread",
        "summary": "Unread totals across everything this credential can see.",
        "description": "Unread totals across everything this credential can see.\n\nEvery visible unread message, not the unread on one page of conversations. notification_unread counts only what a person has asked to be notified about, so a badge and an inbox count can differ honestly. Both are 0 for an API key, which has read nothing and has no place to have read up to.",
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Narrow to one project. Omit for every project this credential reaches.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unread totals across everything this credential can see.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "unread": 0,
                  "latest_unread_at": null,
                  "notification_unread": 0,
                  "latest_notification_at": null
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/read-receipts": {
      "post": {
        "operationId": "postV1ReadReceipts",
        "summary": "Tell the customer their message was seen.",
        "description": "Tell the customer their message was seen.\n\nThe \"Read\" that appears under their message on their phone. Distinct from POST /v1/threads/{chat_id}/read, which records where a person has read to so their own unread count is right and which nobody outside this system sees. Nothing sends this automatically: it is a claim that a human looked, and a receipt that fired because a dashboard was open is a lie told at scale.",
        "tags": [
          "Conversations"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chat_id": {
                    "type": "string",
                    "description": "The conversation to acknowledge."
                  },
                  "read": {
                    "type": "boolean",
                    "description": "Defaults to true. False marks it unread again."
                  }
                },
                "required": [
                  "chat_id"
                ]
              },
              "example": {
                "chat_id": "iMessage;-;+15555550100",
                "read": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Tell the customer their message was seen."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation on a number your key holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The number is temporarily unavailable (agent_offline). Nothing was acknowledged; retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/typing": {
      "post": {
        "operationId": "postV1Typing",
        "summary": "Show or hide the typing bubble in a conversation.",
        "description": "Show or hide the typing bubble in a conversation.\n\nDo not block a reply on this. A bubble that arrives after the message it was meant to precede is worse than no bubble — send it, ignore the outcome, and reply. There is no guaranteed stop either: Messages.app clears it on its own after a few seconds of silence, which is the behaviour to rely on.",
        "tags": [
          "Conversations"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chat_id": {
                    "type": "string",
                    "description": "The conversation. Not a bare handle — guessing which of several threads was meant would show a stranger that somebody is typing."
                  },
                  "active": {
                    "type": "boolean",
                    "description": "true to start, false to stop."
                  }
                },
                "required": [
                  "chat_id"
                ]
              },
              "example": {
                "chat_id": "iMessage;-;+15555550100",
                "active": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Show or hide the typing bubble in a conversation."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such conversation on a number your key holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The number is temporarily unavailable (agent_offline). Retry shortly, or skip it — a late typing bubble is worse than none.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/typing-indicators": {
      "post": {
        "operationId": "postV1TypingIndicators",
        "summary": "Show a bounded typing bubble to a contact.",
        "description": "Show a bounded typing bubble to a contact.\n\nUse this form when your integration knows the contact rather than the chat_id. The one-to-one iMessage conversation must already exist. Start defaults to 60 seconds and is always cleared by five minutes; another start extends the active timer, while stop is a safe no-op when nothing is active.",
        "tags": [
          "Conversations"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number": {
                    "type": "string",
                    "description": "The contact who sees the bubble."
                  },
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Which of your numbers sends it. Preferred over a mutable handle."
                  },
                  "from_number": {
                    "type": "string",
                    "description": "Handle alternative to number_id. Omit both only when your key holds one number."
                  },
                  "state": {
                    "type": "string",
                    "description": "Defaults to start."
                  },
                  "max_duration_ms": {
                    "type": "integer",
                    "description": "1–300000. Defaults to 60000."
                  }
                },
                "required": [
                  "number"
                ]
              },
              "example": {
                "number": "+15555550100",
                "from_number": "+16465550142",
                "state": "start",
                "max_duration_ms": 60000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Show a bounded typing bubble to a contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "status": "ACTIVE",
                  "number": "+15555550100",
                  "from_number": "+16465550142",
                  "chat_id": "iMessage;-;+15555550100",
                  "max_duration_ms": 60000
                }
              }
            }
          },
          "400": {
            "description": "The number or duration is invalid, the source is ambiguous, or no prior iMessage conversation exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The source number is not held by this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The source number is temporarily unavailable (agent_offline). Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/groups": {
      "post": {
        "operationId": "postV1Groups",
        "summary": "Create a native iMessage group and send its first message.",
        "description": "Create a native iMessage group and send its first message.",
        "tags": [
          "Group conversations"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "A number held by this project."
                  },
                  "participants": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "2–32 different phone numbers or Apple ID emails."
                  },
                  "initial_message": {
                    "type": "string",
                    "description": "1–20,000 characters. Group creation and the first message are one operation."
                  }
                },
                "required": [
                  "number_id",
                  "participants",
                  "initial_message"
                ]
              },
              "example": {
                "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                "participants": [
                  "+15555550100",
                  "+15555550101"
                ],
                "initial_message": "Welcome to the project group."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Create a native iMessage group and send its first message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "chat_id": "iMessage;+;chat123456789",
                  "participants": [
                    {
                      "id": "p1",
                      "handle": "+15555550100",
                      "name": "Ari"
                    },
                    {
                      "id": "p2",
                      "handle": "+15555550101",
                      "name": "Sam"
                    },
                    {
                      "id": "self",
                      "handle": "+16465550142",
                      "is_self": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Fewer than two different participants, too many participants, or an empty first message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The number is outside this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/groups/{chat_id}": {
      "get": {
        "operationId": "getV1GroupsByChatId",
        "summary": "Pull the group's current name, photo URL, and participants.",
        "description": "Pull the group's current name, photo URL, and participants.",
        "tags": [
          "Group conversations"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;+;chat123456789"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull the group's current name, photo URL, and participants.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "chat_id": "iMessage;+;chat123456789",
                  "title": "Install team",
                  "photo_url": "https://api.missblue.dev/v1/attachments/…/view",
                  "participants": [
                    {
                      "id": "p1",
                      "handle": "+15555550100",
                      "name": "Ari"
                    },
                    {
                      "id": "self",
                      "handle": "+16465550142",
                      "is_self": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such group on a number held by this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patchV1GroupsByChatId",
        "summary": "Rename a group and/or replace its photo.",
        "description": "Rename a group and/or replace its photo.\n\nUpload the JPEG or PNG first. To remove the current photo, omit photo_attachment_id and send remove_photo: true. The complete refreshed group is returned.",
        "tags": [
          "Group conversations"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;+;chat123456789"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "At most 256 characters."
                  },
                  "photo_attachment_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Uploaded JPEG or PNG."
                  },
                  "remove_photo": {
                    "type": "boolean",
                    "description": "Cannot be true while replacing the photo."
                  }
                }
              },
              "example": {
                "title": "Install team",
                "photo_attachment_id": "88de9745-30e0-4b93-bfcb-dbb7f3325dde"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rename a group and/or replace its photo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "chat_id": "iMessage;+;chat123456789",
                  "title": "Install team",
                  "photo_url": "https://api.missblue.dev/v1/attachments/…/view",
                  "participants": [
                    {
                      "id": "p1",
                      "handle": "+15555550100",
                      "name": "Ari"
                    },
                    {
                      "id": "self",
                      "handle": "+16465550142",
                      "is_self": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/groups/{chat_id}/participants": {
      "post": {
        "operationId": "postV1GroupsByChatIdParticipants",
        "summary": "Add or remove one group participant.",
        "description": "Add or remove one group participant.\n\nSet add to false to remove. The response is the complete refreshed group.",
        "tags": [
          "Group conversations"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;+;chat123456789"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "participant": "+15555550102",
                "add": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Add or remove one group participant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "chat_id": "iMessage;+;chat123456789",
                  "title": "Install team",
                  "participants": [
                    {
                      "id": "p1",
                      "handle": "+15555550100",
                      "name": "Ari"
                    },
                    {
                      "id": "p3",
                      "handle": "+15555550102",
                      "name": "Jo"
                    },
                    {
                      "id": "self",
                      "handle": "+16465550142",
                      "is_self": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/locations/{chat_id}/{recipient}": {
      "post": {
        "operationId": "postV1LocationsByChatIdByRecipient",
        "summary": "Send a native Find My location request.",
        "description": "Send a native Find My location request.",
        "tags": [
          "Location sharing"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;-;+15555550100"
          },
          {
            "name": "recipient",
            "in": "path",
            "required": true,
            "description": "The recipient of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "+15555550100"
          }
        ],
        "responses": {
          "200": {
            "description": "Send a native Find My location request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "requested": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The conversation is not on a number held by this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The number could not send the request right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getV1LocationsByChatIdByRecipient",
        "summary": "Pull the sharing lifecycle and newest location fix.",
        "description": "Pull the sharing lifecycle and newest location fix.\n\nstate is not_shared, request_pending, declined, stopped, expired, shared_no_fix_yet, or shared_with_fix. Coordinates are absent until a fix exists; ended_at is present for an ended lifecycle.",
        "tags": [
          "Location sharing"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;-;+15555550100"
          },
          {
            "name": "recipient",
            "in": "path",
            "required": true,
            "description": "The recipient of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "+15555550100"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull the sharing lifecycle and newest location fix.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "handle": "+15555550100",
                  "state": "shared_with_fix",
                  "latitude": 40.7411,
                  "longitude": -73.9897,
                  "horizontal_accuracy": 12.4,
                  "observed_at": "2026-08-28T10:04:00Z",
                  "short_address": "Flatiron District, New York",
                  "expires_at": "2026-08-28T11:04:00Z",
                  "requested_at": "2026-08-28T10:00:00Z",
                  "started_at": "2026-08-28T10:03:12Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/locations/{chat_id}/{recipient}/watch": {
      "get": {
        "operationId": "getV1LocationsByChatIdByRecipientWatch",
        "summary": "Watch changing location state as Server-Sent Events.",
        "description": "Watch changing location state as Server-Sent Events.\n\nA location event is sent only when the JSON snapshot changes. The stream sends a keepalive every 15 seconds. An error event reports a temporary read failure without silently ending the stream.",
        "tags": [
          "Location sharing"
        ],
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "description": "The chat_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "iMessage;-;+15555550100"
          },
          {
            "name": "recipient",
            "in": "path",
            "required": true,
            "description": "The recipient of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "+15555550100"
          }
        ],
        "responses": {
          "200": {
            "description": "Watch changing location state as Server-Sent Events.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "event: location\ndata: event: location\ndata: {\"handle\":\"+15555550100\",\"state\":\"shared_with_fix\",\"latitude\":40.7411,\"longitude\":-73.9897}\n\n"
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/calls": {
      "get": {
        "operationId": "getV1Calls",
        "summary": "Every call on the lines you hold, newest first.",
        "description": "Every call on the lines you hold, newest first.\n\nTakes an optional project_id to narrow to one project's numbers. Returns your workspace's calls; calls made by another workspace on a shared number remain separate. transport is direct or livekit for FaceTime Audio, telnyx for regular phone calls, or null before the path is known. direction is inbound or outbound. outcome is dialing, answered, no_answer, declined, or failed. declined records an incoming FaceTime ring explicitly declined through the app. For outgoing FaceTime calls, no_answer does not distinguish a decline from a ring-out. History and recording completion are separate.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Optionally narrow the history to one project's numbers.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every call on the lines you hold, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "cf33aa62-1c89-40b0-9887-137108be4b48",
                      "number_handle": "+19292727048",
                      "handle": "+15555550100",
                      "started_by": "Ada Lovelace",
                      "transport": "direct",
                      "direction": "outbound",
                      "outcome": "answered",
                      "started_at": "2026-09-06T17:19:37Z",
                      "answered_at": "2026-09-06T17:19:38Z",
                      "ended_at": "2026-09-06T17:20:18Z",
                      "ring_seconds": 1,
                      "talk_seconds": 40,
                      "recording_status": "ready",
                      "recording_bytes": 669427
                    }
                  ],
                  "total": 1
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{number_id}/calls": {
      "get": {
        "operationId": "getV1NumbersByNumberIdCalls",
        "summary": "The same history, for one line.",
        "description": "The same history, for one line.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "number_id",
            "in": "path",
            "required": true,
            "description": "The number_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5d2c1f1e-6f4a-4d43-9d8e-1a2b3c4d5e6f"
          }
        ],
        "responses": {
          "200": {
            "description": "The same history, for one line."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{number_id}/rtc/calls": {
      "post": {
        "operationId": "postV1NumbersByNumberIdRtcCalls",
        "summary": "Dial a FaceTime Audio call from a number.",
        "description": "Dial a FaceTime Audio call from a number.\n\nThe handle must be an E.164 number or Apple ID email. This rings and tracks the call but does not connect your microphone or speaker. Establish the audio leg with the RTC signaling routes, then use the recording upload routes if the number has recording enabled. Poll or delete the session to follow or end it.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "number_id",
            "in": "path",
            "required": true,
            "description": "The number_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5d2c1f1e-6f4a-4d43-9d8e-1a2b3c4d5e6f"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "handle": {
                    "type": "string",
                    "description": "An E.164 phone number or Apple ID email reachable through FaceTime."
                  }
                },
                "required": [
                  "handle"
                ]
              },
              "example": {
                "handle": "+15555550100"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dial a FaceTime Audio call from a number.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "session_id": "271b92eb-0a59-4491-bec3-314c7db48400",
                  "state": "dialing"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The number is not held by this caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The number could not place the call right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/config": {
      "get": {
        "operationId": "getV1NumbersByIdRtcConfig",
        "summary": "Get the ICE configuration for FaceTime browser audio.",
        "description": "Get the ICE configuration for FaceTime browser audio.\n\nPass ice_servers to your WebRTC client. TURN and LiveKit fallback availability depend on this deployment. Keep returned credentials in memory for the session; do not publish or log them.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Get the ICE configuration for FaceTime browser audio.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "ice_servers": [
                    {
                      "urls": [
                        "stun:stun.example.com:3478"
                      ]
                    }
                  ],
                  "turn_available": false,
                  "livekit_fallback_available": false
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/offer": {
      "post": {
        "operationId": "postV1NumbersByIdRtcOffer",
        "summary": "Negotiate the audio connection to the number.",
        "description": "Negotiate the audio connection to the number.\n\nSend a real SDP offer produced by your WebRTC client after gathering ICE candidates. The response is the remote answer to set on that connection. The abbreviated SDP below illustrates the shape, not a usable offer. Signaling does not itself dial a recipient.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "Must be offer."
                  },
                  "sdp": {
                    "type": "string",
                    "description": "The complete local SDP with gathered ICE candidates."
                  }
                },
                "required": [
                  "type",
                  "sdp"
                ]
              },
              "example": {
                "type": "offer",
                "sdp": "v=0\r\n..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Negotiate the audio connection to the number.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "type": "answer",
                  "sdp": "v=0\r\n..."
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Call audio for this number is unavailable right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/livekit": {
      "post": {
        "operationId": "postV1NumbersByIdRtcLivekit",
        "summary": "Start the configured LiveKit audio fallback.",
        "description": "Start the configured LiveKit audio fallback.\n\nUse when the direct connection cannot be established and config reports livekit_fallback_available. The response contains temporary room access for this session, not a reusable account token.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Start the configured LiveKit audio fallback.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "url": "wss://relay.example.com",
                  "token": "<temporary-room-token>"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "LiveKit fallback is not configured, or the number cannot join right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/calls/{session_id}": {
      "get": {
        "operationId": "getV1NumbersByIdRtcCallsBySessionId",
        "summary": "Read the FaceTime session state.",
        "description": "Read the FaceTime session state.\n\nstate is dialing, active, or ended. Use call history for the durable outcome and durations.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The session_id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Read the FaceTime session state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "session_id": "271b92eb-0a59-4491-bec3-314c7db48400",
                  "state": "active"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1NumbersByIdRtcCallsBySessionId",
        "summary": "End a FaceTime Audio call.",
        "description": "End a FaceTime Audio call.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The session_id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "End a FaceTime Audio call.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "session_id": "271b92eb-0a59-4491-bec3-314c7db48400",
                  "state": "ended"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/calls/{session_id}/transport": {
      "post": {
        "operationId": "postV1NumbersByIdRtcCallsBySessionIdTransport",
        "summary": "Report which audio path carried the FaceTime call.",
        "description": "Report which audio path carried the FaceTime call.\n\nAccepts direct or livekit. Report the path that actually connected so call history can identify it.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The session_id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "transport": {
                    "type": "string",
                    "description": "direct or livekit, according to the connected media path."
                  }
                },
                "required": [
                  "transport"
                ]
              },
              "example": {
                "transport": "direct"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Report which audio path carried the FaceTime call."
          },
          "400": {
            "description": "transport is neither direct nor livekit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{number_id}/call-recording": {
      "put": {
        "operationId": "putV1NumbersByNumberIdCallRecording",
        "summary": "Turn call recording on or off for one line.",
        "description": "Turn call recording on or off for one line.\n\nacknowledged is required to enable and refused without it. Recording law is decided by where the people on the call are — some places need every party's consent — and Miss Blue provides the feature without deciding whether a given call may lawfully be recorded. Who switched it on, and when, is stored with the flag. Turning it off asks nothing, and does not delete what was already captured.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "number_id",
            "in": "path",
            "required": true,
            "description": "The number_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5d2c1f1e-6f4a-4d43-9d8e-1a2b3c4d5e6f"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Whether this number should record future calls."
                  },
                  "acknowledged": {
                    "type": "boolean",
                    "description": "Must be true when enabling recording; optional when disabling."
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true,
                "acknowledged": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Turn call recording on or off for one line.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "enabled": true,
                  "enabled_at": "2026-09-06T16:54:38Z",
                  "enabled_by": "Ada Lovelace"
                }
              }
            }
          },
          "400": {
            "description": "Enabling without acknowledging responsibility for consent law.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/rtc/calls/{session_id}/recording": {
      "post": {
        "operationId": "postV1NumbersByIdRtcCallsBySessionIdRecording",
        "summary": "Begin uploading a FaceTime call recording.",
        "description": "Begin uploading a FaceTime call recording.\n\nRecording must be enabled for the number and the call must belong to that line. Capture both the local microphone and remote audio in one WebM stream before sending parts. This endpoint does not record audio by itself.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "description": "The session_id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Begin uploading a FaceTime call recording.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "recording_id": "f4075a5e-b819-4bcf-90c5-e0c43bc4c611",
                  "call_id": "271b92eb-0a59-4491-bec3-314c7db48400",
                  "status": "recording",
                  "mime_type": "audio/webm",
                  "byte_size": null,
                  "parts": 0,
                  "expires_at": null
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Call recording is not enabled for this number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/recordings/{recording_id}/parts/{part}": {
      "put": {
        "operationId": "putV1NumbersByIdRecordingsByRecordingIdPartsByPart",
        "summary": "Upload one WebM recording part.",
        "description": "Upload one WebM recording part.\n\nPart numbers start at 1 and may be at most 3000. Each part must be nonempty and at most 1 MiB. Upload consecutive parts from the same WebM stream in order; the first must contain the WebM header. Retry a part with the same bytes and part number before completion.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recording_id",
            "in": "path",
            "required": true,
            "description": "The recording_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "f4075a5e-b819-4bcf-90c5-e0c43bc4c611"
          },
          {
            "name": "part",
            "in": "path",
            "required": true,
            "description": "The part of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The raw bytes of the file (for example `part-1.webm`).",
          "content": {
            "audio/webm": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Upload one WebM recording part."
          },
          "400": {
            "description": "Part number or byte size is outside the accepted bounds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Recording is disabled or the recording is already finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-required-headers": {
          "Content-Type": "audio/webm"
        }
      }
    },
    "/v1/numbers/{id}/recordings/{recording_id}/complete": {
      "post": {
        "operationId": "postV1NumbersByIdRecordingsByRecordingIdComplete",
        "summary": "Finish and publish the uploaded FaceTime recording.",
        "description": "Finish and publish the uploaded FaceTime recording.\n\nCall after the final part has uploaded. Parts are joined in order and call.recording.ready is emitted once the audio is durable. A missing part can leave only the playable prefix, so confirm every upload before completing. Repeating completion for a ready recording returns that recording. expires_at is null when no retention deadline was configured.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "recording_id",
            "in": "path",
            "required": true,
            "description": "The recording_id of the resource.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finish and publish the uploaded FaceTime recording.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "recording_id": "f4075a5e-b819-4bcf-90c5-e0c43bc4c611",
                  "call_id": "271b92eb-0a59-4491-bec3-314c7db48400",
                  "status": "ready",
                  "mime_type": "audio/webm",
                  "byte_size": 669427,
                  "parts": 8,
                  "expires_at": null
                }
              }
            }
          },
          "400": {
            "description": "No audio was captured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{number_id}/calls/{call_id}/recording": {
      "get": {
        "operationId": "getV1NumbersByNumberIdCallsByCallIdRecording",
        "summary": "A short-lived signed URL for the audio.",
        "description": "A short-lived signed URL for the audio.\n\nFetch it when somebody presses play rather than with the page: it is a link to a recorded conversation. Check recording_status on the call first — an answered call has audio only if the line was recording at the time.",
        "tags": [
          "Calls"
        ],
        "parameters": [
          {
            "name": "number_id",
            "in": "path",
            "required": true,
            "description": "The number_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5d2c1f1e-6f4a-4d43-9d8e-1a2b3c4d5e6f"
          },
          {
            "name": "call_id",
            "in": "path",
            "required": true,
            "description": "The call_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "cf33aa62-1c89-40b0-9887-137108be4b48"
          }
        ],
        "responses": {
          "200": {
            "description": "A short-lived signed URL for the audio.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "url": "https://…"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No recording, or it belongs to another organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The recording failed, was deleted, or is not finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/lookup": {
      "get": {
        "operationId": "getV1Lookup",
        "summary": "Which services have demonstrably reached a handle.",
        "description": "Which services have demonstrably reached a handle.\n\nThis is evidence, not a query to Apple. We cannot ask — so instead this reads what has already happened on your own numbers. An inbound message is proof that service answers; a send that left the Mac is the same proof from the other side. Messages sends over three — iMessage, SMS, and RCS where the carrier and the recipient both support it — so `service` is the best one proven and `services` lists them all, best first. `unknown` means we have never tried, and is not a negative answer.",
        "tags": [
          "Lookup"
        ],
        "parameters": [
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "description": "A phone number or Apple ID email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Which services have demonstrably reached a handle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "handle": "+15555550100",
                  "service": "imessage",
                  "services": [
                    "imessage",
                    "sms"
                  ],
                  "attempts": 3,
                  "detail": "iMessage has worked with this handle before."
                }
              }
            }
          },
          "400": {
            "description": "No handle, an empty one, or one over 256 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts": {
      "get": {
        "operationId": "getV1Contacts",
        "summary": "Every name this project has given a handle.",
        "description": "Every name this project has given a handle.\n\n`reach` says whether iMessage is known to work with each one, from your own traffic. See Lookup for what the three states mean.",
        "tags": [
          "Contacts"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Every name this project has given a handle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                      "handle": "+15551230002",
                      "name": "Head of catering",
                      "reach": "reachable",
                      "created_at": "2026-08-23T09:12:04Z",
                      "updated_at": "2026-08-23T09:12:04Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putV1Contacts",
        "summary": "Name a handle, or rename one already named, and queue it for Apple Contacts.",
        "description": "Name a handle, or rename one already named, and queue it for Apple Contacts.\n\nPUT rather than POST: naming a handle is idempotent, and naming one that already has a name is a rename rather than a conflict. A caller should not have to know which it is doing. The save answers at once and queues the push to Apple Contacts on the book's numbers that have texted them or heard from them; a number that has not gets their card, with this name, when it first texts them, and that first message waits for it so it carries the number's Name & Photo. `sync.status` is when_texted when no number has texted them yet. `sync` in the answer says what was queued; to see it land, read `syncs[].status` on GET /v1/contacts/{id} (queued, then synced or failed) or listen for the contact.synced webhook, sent as each number takes it. This changed: a save used to wait for each number and answer with counts of synced and failed.",
        "tags": [
          "Contacts"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "handle": {
                    "type": "string",
                    "description": "Phone number or Apple ID email."
                  },
                  "name": {
                    "type": "string",
                    "description": "Clearing a name is a delete; there is a route for that."
                  },
                  "sync": {
                    "type": "boolean",
                    "description": "Default true. false keeps the name in Miss Blue only. If one of the book's own numbers texts them, that number gets a card with their number and no name (syncs[].nameless), so its Name & Photo (shared with Contacts only) reaches them; no contact.synced is sent for it. A contact already on one of the book's numbers with its name goes, with its current name, to any other of them that texts it."
                  },
                  "sync_number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Push only to this number. One of this book's own numbers, not a shared line. Any other of the book's numbers that texts them later gets their card too."
                  },
                  "custom_data": {
                    "type": "object",
                    "description": "Fields to set, merged into the ones the contact has: a field given a value is set, one given null, \"\" or only spaces is removed, one not named is kept. Absent changes none. Numbers and true/false are stored as text; a list or an object is 400. A project's first save of somebody its organization has starts with the organization's fields. At most 50 per contact after the merge; names 1 to 80 characters, without }} or %} or both kinds of quote; values up to 2,000. Messages read them as {{ contact.custom.<name> }}, as they are when each message sends."
                  }
                },
                "required": [
                  "handle",
                  "name"
                ]
              },
              "example": {
                "handle": "+15551230002",
                "name": "Ari Chen",
                "custom_data": {
                  "product": "Blue hoodie",
                  "amount": "$48",
                  "checkout_link": "https://shop.example/c/91"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Name a handle, or rename one already named, and queue it for Apple Contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                  "handle": "+15551230002",
                  "name": "Ari Chen",
                  "photo_attachment_id": null,
                  "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                  "custom_data": {
                    "product": "Blue hoodie",
                    "amount": "$48",
                    "checkout_link": "https://shop.example/c/91"
                  },
                  "reach": "unknown",
                  "created_at": "2026-08-23T09:12:04Z",
                  "updated_at": "2026-08-28T10:04:00Z",
                  "sync": {
                    "status": "queued",
                    "numbers": [
                      {
                        "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                        "number_handle": "+16465550142"
                      }
                    ],
                    "check": "GET /v1/contacts/73ef7753-7ca6-493e-b9ff-9eef708828e7 shows syncs[].status for each number: queued, then synced or failed. A contact.synced webhook is sent as each number takes it."
                  }
                }
              }
            }
          },
          "400": {
            "description": "sync_number_id is not one of this book's numbers, or is given with sync: false; or custom_data would take the contact past 50 fields, or has a name or value outside the limits. Nothing is saved, not even the name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts/{id}": {
      "get": {
        "operationId": "getV1ContactsById",
        "summary": "Pull everything known about one project contact.",
        "description": "Pull everything known about one project contact.\n\nIncludes its project and organization context, custom data, and where it stands in Apple Contacts on each number: `syncs[].status` is queued, synced or failed, or when_texted for a number that will get the card when it first texts them, with the last successful sync and the last error. Queued waits for a number that is away however long it takes, and is retried a few times over about an hour and a half if the number refuses it, with last_error saying why, before it is failed. `syncs[].nameless` is true for a card with the number alone and no name: put there because that number texted them, so its Name & Photo (shared with Contacts only) reaches them, while the name stays in Miss Blue. No contact.synced is sent for one.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull everything known about one project contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "contact": {
                    "id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                    "handle": "+15551230002",
                    "name": "Ari Chen",
                    "photo_attachment_id": "d8f25b47-dda6-41ec-a303-4bdfb9d6953e",
                    "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                    "custom_data": {
                      "crm_id": "lead_2048",
                      "tier": "gold"
                    },
                    "reach": "reachable",
                    "created_at": "2026-08-23T09:12:04Z",
                    "updated_at": "2026-08-28T10:04:00Z"
                  },
                  "project_name": "Client Support",
                  "workspace_id": "3480fdd8-2ef3-4edd-9949-bf78b4cd3e35",
                  "workspace_name": "Acme Agency",
                  "syncs": [
                    {
                      "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                      "number_handle": "+16465550142",
                      "number_label": "Main support",
                      "status": "synced",
                      "last_attempted_at": "2026-08-28T10:03:00Z",
                      "last_synced_at": "2026-08-28T10:03:02Z",
                      "last_error": null,
                      "nameless": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putV1ContactsById",
        "summary": "Update a contact's name and custom data, and queue it for Apple Contacts.",
        "description": "Update a contact's name and custom data, and queue it for Apple Contacts.\n\ncustom_data, when given, is the complete replacement map; absent keeps the fields the contact has (a rename without it used to clear them). To set a few fields and keep the rest, save by handle with PUT /v1/contacts. It supports up to 50 fields; keys are at most 80 characters and values at most 2,000. Messages read them as {{ contact.custom.<name> }}. Like a save, it answers at once and queues the push to Apple Contacts on the book's numbers: `contact.sync` says what was queued, and `syncs` shows each number as queued until it takes it. The detailed contact and sync state are returned.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The contact's name."
                  },
                  "custom_data": {
                    "type": "object",
                    "description": "Replaces what the contact had. Absent or null keeps it."
                  },
                  "sync": {
                    "type": "boolean",
                    "description": "Default true. false saves the change in Miss Blue only."
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Ari Chen",
                "custom_data": {
                  "crm_id": "lead_2048",
                  "tier": "gold"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Update a contact's name and custom data, and queue it for Apple Contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "contact": {
                    "id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                    "handle": "+15551230002",
                    "name": "Ari Chen",
                    "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                    "custom_data": {
                      "crm_id": "lead_2048",
                      "tier": "gold"
                    },
                    "reach": "reachable",
                    "sync": {
                      "status": "queued",
                      "numbers": [
                        {
                          "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                          "number_handle": "+16465550142"
                        }
                      ],
                      "check": "GET /v1/contacts/73ef7753-7ca6-493e-b9ff-9eef708828e7 shows syncs[].status for each number: queued, then synced or failed. A contact.synced webhook is sent as each number takes it."
                    }
                  },
                  "project_name": "Client Support",
                  "workspace_id": "3480fdd8-2ef3-4edd-9949-bf78b4cd3e35",
                  "workspace_name": "Acme Agency",
                  "syncs": [
                    {
                      "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                      "number_handle": "+16465550142",
                      "number_label": "Main support",
                      "status": "queued",
                      "last_attempted_at": "2026-08-28T10:03:00Z",
                      "last_synced_at": "2026-08-28T10:03:02Z",
                      "last_error": null,
                      "nameless": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1ContactsById",
        "summary": "Remove a name from the project's book.",
        "description": "Remove a name from the project's book.\n\nAnyone on the project may remove any name, not only whoever added it. A shared book that only its author can tidy is a book nobody tidies.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "responses": {
          "204": {
            "description": "Remove a name from the project's book."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such contact in this project's book.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts/{id}/block": {
      "put": {
        "operationId": "putV1ContactsByIdBlock",
        "summary": "Block or unblock a contact across its organization.",
        "description": "Block or unblock a contact across its organization.\n\nRequires a member session covering the whole organization and project_id or workspace_id for the contact's address book. Outgoing messages fail with contact_blocked (403); unsent queued messages are cancelled. New incoming messages and notifications are suppressed in Miss Blue. Existing history remains. Unblocking does not replay discarded messages. Calls and delivery to the underlying Apple account are unaffected. Deleting the contact does not remove the block.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "project_id": {
                    "type": "string",
                    "description": "Query parameter. Project address book; use workspace_id instead for an organization contact."
                  },
                  "workspace_id": {
                    "type": "string",
                    "description": "Query parameter. Organization address book; mutually exclusive with project_id."
                  },
                  "blocked": {
                    "type": "boolean",
                    "description": "true to block; false to unblock."
                  }
                },
                "required": [
                  "blocked"
                ]
              },
              "example": {
                "blocked": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Block or unblock a contact across its organization."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts/{id}/sync": {
      "post": {
        "operationId": "postV1ContactsByIdSync",
        "summary": "Save this contact to the Mac and its iCloud contact account.",
        "description": "Save this contact to the Mac and its iCloud contact account.\n\nThe way to push a contact again, to one number, when a save's queued push did not take. Unlike a save, it waits for that number's answer. Name the project number whose Mac should receive it. The server resolves the Mac from that authorised number; an agent id is never accepted. The first sync may wait for the Mac operator to approve Contacts access. A project contact that lands also sends the contact.synced webhook.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "A number in this project."
                  }
                },
                "required": [
                  "number_id"
                ]
              },
              "example": {
                "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Save this contact to the Mac and its iCloud contact account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "request_id": "bb56a0f4-4f40-4e85-9cd6-c3268f51dc85",
                  "contact_id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                  "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                  "synced": true
                }
              }
            }
          },
          "400": {
            "description": "The Mac refused or could not save the contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The contact or number is outside this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This number is temporarily unavailable. The push stays queued and goes when the number is back.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts/{id}/photo": {
      "put": {
        "operationId": "putV1ContactsByIdPhoto",
        "summary": "Use an uploaded JPEG or PNG as this contact's address-book photo.",
        "description": "Use an uploaded JPEG or PNG as this contact's address-book photo.\n\nUpload the image through Attachments first. This updates the contact photo in the Mac/iCloud address book when synced; Apple's account-wide outbound Messages name and photo sharing remains a separate Messages setting.",
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "73ef7753-7ca6-493e-b9ff-9eef708828e7"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "attachment_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "An uploaded JPEG or PNG no larger than 10 MiB."
                  },
                  "sync": {
                    "type": "boolean",
                    "description": "Default true: queues the photo for Apple Contacts on the book's numbers, and the answer's `sync` says where. false keeps it in Miss Blue only."
                  }
                },
                "required": [
                  "attachment_id"
                ]
              },
              "example": {
                "attachment_id": "d8f25b47-dda6-41ec-a303-4bdfb9d6953e"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Use an uploaded JPEG or PNG as this contact's address-book photo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "73ef7753-7ca6-493e-b9ff-9eef708828e7",
                  "handle": "+15551230002",
                  "name": "Ari Chen",
                  "photo_attachment_id": "d8f25b47-dda6-41ec-a303-4bdfb9d6953e",
                  "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                  "custom_data": {},
                  "reach": "unknown",
                  "created_at": "2026-08-23T09:12:04Z",
                  "updated_at": "2026-08-28T10:04:00Z",
                  "sync": {
                    "status": "queued",
                    "numbers": [
                      {
                        "number_id": "1d17fe68-c7ad-4adf-874c-9a0e3d254df5",
                        "number_handle": "+16465550142"
                      }
                    ],
                    "check": "GET /v1/contacts/73ef7753-7ca6-493e-b9ff-9eef708828e7 shows syncs[].status for each number: queued, then synced or failed. A contact.synced webhook is sent as each number takes it."
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/contacts/import": {
      "post": {
        "operationId": "postV1ContactsImport",
        "summary": "Name many handles at once.",
        "description": "Name many handles at once.\n\nSaves what is usable and tells you precisely which rows were not, rather than refusing the whole file over one bad line — a five-hundred-row import that fails on row four hundred means somebody edits and retries, several times.",
        "tags": [
          "Contacts"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contacts": {
                    "type": "array",
                    "description": "Objects with `handle` and `name`, and optionally `custom_data`, merged the same way as PUT /v1/contacts. A row whose fields break a limit is skipped with the reason, fields and name together."
                  }
                },
                "required": [
                  "contacts"
                ]
              },
              "example": {
                "contacts": [
                  {
                    "handle": "+15551230002",
                    "name": "Head of catering",
                    "custom_data": {
                      "plan": "Gold"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Name many handles at once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "saved": 1,
                  "skipped": []
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/messages/{id}/attachments/{attachment_id}": {
      "get": {
        "operationId": "getV1MessagesByIdAttachmentsByAttachmentId",
        "summary": "Download a file from a message your project can read.",
        "description": "Download a file from a message your project can read.\n\nAccepts the same project bearer key as the message API. The attachment must appear in this message's attachments array. Returns the original bytes and Content-Type with private, no-store caching. A missing file, unreadable message, or unrelated attachment returns 404. Received files can remain unavailable if the file did not finish uploading; reconcile before a bounded retry. On a Twilio line, each photo, video, sound or file on an incoming MMS is kept the same way and downloads with this route.",
        "tags": [
          "Attachments"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "0b5ed7e8-f7a1-4f6e-9c2a-2a1f0d9e8b77"
          },
          {
            "name": "attachment_id",
            "in": "path",
            "required": true,
            "description": "The attachment_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "7deff905-9038-4e4e-b47c-580a1ab52220"
          }
        ],
        "responses": {
          "200": {
            "description": "Download a file from a message your project can read.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Message or attachment unavailable to this project, or attachment does not belong to the message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/attachments": {
      "post": {
        "operationId": "postV1Attachments",
        "summary": "Upload a file and get an id to send it with.",
        "description": "Upload a file and get an id to send it with.\n\nThe file name and type ride in headers. GIFs, common video and voice-recording formats, PDFs, archives, Office and iWork files are accepted up to 100 MiB. Send one attachment id per message call.",
        "tags": [
          "Attachments"
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "The raw bytes of the file (for example `receipt.png`).",
          "content": {
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload a file and get an id to send it with.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "7deff905-9038-4e4e-b47c-580a1ab52220",
                  "file_name": "receipt.png",
                  "mime_type": "image/png",
                  "size_bytes": 20481,
                  "sha256": "c414cd0e204de974f73753c7e28d7638e7b3691bb8b1a2bab6b25bb7fed7ce77"
                }
              }
            }
          },
          "400": {
            "description": "An empty file, a missing name, or a type we do not allow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Over the size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-required-headers": {
          "Content-Type": "image/png",
          "X-Miss-Blue-File-Name": "receipt.png"
        }
      }
    },
    "/v1/numbers": {
      "get": {
        "operationId": "getV1Numbers",
        "summary": "Your project's numbers, with the name each has been given.",
        "description": "Your project's numbers, with the name each has been given.\n\nThe Mac behind a number is never named. Which machine holds a line is our problem, and telling you would make it yours. availability says whether a number can send right now: { \"status\": \"available\" }, or { \"status\": \"unavailable\", \"message\": … } with a sentence written to be shown to your own users as it is. It is unavailable exactly when a send made now would be queued rather than sent — sends are still accepted, and go out when it is back.",
        "tags": [
          "Numbers"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Your project's numbers, with the name each has been given.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                      "handle": "+16465550142",
                      "label": "Main support",
                      "service": "imessage",
                      "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                      "disabled": false,
                      "simulated": false,
                      "availability": {
                        "status": "available"
                      },
                      "shared": false,
                      "created_at": "2026-08-23T09:12:04Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}": {
      "get": {
        "operationId": "getV1NumbersById",
        "summary": "Pull one number and its project and organization context.",
        "description": "Pull one number and its project and organization context.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull one number and its project and organization context.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "number": {
                    "id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                    "handle": "+16465550142",
                    "label": "Main support",
                    "service": "imessage",
                    "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                    "disabled": false,
                    "simulated": false,
                    "availability": {
                      "status": "available"
                    },
                    "shared": false,
                    "created_at": "2026-08-23T09:12:04Z"
                  },
                  "project_name": "Client Support",
                  "workspace_id": "3480fdd8-2ef3-4edd-9949-bf78b4cd3e35",
                  "workspace_name": "Acme Agency"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/label": {
      "post": {
        "operationId": "postV1NumbersByIdLabel",
        "summary": "Give a number a name.",
        "description": "Give a number a name.\n\nSo a thread says it arrived on Main support rather than +1 646 555 0142. Coming back to a conversation days later, the digits do not tell you which line you used.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "Empty or absent clears it back to the bare handle."
                  }
                }
              },
              "example": {
                "label": "Main support"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Give a number a name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                  "handle": "+16465550142",
                  "label": "Main support",
                  "service": "imessage",
                  "project_id": "e3b7c534-7e5d-4669-b90a-5b617bfe77f0",
                  "disabled": false,
                  "simulated": false,
                  "availability": {
                    "status": "available"
                  },
                  "shared": false,
                  "created_at": "2026-08-23T09:12:04Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{selector}/call-forwarding": {
      "get": {
        "operationId": "getV1NumbersBySelectorCallForwarding",
        "summary": "Pull the currently confirmed call-forwarding destination and latest request.",
        "description": "Pull the currently confirmed call-forwarding destination and latest request.\n\nforwarding_number changes only after staff confirms the carrier update. A pending destination appears under latest_request without pretending it is already active.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "selector",
            "in": "path",
            "required": true,
            "description": "The selector of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull the currently confirmed call-forwarding destination and latest request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                  "number": "+16465550142",
                  "forwarding_number": "+14155550100",
                  "eligible": true,
                  "eligibility_reason": null,
                  "monthly_limit": 2,
                  "requests_used_this_month": 1,
                  "requests_remaining_this_month": 1,
                  "latest_request": {
                    "id": "927c4759-fb1a-43c4-9a39-acde90645fe0",
                    "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                    "forward_to": "+14155550100",
                    "status": "completed",
                    "rejection_reason": null,
                    "created_at": "2026-09-03T16:00:00Z",
                    "updated_at": "2026-09-03T16:30:00Z",
                    "completed_at": "2026-09-03T16:30:00Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The path is neither a UUID nor a canonical E.164 number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The selected number is outside this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1NumbersBySelectorCallForwarding",
        "summary": "Request a new call-forwarding destination.",
        "description": "Request a new call-forwarding destination.\n\nThe source in the path and forward_to must be canonical E.164 phone numbers. Staff complete the carrier change within 2–3 business days. Each number may request two changes per calendar month and have one open request at a time.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "selector",
            "in": "path",
            "required": true,
            "description": "The selector of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "forward_to": {
                    "type": "string",
                    "description": "The destination: +, a non-zero country code, and 7–15 digits total."
                  }
                },
                "required": [
                  "forward_to"
                ]
              },
              "example": {
                "forward_to": "+14155550100"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Request a new call-forwarding destination.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "927c4759-fb1a-43c4-9a39-acde90645fe0",
                  "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                  "forward_to": "+14155550100",
                  "status": "pending",
                  "rejection_reason": null,
                  "created_at": "2026-09-03T16:00:00Z",
                  "updated_at": "2026-09-03T16:00:00Z",
                  "completed_at": null
                }
              }
            }
          },
          "400": {
            "description": "The source or destination is not canonical E.164, or both are the same.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The selected number is outside this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This number already has a pending or processing request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This number already requested two changes in the current calendar month.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/name-photo": {
      "get": {
        "operationId": "getV1NumbersByIdNamePhoto",
        "summary": "Pull this number's current Messages Name & Photo Sharing profile.",
        "description": "Pull this number's current Messages Name & Photo Sharing profile.\n\nThis is the Apple Account-wide outbound identity recipients may be offered, not a local Contacts card. photo_base64 is a bounded preview and has_photo remains authoritative. sharing_enabled says whether it is offered at all (to the number's contacts); Miss Blue keeps it on. Once a name and photo are saved here, saved is what was saved and in_sync whether the number still holds exactly that (photo_sha256 is the fingerprint of the exact image it holds). If it was changed outside Miss Blue, it is put back from what was saved and restored_at says when. The project's people get an email about it, at most one a week per number, and none for a change put back within an hour of a save. A new number starts with a name and photo Miss Blue chose (saved.is_default), and saved.verified_at says when a test message last proved the saved name and photo reach people.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "responses": {
          "200": {
            "description": "Pull this number's current Messages Name & Photo Sharing profile.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "first_name": "Blue",
                  "last_name": "Lover",
                  "has_photo": true,
                  "photo_mime_type": "image/jpeg",
                  "photo_base64": "/9j/4AAQSkZJRg…",
                  "photo_sha256": "53d37af3e156b59a0c7ed129912a7ff8c4b29de3acd0bca2b702b030d70bcd53",
                  "sharing_enabled": true,
                  "saved": {
                    "first_name": "Blue",
                    "last_name": "Lover",
                    "has_photo": true,
                    "photo_attachment_id": "88de9745-30e0-4b93-bfcb-dbb7f3325dde",
                    "saved_at": "2026-10-01T17:10:55Z",
                    "is_default": false
                  },
                  "in_sync": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The number is shared or its Apple Account has more than one number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The number is outside this project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putV1NumbersByIdNamePhoto",
        "summary": "Update and verify the number's Messages name and photo.",
        "description": "Update and verify the number's Messages name and photo.\n\nUpload a JPEG or PNG first. To remove the photo, omit photo_attachment_id and set remove_photo to true. Success is returned only after an independent Messages readback matches the requested name and image. Saving also turns sharing on, for the number's contacts.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "first_name": "Blue",
                "last_name": "Lover",
                "photo_attachment_id": "88de9745-30e0-4b93-bfcb-dbb7f3325dde",
                "remove_photo": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Update and verify the number's Messages name and photo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "first_name": "Blue",
                  "last_name": "Lover",
                  "has_photo": true,
                  "photo_mime_type": "image/jpeg",
                  "photo_base64": "/9j/4AAQSkZJRg…",
                  "photo_sha256": "53d37af3e156b59a0c7ed129912a7ff8c4b29de3acd0bca2b702b030d70bcd53",
                  "sharing_enabled": true,
                  "saved": {
                    "first_name": "Blue",
                    "last_name": "Lover",
                    "has_photo": true,
                    "photo_attachment_id": "88de9745-30e0-4b93-bfcb-dbb7f3325dde",
                    "saved_at": "2026-10-01T17:10:55Z"
                  },
                  "in_sync": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/numbers/{id}/service-status": {
      "get": {
        "operationId": "getV1NumbersByIdServiceStatus",
        "summary": "Pull live iMessage and FaceTime registration for a recipient.",
        "description": "Pull live iMessage and FaceTime registration for a recipient.\n\nEach service is registered, not_registered, or unknown. Unknown is an inconclusive Apple lookup and is never turned into a negative answer.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          },
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "description": "The E.164 phone number or Apple ID email to check.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pull live iMessage and FaceTime registration for a recipient.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "handle": "+15163128403",
                  "imessage": "registered",
                  "facetime": "registered",
                  "checked_at": "2026-08-28T10:04:00Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1NumbersByIdServiceStatus",
        "summary": "Run the same live service lookup with a JSON request.",
        "description": "Run the same live service lookup with a JSON request.",
        "tags": [
          "Numbers"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "handle": "+15163128403"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Run the same live service lookup with a JSON request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "handle": "+15163128403",
                  "imessage": "registered",
                  "facetime": "registered",
                  "checked_at": "2026-08-28T10:04:00Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/announcements": {
      "get": {
        "operationId": "getV1ProjectsByIdAnnouncements",
        "summary": "Announcements in this project, newest first, with how each is going.",
        "description": "Announcements in this project, newest first, with how each is going.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "Announcements in this project, newest first, with how each is going.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90",
                      "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                      "title": "Spring hours",
                      "body": "We open at nine from Monday.",
                      "status": "sending",
                      "send_at": null,
                      "time_zone": null,
                      "reply_window_hours": 72,
                      "review_note": null,
                      "started_at": "2026-09-26T14:00:00Z",
                      "finished_at": null,
                      "created_at": "2026-09-26T13:58:00Z",
                      "stats": {
                        "total": 120,
                        "sent": 64,
                        "skipped": 2,
                        "invalid": 0,
                        "failed": 0,
                        "pending": 54,
                        "replied": 9,
                        "unsubscribed": 1
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdAnnouncements",
        "summary": "Draft an announcement. Nothing is sent.",
        "description": "Draft an announcement. Nothing is sent.\n\nRecipients are the handles you pass, everybody on each list, and everybody carrying each tag, once each. Lists and tags are read now: somebody added later is not included. Opted-out and blocked people are marked skipped before the response, so the counts are what sending would do. At most 5,000 people.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "One of this project's numbers."
                  },
                  "title": {
                    "type": "string",
                    "description": "Internal. Recipients never see it."
                  },
                  "body": {
                    "type": "string",
                    "description": "The message, in Liquid, filled in for each person as it sends: {{ contact.first_name | default: \"there\" }}, {{ contact.custom.<name> }}. See /docs/templates. Required unless `variants`; with them, version A's text."
                  },
                  "variants": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "An A/B/C test: 2 or 3 of `{letter, body, weight}`, letters A to C with an A, weights whole percents adding up to 100 (leave them all out for an even split). Each person is given one when the draft is made, cut by the weights exactly."
                  },
                  "test_first": {
                    "type": "object",
                    "description": "With `variants`: `{percent, wait_hours}`. Send the versions to `percent` of the list first (5 to 50), wait `wait_hours` (1 to 72) from when the last of their messages left the number, then send the rest the version with the best reply rate, leaving out one that looks to cause more opt-outs. The pick waits (another `wait_hours` at a time, up to 72 hours, saying so in `pick_note`) until at least half of those whose message went have been reached."
                  },
                  "recipients": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Phone numbers or emails."
                  },
                  "list_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Everybody on these lists, as they stand now."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Everybody carrying these tags, as they stand now."
                  },
                  "send_at": {
                    "type": "string",
                    "description": "When to start. Absent: when it is sent."
                  },
                  "time_zone": {
                    "type": "string",
                    "description": "The zone send_at was chosen in. Shown in the console."
                  },
                  "quiet_hours": {
                    "type": "boolean",
                    "description": "On unless false: each person is reached only 8 AM to 9 PM in their own time (tighter where their state requires it), and held until their morning otherwise."
                  },
                  "reply_window_hours": {
                    "type": "integer",
                    "description": "1 to 720. How long a reply counts as a reply to this. 72 by default."
                  }
                },
                "required": [
                  "number_id",
                  "title"
                ]
              },
              "example": {
                "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                "title": "Spring hours",
                "body": "We open at nine from Monday.",
                "recipients": [
                  "+15551230002"
                ],
                "list_ids": [
                  "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
                ],
                "tags": [
                  "vip"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft an announcement. Nothing is sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90",
                  "status": "draft",
                  "stats": {
                    "total": 120,
                    "sent": 0,
                    "skipped": 2,
                    "invalid": 0,
                    "failed": 0,
                    "pending": 118,
                    "replied": 0,
                    "unsubscribed": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "No body, nobody to send to, more than 5,000 people, a reply window outside 1 to 720 hours, versions that are not 2 or 3 with an A, weights that do not add up to 100, a version that will not parse, or test first without versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The number or a list is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/announcements/{announcement_id}/split": {
      "get": {
        "operationId": "getV1ProjectsByIdAnnouncementsByAnnouncementIdSplit",
        "summary": "Preview what sending it would do, before anybody does it.",
        "description": "Preview what sending it would do, before anybody does it.\n\nShow this to whoever approves the send. `established` people have written to the number and go out at normal speed. `first_contact` people have not written to it (whether or not it messaged them before), so they keep the number's pace, about seven an hour; those it never messaged also count against its new people a day. `first_contact_days` estimates them at `first_contact_per_day` a day. `skipped` were left out: opted out, blocked, or not a phone number or email. `needs_review` is true for a project's first announcement. With versions, `versions` is how many get each one; with a test first those are the test slice, and the row with `variant: null` is the rest, who get the best version after the wait.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "announcement_id",
            "in": "path",
            "required": true,
            "description": "The announcement_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90"
          }
        ],
        "responses": {
          "200": {
            "description": "Preview what sending it would do, before anybody does it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 120,
                  "established": 70,
                  "first_contact": 48,
                  "skipped": 2,
                  "first_contact_days": 1,
                  "first_contact_per_day": 50,
                  "needs_review": false,
                  "versions": [
                    {
                      "variant": "A",
                      "people": 8,
                      "in_test": 8
                    },
                    {
                      "variant": "B",
                      "people": 8,
                      "in_test": 8
                    },
                    {
                      "variant": "C",
                      "people": 8,
                      "in_test": 8
                    },
                    {
                      "variant": null,
                      "people": 94,
                      "in_test": 0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/announcements/{announcement_id}/send": {
      "post": {
        "operationId": "postV1ProjectsByIdAnnouncementsByAnnouncementIdSend",
        "summary": "Send a draft.",
        "description": "Send a draft.\n\nA project's first announcement becomes `awaiting_review` until a person at Miss Blue reads it. After that, one with a `send_at` becomes `scheduled` and one without starts `sending`. Preview it with the split first: this cannot be undone for the people it has already reached.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "announcement_id",
            "in": "path",
            "required": true,
            "description": "The announcement_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90"
          }
        ],
        "responses": {
          "200": {
            "description": "Send a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90",
                  "status": "sending"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It has already been sent or cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/announcements/{announcement_id}/cancel": {
      "post": {
        "operationId": "postV1ProjectsByIdAnnouncementsByAnnouncementIdCancel",
        "summary": "Cancel a draft, a scheduled announcement, or the rest of one that is sending.",
        "description": "Cancel a draft, a scheduled announcement, or the rest of one that is sending.\n\nWhat has already gone stays gone. Cancelling stops the people not yet reached.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "announcement_id",
            "in": "path",
            "required": true,
            "description": "The announcement_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancel a draft, a scheduled announcement, or the rest of one that is sending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "cancelled": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It has already finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/announcements/{announcement_id}/variants/{letter}/use": {
      "post": {
        "operationId": "postV1ProjectsByIdAnnouncementsByAnnouncementIdVariantsByLetterUse",
        "summary": "Use this one: everybody an A/B/C announcement has not reached yet gets this version.",
        "description": "Use this one: everybody an A/B/C announcement has not reached yet gets this version.\n\nDuring a test first's wait it is the pick, made by hand instead of by the numbers. Once only. The announcement comes back with `picked_variant` and `pick_note`, and `test`: the versions side by side (the test slice only, with a test first) with a verdict, in the shape of an automation step's `test`.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "announcement_id",
            "in": "path",
            "required": true,
            "description": "The announcement_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90"
          },
          {
            "name": "letter",
            "in": "path",
            "required": true,
            "description": "The letter of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "B"
          }
        ],
        "responses": {
          "200": {
            "description": "Use this one: everybody an A/B/C announcement has not reached yet gets this version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "5f0c1a2e-8a4b-4c1d-9e7f-2b6d3c8a1e90",
                  "status": "sending",
                  "picked_variant": "B",
                  "pick_note": "B was chosen by hand."
                }
              }
            }
          },
          "400": {
            "description": "It has one text.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "It has no such version, or is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A version was already picked, or it has finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/scheduled": {
      "get": {
        "operationId": "getV1ProjectsByIdScheduled",
        "summary": "Messages scheduled for later, and what became of them.",
        "description": "Messages scheduled for later, and what became of them.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "Messages scheduled for later, and what became of them.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "9b2e4c61-3f8a-4d7e-b1c0-6a5d8e2f7c13",
                      "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                      "recipient": "+15551230002",
                      "body": "See you at nine",
                      "send_at": "2026-10-02T14:00:00Z",
                      "time_zone": "America/Chicago",
                      "status": "pending",
                      "error": null,
                      "sent_message_id": null,
                      "created_at": "2026-09-26T13:58:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdScheduled",
        "summary": "Schedule one message for later.",
        "description": "Schedule one message for later.\n\n`send_at` is absolute. Choose it in the recipient's local time and pass their offset. When it goes, it goes through the same path as a send now: opt-outs, blocks and first-contact pacing apply at that moment.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "One of this project's numbers."
                  },
                  "recipient": {
                    "type": "string",
                    "description": "Phone number or email."
                  },
                  "body": {
                    "type": "string",
                    "description": "Optional only with attachment_ids. Liquid, filled in from the recipient's contact when it sends, not when it is scheduled."
                  },
                  "send_at": {
                    "type": "string",
                    "description": "At least 30 seconds and at most 365 days ahead."
                  },
                  "time_zone": {
                    "type": "string",
                    "description": "The zone it was chosen in, for display."
                  },
                  "quiet_hours": {
                    "type": "boolean",
                    "description": "On unless false: a send_at in the recipient's night (8 AM to 9 PM their time, tighter where their state requires it) goes at their morning. goes_at in the response says when."
                  },
                  "attachment_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Uploaded files to send with it."
                  }
                },
                "required": [
                  "number_id",
                  "recipient",
                  "body",
                  "send_at"
                ]
              },
              "example": {
                "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                "recipient": "+15551230002",
                "body": "See you at nine",
                "send_at": "2026-10-02T09:00:00-05:00",
                "time_zone": "America/Chicago"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Schedule one message for later.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "9b2e4c61-3f8a-4d7e-b1c0-6a5d8e2f7c13",
                  "status": "pending",
                  "send_at": "2026-10-02T14:00:00Z"
                }
              }
            }
          },
          "400": {
            "description": "A time that has passed, or more than a year ahead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/scheduled/{scheduled_id}": {
      "post": {
        "operationId": "postV1ProjectsByIdScheduledByScheduledId",
        "summary": "Change the text or the time of a message that has not gone.",
        "description": "Change the text or the time of a message that has not gone.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "scheduled_id",
            "in": "path",
            "required": true,
            "description": "The scheduled_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "9b2e4c61-3f8a-4d7e-b1c0-6a5d8e2f7c13"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "body": "See you at ten",
                "send_at": "2026-10-02T10:00:00-05:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Change the text or the time of a message that has not gone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "9b2e4c61-3f8a-4d7e-b1c0-6a5d8e2f7c13",
                  "status": "pending",
                  "send_at": "2026-10-02T15:00:00Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It has already been sent or cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1ProjectsByIdScheduledByScheduledId",
        "summary": "Cancel a scheduled message.",
        "description": "Cancel a scheduled message.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "scheduled_id",
            "in": "path",
            "required": true,
            "description": "The scheduled_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "9b2e4c61-3f8a-4d7e-b1c0-6a5d8e2f7c13"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancel a scheduled message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "cancelled": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It has already been sent or cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomations",
        "summary": "Automations in this project: what starts each, its steps, and whether it is on.",
        "description": "Automations in this project: what starts each, its steps, and whether it is on.\n\n`results` is the last 30 days: the same totals as the automation's stats.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "Automations in this project: what starts each, its steps, and whether it is on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                      "name": "Welcome",
                      "enabled": true,
                      "trigger_kind": "tag_added",
                      "trigger_keywords": [],
                      "trigger_list_id": null,
                      "trigger_tag": "new-customer",
                      "number_id": null,
                      "allow_repeat": false,
                      "stop_on_reply": true,
                      "send_hours": {
                        "start": "08:00",
                        "end": "18:00",
                        "time_zone": "America/New_York",
                        "days": [
                          "mon",
                          "tue",
                          "wed",
                          "thu",
                          "fri"
                        ]
                      },
                      "number_note": null,
                      "review_status": "approved",
                      "review_note": null,
                      "reaches_strangers": true,
                      "steps": [
                        {
                          "id": "…",
                          "position": 10,
                          "kind": "send",
                          "body": "Welcome aboard.",
                          "wait_seconds": null,
                          "condition_kind": null,
                          "next_step_id": null
                        },
                        {
                          "id": "…",
                          "position": 20,
                          "kind": "wait",
                          "body": null,
                          "wait_seconds": 172800,
                          "condition_kind": null,
                          "next_step_id": null
                        },
                        {
                          "id": "…",
                          "position": 30,
                          "kind": "condition",
                          "body": null,
                          "wait_seconds": null,
                          "condition_kind": "not_replied_since_last",
                          "next_step_id": null
                        },
                        {
                          "id": "…",
                          "position": 40,
                          "kind": "send",
                          "body": "Anything we can help with?",
                          "wait_seconds": null,
                          "condition_kind": null,
                          "next_step_id": null
                        }
                      ],
                      "running": 12,
                      "completed": 30,
                      "created_at": "2026-09-20T10:00:00Z",
                      "results": {
                        "days": 30,
                        "sent": 118,
                        "delivered": 104,
                        "read": 61,
                        "replied": 19,
                        "opted_out": 2,
                        "failed": 3,
                        "cancelled": 6,
                        "pending": 0,
                        "reached": 105,
                        "reply_rate": 0.181,
                        "opt_out_rate": 0.019
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdAutomations",
        "summary": "Create an automation, from a one-message reply to a multi-step sequence. It starts switched off.",
        "description": "Create an automation, from a one-message reply to a multi-step sequence. It starts switched off.\n\nThe list and the number it names must be this project's. Triggers: `keyword`, `first_message`, `conversation_opened`, `added_to_list` and `tag_added`. Steps run in order: `send` (body), `wait` (wait_seconds, up to 31 days), `condition` (carries on only when it holds, and otherwise ends the run), and `stop`. A send body is Liquid, filled in for each person when that step sends, from their contact as it is then: {{ contact.first_name | default: \"there\" }}, {{ contact.custom.checkout_link }}. Update a field before a follow-up and the follow-up says the new value. `not_replied` and `replied` count anything since the run started; `not_replied_since_last` and `replied_since_last` count only what came after its latest message. A list or tag automation with no `number_id` sends from Auto: each person gets the best of the project's numbers when their first message goes. If a pinned number leaves the project, the automation moves to another of its numbers, or is switched off when there is none, and `number_note` says which. Only point one at people who asked to hear from you.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": ""
                  },
                  "trigger_kind": {
                    "type": "string",
                    "description": "What starts it."
                  },
                  "trigger_keywords": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "For `keyword`. Matched as whole messages, case-insensitively."
                  },
                  "trigger_list_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "For `added_to_list`."
                  },
                  "trigger_tag": {
                    "type": "string",
                    "description": "For `tag_added`."
                  },
                  "number_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The number it listens on or sends from. Absent: every number for a keyword or message trigger, Auto for a list or tag."
                  },
                  "allow_repeat": {
                    "type": "boolean",
                    "description": "Let one person go through it more than once."
                  },
                  "stop_on_reply": {
                    "type": "boolean",
                    "description": "End a person's run when they reply. On when absent. A condition that waits for a reply never holds while it is on."
                  },
                  "skip_if_wrote_within_days": {
                    "type": "string",
                    "description": "For a list or tag automation: don't start it for anybody who wrote to the project in the last 1 to 365 days. Off when absent or null. Stop on reply only hears replies to the automation's own messages; this hears the conversation before it."
                  },
                  "exit_tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 20 tags, normalised as tags are. It doesn't start for anybody carrying one, and a run ends (`stopped`, reason `they were tagged “paid”`) the moment one is added, calling off what it had waiting. Applies to every trigger."
                  },
                  "send_hours": {
                    "type": "object",
                    "description": "Only send during these hours: `start` and `end` as `HH:MM` local time, `time_zone` (IANA, like `America/New_York`), `days` (`mon` to `sun`, every day when absent). `end` must be after `start`; no overnight hours yet. What starts it still starts it at any time; a message due outside the hours waits until they open. Used instead of quiet hours. Absent or null: quiet hours, unless quiet_hours is false (then any time)."
                  },
                  "quiet_hours": {
                    "type": "boolean",
                    "description": "Applies when there are no send_hours; with send_hours, those are used instead. On unless false: each message waits for 8 AM to 9 PM in its recipient's own time (tighter where their state requires it). A reply to somebody who wrote in the last hour still goes. Absent on an update keeps what it has."
                  },
                  "steps": {
                    "type": "array",
                    "description": "Up to 40. A `send` step can be an A/B/C test: `variants`, 2 or 3 of `{letter, body, weight}` (letters A to C with an A, weights adding up to 100, or none for an even split) instead of `body`. A person is given a version at the first step with versions, by its weights, and keeps it for every later one (A where a step has none): a later step follows that split, and may only have that step's letters. `fresh_results: true` starts a step's comparison from now."
                  }
                },
                "required": [
                  "name",
                  "trigger_kind",
                  "steps"
                ]
              },
              "example": {
                "name": "Cart reminder",
                "trigger_kind": "tag_added",
                "trigger_tag": "cart-abandoned",
                "stop_on_reply": true,
                "send_hours": {
                  "start": "08:00",
                  "end": "18:00",
                  "time_zone": "America/New_York",
                  "days": [
                    "mon",
                    "tue",
                    "wed",
                    "thu",
                    "fri"
                  ]
                },
                "steps": [
                  {
                    "kind": "send",
                    "body": "Hi {{ contact.first_name | default: \"there\" }}, your {{ contact.custom.product }} ({{ contact.custom.amount }}) is still waiting: {{ contact.custom.checkout_link }}"
                  },
                  {
                    "kind": "wait",
                    "wait_seconds": 86400
                  },
                  {
                    "kind": "condition",
                    "condition_kind": "not_replied_since_last"
                  },
                  {
                    "kind": "send",
                    "body": "Still thinking it over, {{ contact.first_name | default: \"there\" }}? Your cart is saved: {{ contact.custom.checkout_link }}"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Create an automation, from a one-message reply to a multi-step sequence. It starts switched off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "enabled": false,
                  "review_status": "not_needed"
                }
              }
            }
          },
          "400": {
            "description": "A missing trigger detail, an empty send, a wait over a month, an unknown condition, or sending hours that end before they start, have no days, or name an unknown zone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The list or the number is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}": {
      "post": {
        "operationId": "postV1ProjectsByIdAutomationsByAutomationId",
        "summary": "Replace an automation's settings and steps.",
        "description": "Replace an automation's settings and steps.\n\nTakes the same body as creating one, with the same checks. `stop_on_reply`, `send_hours`, `skip_if_wrote_within_days` and `exit_tags` absent keep what it has, and `send_hours: null` and `skip_if_wrote_within_days: null` clear them; `number_id` absent means Auto (or every number), not \"unchanged\". Saving clears `number_note`. People partway through keep their step number (their position in `steps`, counted from the top): the step now at that number is what they do next, so a step added or removed above them changes it, and one whose number no longer exists is finished, `done`, once its last message has gone. A message already on its way is not recalled. Steps sent unchanged are left as they are. Changing or clearing `send_hours` looks again at everybody waiting for the old hours. Editing a switched-on automation into a list or tag automation gets the same review as switching one on. A send step that leaves `variants` out keeps the versions of the tested step with the same text, wherever it has moved; changing a tested step's text without `variants` is 409, and `variants: []` goes back to one text. A text already waiting to go keeps the words it was written in, unless `reword_queued: true`: then each queued text of a step whose words changed that would now say something else is written again in the new words within a few minutes, behind what its number already has waiting, and the answer's `queued_reworded` says how many. The old text is called off, so it fires `message.failed` (\"Cancelled before it was sent.\") and its replacement `message.sent` later. Only while the automation keeps its shape (no step added, removed or moved, and no message taking another step's words): otherwise people's step numbers point at different messages, and nothing is rewritten. A text already handed to a Mac (back in the queue for a retry), held for review, or whose person has moved on to a later step keeps its words. Every change to the steps is kept in the automation's history (`GET …/changes`).",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Welcome",
                "trigger_kind": "tag_added",
                "trigger_tag": "new-customer",
                "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                "stop_on_reply": false,
                "steps": [
                  {
                    "kind": "send",
                    "body": "Welcome aboard!"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replace an automation's settings and steps.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "enabled": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1ProjectsByIdAutomationsByAutomationId",
        "summary": "Delete an automation, and end everybody partway through it.",
        "description": "Delete an automation, and end everybody partway through it.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          }
        ],
        "responses": {
          "200": {
            "description": "Delete an automation, and end everybody partway through it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/effect": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationsByAutomationIdEffect",
        "summary": "Preview what switching it on would do.",
        "description": "Preview what switching it on would do.\n\nHow many people are already on the list or carry the tag, how many of them are first contacts and how long those take, and whether it will wait for review. `lines` is how many numbers it sends from: one when pinned, every usable one on Auto, 0 when none can take it. `first_contact_per_day` is the pace across all of them: with sending hours, what fits in them at the first-contact spacing. `first_contact_days` counts calendar days.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          }
        ],
        "responses": {
          "200": {
            "description": "Preview what switching it on would do.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "already_there": 400,
                  "first_contact": 380,
                  "first_contact_days": 4,
                  "first_contact_per_day": 100,
                  "lines": 2,
                  "needs_review": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/enabled": {
      "post": {
        "operationId": "postV1ProjectsByIdAutomationsByAutomationIdEnabled",
        "summary": "Switch an automation on or off.",
        "description": "Switch an automation on or off.\n\nOff ends every run in progress. On, a list or tag automation from a project that has never had one approved is held as `awaiting_review` until a person at Miss Blue reads it. `include_existing` also starts it for everybody already on the list or carrying the tag, which messages them: preview with the effect first.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": ""
                  },
                  "include_existing": {
                    "type": "boolean",
                    "description": "Start it for the people already there too. Off by default."
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true,
                "include_existing": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Switch an automation on or off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "enabled": true,
                  "review_status": "awaiting_review"
                }
              }
            }
          },
          "400": {
            "description": "It has no steps.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/runs": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationsByAutomationIdRuns",
        "summary": "Who is in an automation, how far each person got, and when they hear next.",
        "description": "Who is in an automation, how far each person got, and when they hear next.\n\nNewest first, a page at a time; `order=next` puts the running people whose next message is soonest first. `total` is how many there are in all, so you know when you have them. `waiting_for` says what a person's next message waits for: `quiet_hours`, `sending_hours`, `paced`, `daily_limit`, `line_unavailable`, `retry`, `waiting`, `wait_step`, or null when it is going now; `next_message_at` is when. `time_zone` and `state` come from their number, to show it in their time.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "`running`, `done`, `stopped` or `failed`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "`next` (soonest next message first) or `newest` (the default).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 500, 100 by default.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "How many to skip, for the next page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Who is in an automation, how far each person got, and when they hear next.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 240,
                  "data": [
                    {
                      "run_id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                      "handle": "+15551230002",
                      "status": "running",
                      "reason": null,
                      "step_position": 20,
                      "due_at": "2026-09-28T14:00:00Z",
                      "deferred_until": "2026-09-28T14:00:00Z",
                      "waiting_for": "quiet_hours",
                      "next_message_at": "2026-09-28T14:00:00Z",
                      "time_zone": "America/Denver",
                      "state": "CO",
                      "started_at": "2026-09-26T14:00:00Z",
                      "finished_at": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/stats": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationsByAutomationIdStats",
        "summary": "How an automation is doing, step by step: sent, delivered, read, replied and opted out.",
        "description": "How an automation is doing, step by step: sent, delivered, read, replied and opted out.\n\nCounts the messages it sent in the range, by step (numbered as the editor numbers them), and how the people who started in the range ended. `sent`, `delivered` and `read` count messages. `read` is a floor: iMessage has no separate open, a read receipt is the open, and only people with read receipts on send one, so it has no rate. `replied` and `opted_out` count people, once each however many texts they sent. A reply counts for a step when that step's message was the latest thing sent to them in the conversation before they wrote (by their phone's time, with a minute's allowance), within 7 days; after a message from your team or an announcement it counts for neither. `opted_out` counts replies that opted them out, when the project acts on STOP. An automation older than the record of which step sent each message is counted from `recorded_since`, runs included, and `from_recorded_since` says the range was moved. `reached` (delivered, or sent as a text message, which reports no delivery, or answered) is the denominator of `reply_rate` and `opt_out_rate`. `variants` are one row per version of an A/B/C test, with the same counts. `test`, on a step with versions, puts them side by side with each one's text and weight and a verdict: `not_enough` until every version has reached 30 people, then the reply rate (two-proportion z-test at 95%) and, separately, the opt-out rate (Fisher's exact test at 95%, corrected for the number of versions), in one sentence (`summary`), and `best` when one is clearly ahead on replies without clearly more opt-outs. A later step has `follows_step`: it follows that step's split. `periods`, on a step whose words changed in the range, splits its counts at each change, oldest first: `from` the change (`null` before the first), `to` the next (`null` for now), what the step said then (`text`, and `versions` with their weights) and the same counts, by version too. A message is placed by when it was written, which is when its words were decided. Empty for a step whose words have not changed since changes were kept.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "RFC 3339, or a date for midnight UTC. Absent: since it began.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Up to, not including. Absent: now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "variant",
            "in": "query",
            "required": false,
            "description": "Only this version of each step's text, like `A`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "How an automation is doing, step by step: sent, delivered, read, replied and opted out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "automation_id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "from": "2026-08-28T00:00:00Z",
                  "to": "2026-09-27T00:00:00Z",
                  "recorded_since": null,
                  "from_recorded_since": false,
                  "variant": null,
                  "reply_window_days": 7,
                  "totals": {
                    "sent": 118,
                    "delivered": 104,
                    "read": 61,
                    "replied": 19,
                    "opted_out": 2,
                    "failed": 3,
                    "cancelled": 6,
                    "pending": 0,
                    "reached": 105,
                    "reply_rate": 0.181,
                    "opt_out_rate": 0.019
                  },
                  "steps": [
                    {
                      "step": 1,
                      "text": "Hi {{first_name}}, still looking?",
                      "in_flow": true,
                      "sent": 40,
                      "delivered": 37,
                      "read": 22,
                      "replied": 11,
                      "opted_out": 0,
                      "failed": 1,
                      "cancelled": 0,
                      "pending": 0,
                      "reached": 37,
                      "reply_rate": 0.2973,
                      "opt_out_rate": 0,
                      "variants": [],
                      "test": null
                    },
                    {
                      "step": 4,
                      "text": "Thanks for coming by today!",
                      "in_flow": true,
                      "sent": 112,
                      "delivered": 108,
                      "read": 60,
                      "replied": 22,
                      "opted_out": 2,
                      "failed": 0,
                      "cancelled": 0,
                      "pending": 0,
                      "reached": 108,
                      "reply_rate": 0.2037,
                      "opt_out_rate": 0.0185,
                      "variants": [
                        {
                          "variant": "A",
                          "sent": 60,
                          "replied": 7
                        },
                        {
                          "variant": "B",
                          "sent": 52,
                          "replied": 15
                        }
                      ],
                      "test": {
                        "since": null,
                        "follows_step": null,
                        "versions": [
                          {
                            "variant": "A",
                            "text": "Thanks for coming by today!",
                            "weight": 50,
                            "sent": 60,
                            "delivered": 58,
                            "read": 31,
                            "replied": 7,
                            "reply_rate": 0.1207,
                            "opted_out": 1,
                            "opt_out_rate": 0.0172,
                            "reached": 58,
                            "pending": 0
                          },
                          {
                            "variant": "B",
                            "text": "Want a second look at the unit?",
                            "weight": 50,
                            "sent": 52,
                            "delivered": 50,
                            "read": 29,
                            "replied": 15,
                            "reply_rate": 0.3,
                            "opted_out": 1,
                            "opt_out_rate": 0.02,
                            "reached": 50,
                            "pending": 0
                          }
                        ],
                        "verdict": {
                          "status": "ready",
                          "summary": "B gets more replies (30% vs 12%), and opt-outs are about the same.",
                          "minimum_per_version": 30,
                          "more_per_version": null,
                          "replies": {
                            "leader": "B",
                            "leader_rate": 0.3,
                            "against": "A",
                            "against_rate": 0.1207,
                            "z": 2.31,
                            "p": null,
                            "clear": true
                          },
                          "opt_outs": {
                            "leader": "B",
                            "leader_rate": 0.02,
                            "against": "A",
                            "against_rate": 0.0172,
                            "z": 0.11,
                            "p": 1,
                            "clear": false
                          },
                          "best": "B"
                        }
                      }
                    }
                  ],
                  "variants": [],
                  "runs": {
                    "started": 40,
                    "in_progress": 9,
                    "finished": 31,
                    "outcomes": {
                      "completed": 12,
                      "replied": 15,
                      "stopped": 2,
                      "number_left": 0,
                      "gave_up": 0,
                      "failed": 2,
                      "unknown": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "An unreadable `from` or `to`, `from` not before `to`, or a variant that is not one to eight letters or digits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The automation is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/changes": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationsByAutomationIdChanges",
        "summary": "An automation's history: every change to what its steps say, newest first.",
        "description": "An automation's history: every change to what its steps say, newest first.\n\nOne entry per change to the steps' kinds, words, waits, conditions or versions, and per Start fresh results: `created`, `edited` (a save that changed them; one that changed only the name or the hours is not kept), `version_chosen` (Use this one) or `weights_changed`. Each says when, who (`changed_by.kind`: `member`, `staff` for Miss Blue support acting for a member, `api_key`, or `unknown`), and every step that differs, `before` and `after` (`null` for a step added or removed). `changed_steps` are the steps whose words changed: where the results' `periods` split. New weights, waits and conditions change no words. Kept from 2026-10-15.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 100, 50 by default.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An automation's history: every change to what its steps say, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "5b0f2c1e-7a3d-4e9b-9c2f-0d8e6a4b1c37",
                      "kind": "edited",
                      "changed_at": "2026-10-15T16:37:24Z",
                      "changed_by": {
                        "kind": "member",
                        "name": "Dana"
                      },
                      "steps": [
                        {
                          "step": 1,
                          "before": {
                            "step": 1,
                            "kind": "send",
                            "body": "Saw you started your assessment.",
                            "wait_seconds": null,
                            "condition_kind": null,
                            "variants": []
                          },
                          "after": {
                            "step": 1,
                            "kind": "send",
                            "body": "Your plan options are ready whenever you are.",
                            "wait_seconds": null,
                            "condition_kind": null,
                            "variants": []
                          },
                          "words_changed": true
                        }
                      ],
                      "changed_steps": [
                        1
                      ],
                      "fresh_results_steps": [],
                      "queued_reworded": 12
                    }
                  ],
                  "total": 1
                }
              }
            }
          },
          "400": {
            "description": "A `limit` that is not 1 to 100.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The automation is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/replies": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationsByAutomationIdReplies",
        "summary": "Which texts got the replies: every reply, the message it answered, and a link to the conversation.",
        "description": "Which texts got the replies: every reply, the message it answered, and a link to the conversation.\n\nNewest first, a page at a time. `total` and `people` count every page; pass `next_cursor` as `cursor` for the next, and it is null on the last. `opted_out` marks a reply that opted them out; `said_stop` one that said STOP to a project that handles opt-outs itself, which is not counted as an opt-out.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "step",
            "in": "query",
            "required": false,
            "description": "Only replies to this step's messages: 1 to 40.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Only replies to messages sent from this time, as for stats.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "And before this one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "variant",
            "in": "query",
            "required": false,
            "description": "Only replies to this version of the text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the page before.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 100, 25 by default.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Which texts got the replies: every reply, the message it answered, and a link to the conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "4b1e2c3d-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
                      "step": 4,
                      "variant": null,
                      "handle": "+15551230002",
                      "contact_name": "Dana Whitfield",
                      "text": "Sorry, just saw this. Is Saturday open?",
                      "received_at": "2026-09-26T15:02:11Z",
                      "opted_out": false,
                      "said_stop": false,
                      "answered": {
                        "message_id": "9a0c1b2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
                        "text": "Last one from us, Dana.",
                        "sent_at": "2026-09-26T14:00:03Z"
                      },
                      "conversation": {
                        "number_id": "8f14e45f-ceea-467a-9a1b-1b1e5f9e2c3d",
                        "chat_id": "iMessage;-;+15551230002",
                        "link_code": "k7Qm3xT9pa",
                        "link": "/c/k7Qm3xT9pa"
                      }
                    }
                  ],
                  "total": 23,
                  "people": 19,
                  "next_cursor": "1790000000123456_4b1e2c3d-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
                  "variants": []
                }
              }
            }
          },
          "400": {
            "description": "A step that is not 1 to 40, a limit out of range, a cursor this route did not write, or an unreadable date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The automation is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/runs/stop": {
      "post": {
        "operationId": "postV1ProjectsByIdAutomationsByAutomationIdRunsStop",
        "summary": "Take one person out of one automation. Everybody else carries on.",
        "description": "Take one person out of one automation. Everybody else carries on.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "handle": "+15551230002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Take one person out of one automation. Everybody else carries on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "stopped": 1
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/steps/{step}/variants/{letter}/use": {
      "post": {
        "operationId": "postV1ProjectsByIdAutomationsByAutomationIdStepsByStepVariantsByLetterUse",
        "summary": "Use this one: a step's version for everybody who reaches it from now on.",
        "description": "Use this one: a step's version for everybody who reaches it from now on.\n\nSets that version's weight to 100 and the others' to 0. Everybody who reaches the step from now on gets it, including people given another version earlier (their own version still decides the other steps). Messages already sent stay as they were, and every version's text and results stay. With `?reword_queued=true`, texts already waiting to go in another version are written again in this one (`queued_reworded` in the answer). `step` is the step's number from 1, as the results number it, or its id. Answers with the automation.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "step",
            "in": "path",
            "required": true,
            "description": "The step of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "4"
          },
          {
            "name": "letter",
            "in": "path",
            "required": true,
            "description": "The letter of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "B"
          }
        ],
        "responses": {
          "200": {
            "description": "Use this one: a step's version for everybody who reaches it from now on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "steps": [
                    {
                      "kind": "send",
                      "body": "Thanks for coming by today!",
                      "variants": [
                        {
                          "letter": "A",
                          "body": "Thanks for coming by today!",
                          "weight": 0
                        },
                        {
                          "letter": "B",
                          "body": "Want a second look at the unit?",
                          "weight": 100
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The step is not a message, or has one text.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such step or version, or the automation is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automations/{automation_id}/steps/{step}/weights": {
      "put": {
        "operationId": "putV1ProjectsByIdAutomationsByAutomationIdStepsByStepWeights",
        "summary": "Change the weights of the first step with versions, for people given one from now on.",
        "description": "Change the weights of the first step with versions, for people given one from now on.\n\nEvery version of the step, in letter order or by letter, adding up to 100. Anybody already given a version keeps it. Only the first step with versions has weights: a later step follows its split, and is 400. Answers with the automation.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "automation_id",
            "in": "path",
            "required": true,
            "description": "The automation_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21"
          },
          {
            "name": "step",
            "in": "path",
            "required": true,
            "description": "The step of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "4"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weights": {
                    "type": "string",
                    "description": "`[70, 30]`, or `{\"A\": 70, \"B\": 30}`."
                  }
                },
                "required": [
                  "weights"
                ]
              },
              "example": {
                "weights": [
                  70,
                  30
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Change the weights of the first step with versions, for people given one from now on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                  "steps": [
                    {
                      "kind": "send",
                      "variants": [
                        {
                          "letter": "A",
                          "weight": 70
                        },
                        {
                          "letter": "B",
                          "weight": 30
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Weights that do not add up to 100, miss a version, or name one the step does not have; a step that is not a message with versions; a later step, which follows the first step's split.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such step, or the automation is not this project's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automation-runs": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationRuns",
        "summary": "Which automations are messaging one person right now.",
        "description": "Which automations are messaging one person right now.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "description": "Phone number or email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Which automations are messaging one person right now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "run_id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                      "automation_id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                      "automation_name": "Welcome",
                      "due_at": "2026-09-28T14:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/automation-status": {
      "get": {
        "operationId": "getV1ProjectsByIdAutomationStatus",
        "summary": "One person and your automations: what they are in, what they were in and why it ended, and what would start for them now.",
        "description": "One person and your automations: what they are in, what they were in and why it ended, and what would start for them now.\n\n`free` is whether nothing project-wide keeps an automation from starting for them: with one automation at a time on, that they are in none. `automations` lists every automation with `would_start` and, when it would not, the `reason`: switched off, waiting for review, in it now, been through it once, an exit tag, partway through another (one automation at a time), or wrote in recently. `ended` is the latest 20, newest first, with the outcome and reason `automation.run.finished` sent. Somebody an automation left out was never started, so there is no run and no webhook for them; this is where it says why.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "handle",
            "in": "query",
            "required": true,
            "description": "Phone number or email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One person and your automations: what they are in, what they were in and why it ended, and what would start for them now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "handle": "+15555550100",
                  "one_automation_at_a_time": true,
                  "free": false,
                  "running": [
                    {
                      "run_id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                      "automation_id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                      "automation_name": "Cart reminder",
                      "due_at": "2026-09-28T14:00:00Z",
                      "waiting_for": "wait",
                      "next_message_at": "2026-09-28T14:00:00Z"
                    }
                  ],
                  "ended": [
                    {
                      "run_id": "5d6e7f80-9a1b-4c2d-8e3f-4a5b6c7d8e9f",
                      "automation_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
                      "automation_name": "Welcome",
                      "outcome": "stopped",
                      "reason": "they were tagged “paid”",
                      "started_at": "2026-09-20T15:00:00Z",
                      "finished_at": "2026-09-21T09:30:00Z"
                    }
                  ],
                  "automations": [
                    {
                      "automation_id": "e7d1c3a9-2b4f-4e6a-8c0d-1f3a5b7c9e21",
                      "automation_name": "Cart reminder",
                      "trigger_kind": "tag_added",
                      "would_start": false,
                      "reason": "they are in it now"
                    },
                    {
                      "automation_id": "c3d4e5f6-a7b8-4c9d-8e0f-2a3b4c5d6e7f",
                      "automation_name": "Checkout follow-up",
                      "trigger_kind": "tag_added",
                      "would_start": false,
                      "reason": "they are partway through “Cart reminder”"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A handle that is not a phone number or an email address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/lists": {
      "get": {
        "operationId": "getV1ProjectsByIdLists",
        "summary": "Lists in this project, and how many people are on each.",
        "description": "Lists in this project, and how many people are on each.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "Lists in this project, and how many people are on each.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "id": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68",
                      "name": "Spring customers",
                      "description": null,
                      "members": 118,
                      "created_at": "2026-09-01T10:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdLists",
        "summary": "Make a list.",
        "description": "Make a list.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Spring customers",
                "description": "Bought in March or April"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Make a list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68",
                  "name": "Spring customers",
                  "description": "Bought in March or April",
                  "members": 0,
                  "created_at": "2026-09-01T10:00:00Z"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This project already has a list with that name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/lists/{list_id}": {
      "patch": {
        "operationId": "patchV1ProjectsByIdListsByListId",
        "summary": "Rename a list or change its description.",
        "description": "Rename a list or change its description.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "list_id",
            "in": "path",
            "required": true,
            "description": "The list_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Spring 2026 customers"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rename a list or change its description.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "saved": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteV1ProjectsByIdListsByListId",
        "summary": "Delete a list. The people on it are not deleted.",
        "description": "Delete a list. The people on it are not deleted.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "list_id",
            "in": "path",
            "required": true,
            "description": "The list_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
          }
        ],
        "responses": {
          "200": {
            "description": "Delete a list. The people on it are not deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/lists/{list_id}/members": {
      "get": {
        "operationId": "getV1ProjectsByIdListsByListIdMembers",
        "summary": "Who is on a list, newest first. Up to 500.",
        "description": "Who is on a list, newest first. Up to 500.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "list_id",
            "in": "path",
            "required": true,
            "description": "The list_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
          }
        ],
        "responses": {
          "200": {
            "description": "Who is on a list, newest first. Up to 500.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "handle": "+15551230002",
                      "source": "api",
                      "added_at": "2026-09-26T13:50:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdListsByListIdMembers",
        "summary": "Add people to a list.",
        "description": "Add people to a list.\n\nIf an automation starts on this list, each person who was not already on it starts that automation, which messages them. `started` says how many. Up to 5,000 per call.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "list_id",
            "in": "path",
            "required": true,
            "description": "The list_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "handles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Phone numbers or emails."
                  },
                  "source": {
                    "type": "string",
                    "description": "`manual`, `import` or `api`. `api` by default."
                  }
                },
                "required": [
                  "handles"
                ]
              },
              "example": {
                "handles": [
                  "+15551230002",
                  "+15551230003"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Add people to a list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "added": 2,
                  "already": 0,
                  "started": 0
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/lists/{list_id}/remove": {
      "post": {
        "operationId": "postV1ProjectsByIdListsByListIdRemove",
        "summary": "Take somebody off a list.",
        "description": "Take somebody off a list.\n\nAn automation they are already in carries on. To stop messaging somebody, record an opt-out or stop their run.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "list_id",
            "in": "path",
            "required": true,
            "description": "The list_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "handle": "+15551230002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Take somebody off a list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "removed": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/tags": {
      "get": {
        "operationId": "getV1ProjectsByIdTags",
        "summary": "Tags in this project, and how many people carry each.",
        "description": "Tags in this project, and how many people carry each.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags in this project, and how many people carry each.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "tag": "vip",
                      "people": 14
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdTags",
        "summary": "Tag people.",
        "description": "Tag people.\n\nTags are stored lowercase with single spaces, so `VIP ` and `vip` are one tag. If an automation starts on this tag, each newly tagged person starts it, which messages them, unless the automation leaves them out (an exit tag, writing in recently, or one automation at a time). An automation that lists this tag as an exit tag ends for each newly tagged person first. `started` counts the runs started.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "tag": "vip",
                "handles": [
                  "+15551230002"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag people.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "added": 1,
                  "already": 0,
                  "started": 0
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/tags/remove": {
      "post": {
        "operationId": "postV1ProjectsByIdTagsRemove",
        "summary": "Take a tag off somebody.",
        "description": "Take a tag off somebody.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "tag": "vip",
                "handle": "+15551230002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Take a tag off somebody.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "removed": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/audience/{handle}": {
      "get": {
        "operationId": "getV1ProjectsByIdAudienceByHandle",
        "summary": "The lists one person is on and the tags they carry.",
        "description": "The lists one person is on and the tags they carry.\n\n`tag_details` says when each tag was added and how: `manual`, `import`, `api` or `automation`. Tagging somebody again keeps the time it was added; taking a tag off and adding it back starts a new one.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "handle",
            "in": "path",
            "required": true,
            "description": "The handle of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "+15551230002"
          }
        ],
        "responses": {
          "200": {
            "description": "The lists one person is on and the tags they carry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "lists": [
                    {
                      "id": "c2a7e1d4-6b3f-4a8e-9d1c-5e7f0a2b4c68",
                      "name": "Spring customers"
                    }
                  ],
                  "tags": [
                    "vip"
                  ],
                  "tag_details": [
                    {
                      "tag": "vip",
                      "added_at": "2026-10-14T19:05:12.481234Z",
                      "source": "api"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/templates/variables": {
      "get": {
        "operationId": "getV1ProjectsByIdTemplatesVariables",
        "summary": "The variables a message body can use in this project, built-in and custom.",
        "description": "The variables a message body can use in this project, built-in and custom.\n\nThe built-in ones (`kind: built_in`), then every custom field name this project's contacts carry (`kind: custom`), each once, sorted, up to 200. `name` is what to write between the braces: a field whose name is not a plain word is written with brackets, like contact.custom[\"Checkout link\"]. A field is listed because some contact has it; the preview says how many of the people you are sending to have it empty.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "The variables a message body can use in this project, built-in and custom.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": [
                    {
                      "name": "contact.name",
                      "about": "Their name from your contacts",
                      "kind": "built_in"
                    },
                    {
                      "name": "contact.first_name",
                      "about": "The first word of their name",
                      "kind": "built_in"
                    },
                    {
                      "name": "contact.last_name",
                      "about": "The rest of their name, after the first word",
                      "kind": "built_in"
                    },
                    {
                      "name": "contact.phone_number",
                      "about": "The number this is going to",
                      "kind": "built_in"
                    },
                    {
                      "name": "system.24hr",
                      "about": "The time it sends, like 14:30",
                      "kind": "built_in"
                    },
                    {
                      "name": "contact.custom[\"Checkout link\"]",
                      "about": "A custom field on their contact",
                      "kind": "custom",
                      "field": "Checkout link"
                    },
                    {
                      "name": "contact.custom.product",
                      "about": "A custom field on their contact",
                      "kind": "custom",
                      "field": "product"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/templates/preview": {
      "post": {
        "operationId": "postV1ProjectsByIdTemplatesPreview",
        "summary": "Show what a body will say for one person, and who it would leave a gap for.",
        "description": "Show what a body will say for one person, and who it would leave a gap for.\n\nThe same renderer the sender uses. `text` is the body filled in for `handle` (or the first of `handles`). `missing` counts, for each variable, how many of `handles` have nothing for it, up to 500 people. A body that will not parse comes back with `text: null` and one line in `error`.",
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "Liquid."
                  },
                  "handle": {
                    "type": "string",
                    "description": "Render it as this person would read it."
                  },
                  "handles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Count empty variables across these people."
                  }
                },
                "required": [
                  "body"
                ]
              },
              "example": {
                "body": "Hi {{ contact.first_name | default: \"there\" }}, your {{ contact.custom.product | default: \"order\" }} is ready.",
                "handles": [
                  "+15551230002",
                  "+15551230003"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Show what a body will say for one person, and who it would leave a gap for.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "text": "Hi Ari, your Blue hoodie is ready.",
                  "error": null,
                  "variables": [
                    "contact.custom.product",
                    "contact.first_name"
                  ],
                  "unresolved": [],
                  "missing": [
                    {
                      "name": "contact.custom.product",
                      "people": 1
                    }
                  ],
                  "counted": 2
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/webhooks": {
      "get": {
        "operationId": "getV1ProjectsByIdWebhooks",
        "summary": "List the endpoints this project sends events to.",
        "description": "List the endpoints this project sends events to.\n\nYour key may manage its own project's endpoints and no others. Another project's id returns 404 rather than 403, because whether it exists is not something a stranger should learn.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "responses": {
          "200": {
            "description": "List the endpoints this project sends events to.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "b71c0d92-5a83-4e1f-9d70-6c2e8a4b3f15",
                      "project_id": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60",
                      "url": "https://example.com/hooks/miss-blue",
                      "events": [
                        "message.received",
                        "group.updated",
                        "contact.synced"
                      ],
                      "enabled": true,
                      "disabled_reason": null,
                      "failure_streak": 0,
                      "created_at": "2026-08-28T10:04:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postV1ProjectsByIdWebhooks",
        "summary": "Register where events should go.",
        "description": "Register where events should go.\n\nAnswers “tell me about everything on this line”: messages, typing, group changes, location requests, number profile changes, contacts, syncs, provisioning, calls, and campaigns — automation runs starting and finishing (automation.run.finished carries an outcome: completed, replied, stopped, number_left, gave_up or failed), announcements from review to finish with their counts, and scheduled messages sent, failed or cancelled. Every message event carries data.message.source: null for inbound, otherwise { type: automation | announcement | scheduled | api | console | mac } with the automation_id, run_id and step, the announcement_id, or the scheduled_id it came from. Every message event also carries data.conversation: { url, link_code, chat_id }, the conversation's short link (url is https://missblue.dev/c/<link_code>), so a handler can link to it without another call. The link opens only for somebody who may read the conversation. Message events put their fields under data.message on every line, Twilio included; group, location, number profile, contact sync and call events put their object under data.resource, beside data.number; contact.updated, number.provisioned, typing.indicator and campaign events put their fields directly under data. The call.started, answered, ended and failed events are sent for FaceTime Audio calls only for now; a phone call sends call.recording.ready when it was recorded. Omit events to receive all supported events, including ones added later; naming events delivers only those. The signing secret is returned only once. Each delivery is signed and retried with backoff; an endpoint that keeps failing is switched off and the reason is exposed.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Must be https, and must not resolve to a private address."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "message.received, message.sent, message.delivered, message.failed, typing.indicator, group.created, group.updated, location.requested, number.profile.updated, contact.updated, contact.synced, number.provisioned, call.started, call.answered, call.ended, call.failed, call.recording.ready, automation.run.started, automation.run.finished, automation.review, announcement.awaiting_review, announcement.approved, announcement.rejected, announcement.started, announcement.finished, announcement.cancelled, scheduled.sent, scheduled.failed, or scheduled.cancelled. Omit for all."
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://example.com/hooks/miss-blue",
                "events": [
                  "message.received",
                  "message.delivered",
                  "group.updated",
                  "contact.synced"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Register where events should go.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "b71c0d92-5a83-4e1f-9d70-6c2e8a4b3f15",
                  "project_id": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60",
                  "url": "https://example.com/hooks/miss-blue",
                  "events": [
                    "message.received",
                    "message.delivered",
                    "group.updated",
                    "contact.synced"
                  ],
                  "enabled": true,
                  "disabled_reason": null,
                  "failure_streak": 0,
                  "created_at": "2026-08-28T10:04:00Z",
                  "secret": "whsec_…"
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/settings/one-automation-at-a-time": {
      "post": {
        "operationId": "postV1ProjectsByIdSettingsOneAutomationAtATime",
        "summary": "Keep anybody partway through one automation out of the others until it finishes.",
        "description": "Keep anybody partway through one automation out of the others until it finishes.\n\nOn, a list or tag automation doesn't start for somebody who is in another of this project's: the one they are in finishes, and the other doesn't start for them then or later. An automation somebody starts by texting you (a keyword, a first message, any message) still answers them. Applies from the next start; nobody already in two is taken out of either. Off for every project until changed. Project admins and a key belonging to this project may change it; the project's detail says what it is.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "one_automation_at_a_time": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Keep anybody partway through one automation out of the others until it finishes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "project_id": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60",
                  "one_automation_at_a_time": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/settings/auto-typing-indicator": {
      "post": {
        "operationId": "postV1ProjectsByIdSettingsAutoTypingIndicator",
        "summary": "Automatically start typing after an inbound iMessage.",
        "description": "Automatically start typing after an inbound iMessage.\n\nWhen enabled, each inbound one-to-one iMessage immediately starts a bounded native typing indicator while your responder prepares a reply. SMS, RCS, group chats, action messages, and hidden messages never trigger it. Project admins and a key belonging to this project may change the setting.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "auto_typing_indicator": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Automatically start typing after an inbound iMessage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "project_id": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60",
                  "auto_typing_indicator": true
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/webhooks/{endpoint_id}/enabled": {
      "post": {
        "operationId": "postV1ProjectsByIdWebhooksByEndpointIdEnabled",
        "summary": "Switch an endpoint on or off without deleting it.",
        "description": "Switch an endpoint on or off without deleting it.\n\nAn endpoint that keeps failing is switched off by us for the same reason you would switch one off during a deploy: to stop hammering something that is not listening. Turning it back on is this call.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "The endpoint_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "b71c0d92-5a83-4e1f-9d70-6c2e8a4b3f15"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": ""
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Switch an endpoint on or off without deleting it."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/webhooks/{endpoint_id}/deliveries": {
      "get": {
        "operationId": "getV1ProjectsByIdWebhooksByEndpointIdDeliveries",
        "summary": "What we tried to send you, and what came back.",
        "description": "What we tried to send you, and what came back.\n\nEvery attempt, its response status, and how many retries it took. This is the first place to look when your endpoint is up but you are not seeing events — it distinguishes “we never sent it” from “we sent it and your server said 500”.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "The endpoint_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "b71c0d92-5a83-4e1f-9d70-6c2e8a4b3f15"
          }
        ],
        "responses": {
          "200": {
            "description": "What we tried to send you, and what came back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "total": 1,
                  "data": [
                    {
                      "id": "d91d6b51-8639-43ad-8909-fd0439ecb80e",
                      "event": "group.updated",
                      "status": "delivered",
                      "attempts": 1,
                      "response_status": 204,
                      "response_body": "",
                      "created_at": "2026-08-28T10:04:00Z",
                      "delivered_at": "2026-08-28T10:04:01Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{id}/webhooks/{endpoint_id}": {
      "delete": {
        "operationId": "deleteV1ProjectsByIdWebhooksByEndpointId",
        "summary": "Stop sending to an endpoint.",
        "description": "Stop sending to an endpoint.",
        "tags": [
          "Delivery"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "3c9a1f77-2d64-4b0e-8a5c-7e1d4b2f9a60"
          },
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "description": "The endpoint_id of the resource.",
            "schema": {
              "type": "string"
            },
            "example": "b71c0d92-5a83-4e1f-9d70-6c2e8a4b3f15"
          }
        ],
        "responses": {
          "204": {
            "description": "Stop sending to an endpoint."
          },
          "401": {
            "description": "The key is missing, malformed, or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}