{
  "openapi": "3.1.0",
  "info": {
    "title": "BloomText API",
    "version": "1.0.0",
    "summary": "Send and read messages with patients, manage patient records, and manage the organizations, members and teams on a BloomText account, with an API key.",
    "description": "The BloomText API brings a practice's patient communication and staff directory into your own software. You call it with an API key, which an account's Owners and IT create in BloomText. A key reaches all of the account's organizations or only the ones chosen, and a call names one with `org_id` when the key reaches several. With an API key you find, create, update and delete patient records by patient ID, phone number or MRN; send messages to patients and read patient messages and conversations; list the clinic phone numbers; and, with member scopes, list organizations, add, change and remove members, place them on teams, and read member events. Messages to patients go through a secure link unless the request asks for plain SMS. Some features are turned on per account: BloomText turns them on for an account that asks. They are marked `x-status: planned` here: OAuth 2.0 (authorization code for apps acting for a person, including everything under /me and /users, and client credentials for an existing key), webhooks, campaigns, patient imports and audit events. Every other operation is served today.",
    "contact": {
      "name": "BloomText API access",
      "url": "https://www.bloomtext.com/developers/api/authentication/"
    }
  },
  "servers": [
    {
      "url": "https://api.bloomtext.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "oauth2": []
    },
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Organization",
      "description": "The organization an API key acts on."
    },
    {
      "name": "Patients",
      "description": "Patient records, always found by patient ID, phone number, or MRN. Patient API."
    },
    {
      "name": "Patient imports",
      "description": "Bulk imports of patient records. Patient API."
    },
    {
      "name": "Patient messages",
      "description": "Messages with patients: send to a patient by ID or phone number, read patient messages by time range, and check delivery. Patient API."
    },
    {
      "name": "Patient conversations",
      "description": "Conversations with patients, one per patient and clinic phone number. Patient API."
    },
    {
      "name": "Phone numbers",
      "description": "The clinic phone numbers messages to patients come from, and the team each belongs to."
    },
    {
      "name": "Campaigns",
      "description": "One message sent to many patients from a clinic phone number, with per-patient delivery."
    },
    {
      "name": "Profile",
      "description": "The person who connected your app with OAuth, and the grant itself."
    },
    {
      "name": "Messages",
      "description": "The connected person's own messages, in team chats and patient conversations. OAuth only."
    },
    {
      "name": "Reactions",
      "description": "Emoji reactions on the connected person's messages. OAuth only."
    },
    {
      "name": "Conversations",
      "description": "The connected person's own conversations, team chats and patient conversations. OAuth only."
    },
    {
      "name": "Participants",
      "description": "Staff and patients in one of the connected person's conversations. OAuth only."
    },
    {
      "name": "Users",
      "description": "Staff members of the connected person's organization. OAuth only."
    },
    {
      "name": "Organizations",
      "description": "The organizations on your account an account API key reaches. Account API keys only."
    },
    {
      "name": "Members",
      "description": "People in an organization on your account, and who manages each membership. Account API keys only."
    },
    {
      "name": "Teams",
      "description": "Teams in an organization on your account, and the people the API placed on them. Account API keys only."
    },
    {
      "name": "Member events",
      "description": "Who joined, was approved, or left an organization on your account, and who did it. Account API keys only."
    },
    {
      "name": "Audit events",
      "description": "What people did in an organization on your account: sign-ins, chat views, exports, member and setting changes. Kept six years. Account API keys only."
    }
  ],
  "paths": {
    "/organization": {
      "get": {
        "tags": [
          "Organization"
        ],
        "operationId": "getOrganization",
        "summary": "Retrieve the organization",
        "description": "Returns the organization the API key or access token belongs to. Works with any valid credential and scope, so it's a quick way to check a key or token.",
        "x-bloomtext-required-scopes": [],
        "x-bloomtext-any-scope": true,
        "security": [
          {
            "oauth2": []
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "description": "The organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patients": {
      "get": {
        "tags": [
          "Patients"
        ],
        "operationId": "listPatients",
        "summary": "Find patients",
        "description": "Finds the patients with a phone number or MRN. Pass at least one of `phone` or `mrn`; there's no way to list the whole directory. A phone number can match several patients, like a shared family phone.",
        "x-bloomtext-required-scopes": [
          "patients.read"
        ],
        "security": [
          {
            "oauth2": [
              "patients.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            400,
            "patient_filter_required",
            "Look patients up by `phone` or `mrn`. Bulk listing isn't available; contact BloomText if you need a sync."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/PatientPhone"
          },
          {
            "$ref": "#/components/parameters/PatientMrn"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PatientList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Patients"
        ],
        "operationId": "createPatient",
        "summary": "Create a patient",
        "description": "Creates a patient, or returns the existing one that matches by MRN, then phone, then email. A new patient returns `201`; a match returns `200` with `matched_by` and doesn't change the existing record.",
        "x-bloomtext-required-scopes": [
          "patients.write"
        ],
        "security": [
          {
            "oauth2": [
              "patients.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatientCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Patient"
          },
          "201": {
            "$ref": "#/components/responses/Patient"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patients/{patientId}": {
      "get": {
        "tags": [
          "Patients"
        ],
        "operationId": "getPatient",
        "summary": "Retrieve a patient",
        "description": "Returns a patient.",
        "x-bloomtext-required-scopes": [
          "patients.read"
        ],
        "security": [
          {
            "oauth2": [
              "patients.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PatientId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Patient"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "patch": {
        "tags": [
          "Patients"
        ],
        "operationId": "updatePatient",
        "summary": "Update a patient",
        "description": "Updates the fields you send and leaves the rest unchanged.",
        "x-bloomtext-required-scopes": [
          "patients.write"
        ],
        "security": [
          {
            "oauth2": [
              "patients.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PatientId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatientUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Patient"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "delete": {
        "tags": [
          "Patients"
        ],
        "operationId": "deletePatient",
        "summary": "Delete a patient",
        "description": "Deletes a patient record, as deleting a patient in BloomText does. Their conversations stay in BloomText for your records and for the staff in them. Pending campaign messages to the patient are cancelled.",
        "x-bloomtext-required-scopes": [
          "patients.write"
        ],
        "security": [
          {
            "oauth2": [
              "patients.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PatientId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "204": {
            "description": "No content."
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-imports": {
      "post": {
        "tags": [
          "Patient imports"
        ],
        "operationId": "createPatientImport",
        "x-status": "planned",
        "summary": "Import patients",
        "description": "Starts importing up to 10,000 patients in the background. Send JSON, or CSV with `Content-Type text/csv` and the columns first_name, last_name, dob, mrn, phone, email. Each row is matched like Create a patient.",
        "x-bloomtext-required-scopes": [
          "patients.write"
        ],
        "security": [
          {
            "oauth2": [
              "patients.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatientImportCreate"
              }
            },
            "text/csv": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/PatientImport"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-imports/{importId}": {
      "get": {
        "tags": [
          "Patient imports"
        ],
        "operationId": "getPatientImport",
        "x-status": "planned",
        "summary": "Retrieve an import",
        "description": "Returns an import's progress and, once it finishes, the outcome of every row.",
        "x-bloomtext-required-scopes": [
          "patients.write"
        ],
        "security": [
          {
            "oauth2": [
              "patients.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ImportId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PatientImport"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-messages": {
      "get": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "listPatientMessages",
        "summary": "List patient messages",
        "description": "Lists messages in patient conversations in a time range, newest first, with a cursor. `group_by=message` (the default) returns them one by one; `group_by=conversation` returns every message in the range from one conversation before moving to the next. Narrow to one patient or conversation, or search with `q`. Messages include everyone's in each conversation: the patient, staff, and API keys. With an API key, pass `after`, `patient_id`, or `conversation_id`.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/GroupBy"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "$ref": "#/components/parameters/PatientFilter"
          },
          {
            "$ref": "#/components/parameters/ConversationFilter"
          },
          {
            "$ref": "#/components/parameters/MessageQuery"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            400,
            "range_required",
            "An API key must pass `after`, `patient_id`, or `conversation_id`, so every read is bounded."
          ]
        ]
      },
      "post": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "sendMessage",
        "summary": "Send a message to a patient",
        "x-bloomtext-returns": "Returns `201 Created` with the sent [Message object](/reference/message-object/) in `message`, plus `patient_created` and `conversation_created` to say whether BloomText created either one. Returns `202 Accepted` with the same body when the message waits for the patient's consent to texting, with `delivery.status` `consent_requested`.",
        "description": "Sends a message to a patient by `patient_id` or phone number, from one of the organization's numbers: `from`, else the key's default number, else the organization's only number. An organization with several numbers and no default answers 422 `from_required`. The message goes in that number's team's conversation with the patient, and comes from the API identity the key belongs to (an automated sender on the account, never a staff member); until identities are on for the account, it shows the key's name with an API marker. BloomText finds the patient (or creates one for a new phone number), finds or starts the conversation, and sends. Messages go by secure link unless `channel` is `sms`.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "x-bloomtext-idempotency": "Required for safe retries.",
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "plan_required",
            "The organization's plan doesn't include patient messaging."
          ],
          [
            404,
            "not_found",
            "The `patient_id` doesn't exist in the organization, or `from` isn't one of its phone numbers."
          ],
          [
            409,
            "ambiguous_recipient",
            "The phone number belongs to more than one patient. `candidates` lists them; send again with a `patient_id`, or add `patient.mrn`."
          ],
          [
            409,
            "recipient_mismatch",
            "`patient.mrn` belongs to a patient with a different phone number, or the number's only patient has a different MRN. Nothing is sent. `candidates` lists that patient; update their phone, or send with their `patient_id`."
          ],
          [
            422,
            "from_required",
            "The organization has several phone numbers and no default. `detail` and `numbers` list them; set `from` to one."
          ],
          [
            422,
            "channel_required",
            "`channel` is missing. Set it to `sms` or `secure`; there's no default."
          ],
          [
            422,
            "too_long",
            "The body is over `max` characters (1,600 for `sms`, 40,000 for `secure`). `length` and `max` say by how much."
          ],
          [
            422,
            "patient_has_no_phone",
            "An `sms` send to a patient with no phone number."
          ],
          [
            422,
            "no_contact_method",
            "A `secure` send to a patient with neither phone nor email."
          ],
          [
            422,
            "patient_opted_out",
            "The patient opted out: they replied STOP to one of the organization's numbers, or staff recorded their opt-out. No number can text them until they reply START. `number` and `since` say which number the text would have come from and since when."
          ],
          [
            422,
            "carrier_blocked",
            "The carrier refused an earlier text to the patient because they replied STOP to that number, so the organization's numbers don't text them until they text START to one of them. `number` and `since` as above."
          ],
          [
            422,
            "number_blocked",
            "An admin in your organization blocked the patient's number. Only an admin can unblock it, in the app. `number` and `since` as above."
          ],
          [
            422,
            "number_unreachable",
            "An earlier text failed because the patient's number can't receive texts: it's a landline, or its carrier couldn't be reached. It clears when the patient texts one of the organization's numbers, when their phone number is changed, or when an admin clears it in the app. `number` and `since` as above."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageSend"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/SendResult"
          },
          "202": {
            "$ref": "#/components/responses/SendResultConsentRequested"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/messages:batch": {
      "post": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "sendMessageBatch",
        "summary": "Send messages to patients in a batch",
        "x-bloomtext-returns": "Returns `207 Multi-Status` with one result per message, in the order you sent them: an `id` and a `status` (`queued`, or `consent_requested` when the message waits for the patient's consent), or an `error` with a problem details object. One message's error never fails the others.",
        "description": "Sends up to 60 messages in one request. Each item is the body of a single send and is checked the same way: recipient, channel, phone number, and opt-out. The batch counts as one request toward the per-second and per-minute request limits, and each message counts toward the per-minute message limit. All of an organization's API keys share these limits. One `Idempotency-Key` covers the whole batch: retry with the same key and the same body to get the same results without sending anything twice.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "x-bloomtext-idempotency": "Required. One key covers the whole batch.",
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "plan_required",
            "The organization's plan doesn't include patient messaging. The whole batch is refused and nothing is sent."
          ],
          [
            422,
            "validation_failed",
            "`messages` is empty or holds more than 60 messages. Errors in individual messages come back in their results instead."
          ],
          [
            422,
            "unknown_field",
            "A message has a field the API doesn't take. The whole batch is refused, naming the message (`/messages/3/...`), and nothing is sent."
          ],
          [
            429,
            "rate_limited",
            "The batch's messages don't fit in what's left of the organization's per-minute message limit. Nothing was sent; retry with the same `Idempotency-Key` after `Retry-After`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageBatch"
              }
            }
          }
        },
        "responses": {
          "207": {
            "$ref": "#/components/responses/BatchResult"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-messages/{messageId}": {
      "get": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "getPatientMessage",
        "summary": "Retrieve a patient message",
        "description": "Returns one message in a patient conversation, with its delivery status. Use it to check whether a text reached the patient.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Message"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-conversations": {
      "get": {
        "tags": [
          "Patient conversations"
        ],
        "operationId": "listPatientConversations",
        "summary": "List patient conversations",
        "description": "Lists patient conversations with messages in a time range, most recently active first, each with its latest message and how many messages fell in the range. Filter to one patient with `patient_id`. Every request passes `after` or `patient_id`, so a read is always bounded; there's no search by patient name.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            400,
            "range_required",
            "Pass `after` or `patient_id`, so every read is bounded."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/PatientFilter"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ConversationList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/patient-conversations/{conversationId}": {
      "get": {
        "tags": [
          "Patient conversations"
        ],
        "operationId": "getPatientConversation",
        "summary": "Retrieve a patient conversation",
        "description": "Returns a patient conversation: the patient, its phone number, and its channel.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Conversation"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/phone-numbers": {
      "get": {
        "tags": [
          "Phone numbers"
        ],
        "operationId": "listPhoneNumbers",
        "summary": "List phone numbers",
        "description": "Lists the clinic phone numbers messages to patients can come from, each with the team it belongs to: every number in the organization. `is_default` marks the number a send uses when it leaves out `from`: the key's default number, or the organization's only number.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PhoneNumberList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns": {
      "get": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "listCampaigns",
        "x-status": "planned",
        "summary": "List campaigns",
        "description": "Lists the organization's campaigns, newest first. Archived campaigns are left out unless `archived=true`.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/CampaignStatus"
          },
          {
            "$ref": "#/components/parameters/Archived"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CampaignList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "createCampaign",
        "x-status": "planned",
        "summary": "Create a campaign",
        "description": "Creates a draft campaign. Add recipients, then start it. Every recipient gets the same message, from the phone number you choose. It's sent from the API identity the key belongs to; until identities are on for the account, it shows the key's name.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            422,
            "from_required",
            "There are several phone numbers and no default. Pass `from`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}": {
      "get": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "getCampaign",
        "x-status": "planned",
        "summary": "Retrieve a campaign",
        "description": "Returns a campaign with its progress counts.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "patch": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "updateCampaign",
        "x-status": "planned",
        "summary": "Update a campaign",
        "description": "Changes a campaign's name, message, channel, phone number, or pace while it's a draft or paused, and archives or unarchives it when it isn't running.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_editable",
            "The campaign is running, completed, or stopped. Pause it to change the message; archive it only when it isn't running."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "delete": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "deleteCampaign",
        "x-status": "planned",
        "summary": "Delete a campaign",
        "description": "Deletes a draft campaign. A campaign that has started can't be deleted, because its messages are in patients' conversations; archive it instead.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_deletable",
            "The campaign has started. Archive it with Update a campaign instead."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "204": {
            "description": "No content."
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}/recipients": {
      "get": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "listCampaignRecipients",
        "x-status": "planned",
        "summary": "List campaign recipients",
        "x-bloomtext-returns": "Returns `200 OK` with a `data` array of recipients, each with its `patient`, delivery `status`, `message_id` once sent, and `error`, plus a `pagination` object. See [pagination](/pagination/).",
        "description": "Lists a campaign's recipients in the order they're sent, each with its delivery status.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/RecipientStatus"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CampaignRecipientList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "addCampaignRecipients",
        "x-status": "planned",
        "x-bloomtext-returns": "Returns `200 OK` with `changed`, the number of patients added, and `skipped`, each patient left unchanged with its `reason`.",
        "summary": "Add campaign recipients",
        "description": "Adds up to 10,000 patients to a campaign that isn't completed or stopped. Unknown patient IDs, and patients already on the campaign, are skipped and listed in `skipped`. Recipients added to a running campaign are sent after the ones already waiting.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_editable",
            "The campaign is completed or stopped."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignRecipientsChange"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CampaignRecipientsResult"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}/recipients/remove": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "removeCampaignRecipients",
        "x-status": "planned",
        "x-bloomtext-returns": "Returns `200 OK` with `changed`, the number of patients removed, and `skipped`, each patient left unchanged with its `reason`.",
        "summary": "Remove campaign recipients",
        "description": "Removes patients from a campaign before they're sent. Patients already sent to are skipped and listed in `skipped`.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignRecipientsChange"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CampaignRecipientsResult"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}/start": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "startCampaign",
        "x-status": "planned",
        "summary": "Start a campaign",
        "description": "Starts sending a draft campaign, or resumes a paused one. Messages go out one at a time, `send_interval_seconds` apart.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_startable",
            "The campaign is already running, completed, stopped, or archived."
          ],
          [
            422,
            "campaign_empty",
            "The campaign has no recipients waiting to be sent."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}/pause": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "pauseCampaign",
        "x-status": "planned",
        "summary": "Pause a campaign",
        "description": "Stops sending after the message in progress. Start the campaign again to resume where it left off.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_running",
            "Only a running campaign can be paused."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/campaigns/{campaignId}/stop": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "stopCampaign",
        "x-status": "planned",
        "summary": "Stop a campaign",
        "description": "Stops a running or paused campaign for good. Recipients not yet sent are cancelled. A stopped campaign can't be started again.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          },
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "oauth",
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            409,
            "campaign_not_running",
            "Only a running or paused campaign can be stopped."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Campaign"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me": {
      "get": {
        "tags": [
          "Profile"
        ],
        "operationId": "getProfile",
        "x-status": "planned",
        "summary": "Retrieve the profile",
        "description": "Returns the person who connected your app, their organization, the scopes they granted, and when the grant expires.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "responses": {
          "200": {
            "description": "The profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/messages": {
      "get": {
        "tags": [
          "Messages"
        ],
        "operationId": "listMessages",
        "x-status": "planned",
        "summary": "List messages",
        "description": "Lists messages in the connected person's own conversations, team chats and patient conversations alike, in a time range, newest first, with a cursor. `group_by=message` (the default) returns them one by one; `group_by=conversation` returns every message in the range from one conversation before moving to the next. A conversation's messages include everyone's, not only the person's own.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/GroupBy"
          },
          {
            "$ref": "#/components/parameters/Order"
          },
          {
            "$ref": "#/components/parameters/ConversationFilter"
          },
          {
            "$ref": "#/components/parameters/PatientFilter"
          },
          {
            "$ref": "#/components/parameters/MessageQuery"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/messages/{messageId}": {
      "get": {
        "tags": [
          "Messages"
        ],
        "operationId": "getMessage",
        "x-status": "planned",
        "summary": "Retrieve a message",
        "description": "Returns one message in the connected person's own conversations.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Message"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/messages/{messageId}/reactions": {
      "get": {
        "tags": [
          "Reactions"
        ],
        "operationId": "listReactions",
        "x-status": "planned",
        "summary": "List reactions",
        "description": "Lists the emoji reactions on a message.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ReactionList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Reactions"
        ],
        "operationId": "createReaction",
        "x-status": "planned",
        "summary": "Add a reaction",
        "description": "Adds an emoji reaction to a message as the user.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReactionCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Reaction"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "delete": {
        "tags": [
          "Reactions"
        ],
        "operationId": "deleteReaction",
        "x-status": "planned",
        "summary": "Remove a reaction",
        "description": "Removes the user's reaction from a message. Reactions have no ID of their own, so pass the exact emoji.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          },
          {
            "$ref": "#/components/parameters/ReactionEmoji"
          }
        ],
        "responses": {
          "204": {
            "description": "No content."
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/conversations": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "operationId": "listConversations",
        "x-status": "planned",
        "summary": "List conversations",
        "description": "Lists the connected person's own conversations, team chats and patient conversations alike, with messages in a time range, most recently active first, each with its latest message and how many messages fell in the range.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/PatientFilter"
          },
          {
            "$ref": "#/components/parameters/ConversationQuery"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ConversationList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/conversations/{conversationId}": {
      "get": {
        "tags": [
          "Conversations"
        ],
        "operationId": "getConversation",
        "x-status": "planned",
        "summary": "Retrieve a conversation",
        "description": "Returns one of the connected person's own conversations, with their unread count.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Conversation"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/conversations/{conversationId}/messages": {
      "post": {
        "tags": [
          "Messages"
        ],
        "operationId": "createMessage",
        "x-status": "planned",
        "summary": "Send a message",
        "description": "Sends a text message as the connected person in one of their own conversations, a team chat or a patient conversation, and marks it as read for them. Set `reply_to_message_id` to reply in a thread. To message a patient by patient ID or phone number, use Send a message to a patient.",
        "x-bloomtext-required-scopes": [
          "messages.write"
        ],
        "x-bloomtext-idempotency": "Required for safe retries.",
        "security": [
          {
            "oauth2": [
              "messages.write"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Message"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/me/conversations/{conversationId}/participants": {
      "get": {
        "tags": [
          "Participants"
        ],
        "operationId": "listParticipants",
        "x-status": "planned",
        "summary": "List participants",
        "description": "Lists the staff and patients in a conversation.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConversationId"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ParticipantList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listUsers",
        "x-status": "planned",
        "summary": "List users",
        "description": "Lists the staff members of the user's organization, ordered by name.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/UserList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/users/{userId}": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "getUser",
        "x-status": "planned",
        "summary": "Retrieve a user",
        "description": "Returns a single staff member of the user's organization.",
        "x-bloomtext-required-scopes": [
          "messages.read"
        ],
        "security": [
          {
            "oauth2": [
              "messages.read"
            ]
          }
        ],
        "x-bloomtext-credentials": [
          "oauth"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/UserId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/User"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/orgs": {
      "get": {
        "tags": [
          "Organizations"
        ],
        "operationId": "listOrgs",
        "summary": "List organizations",
        "description": "Lists the organizations on your account that the API key reaches, with the default organization marked. `org_id` on every other call takes one of these IDs.",
        "x-bloomtext-required-scopes": [
          "members.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/OrgList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/members": {
      "get": {
        "tags": [
          "Members"
        ],
        "operationId": "listMembers",
        "summary": "List members",
        "description": "Lists the members of an organization by name, with their role, account role and who manages each membership: `admin` (people in BloomText), `api` (an API key) or `account` (an Owner or IT person, in every organization).",
        "x-bloomtext-required-scopes": [
          "members.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MemberList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "post": {
        "tags": [
          "Members"
        ],
        "operationId": "createMember",
        "summary": "Add a member",
        "description": "Adds a person to an organization as a membership the API manages. A new email creates their BloomText user, owned by your account. They get an invite email only with `send_invite_email: true`. Only API keys change memberships the API made; people in BloomText see them locked as \"Managed by API\".",
        "x-bloomtext-required-scopes": [
          "members.write"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ],
          [
            409,
            "already_member",
            "That person is already a member of this organization."
          ],
          [
            409,
            "patient_account",
            "That email belongs to a patient's account."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Member"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/members/{memberId}": {
      "get": {
        "tags": [
          "Members"
        ],
        "operationId": "getMember",
        "summary": "Retrieve a member",
        "description": "Returns one member of the organization.",
        "x-bloomtext-required-scopes": [
          "members.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemberId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Member"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "patch": {
        "tags": [
          "Members"
        ],
        "operationId": "updateMember",
        "summary": "Update a member",
        "description": "Changes the role or title of a membership the API manages. A membership people manage in BloomText answers 409 `managed_by_admin`; an Owner or IT person's answers 409 `managed_by_account`.",
        "x-bloomtext-required-scopes": [
          "members.write"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ],
          [
            409,
            "managed_by_admin",
            "People in BloomText manage this membership."
          ],
          [
            409,
            "managed_by_account",
            "This is an Owner or IT person's membership."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemberId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Member"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      },
      "delete": {
        "tags": [
          "Members"
        ],
        "operationId": "deleteMember",
        "summary": "Remove a member",
        "description": "Removes a membership the API manages. When it's the person's only organization and your account owns their user, removing them deletes the user, so the call must add `confirm_delete_user=true` (it then answers `204` as for any removal); without it the call answers 409 `confirmation_required` with `reason: would_delete_user` and changes nothing.",
        "x-bloomtext-required-scopes": [
          "members.write"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ],
          [
            409,
            "managed_by_admin",
            "People in BloomText manage this membership."
          ],
          [
            409,
            "managed_by_account",
            "This is an Owner or IT person's membership."
          ],
          [
            409,
            "confirmation_required",
            "Removing them deletes their user: add `confirm_delete_user=true`."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MemberId"
          },
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/ConfirmDeleteUser"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/teams": {
      "get": {
        "tags": [
          "Teams"
        ],
        "operationId": "listTeams",
        "summary": "List teams",
        "description": "Lists the teams of an organization, its default team first.",
        "x-bloomtext-required-scopes": [
          "members.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/TeamList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/teams/{teamId}/members": {
      "post": {
        "tags": [
          "Teams"
        ],
        "operationId": "addTeamMember",
        "summary": "Add a member to a team",
        "description": "Puts a member of the team's organization on the team. Someone already on it stays as they are.",
        "x-bloomtext-required-scopes": [
          "teams.write"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamMemberAdd"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Team"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/teams/{teamId}/members/{memberId}": {
      "delete": {
        "tags": [
          "Teams"
        ],
        "operationId": "removeTeamMember",
        "summary": "Remove a member from a team",
        "description": "Takes someone the API put on a team off it. Someone people placed in BloomText answers 409 `managed_by_admin`.",
        "x-bloomtext-required-scopes": [
          "teams.write"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ],
          [
            409,
            "managed_by_admin",
            "People in BloomText put this person on the team."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TeamId"
          },
          {
            "$ref": "#/components/parameters/MemberId"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/member-events": {
      "get": {
        "tags": [
          "Member events"
        ],
        "operationId": "listMemberEvents",
        "summary": "List member events",
        "description": "Lists membership changes in an organization: who was added, approved or removed, when, and by whom. A change an API key made names it as `API key: <name>`. Pages by membership, newest first.",
        "x-bloomtext-required-scopes": [
          "members.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MemberEventList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/audit-events": {
      "get": {
        "tags": [
          "Audit events"
        ],
        "operationId": "listAuditEvents",
        "x-status": "planned",
        "summary": "List audit events",
        "description": "Lists an organization's audit events, newest first, with the filters of Account → Audit: a date range, a person, actions and a patient. Each event names its action with a dotted name such as `chat.viewed`, the same name the audit log's CSV export uses. In BloomText today, Owners and IT see the audit log under Settings → Account → Audit and download it as CSV.",
        "x-bloomtext-required-scopes": [
          "audit.read"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-bloomtext-credentials": [
          "api_key"
        ],
        "x-bloomtext-errors": [
          [
            403,
            "org_not_reachable",
            "The key can't reach the organization in `org_id`."
          ],
          [
            400,
            "org_id_required",
            "The key reaches more than one organization: pass `org_id`."
          ],
          [
            403,
            "accounts_not_enabled",
            "The account layer isn't turned on for this account yet."
          ]
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrgId"
          },
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/AuditActor"
          },
          {
            "$ref": "#/components/parameters/AuditAction"
          },
          {
            "$ref": "#/components/parameters/AuditPatient"
          },
          {
            "$ref": "#/components/parameters/PageLimit"
          },
          {
            "$ref": "#/components/parameters/PageAfter"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/AuditEventList"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "webhooks": {
    "message.received": {
      "post": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "messageReceivedWebhook",
        "x-status": "planned",
        "summary": "A patient sent a message",
        "description": "Sent to an API key's webhook URL when a patient sends a message to one of the organizations, and phone numbers, the key reaches, for keys with `messages.read` whose webhook picked this event. Signed with the webhook's signing secret, following Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature`). The payload carries IDs only; fetch the message with the API.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 5 seconds to acknowledge it. Anything else is retried at 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 24 hours; `410 Gone` stops retries."
          }
        }
      }
    },
    "message.status": {
      "post": {
        "tags": [
          "Patient messages"
        ],
        "operationId": "messageStatusWebhook",
        "x-status": "planned",
        "summary": "A message's delivery status changed",
        "description": "Sent when a text this API key sent is delivered or fails, for keys with `messages.write` whose webhook picked this event. Signed like every webhook. The payload carries the message ID and its new status.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx within 5 seconds to acknowledge it. Anything else is retried at 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 24 hours; `410 Gone` stops retries."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "x-status": "planned",
        "description": "OAuth 2.0 in two flows. Authorization code with PKCE lets an app registered with BloomText act for a signed-in org admin, reaching the patient operations and the admin's own conversations under /me. Client credentials is a second way for a server to authenticate an existing API key, with that key's scopes. Send the access token as `Authorization: Bearer <token>`. Access tokens last 1 hour. Refresh tokens rotate on every use, expire after 90 days unused, and stop working 12 months after the user approved the app.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.bloomtext.com/oauth/authorize",
            "tokenUrl": "https://app.bloomtext.com/oauth/token",
            "refreshUrl": "https://app.bloomtext.com/oauth/token",
            "scopes": {
              "messages.read": "Read patient messages and conversations in a time range, a message's delivery status, and phone numbers. With the authorization code flow, also the person's own conversations under /me, team chats included, plus reactions, participants, and the staff directory.",
              "messages.write": "Send messages to patients by patient ID or phone number (a new number creates the patient). With the authorization code flow, also send in the person's own conversations and react to messages. Grants no read access on its own.",
              "patients.read": "Read patient records, looked up by patient ID, phone number, or MRN.",
              "patients.write": "Create, update, delete, and import patient records.",
              "offline_access": "Issue a refresh token so the app can keep working while the user is signed out."
            }
          },
          "clientCredentials": {
            "x-status": "planned",
            "tokenUrl": "https://app.bloomtext.com/oauth/token",
            "x-description": "A server-to-server token for an existing API key: exchange the key's client ID and secret for an access token that carries the key's scopes. A second way to authenticate the same key.",
            "scopes": {
              "messages.read": "Read patient messages and conversations, and phone numbers.",
              "messages.write": "Send messages to patients.",
              "patients.read": "Read patient records.",
              "patients.write": "Create, update and delete patient records.",
              "members.read": "List organizations, members and teams, and read member events.",
              "members.write": "Add, change and remove members.",
              "teams.write": "Add and remove people on teams."
            }
          }
        }
      },
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bt_key_…",
        "description": "An API key, sent as `Authorization: Bearer bt_key_…`. It isn't tied to a person. An account's Owners and IT create keys in BloomText under Settings → Account → API keys. A key reaches all of the account's organizations or chosen ones, and a call names one with `org_id` when the key reaches several. On request, a key can also be limited to some phone numbers: it then sends from, and reads the messages, conversations and numbers of, only those numbers' teams; patients stay organization-wide. Keys carry the scopes chosen when the key is created: `messages.read`, `messages.write`, `patients.read`, `patients.write`, and for staff provisioning `members.read`, `members.write` and `teams.write`. A key can have a default phone number, used when a send leaves out `from`. On request, keys belong to an API identity, the automated sender (not a person or a seat) whose name, avatar, teams and numbers Owners and IT choose; a key with `messages.write` must belong to one, and read-only or provisioning keys need none."
      }
    },
    "parameters": {
      "AuditActor": {
        "name": "actor_id",
        "in": "query",
        "description": "Only events this person did, by user ID.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AuditAction": {
        "name": "action",
        "in": "query",
        "description": "Only these actions, by dotted name. Repeat the parameter for several.",
        "schema": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/AuditAction"
          }
        },
        "style": "form",
        "explode": true
      },
      "AuditPatient": {
        "name": "patient_id",
        "in": "query",
        "description": "Only events about this patient.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "OrgId": {
        "name": "org_id",
        "in": "query",
        "description": "The organization the call acts on, from List organizations. Required when an account API key reaches more than one organization; an organization key or a key limited to one organization can leave it out.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "MemberId": {
        "name": "memberId",
        "in": "path",
        "required": true,
        "description": "The member's ID.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "TeamId": {
        "name": "teamId",
        "in": "path",
        "required": true,
        "description": "The team's ID.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ConfirmDeleteUser": {
        "name": "confirm_delete_user",
        "in": "query",
        "description": "`true` to remove someone from their only organization when that deletes their BloomText user.",
        "schema": {
          "type": "boolean"
        }
      },
      "PageLimit": {
        "name": "page[limit]",
        "in": "query",
        "description": "Number of records to return. Defaults to 50, maximum 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "PageAfter": {
        "name": "page[after]",
        "in": "query",
        "description": "Cursor from the previous page's `pagination.next_cursor`.",
        "schema": {
          "type": "string"
        }
      },
      "MessageQuery": {
        "name": "q",
        "in": "query",
        "description": "Words and operators to search for, like `refill from:patient is:failed`. Supports free text, `\"exact phrases\"`, `from:`, `patient:`, `mrn:`, `phone:`, `is:failed`, `-` to negate, and `OR`; with OAuth, also `is:unread`. Use `after` and `before` for time ranges. See Search.",
        "example": "from:patient",
        "schema": {
          "type": "string",
          "maxLength": 1000
        }
      },
      "PatientPhone": {
        "name": "phone",
        "in": "query",
        "description": "A phone number, in any common format.",
        "example": "+15125550143",
        "schema": {
          "type": "string"
        }
      },
      "PatientMrn": {
        "name": "mrn",
        "in": "query",
        "description": "The patient's ID in your system.",
        "schema": {
          "type": "string",
          "maxLength": 36
        }
      },
      "ConversationQuery": {
        "name": "q",
        "in": "query",
        "description": "Words and operators to filter conversations by, like `is:unread doe`. Free text matches conversation titles and participant names. Also supports `is:unread` and `with:` a user or patient ID. See Search.",
        "schema": {
          "type": "string",
          "maxLength": 1000
        }
      },
      "Order": {
        "name": "order",
        "in": "query",
        "description": "`desc` for newest first (the default) or `asc` for oldest first. With `group_by=conversation`, it orders the conversations by their latest message and the messages within each.",
        "schema": {
          "type": "string",
          "enum": [
            "desc",
            "asc"
          ],
          "default": "desc"
        }
      },
      "ReactionEmoji": {
        "name": "emoji",
        "in": "query",
        "required": true,
        "description": "The exact emoji the user reacted with.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 8
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "A UUID you generate for each write. Retrying with the same key returns the original result for 24 hours. Reusing a key with a different method, path, or body returns 409.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "UserId": {
        "name": "userId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "ID of the user."
      },
      "CampaignId": {
        "name": "campaignId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "ID of the campaign."
      },
      "CampaignStatus": {
        "name": "status",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "draft",
            "running",
            "paused",
            "completed",
            "stopped"
          ]
        },
        "description": "Only campaigns with this status."
      },
      "Archived": {
        "name": "archived",
        "in": "query",
        "schema": {
          "type": "boolean",
          "default": false
        },
        "description": "List archived campaigns instead of active ones."
      },
      "RecipientStatus": {
        "name": "status",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "pending",
            "sending",
            "sent",
            "delivered",
            "opened",
            "failed",
            "undelivered",
            "skipped",
            "cancelled"
          ]
        },
        "description": "Only recipients with this status."
      },
      "PatientId": {
        "name": "patientId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "ID of the patient."
      },
      "ImportId": {
        "name": "importId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "ID of the import."
      },
      "ConversationId": {
        "name": "conversationId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "ID of the conversation."
      },
      "MessageId": {
        "name": "messageId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "ID of the message."
      },
      "After": {
        "name": "after",
        "in": "query",
        "description": "Only messages sent at or after this time. An RFC 3339 timestamp, or a date for midnight in the organization's time zone.",
        "example": "2026-09-21T00:00:00Z",
        "schema": {
          "type": "string"
        }
      },
      "Before": {
        "name": "before",
        "in": "query",
        "description": "Only messages sent before this time. Same format as `after`.",
        "schema": {
          "type": "string"
        }
      },
      "GroupBy": {
        "name": "group_by",
        "in": "query",
        "description": "`message` (the default) lists messages one by one across conversations. `conversation` lists every message in the range from one conversation, then the next.",
        "schema": {
          "type": "string",
          "enum": [
            "message",
            "conversation"
          ],
          "default": "message"
        }
      },
      "ConversationFilter": {
        "name": "conversation_id",
        "in": "query",
        "description": "Only messages in this conversation.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "PatientFilter": {
        "name": "patient_id",
        "in": "query",
        "description": "Only conversations with this patient, on any phone number.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "AuditEventList": {
        "description": "A page of audit events.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuditEventPage"
            }
          }
        }
      },
      "OrgList": {
        "description": "The organizations.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OrgPage"
            }
          }
        }
      },
      "Member": {
        "description": "A member.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Member"
            }
          }
        }
      },
      "MemberList": {
        "description": "A page of members.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MemberPage"
            }
          }
        }
      },
      "TeamList": {
        "description": "The teams.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TeamPage"
            }
          }
        }
      },
      "Team": {
        "description": "The team the member is now on.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Team"
            }
          }
        }
      },
      "MemberEventList": {
        "description": "A page of member events.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MemberEventPage"
            }
          }
        }
      },
      "Problem": {
        "description": "Error response.",
        "headers": {
          "Retry-After": {
            "description": "Present on 429 responses; whole seconds until the client may retry.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          "WWW-Authenticate": {
            "description": "Present on 401 and 403 responses, following RFC 6750, like `Bearer error=\"invalid_token\"` or `Bearer error=\"insufficient_scope\", scope=\"patients.read\"`.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Patient": {
        "description": "A patient.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Patient"
            }
          }
        }
      },
      "PatientList": {
        "description": "A page of patients.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PatientPage"
            }
          }
        }
      },
      "PatientImport": {
        "description": "The import.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PatientImport"
            }
          }
        }
      },
      "PhoneNumberList": {
        "description": "The phone numbers.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PhoneNumberPage"
            }
          }
        }
      },
      "Campaign": {
        "description": "A campaign.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Campaign"
            }
          }
        }
      },
      "CampaignList": {
        "description": "A page of campaigns.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CampaignPage"
            }
          }
        }
      },
      "CampaignRecipientList": {
        "description": "A page of recipients.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CampaignRecipientPage"
            }
          }
        }
      },
      "CampaignRecipientsResult": {
        "description": "What changed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CampaignRecipientsResult"
            }
          }
        }
      },
      "SendResult": {
        "description": "The sent message.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SendResult"
            }
          }
        }
      },
      "SendResultConsentRequested": {
        "description": "The message is in the conversation, and its text waits for the patient's consent: the organization requires consent before texting and this phone hasn't agreed yet. BloomText texted the patient a consent request, and the message goes out when they reply YES. `delivery.status` is `consent_requested`. Don't resend: a retry with the same `Idempotency-Key` returns this stored result and sends nothing new. When the patient replies YES, the text goes out under the same message `id`; Retrieve a patient message shows its current status.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SendResult"
            }
          }
        }
      },
      "BatchResult": {
        "description": "One result per message, in input order.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/BatchResult"
            }
          }
        }
      },
      "User": {
        "description": "A user.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/User"
            }
          }
        }
      },
      "UserList": {
        "description": "A page of users.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/UserPage"
            }
          }
        }
      },
      "Conversation": {
        "description": "A conversation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Conversation"
            }
          }
        }
      },
      "ConversationList": {
        "description": "A page of conversations.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ConversationPage"
            }
          }
        }
      },
      "Message": {
        "description": "A message.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "MessageList": {
        "description": "A page of messages.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MessagePage"
            }
          }
        }
      },
      "Reaction": {
        "description": "A reaction.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Reaction"
            }
          }
        }
      },
      "ReactionList": {
        "description": "A page of reactions.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ReactionPage"
            }
          }
        }
      },
      "ParticipantList": {
        "description": "A page of participants.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ParticipantPage"
            }
          }
        }
      }
    },
    "schemas": {
      "WebhookEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "occurred_at",
          "org_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The event's ID, the same on every retry."
          },
          "type": {
            "type": "string",
            "enum": [
              "message.received",
              "message.status",
              "webhook.test"
            ]
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "org_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "type": "object",
            "description": "IDs and status only, never message text. Fetch details with the API key.",
            "required": [
              "message_id",
              "conversation_id"
            ],
            "properties": {
              "message_id": {
                "type": "string",
                "format": "uuid"
              },
              "conversation_id": {
                "type": "string",
                "format": "uuid"
              },
              "patient_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "channel": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "secure",
                  "sms",
                  null
                ],
                "description": "On `message.received`: how the patient wrote."
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "On `message.status`: the new delivery status, as in `delivery.status`."
              },
              "error_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "On `message.status`: the carrier error code when the text failed."
              }
            }
          }
        }
      },
      "AuditAction": {
        "type": "string",
        "description": "What happened, as `object.past_tense_verb`.",
        "enum": [
          "audit_log.exported",
          "chat.exported",
          "chat.viewed",
          "member.added",
          "member.deleted",
          "member.moved",
          "member.password_reset",
          "member.removed",
          "member.role_changed",
          "members.exported",
          "message.deleted",
          "org.setting_changed",
          "patient.account_deleted",
          "patient.archived",
          "patient.created",
          "patient.deleted",
          "patient.restored",
          "patient.updated",
          "patients.imported",
          "sms_logs.exported",
          "support.signed_in",
          "support.signed_out",
          "team.member_added",
          "team.member_removed",
          "user.sign_in_failed",
          "user.signed_in",
          "user.signed_out",
          "audit_log.turned_on",
          "audit_log.turned_off"
        ]
      },
      "AuditEvent": {
        "type": "object",
        "required": [
          "id",
          "org_id",
          "occurred_at",
          "action",
          "actor"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "org_id": {
            "type": "string",
            "format": "uuid"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "action": {
            "$ref": "#/components/schemas/AuditAction"
          },
          "actor": {
            "type": "object",
            "required": [
              "name",
              "is_support"
            ],
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The person's user ID; null for BloomText Support."
              },
              "name": {
                "type": "string"
              },
              "is_support": {
                "type": "boolean",
                "description": "BloomText Support acted, while helping your organization."
              }
            }
          },
          "member_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The member acted on, for member, role and team changes."
          },
          "patient_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "team_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "details": {
            "type": [
              "string",
              "null"
            ],
            "description": "What changed, such as a setting's old and new value. Never message text or patient details."
          }
        }
      },
      "AuditEventPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditEvent"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Org": {
        "type": "object",
        "required": [
          "id",
          "name",
          "is_default"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean",
            "description": "The account's default organization, where account-wide calls are recorded."
          }
        }
      },
      "OrgPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Org"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Member": {
        "type": "object",
        "required": [
          "id",
          "org_id",
          "email",
          "first_name",
          "last_name",
          "role",
          "account_role",
          "managed_by",
          "managed_by_name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The person's BloomText user ID."
          },
          "org_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "user"
            ],
            "description": "The role in this organization."
          },
          "account_role": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "owner",
              "it",
              null
            ],
            "description": "Owner or IT on the account, set only in BloomText."
          },
          "managed_by": {
            "type": "string",
            "enum": [
              "admin",
              "api",
              "account"
            ],
            "description": "Who changes this membership: people in BloomText, an API key, or the account (an Owner or IT person's membership in every organization)."
          },
          "managed_by_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The API key's name when `managed_by` is `api`."
          }
        }
      },
      "MemberPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Member"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "MemberCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "The person's email address. A new one creates their BloomText user."
          },
          "first_name": {
            "type": "string",
            "maxLength": 100
          },
          "last_name": {
            "type": "string",
            "maxLength": 100
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "user"
            ],
            "default": "user"
          },
          "title": {
            "type": "string",
            "maxLength": 100
          },
          "send_invite_email": {
            "type": "boolean",
            "default": false,
            "description": "Email them an invite to set a password, if they haven't signed in yet."
          }
        }
      },
      "MemberUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "user"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "Team": {
        "type": "object",
        "required": [
          "id",
          "org_id",
          "name",
          "is_default"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "org_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          }
        }
      },
      "TeamPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Team"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "TeamMemberAdd": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "member_id"
        ],
        "properties": {
          "member_id": {
            "type": "string",
            "format": "uuid",
            "description": "A member of the team's organization."
          }
        }
      },
      "MemberEvent": {
        "type": "object",
        "required": [
          "type",
          "member_id",
          "org_id",
          "role",
          "occurred_at",
          "actor_name"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "added",
              "approved",
              "removed"
            ]
          },
          "member_id": {
            "type": "string",
            "format": "uuid"
          },
          "org_id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who made the change: a person's name, or `API key: <name>`."
          }
        }
      },
      "MemberEventPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MemberEvent"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Profile": {
        "type": "object",
        "required": [
          "user",
          "organization",
          "app",
          "scopes",
          "expires_at"
        ],
        "properties": {
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "organization": {
            "$ref": "#/components/schemas/Organization"
          },
          "app": {
            "$ref": "#/components/schemas/App"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scopes the user granted your app."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "When this grant reaches its 12-month limit and the user must approve your app again."
          }
        }
      },
      "Organization": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Stable organization UUID."
          },
          "name": {
            "type": "string",
            "description": "Organization display name."
          }
        }
      },
      "App": {
        "type": "object",
        "required": [
          "client_id",
          "name"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "description": "Your app's OAuth client ID."
          },
          "name": {
            "type": "string",
            "description": "Your app's name as users and staff see it."
          }
        },
        "description": "A registered app."
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "name",
          "role"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the user."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "role": {
            "type": "string",
            "description": "The user's role in the organization."
          }
        }
      },
      "Patient": {
        "type": "object",
        "required": [
          "id",
          "first_name",
          "last_name",
          "date_of_birth",
          "phone",
          "email",
          "mrn",
          "sms_opted_out",
          "sms_undeliverable_reasons",
          "conversation_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the patient."
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "First name. Null for a patient created from an unknown phone number."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last name."
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date of birth."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mobile number in E.164 format."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address."
          },
          "mrn": {
            "type": [
              "string",
              "null"
            ],
            "description": "The patient's ID in your system, such as an MRN. Up to 36 characters, unique in the organization."
          },
          "sms_opted_out": {
            "type": "boolean",
            "description": "Whether your organization's numbers can't text the patient, for any reason: true exactly when sms_undeliverable_reasons isn't empty. Secure-link messages by email still work."
          },
          "sms_undeliverable_reasons": {
            "type": "array",
            "description": "Why your organization's numbers can't text the patient, most important first: opted_out (the patient replied STOP to one of your numbers, or staff recorded their opt-out; the patient lifts it by texting START), carrier_blocked (the carrier refused a text because the patient replied STOP to that number; they lift it by texting START to one of your numbers), admin_blocked (an admin blocked the number; only an admin can unblock it), unreachable (an earlier text failed because the number is a landline or its carrier can't be reached; it clears when the patient texts one of your numbers, their phone number is changed, or an admin clears it). Each reason blocks every one of your numbers. Empty when the patient can be texted.",
            "items": {
              "type": "string",
              "enum": [
                "opted_out",
                "carrier_blocked",
                "admin_blocked",
                "unreachable"
              ]
            }
          },
          "conversation_count": {
            "type": "integer",
            "minimum": 0,
            "description": "How many conversations the patient has in the organization, on any phone number."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the patient was created."
          },
          "matched_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "mrn",
              "phone",
              "email",
              null
            ],
            "description": "On Create a patient: which field matched an existing patient. Null when the patient was just created."
          }
        }
      },
      "PatientDetails": {
        "additionalProperties": false,
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "maxLength": 100,
            "description": "First name."
          },
          "last_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Last name."
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "Date of birth."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address."
          },
          "mrn": {
            "type": "string",
            "maxLength": 36,
            "description": "The patient's ID in your system."
          }
        },
        "description": "Details for a patient BloomText creates while sending. Ignored when the phone number matches an existing patient."
      },
      "PatientCreate": {
        "additionalProperties": false,
        "type": "object",
        "anyOf": [
          {
            "required": [
              "phone"
            ]
          },
          {
            "required": [
              "email"
            ]
          }
        ],
        "properties": {
          "first_name": {
            "type": "string",
            "maxLength": 100,
            "description": "First name."
          },
          "last_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Last name."
          },
          "date_of_birth": {
            "type": "string",
            "format": "date",
            "description": "Date of birth."
          },
          "phone": {
            "type": "string",
            "description": "Mobile number. Any common format; BloomText stores it as E.164."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address."
          },
          "mrn": {
            "type": "string",
            "maxLength": 36,
            "description": "The patient's ID in your system. Matched first."
          }
        },
        "description": "Provide a phone number, an email address, or both."
      },
      "PatientUpdate": {
        "additionalProperties": false,
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "maxLength": 100,
            "description": "First name."
          },
          "last_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Last name."
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Date of birth."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mobile number."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Email address."
          },
          "mrn": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 36,
            "description": "The patient's ID in your system."
          }
        },
        "description": "Only the fields you send change. Send null to clear a field."
      },
      "PatientRef": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the patient."
          },
          "name": {
            "type": "string",
            "description": "The patient's name, or their phone number if they have no name yet."
          }
        },
        "description": "The patient a conversation or message is with."
      },
      "PatientImportCreate": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "patients"
        ],
        "properties": {
          "patients": {
            "type": "array",
            "maxItems": 10000,
            "items": {
              "$ref": "#/components/schemas/PatientCreate"
            },
            "description": "Up to 10,000 patients."
          }
        },
        "description": "Patients to import. To send CSV instead, use `Content-Type: text/csv`."
      },
      "PatientImport": {
        "type": "object",
        "required": [
          "id",
          "status",
          "rows",
          "created",
          "matched",
          "failed",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique ID of the import."
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed"
            ],
            "description": "Progress of the import."
          },
          "rows": {
            "type": "integer",
            "minimum": 0,
            "description": "Rows received."
          },
          "created": {
            "type": "integer",
            "minimum": 0,
            "description": "Rows that created a new patient."
          },
          "matched": {
            "type": "integer",
            "minimum": 0,
            "description": "Rows that matched an existing patient."
          },
          "failed": {
            "type": "integer",
            "minimum": 0,
            "description": "Rows that couldn't be imported."
          },
          "results": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/PatientImportRow"
            },
            "description": "One result per row, once the import finishes. Null while it's running."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the import started."
          }
        }
      },
      "PatientImportRow": {
        "type": "object",
        "required": [
          "row",
          "outcome"
        ],
        "properties": {
          "row": {
            "type": "integer",
            "minimum": 1,
            "description": "Row number, starting at 1."
          },
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "matched",
              "failed"
            ],
            "description": "What happened to the row."
          },
          "matched_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "mrn",
              "phone",
              "email",
              null
            ],
            "description": "Which field matched, for `matched` rows."
          },
          "patient_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The created or matched patient."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the row failed, like `phone_or_email_required`."
          }
        },
        "description": "The outcome of one imported row."
      },
      "PhoneNumber": {
        "type": "object",
        "required": [
          "number",
          "team",
          "is_default"
        ],
        "properties": {
          "number": {
            "type": "string",
            "description": "The phone number in E.164 format, like +15125550100. Pass it as `from` when sending."
          },
          "team": {
            "type": "string",
            "description": "The team the number belongs to, like \"Front desk\". Patient replies go to this team's conversation."
          },
          "is_default": {
            "type": "boolean",
            "description": "Whether sends use this number when they don't pass `from`."
          }
        }
      },
      "PhoneNumberRef": {
        "type": "object",
        "required": [
          "number",
          "team"
        ],
        "properties": {
          "number": {
            "type": "string",
            "description": "The phone number."
          },
          "team": {
            "type": "string",
            "description": "The team it belongs to."
          }
        },
        "description": "The clinic phone number a patient conversation is on."
      },
      "Recipient": {
        "additionalProperties": false,
        "type": "object",
        "oneOf": [
          {
            "required": [
              "patient_id"
            ]
          },
          {
            "required": [
              "phone"
            ]
          }
        ],
        "properties": {
          "patient_id": {
            "type": "string",
            "format": "uuid",
            "description": "The patient to message."
          },
          "phone": {
            "type": "string",
            "description": "A mobile number. BloomText finds the patient with this number, or creates one."
          },
          "patient": {
            "$ref": "#/components/schemas/PatientDetails"
          }
        },
        "description": "Who to message. Provide exactly one of patient_id or phone."
      },
      "MessageSend": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "to",
          "body",
          "channel"
        ],
        "properties": {
          "to": {
            "$ref": "#/components/schemas/Recipient"
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40000,
            "description": "Message text: up to 1,600 characters for `sms`, or 40,000 for `secure`. A longer one returns `422 too_long`."
          },
          "channel": {
            "type": "string",
            "enum": [
              "secure",
              "sms"
            ],
            "description": "Required; there's no default. `sms` sends the text itself as a regular SMS from the clinic's number. `secure` sends a text or email with a secure link, and the message stays in BloomText."
          },
          "from": {
            "type": "string",
            "description": "The clinic phone number to send from, in E.164 format, like +15125550100. Defaults to the API key's default number, else the organization's only number; an organization with several numbers and no default answers 422 `from_required`. See List phone numbers."
          }
        },
        "description": "A message to a patient."
      },
      "MessageBatch": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "$ref": "#/components/schemas/MessageSend"
            },
            "description": "Up to 60 messages, each the body of a single send."
          }
        },
        "description": "Messages to send in one request."
      },
      "BatchResult": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One result per message, in the order they were sent.",
            "items": {
              "type": "object",
              "required": [
                "index"
              ],
              "properties": {
                "index": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "The message's position in `messages`, from 0."
                },
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The new message's ID, when it was queued."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "queued",
                    "consent_requested"
                  ],
                  "description": "`queued` when the message was accepted, or `consent_requested` when it waits for the patient's consent (see the 202 of a single send). Its delivery status is on the message."
                },
                "error": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "SendResult": {
        "type": "object",
        "required": [
          "message",
          "patient_created",
          "conversation_created"
        ],
        "properties": {
          "message": {
            "$ref": "#/components/schemas/Message"
          },
          "patient_created": {
            "type": "boolean",
            "description": "Whether BloomText created the patient for a new phone number."
          },
          "conversation_created": {
            "type": "boolean",
            "description": "Whether this message started a new conversation."
          }
        }
      },
      "Campaign": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "channel",
          "body",
          "phone_number",
          "sender",
          "send_interval_seconds",
          "counts",
          "archived",
          "created_at",
          "started_at",
          "finished_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the campaign."
          },
          "name": {
            "type": "string",
            "description": "A name for staff, like \"Flu shot reminders, October\". Patients never see it."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "running",
              "paused",
              "completed",
              "stopped"
            ],
            "description": "`draft` until started; `completed` once every recipient is sent; `stopped` if someone stopped it early."
          },
          "channel": {
            "type": "string",
            "enum": [
              "secure",
              "sms"
            ],
            "description": "How recipients receive the message. Secure unless the campaign asks for SMS."
          },
          "body": {
            "type": "string",
            "description": "The message every recipient gets."
          },
          "phone_number": {
            "$ref": "#/components/schemas/PhoneNumberRef"
          },
          "sender": {
            "$ref": "#/components/schemas/CampaignSender"
          },
          "send_interval_seconds": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 30,
            "description": "Seconds between messages."
          },
          "counts": {
            "$ref": "#/components/schemas/CampaignCounts"
          },
          "archived": {
            "type": "boolean",
            "description": "Whether the campaign is archived."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the campaign was created."
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it first started. Null for a draft."
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it completed or was stopped. Null otherwise."
          }
        }
      },
      "CampaignSender": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the admin or API key."
          },
          "name": {
            "type": "string",
            "description": "The API identity's name, like \"Appointment reminders\", or, until identities are on for the account, the API key's name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "api_key",
              "api_identity"
            ],
            "description": "`api_identity` when the key belongs to an API identity (an automated sender, not a person or a seat), else `api_key`."
          }
        },
        "description": "Who the campaign's messages come from."
      },
      "CampaignCounts": {
        "type": "object",
        "required": [
          "recipients",
          "pending",
          "sent",
          "delivered",
          "failed",
          "skipped"
        ],
        "properties": {
          "recipients": {
            "type": "integer",
            "minimum": 0,
            "description": "Patients on the campaign."
          },
          "pending": {
            "type": "integer",
            "minimum": 0,
            "description": "Waiting to be sent."
          },
          "sent": {
            "type": "integer",
            "minimum": 0,
            "description": "Sent, including delivered."
          },
          "delivered": {
            "type": "integer",
            "minimum": 0,
            "description": "Confirmed delivered or opened."
          },
          "failed": {
            "type": "integer",
            "minimum": 0,
            "description": "Failed or undelivered."
          },
          "skipped": {
            "type": "integer",
            "minimum": 0,
            "description": "Not sent: opted out, no contact method, or cancelled."
          }
        },
        "description": "Progress across the campaign's recipients."
      },
      "CampaignCreate": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name",
          "body"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "A name for staff."
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1600,
            "description": "The message, up to 1,600 characters. Every recipient gets the same text."
          },
          "channel": {
            "type": "string",
            "enum": [
              "secure",
              "sms"
            ],
            "default": "secure",
            "description": "`secure` (the default) or `sms`, as in Send a message to a patient."
          },
          "from": {
            "type": "string",
            "description": "The clinic phone number to send from, in E.164 format, like +15125550100. Defaults to the API key's default number, else the organization's only number; an organization with several numbers and no default answers 422 `from_required`. See List phone numbers."
          },
          "send_interval_seconds": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 30,
            "default": 1,
            "description": "Seconds between messages."
          }
        }
      },
      "CampaignUpdate": {
        "additionalProperties": false,
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "A name for staff."
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1600,
            "description": "The message. Draft or paused campaigns only."
          },
          "channel": {
            "type": "string",
            "enum": [
              "secure",
              "sms"
            ],
            "description": "`secure` or `sms`. Draft or paused campaigns only."
          },
          "from": {
            "type": "string",
            "description": "The phone number to send from. Draft campaigns only."
          },
          "send_interval_seconds": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 30,
            "description": "Seconds between messages."
          },
          "archived": {
            "type": "boolean",
            "description": "Archive or unarchive the campaign. Not while it's running."
          }
        },
        "description": "Only the fields you send change."
      },
      "CampaignRecipient": {
        "type": "object",
        "required": [
          "patient",
          "status",
          "message_id",
          "error",
          "updated_at"
        ],
        "properties": {
          "patient": {
            "$ref": "#/components/schemas/PatientRef"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sending",
              "sent",
              "delivered",
              "opened",
              "failed",
              "undelivered",
              "skipped",
              "cancelled"
            ],
            "description": "Where this recipient's message is. `skipped` means it couldn't be sent, and `cancelled` means the campaign was stopped first."
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The message sent to this patient, once sent. It's in the patient's conversation on the campaign's phone number."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it failed or was skipped, like `opted_out`, `no_contact_method`, or `landline`."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the status last changed."
          }
        },
        "description": "One patient on a campaign."
      },
      "CampaignRecipientsChange": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "patient_ids"
        ],
        "properties": {
          "patient_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10000,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Up to 10,000 patient IDs."
          }
        }
      },
      "CampaignRecipientsResult": {
        "type": "object",
        "required": [
          "changed",
          "skipped"
        ],
        "properties": {
          "changed": {
            "type": "integer",
            "minimum": 0,
            "description": "Patients added or removed."
          },
          "skipped": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SkippedPatient"
            },
            "description": "Patients left unchanged, and why."
          }
        }
      },
      "SkippedPatient": {
        "type": "object",
        "required": [
          "patient_id",
          "reason"
        ],
        "properties": {
          "patient_id": {
            "type": "string",
            "format": "uuid",
            "description": "The patient."
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_found",
              "already_added",
              "not_on_campaign",
              "already_sent"
            ],
            "description": "Why the patient was left unchanged."
          }
        }
      },
      "Delivery": {
        "type": "object",
        "required": [
          "status",
          "updated_at"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "sent",
              "delivered",
              "failed",
              "undelivered",
              "rejected",
              "consent_requested"
            ],
            "description": "Where the message is: `queued` (accepted, not yet with the carrier), `sent` (with the carrier, or a secure message sent), `delivered` (the carrier confirmed it), `failed` or `undelivered` (it didn't reach the patient), `rejected` (BloomText didn't send it, because the patient opted out, the carrier blocks texts to them, an admin blocked the number, the number can't receive texts, the organization requires consent and the patient hasn't given it, or the text waited for consent and wasn't sent), or `consent_requested` (the organization requires consent before texting: BloomText asked the patient, and the message goes when they reply YES). For a message to several patients, the worst of theirs: `failed` if any failed, `delivered` only when all were."
          },
          "error_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it wasn't delivered: the carrier's code, like `30005`, or BloomText's reason: `patient_opted_out`, `carrier_blocked`, `number_blocked`, `number_unreachable`, `consent_required` (the organization requires consent and the patient hasn't given it) or `consent_not_sent` (the text waited for consent and wasn't sent). Null otherwise."
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "What `error_code` means, in plain English. Null when there's no error."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the status last changed. Carrier reports arrive within minutes."
          },
          "recipients": {
            "type": "array",
            "description": "For a message to several patients (a group conversation), each one's delivery. Absent for one patient.",
            "items": {
              "type": "object",
              "required": [
                "patient_id",
                "status",
                "error_code",
                "detail",
                "updated_at"
              ],
              "properties": {
                "patient_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The patient."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "queued",
                    "sent",
                    "delivered",
                    "failed",
                    "undelivered",
                    "rejected",
                    "consent_requested"
                  ],
                  "description": "This patient's status."
                },
                "error_code": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Why it wasn't delivered to this patient."
                },
                "detail": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "What `error_code` means."
                },
                "updated_at": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When this patient's status last changed."
                }
              }
            }
          }
        },
        "description": "Delivery status of a message to patients. Messages from patients have no delivery (`null`)."
      },
      "Conversation": {
        "type": "object",
        "required": [
          "id",
          "organization_id",
          "type",
          "title",
          "created_at",
          "last_message_at",
          "unread_count",
          "seen_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the conversation."
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "Organization the conversation belongs to."
          },
          "type": {
            "type": "string",
            "enum": [
              "patient",
              "direct",
              "group",
              "broadcast"
            ],
            "description": "A conversation with a patient, or a staff direct message, group, or broadcast. API keys see only `patient`."
          },
          "title": {
            "type": "string",
            "description": "The conversation's name as the user sees it in BloomText."
          },
          "patient": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PatientRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient, for patient conversations. Null otherwise."
          },
          "phone_number": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PhoneNumberRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clinic phone number, for patient conversations by SMS. Null otherwise."
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "secure",
              "sms",
              null
            ],
            "description": "How the patient receives messages, for patient conversations. Null otherwise."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the conversation was created."
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the latest message was sent. Null for a conversation with no messages."
          },
          "unread_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Always 0 with an API key, which has no read state. With OAuth, messages the person hasn't read."
          },
          "seen_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Always null with an API key. With OAuth, when the person last read the conversation."
          },
          "messages_in_range": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "On List conversations with `after` or `before`: how many of its messages fell in the range. Null otherwise."
          },
          "latest_message": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Message"
              },
              {
                "type": "null"
              }
            ],
            "description": "On List conversations: the conversation's latest message in the range. Null otherwise."
          }
        }
      },
      "ConversationRef": {
        "type": "object",
        "required": [
          "id",
          "title"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the conversation."
          },
          "title": {
            "type": "string",
            "description": "The conversation's name as the user sees it."
          }
        },
        "description": "The conversation a message belongs to."
      },
      "Sender": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "ID of the sender."
          },
          "name": {
            "type": "string",
            "description": "The sender's display name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "staff",
              "patient",
              "api_key",
              "api_identity"
            ],
            "description": "A staff member, a patient, an API key (`name` is the key's name, like \"Reminder service\"), or an API identity (the automated sender the key belongs to, like \"Appointment reminders\"; not a staff member)."
          }
        },
        "description": "Who sent a message."
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "conversation",
          "sender",
          "patient",
          "channel",
          "created_at",
          "type",
          "unread"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the message."
          },
          "conversation": {
            "$ref": "#/components/schemas/ConversationRef"
          },
          "sender": {
            "$ref": "#/components/schemas/Sender"
          },
          "patient": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PatientRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The patient in the conversation, for patient conversations. Null otherwise."
          },
          "channel": {
            "type": "string",
            "enum": [
              "app",
              "secure",
              "sms"
            ],
            "description": "How the message traveled: inside BloomText (`app`), by secure link, or by plain SMS."
          },
          "phone_number": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PhoneNumberRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clinic phone number, for SMS messages. Null otherwise."
          },
          "delivery": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Delivery"
              },
              {
                "type": "null"
              }
            ],
            "description": "Delivery status, for messages sent to a patient. Null otherwise."
          },
          "campaign_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The campaign that sent this message. Null otherwise."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the message was sent."
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the message was last edited. Null if it never was."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "file"
            ],
            "description": "Whether the message carries text or a file."
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Message text. Null for file messages."
          },
          "file": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FileReference"
              },
              {
                "type": "null"
              }
            ],
            "description": "File metadata. Null for text messages."
          },
          "reply_to_message_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Message this is a reply to. Null for top-level messages."
          },
          "unread": {
            "type": "boolean",
            "description": "Always false with an API key, which has no read state. With OAuth, whether the connected person hasn't read it."
          },
          "sent_via": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/App"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null for messages sent in BloomText or with an API key. With OAuth, the app that sent it for the connected person; staff see this, patients don't."
          }
        }
      },
      "MessageCreate": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000,
            "description": "Message text, up to 10,000 characters."
          },
          "reply_to_message_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Set to reply in a thread."
          }
        },
        "description": "A text message. Sending files through the API isn't available yet."
      },
      "FileReference": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique ID of the file."
          },
          "name": {
            "type": "string",
            "description": "Original file name."
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "MIME type, such as `application/pdf`."
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "File size in bytes."
          }
        },
        "description": "Metadata for a file attached to a message."
      },
      "Reaction": {
        "type": "object",
        "required": [
          "message_id",
          "user_id",
          "emoji",
          "created_at"
        ],
        "properties": {
          "message_id": {
            "type": "string",
            "format": "uuid",
            "description": "Message the reaction is on."
          },
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "User who reacted."
          },
          "emoji": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8,
            "description": "The emoji."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the reaction was added."
          }
        }
      },
      "ReactionCreate": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "emoji"
        ],
        "properties": {
          "emoji": {
            "type": "string",
            "minLength": 1,
            "maxLength": 8,
            "description": "A single emoji, such as 👍."
          }
        }
      },
      "Participant": {
        "type": "object",
        "required": [
          "user_id",
          "name",
          "kind",
          "conversation_id",
          "joined_at"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid",
            "description": "The participating user or patient."
          },
          "name": {
            "type": "string",
            "description": "Display name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "staff",
              "patient"
            ],
            "description": "Whether the participant is a staff member or a patient."
          },
          "conversation_id": {
            "type": "string",
            "format": "uuid",
            "description": "The conversation."
          },
          "joined_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the participant joined the conversation."
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": [
          "next_cursor",
          "has_more"
        ],
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `page[after]` to get the next page. Null on the last page."
          },
          "has_more": {
            "type": "boolean",
            "description": "Whether more results may exist. An empty `data` comes with `has_more: false`, except when `partial` is true."
          },
          "partial": {
            "type": "boolean",
            "description": "Only on a filtered message read that ran out of time before finding a match: `data` is empty, but the range isn't finished. Follow `next_cursor`. Absent otherwise.",
            "x-bloomtext-sample": "omit"
          }
        }
      },
      "CampaignPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Campaign"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "CampaignRecipientPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CampaignRecipient"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "PatientPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Patient"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "PhoneNumberPage": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhoneNumber"
            }
          }
        }
      },
      "UserPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/User"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "ConversationPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conversation"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "MessagePage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "ReactionPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Reaction"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "ParticipantPage": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Participant"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "request_id",
          "field_errors"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URL identifying the problem type."
          },
          "title": {
            "type": "string",
            "description": "Short summary of the problem."
          },
          "status": {
            "type": "integer",
            "enum": [
              400,
              401,
              403,
              404,
              405,
              409,
              422,
              429,
              500,
              503
            ],
            "description": "HTTP status code."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable error code."
          },
          "detail": {
            "type": "string",
            "description": "Explanation of this occurrence."
          },
          "request_id": {
            "type": "string",
            "format": "uuid",
            "description": "Include this when contacting support."
          },
          "field_errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            },
            "description": "Per-field validation errors, for 422 responses."
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PatientRef"
            },
            "description": "For `ambiguous_recipient` and `recipient_mismatch`: the matching patients."
          },
          "numbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhoneNumberRef"
            },
            "description": "For `from_required`: the organization's numbers, any of which can be `from`."
          },
          "number": {
            "type": "string",
            "description": "For `patient_opted_out`, `carrier_blocked`, `number_blocked` and `number_unreachable`: the number the text would have come from, in E.164 format. None of the organization's numbers can text the patient."
          },
          "since": {
            "type": "string",
            "format": "date-time",
            "description": "For `patient_opted_out`, `carrier_blocked`, `number_blocked` and `number_unreachable`: when the opt-out or block began, or for `number_unreachable`, when the carrier reported the failed text."
          },
          "length": {
            "type": "integer",
            "minimum": 0,
            "description": "For `too_long`: the body's length in characters."
          },
          "max": {
            "type": "integer",
            "minimum": 0,
            "description": "For `too_long`: the most characters allowed on that channel."
          },
          "reason": {
            "type": "string",
            "enum": [
              "would_delete_user"
            ],
            "description": "For `confirmation_required`: what the call must confirm."
          }
        },
        "description": "RFC 9457 problem details. See Errors for the status codes."
      },
      "FieldError": {
        "type": "object",
        "required": [
          "path",
          "code",
          "message"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "JSON pointer or query/header name."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable validation code."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation."
          }
        }
      }
    }
  }
}
