{
  "openapi": "3.1.0",
  "info": {
    "title": "iBlueText API",
    "version": "1.1.0",
    "description": "iMessage (blue), SMS/RCS and WhatsApp messaging, campaigns, iPhone lookup, WhatsApp lookup (beta), phone verification (OTP), contacts with lead score, conversations, sequences, lines and webhooks. Auth: send your API key as `Authorization: Bearer <key>` (get one at https://ibluetext.com/portal/integrations.php). A key can be limited to one line. 402 responses mean a human must pay (see `PaymentRequired`). Docs for humans and agents: https://ibluetext.com/docs/ and https://ibluetext.com/llms.txt",
    "contact": {
      "name": "iBlueText",
      "url": "https://ibluetext.com",
      "email": "support@ibluetext.com"
    }
  },
  "servers": [
    {
      "url": "https://ibluetext.com/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/lookup": {
      "post": {
        "summary": "Submit numbers to check if they are on iMessage (iPhone).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "numbers"
                ],
                "properties": {
                  "numbers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "+18135550142",
                      "+17275550199"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Job created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "poll_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      },
      "get": {
        "summary": "Get lookup job status + results.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "completed": {
                      "type": "integer"
                    },
                    "iphones": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "number": {
                            "type": "string"
                          },
                          "imessage": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          },
                          "checked": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/send": {
      "post": {
        "summary": "Queue an iMessage/SMS — text and/or attachments (photo, video, voice memo, contact card .vcf, PDF) — from your active sending line.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "example": "+18135550142"
                  },
                  "body": {
                    "type": "string",
                    "example": "Hi {firstname}!",
                    "description": "Message text. Optional when media is given (except the first message to a new contact)."
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "line_id": {
                    "type": "integer"
                  },
                  "channel": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "imessage",
                      "sms"
                    ],
                    "default": "auto"
                  },
                  "media": {
                    "type": "array",
                    "maxItems": 5,
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "url"
                          ],
                          "properties": {
                            "url": {
                              "type": "string",
                              "format": "uri",
                              "example": "https://example.com/card.vcf"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "base64"
                          ],
                          "properties": {
                            "base64": {
                              "type": "string",
                              "description": "File bytes, base64 (a data: URL is accepted)"
                            },
                            "filename": {
                              "type": "string",
                              "example": "photo.jpg"
                            },
                            "mime": {
                              "type": "string",
                              "example": "image/jpeg"
                            }
                          }
                        }
                      ]
                    }
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "confetti",
                      "balloons",
                      "fireworks",
                      "love",
                      "celebration",
                      "lasers",
                      "echo",
                      "spotlight",
                      "slam",
                      "loud",
                      "gentle",
                      "invisible_ink"
                    ],
                    "description": "iMessage screen/bubble effect. Plays on iPhones; skipped if the message goes by SMS."
                  },
                  "typing": {
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 8
                      }
                    ],
                    "description": "Show the iMessage typing bubble first (true = auto length, or seconds). Existing iMessage conversations only."
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string"
                  },
                  "line_id": {
                    "type": "integer"
                  },
                  "channel": {
                    "type": "string"
                  },
                  "media[]": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "409": {
            "description": "Recipient opted out"
          },
          "400": {
            "description": "Bad input — e.g. unsupported attachment type, file over 5 MB, more than 5 files"
          }
        },
        "description": "Attachments: up to 5 files, 5 MB each. Give each as {\"url\": \"https://…\"} (public https) or {\"base64\": \"…\", \"filename\": \"card.vcf\", \"mime\": \"text/vcard\"}, or upload them as multipart/form-data in media[]. Over iMessage they arrive as normal attachments; over SMS the line's phone sends a picture message (MMS). The first message to a contact you've never texted must include body text with the attachment."
      },
      "get": {
        "summary": "Get message status.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status (queued|sent|delivered|failed)"
          }
        }
      }
    },
    "/verify": {
      "post": {
        "summary": "Start a verification (sends a 6-digit code), or check a code.",
        "description": "To start: POST { phone, name?, email? }. To check: POST ?action=check { id, code }. Server mode uses your Bearer key; browser/widget mode uses a site_key in the body with the request Origin checked.",
        "parameters": [
          {
            "name": "action",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "check"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phone": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "id": {
                    "type": "integer"
                  },
                  "code": {
                    "type": "string"
                  },
                  "site_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Verification queued (start)"
          },
          "200": {
            "description": "Check result { verified, token, phone }"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          }
        }
      }
    },
    "/siteverify": {
      "post": {
        "summary": "Confirm a widget token with your secret key (single-use).",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "secret",
                  "token"
                ],
                "properties": {
                  "secret": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ success, phone, channel, verified_at }"
          }
        }
      }
    },
    "/sites": {
      "get": {
        "summary": "List your verify-widget sites.",
        "responses": {
          "200": {
            "description": "{ sites: [...] }"
          }
        }
      },
      "post": {
        "summary": "Create a verify-widget site (returns site_key + secret + embed).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "domains": {
                    "type": "string"
                  },
                  "theme": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "light",
                      "dark"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{ id, site_key, secret, embed }"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "summary": "Verified contacts (paginate with since), or one contact record with fields, tags, opt-out and lead score (?phone=).",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 200
            }
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Return { contact:{ phone, fields, tags, opted_out, last_activity, lead } }."
          }
        ],
        "responses": {
          "200": {
            "description": "{ contacts: [...], count, next_since }"
          }
        }
      },
      "post": {
        "summary": "Create or update a contact: fields, tags, opt-out.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone"
                ],
                "properties": {
                  "phone": {
                    "type": "string"
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "remove_tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "opted_out": {
                    "type": "boolean",
                    "description": "false re-subscribes; only when the person asked to hear from you again."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contact": {
                      "type": "object",
                      "properties": {
                        "phone": {
                          "type": "string"
                        },
                        "fields": {
                          "type": "object"
                        },
                        "tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "opted_out": {
                          "type": "boolean"
                        },
                        "last_activity": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "lead": {
                          "$ref": "#/components/schemas/Lead"
                        }
                      }
                    },
                    "created": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/balance": {
      "get": {
        "summary": "Wallet balance + per-line daily usage",
        "description": "Returns prepaid credits, free-trial + free-daily lookups remaining, and each line’s new-contact / non-replier usage for today. Poll to warn before $0 or a daily cap.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Balance and usage"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "summary": "Read reply-bot webhook settings: signing secret, account webhook, and every line.",
        "description": "Lets an AI agent wire up a reply bot with only the API key (no dashboard login). Scope: webhook settings only. Events always sent: message.reply, message.optout, whatsapp.reply (WhatsApp inbound), message.test. Opt-in status events (POST {\"status_events\":[...]}): message.sent, message.delivered, message.failed, message.read, reaction.received, line.status, contact.created, call.started, call.ringing, call.ended, call.failed.",
        "operationId": "getWebhooks",
        "responses": {
          "200": {
            "description": "Webhook settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      },
      "post": {
        "summary": "Save the reply-bot webhook URL for the account, all lines, reserved lines, or specific lines — and optionally send a test.",
        "operationId": "setWebhooks",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "https URL of your bot. Empty string clears it."
                  },
                  "lines": {
                    "description": "\"account\" (default), \"all\", \"reserved\", or an array of line_ids",
                    "oneOf": [
                      {
                        "type": "string",
                        "enum": [
                          "account",
                          "all",
                          "reserved"
                        ]
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      }
                    ]
                  },
                  "replies_handled_by_webhook": {
                    "type": "boolean",
                    "default": true,
                    "description": "Turn the built-in AI off for these replies"
                  },
                  "test": {
                    "type": "boolean",
                    "description": "POST a message.test event (from fake +15550100099) after saving"
                  },
                  "rotate_secret": {
                    "type": "boolean",
                    "description": "Generate a new signing secret"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved; returns current settings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "422": {
            "description": "Bad URL (must be public https) or not your line"
          }
        },
        "description": "Events always sent: message.reply, message.optout, whatsapp.reply (WhatsApp inbound), message.test. Opt-in status events (POST {\"status_events\":[...]}): message.sent, message.delivered, message.failed, message.read, reaction.received, line.status, contact.created, call.started, call.ringing, call.ended, call.failed."
      }
    },
    "/react": {
      "post": {
        "summary": "React (tapback) to a message someone sent you — iMessage only, free.",
        "operationId": "react",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reaction"
                ],
                "properties": {
                  "message_id": {
                    "type": "string",
                    "example": "in_3909",
                    "description": "Inbound message id from the webhook or /conversations."
                  },
                  "to": {
                    "type": "string",
                    "example": "+18135550142",
                    "description": "Instead of message_id: react to this contact's latest message."
                  },
                  "reaction": {
                    "type": "string",
                    "enum": [
                      "love",
                      "like",
                      "dislike",
                      "laugh",
                      "emphasize",
                      "question",
                      "-love",
                      "-like",
                      "-dislike",
                      "-laugh",
                      "-emphasize",
                      "-question"
                    ],
                    "description": "Prefix '-' removes it."
                  },
                  "line_id": {
                    "type": "integer",
                    "description": "Optional; defaults to the line the conversation is on."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "example": "queued"
                    },
                    "reaction": {
                      "type": "string"
                    },
                    "message_id": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "poll_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad reaction / message_id"
          },
          "404": {
            "description": "Message not found"
          },
          "422": {
            "description": "Can't react (no iMessage id, or line unknown)"
          }
        }
      },
      "get": {
        "summary": "Reaction status",
        "operationId": "reactStatus",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "queued | sent | failed (with error)"
          }
        }
      }
    },
    "/campaigns": {
      "get": {
        "summary": "List campaigns, or one campaign with stats (?id=).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaigns": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "channel": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "total": {
                            "type": "integer"
                          },
                          "sent": {
                            "type": "integer"
                          },
                          "replies": {
                            "type": "integer"
                          },
                          "created_at": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "campaign": {
                      "$ref": "#/components/schemas/Campaign"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a campaign (iMessage, SMS, auto-detect or WhatsApp), or pause/resume one.",
        "description": "Same engine as the dashboard Campaigns page: opt-outs removed, duplicates removed, every {merge} field must match a recipient field (else 422 and nothing is sent), people you already text stay on their line, new people are spread across lines. channel=auto looks numbers up first (status 'resolving') then sends iMessage or SMS. channel=whatsapp sends from your WhatsApp number(s): each number's warm-up applies (10 new conversations on day 1 rising to 300/day on day 21; the rest wait), and anyone who never replied to your last 3 WhatsApp messages is skipped (see `note`). Pause/resume: send {id, action}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "name",
                      "message",
                      "recipients"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Unique per account."
                      },
                      "message": {
                        "type": "string",
                        "example": "Hey, is this {first_name}?"
                      },
                      "channel": {
                        "type": "string",
                        "enum": [
                          "imessage",
                          "sms",
                          "auto",
                          "whatsapp"
                        ],
                        "default": "imessage"
                      },
                      "recipients": {
                        "oneOf": [
                          {
                            "type": "array",
                            "items": {
                              "oneOf": [
                                {
                                  "type": "object",
                                  "required": [
                                    "phone"
                                  ],
                                  "properties": {
                                    "phone": {
                                      "type": "string"
                                    }
                                  },
                                  "additionalProperties": true
                                },
                                {
                                  "type": "string"
                                }
                              ]
                            }
                          },
                          {
                            "type": "string",
                            "description": "CSV text with a header row (a phone column + any fields), or \"Name,+1…\" / bare numbers one per line."
                          }
                        ]
                      },
                      "line_id": {
                        "oneOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string",
                            "enum": [
                              "all",
                              "reserved"
                            ]
                          }
                        ],
                        "default": "all"
                      },
                      "wa_session_id": {
                        "type": "integer",
                        "description": "WhatsApp: one number; default spreads across your numbers."
                      },
                      "media": {
                        "type": "array",
                        "maxItems": 5,
                        "description": "Optional attachments (photo, video, voice memo, contact card .vcf, PDF; 5 MB each), same as /send.",
                        "items": {
                          "oneOf": [
                            {
                              "type": "object",
                              "required": [
                                "url"
                              ],
                              "properties": {
                                "url": {
                                  "type": "string",
                                  "format": "uri"
                                }
                              }
                            },
                            {
                              "type": "object",
                              "required": [
                                "base64"
                              ],
                              "properties": {
                                "base64": {
                                  "type": "string"
                                },
                                "filename": {
                                  "type": "string"
                                },
                                "mime": {
                                  "type": "string"
                                }
                              }
                            }
                          ]
                        }
                      },
                      "effect": {
                        "type": "string",
                        "description": "Optional iMessage effect, e.g. confetti."
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "id",
                      "action"
                    ],
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "action": {
                        "type": "string",
                        "enum": [
                          "pause",
                          "resume"
                        ]
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "campaign_id": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "channel": {
                      "type": "string"
                    },
                    "skipped": {
                      "type": "integer"
                    },
                    "resolving": {
                      "type": "boolean"
                    },
                    "status_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Paused/resumed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "campaign": {
                      "$ref": "#/components/schemas/Campaign"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Needs a sending line",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            }
          },
          "403": {
            "description": "Key limited to another line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Nothing was sent (bad merge field, duplicate name, no recipients…)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/whatsapp": {
      "post": {
        "summary": "Send a WhatsApp message from your linked or hosted WhatsApp number.",
        "description": "Warm-up: a number can start 10 new conversations in its first 24 hours, rising evenly to 300/day by day 21; over that, the message stays queued with held_until and sends automatically. Replies are never limited. 3 tries: someone who never replied gets at most 3 messages — the 3rd returns `warning`, a 4th is refused (422, code no_reply_limit) until they reply.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "example": "+447700900123"
                  },
                  "body": {
                    "type": "string"
                  },
                  "media_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "One file per message (≤16 MB); body becomes the caption."
                  },
                  "session_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "to": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "queued"
                      ]
                    },
                    "poll_url": {
                      "type": "string"
                    },
                    "warning": {
                      "type": "string",
                      "description": "3rd message to someone who never replied."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Not queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "no_reply_limit"
                      ]
                    },
                    "max_tries": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Message status (?id=) or your WhatsApp numbers with warm-up and reply-rate stats (?numbers=1).",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "numbers",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "to": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "sent",
                            "delivered",
                            "read",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "sent_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "held_until": {
                          "type": "string",
                          "description": "Present while waiting on the warm-up/slow-down limit or a paused campaign."
                        },
                        "held_reason": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "numbers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/WhatsAppNumber"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/wa_lookup": {
      "x-status": "beta — enabled per account until the live test passes (403 otherwise)",
      "post": {
        "summary": "BETA: check which numbers are on WhatsApp ($0.002/number; unknowns refunded).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "numbers"
                ],
                "properties": {
                  "numbers": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "also_imessage": {
                    "type": "boolean",
                    "description": "Also run the iPhone lookup on the same list (linked job)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "poll_url": {
                      "type": "string"
                    },
                    "imessage_job_id": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "imessage_poll_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequired"
                }
              }
            }
          },
          "403": {
            "description": "Not enabled on this account yet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "BETA: job status + results.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "completed": {
                      "type": "integer"
                    },
                    "on_whatsapp": {
                      "type": "integer"
                    },
                    "unknown": {
                      "type": "integer"
                    },
                    "imessage_job_id": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "number": {
                            "type": "string"
                          },
                          "whatsapp": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          },
                          "checked": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/conversations": {
      "get": {
        "summary": "Full two-way transcripts per contact (iMessage/SMS), with fields, opt-out and lead score.",
        "parameters": [
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "description": "ISO time; only conversations with activity after it."
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 200,
              "maximum": 1000
            }
          },
          {
            "name": "include_no_reply",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "conversations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "phone": {
                            "type": "string"
                          },
                          "company_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "link": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "fields": {
                            "type": "object"
                          },
                          "opted_out": {
                            "type": "boolean"
                          },
                          "last_activity": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "lead": {
                            "$ref": "#/components/schemas/Lead"
                          },
                          "messages": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "dir": {
                                  "type": "string",
                                  "enum": [
                                    "in",
                                    "out"
                                  ]
                                },
                                "message_id": {},
                                "body": {
                                  "type": "string"
                                },
                                "at": {
                                  "type": "string"
                                },
                                "channel": {
                                  "type": "string"
                                },
                                "status": {
                                  "type": "string"
                                },
                                "attachments": {
                                  "type": "array"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Pause or resume the built-in AI in one conversation.",
        "description": "Stops (or restarts) iBlueText AI replies to one contact, so a person can take over. Pause lasts until resumed. The AI also steps back on its own for 24 hours (account setting) after someone replies by hand from the Inbox or the app. GET /conversations?phone=… returns the same `ai` object.",
        "operationId": "conversationAi",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone",
                  "ai"
                ],
                "properties": {
                  "phone": {
                    "type": "string",
                    "example": "+18135550142"
                  },
                  "ai": {
                    "type": "string",
                    "enum": [
                      "pause",
                      "resume"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New AI state for that conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "phone": {
                      "type": "string"
                    },
                    "ai": {
                      "type": "object",
                      "properties": {
                        "available": {
                          "type": "boolean",
                          "description": "the account has AI replies on"
                        },
                        "paused": {
                          "type": "boolean"
                        },
                        "until": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "UTC; null = until resumed"
                        },
                        "reason": {
                          "type": "string",
                          "enum": [
                            "",
                            "manual",
                            "you_replied",
                            "line"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Missing phone or ai"
          }
        }
      }
    },
    "/sequences": {
      "get": {
        "summary": "Your sequences (drip follow-ups).",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sequences": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "steps": {
                            "type": "integer"
                          },
                          "active_contacts": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Enroll a contact in a sequence, or stop them (stop:true).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sequence_id",
                  "phone"
                ],
                "properties": {
                  "sequence_id": {
                    "type": "integer"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "stop": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/lines": {
      "get": {
        "summary": "Your sending lines with status, today's usage, caps and health. A line-limited key sees only its line.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key_limited_to_line": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "lines": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "label": {
                            "type": "string"
                          },
                          "phone": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "provisioning",
                              "active",
                              "paused"
                            ]
                          },
                          "reserved_until": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "health": {
                            "type": "object",
                            "properties": {
                              "last_sent_at": {},
                              "sent_today": {
                                "type": "integer"
                              },
                              "failed_today": {
                                "type": "integer"
                              },
                              "last_failed_at": {},
                              "last_error": {}
                            }
                          },
                          "caps": {
                            "type": "object",
                            "properties": {
                              "new_contacts_per_channel_today": {
                                "type": "integer"
                              },
                              "used": {
                                "type": "object",
                                "properties": {
                                  "imessage": {
                                    "type": "integer"
                                  },
                                  "sms": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "replies": {
                                "type": "string"
                              }
                            }
                          },
                          "send_window": {
                            "type": "object",
                            "properties": {
                              "start_hour": {},
                              "end_hour": {},
                              "timezone": {
                                "type": "string"
                              }
                            }
                          },
                          "webhook_url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/quick_answers": {
      "get": {
        "summary": "List Quick answers (free keyword auto-replies).",
        "description": "Quick answers are checked before the built-in AI. A match is answered instantly with the fixed reply and never counts as an AI reply, so they keep AI costs low. They work without the AI add-on too.",
        "operationId": "quickAnswersList",
        "responses": {
          "200": {
            "description": "Your Quick answers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "quick_answers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "keywords": {
                            "type": "string"
                          },
                          "match": {
                            "type": "string",
                            "enum": [
                              "contains",
                              "exact"
                            ]
                          },
                          "reply": {
                            "type": "string"
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "used": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add, turn off/on, or delete a Quick answer.",
        "description": "Add: {keywords, reply, match}. keywords = comma-separated words or phrases (any one matches; case and punctuation ignored). Change: {id, active:false} or {id, delete:true}. Max 100 per account; STOP-type words are not allowed (opt-outs are automatic).",
        "operationId": "quickAnswersWrite",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keywords": {
                    "type": "string",
                    "example": "hours, open, what time"
                  },
                  "reply": {
                    "type": "string",
                    "example": "We're open Mon-Sat 9am-6pm."
                  },
                  "match": {
                    "type": "string",
                    "enum": [
                      "contains",
                      "exact"
                    ],
                    "default": "contains"
                  },
                  "id": {
                    "type": "integer"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "delete": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated or deleted"
          },
          "201": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "quick_answer": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "keywords": {
                          "type": "string"
                        },
                        "match": {
                          "type": "string",
                          "enum": [
                            "contains",
                            "exact"
                          ]
                        },
                        "reply": {
                          "type": "string"
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "used": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          },
          "422": {
            "description": "Missing keywords/reply, an opt-out word, or over the limit"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your iBlueText API key (ibt_live_...)"
      }
    },
    "schemas": {
      "PaymentRequired": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "payment_required"
          },
          "reason": {
            "type": "string",
            "enum": [
              "insufficient_credits",
              "subscription_required"
            ],
            "example": "insufficient_credits"
          },
          "message": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "enum": [
              "lookup",
              "verify",
              "send"
            ]
          },
          "needed": {
            "type": "integer"
          },
          "available": {
            "type": "integer"
          },
          "unit_price_usd": {
            "type": "number",
            "example": 0.005
          },
          "human_action_required": {
            "type": "boolean",
            "example": true,
            "description": "A human must complete payment; agents cannot enter card details."
          },
          "buy_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "WebhookConfig": {
        "type": "object",
        "properties": {
          "signing_secret": {
            "type": "string",
            "description": "Verify X-IBT-Signature = hex HMAC-SHA256(raw body, signing_secret)"
          },
          "account_webhook": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "nullable": true
              },
              "replies_handled_by_webhook": {
                "type": "boolean"
              }
            }
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "line_id": {
                  "type": "integer"
                },
                "label": {
                  "type": "string"
                },
                "phone": {
                  "type": "string",
                  "nullable": true
                },
                "reserved_until": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "reserved_note": {
                  "type": "string",
                  "nullable": true
                },
                "webhook_url": {
                  "type": "string",
                  "nullable": true
                },
                "replies_handled_by_webhook": {
                  "type": "boolean"
                }
              }
            }
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "docs": {
            "type": "string"
          },
          "updated": {
            "type": "integer",
            "description": "POST only: how many targets were changed"
          },
          "test": {
            "type": "object",
            "description": "POST with test:true only",
            "properties": {
              "ok": {
                "type": "boolean"
              },
              "http_code": {
                "type": "integer",
                "nullable": true
              },
              "error": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "error": {
            "type": "string"
          }
        }
      },
      "CampaignStats": {
        "type": "object",
        "properties": {
          "sent": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "read": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "waiting": {
            "type": "integer",
            "description": "Not sent yet (queued)."
          },
          "held_by_warmup": {
            "type": "integer",
            "description": "WhatsApp: waiting on a number's daily new-contact limit."
          },
          "held_by_daily_cap": {
            "type": "integer",
            "description": "iMessage/SMS: waiting for the line's 4am reset."
          },
          "imessage": {
            "type": "integer"
          },
          "sms": {
            "type": "integer"
          }
        }
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "channel": {
            "type": "string",
            "enum": [
              "text",
              "whatsapp"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "sending",
              "sent",
              "paused",
              "resolving"
            ]
          },
          "total": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "line_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "replies": {
            "type": "integer"
          },
          "reply_rate": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Replies as % of people reached."
          },
          "stats": {
            "$ref": "#/components/schemas/CampaignStats"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "E.g. \"3 skipped: never replied to your last 3 WhatsApp messages\", or the auto-detect split."
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "WhatsAppNumber": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "integer"
          },
          "phone": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pairing",
              "connected",
              "disconnected"
            ]
          },
          "hosted": {
            "type": "boolean",
            "description": "true = a number iBlueText hosts on its own SIM ($49 add-on on your line or $99 standalone)."
          },
          "warmup": {
            "type": "object",
            "properties": {
              "day": {
                "type": "integer"
              },
              "of": {
                "type": "integer"
              },
              "new_contacts_limit_24h": {
                "type": "integer"
              },
              "used_24h": {
                "type": "integer"
              },
              "left_24h": {
                "type": "integer"
              },
              "held": {
                "type": "integer"
              },
              "next_free_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "reply_rate": {
            "type": "object",
            "properties": {
              "7d": {
                "type": "object",
                "properties": {
                  "new": {
                    "type": "integer"
                  },
                  "replied": {
                    "type": "integer"
                  },
                  "rate": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              },
              "30d": {
                "type": "object"
              }
            }
          },
          "unanswered_2plus": {
            "type": "integer",
            "description": "People who got 2+ messages and never replied."
          },
          "health": {
            "type": "string",
            "enum": [
              "new",
              "good",
              "watch",
              "risk"
            ]
          },
          "slowed": {
            "type": "boolean",
            "description": "true = under 15% of new contacts replied in 7 days, so the daily limit is halved until it recovers."
          }
        }
      },
      "Lead": {
        "type": "object",
        "description": "Lead score: replied +30, replied in 7 days +20 (8-14 days +10), +2 per message (max 20), pipeline +10 (+5 with value), hot/customer/booked tag +10, upcoming booking +15, quiet 30+ days -20; opted out/blocked = 0. Tier: hot 70+, warm 40-69, cold <40; manual:true when you set fields.lead_tier.",
        "properties": {
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "tier": {
            "type": "string",
            "enum": [
              "hot",
              "warm",
              "cold",
              "dead"
            ]
          },
          "manual": {
            "type": "boolean"
          }
        }
      }
    },
    "responses": {
      "PaymentRequired": {
        "description": "Payment required — out of credits or no active line. A human must pay at buy_url before retrying.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PaymentRequired"
            }
          }
        }
      }
    }
  }
}
