openapi: '3.0.3'
info:
  title: BulkSMS JSON REST API
  version: "1.1.0"
  x-logo:
    url: /developer/images/bulksms.png
    backgroundColor: '#FFFFFF'
  description: |
    ## Overview

    The JSON REST API allows you to submit and receive [BulkSMS](https://www.bulksms.com/) messages. You can also get access to past messages and see your account profile.

    The base URL to use for this service is `https://api.bulksms.com/v1`.  The base URL cannot be used on its own; you must append a path that identifies an operation and you may have to specify some path parameters as well.

    [Click here](/developer/) to go to the main BulkSMS developer site.

    In order to give you an idea on how the API can be used, some JSON snippets are provided below.  Have a look at the [messages section](#tag/message) for more information.

    Probably the most simple example

    ```
    {
        "to": "+447700900000",
        "body": "Hello World!"
    }
    ```


    You can send unicode automatically using the `auto-unicode` query parameter.
    Alternatively, you can specify `UNICODE` for the `encoding` property in the request body.
    Please note: when `auto-unicode` is specified and the value of the `encoding` property is `UNICODE`, the message will always be sent as `UNICODE`.

    Here is an example that sets the `encoding` explicitly

    ```
    {
      "to": "+447700900000",
      "body": "Dobrá práce! Jak se máš?",
      "encoding": "UNICODE"
    }
    ```

    You can also specify a from number

    ```
    {
        "from": "+447911123456",
        "to": "+447700900000",
        "body": "Hello World!"
    }
    ```

    Similar to above, but repliable

    ```
    {
        "from": { "type": "REPLIABLE" },
        "to": "+447700900000",
        "body": "Hello World!"
    }
    ```

    A message to a group called Everyone

    ```
    {
        "to": { "type": "GROUP", "name": "Everyone" },
        "body": "Hello World!"
    }
    ```

    A message to multiple recipients

    ```
    {
        "to": ["+447700900000", "+447700900001", "+447700900002"],
        "body": "Happy Holidays!"
    }
    ```

    Sending more than one message in the same request

    ```
    [
        {
            "to": "+447700900000",
            "body": "Hello World!"
        },
        {
            "to": "+447700900001",
            "body": "Hello Universe!"
        }
    ]
    ```

    **The insecure base URL `http://api.bulksms.com/v1` is deprecated** and may in future result in a `301` redirect response, or insecure requests may be rejected outright. Please use the secure (`https`) URI above.

    ### HTTP Content Type

    All API methods expect requests to supply a `Content-Type` header with the value `application/json`. All responses will have the `Content-Type` header set to `application/json`.

    ### JSON Formatting

    You are advised to format your JSON resources according to strict JSON format rules. While the API does attempt to parse strictly invalid JSON documents, doing so may lead to incorrect interpretation and unexpected results.

    Good JSON libraries will produce valid JSON suitable for submission, but if you are manually generating the JSON text, be careful to follow the JSON format. This includes correct escaping of control characters and double quoting of property names.

    See the [JSON specification](https://tools.ietf.org/html/rfc4627) for further information.

    ### Date Formatting

    Dates are formatted according to ISO-8601, such as `1970-01-01T10:00:00+01:00` for 1st January 1970, 10AM UTC+1.

    See the [Wikipedia ISO 8601 reference](https://en.wikipedia.org/wiki/ISO_8601) for further information.

    Specifically, calendar dates are formatted with the 'extended' format `YYYY-MM-DD`. Basic format, week dates and ordinal dates are not supported. Times are also formatted in the 'extended' format `hh:mm:ss`. Hours, minutes and seconds are mandatory. Offset from UTC must be provided; this is to ensure that there is no misunderstanding regarding times provided to the API.

    The format we look for is `yyyy-MM-ddThh:mm:ss[Z|[+-]hh:mm]`

    Examples of valid date/times are `2011-12-31T12:00:00Z` `2011-12-31T12:00:00+02:00`

    ### Entity Format Modifications

    It is expected that over time some changes will be made to the request and response formats of various methods available in the API.
    Where possible, these will be implemented in a backwards compatible way.
    To make this possible you are required to ignore unknown properties.
    This enables the addition of information in response documents while maintaining compatibility with older clients.

    ### Optional Request Entity Properties

    There are many instances where requests can be made without having to specify every single property allowable in the request format.
    Any such optional properties are noted as such in the documentation and their default value is noted.

servers:
  - url: https://api.bulksms.com/v1
    description: Production

tags:
  - name: Authentication
    description: |
      All API requests require authentication using [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) with an **API Token**.

      ### Creating an API Token

      Create a new token in your account at [Settings > Developers > API Tokens](https://www.bulksms.com/account/ui/index.html#/account/dev/api-tokens).

      ### Using the Token

      Supply the **token ID** as the username and the **token secret** as the password. The values must be [Base64 encoded](https://en.wikipedia.org/wiki/Base64) before including them in the `Authorization` header.

      For example, if the token ID is `BBDE1B476E03498AA768F66A286AABDC-01-B` and the token secret is `9jSbVDK20!MXdfRGiIIFu#ffUE8*S`, the full header becomes:

      ```
      Authorization: Basic QkJERTFCNDc2RTAzNDk4QUE3NjhGNjZBMjg2QUFCREMtMDEtQjo5alNiVkRLMjAhTVhkZlJHaUlJRnUjZmZVRTgqUw==
      ```

      ### Tips

      - Provide the `Authorization` header in every request. Do not wait for a `401` response before authenticating -- this reduces round trips and improves performance.
      - Most HTTP libraries handle Basic Auth natively. For example, with `curl`: `curl -u TOKEN_ID:TOKEN_SECRET https://api.bulksms.com/v1/messages`

      > **Username and password authentication is no longer supported.** Accounts created after 2026-04-29 can only authenticate with an API token. If you have a legacy integration that cannot be switched to token-based authentication, please [contact Support](https://www.bulksms.com/contact/) to request an exemption.
  - name: Message
    description: |
      A message record represents an SMS that has been sent or received.

      To send messages, create a `SubmissionEntry` and submit it to the Send Messages Operation.

      You can retrieve sent and received messages in a number of ways.

      ### Retrieving messages by status

      Messages are assigned a status as they progress through their lifecycle.  You can use the List Messages Operation to filter by various statuses.  For the status values used to filter, see the `status.type` and `status.subtype` values of the `Message` object.  You supply a value for the `filter` query parameter following this pattern `status.type==SENT`.  Since more than one messages are normally returned, you can also paginate the result using the `sortOrder` and `limit` query parameters.  Note that `limit` is not the only way to limit the number of records.  The result will also be limited by the `filter.submissionDate` property.  The default value for this property is `NOW() - 7 days`.

      Messages can also be retrieved by the webhook mechanism.  See the [webhooks page](/developer/webhooks/) for more information.

      ### Sending and receiving messages

      The examples shown in this section are for a single message submission.

      If you need to send a single text message to one or more recipients you will need to create a `SubmissionEntry` object and submit it to the Send Messages Operation.  For more advanced submission types, make use of the [Send Messages Operation](#tag/message/POST/messages) which supports batch sending (by submitting an array) and scheduling (via the `schedule-date` query parameter).

      The following fields can be set for a `SubmissionEntry`:
      - the `to` field is required and contains the phone number or group details
      - the `body` field is required and contains the message content
      - other fields like `from`, `routingGroup`, `encoding`, etc. are optional

      ### Body Templates

      When sending a message you can use template fields to customise the message text.

      *Field based templates* allow you to create a message with place-holders for custom fields.  Fields are identified by a zero based index; the first field is `F0`, the second is `F1` and so on.

      For example, if you want to send a message "Welcome Jack, you owe $200.00", supply the body as `"Welcome {F0######}, you owe {F1######}"` and send `"fields": ["Jack", "$200.00"]` in the relevant `to` entry.  The `#` characters specify the maximum length of the field.

      If you are sending to contacts in your phonebook, you can use *Phonebook based templates*.  The template for the contact's first name is `{fn######}` and the surname is `{sn######}`.

      See the [Send Messages Operation](#tag/message/POST/messages) for detailed examples.
  - name: Profile
    description: |
      Operations related to the profile of the user
  - name: Webhooks
    description: |
      Manage webhooks that notify your server when messages are sent or received.

      For full details on implementing your webhook — including event types, request headers, payload structure, retry logic, and testing — see the [Webhook Developer Guide](/developer/webhooks/).
  - name: Credits
    description: |
      Credit management operations
  - name: Blocked Numbers
    description: |
      Blocked number management operations
  - name: Attachments
    description: |
      Operations related to SMS attachments

security:
  - basicAuth: []

paths:
  /messages:
    get:
      tags: [Message]
      operationId: message.list
      summary: Retrieve Messages
      description: |
        Retrieve the messages you have sent or received.

        Scheduled messages are available for retrieval only after the delivery date.

        All the parameters are optional.  If a value is not supplied for `filter`, the messages are not filtered.

        Messages can be filtered by supplying query clauses in the `filter` parameter. Each clause has the form `name=value` where `name` is the name of a filter field and `value` is a valid value for that field.  A value for a field is optional. Include a clause for a field in the filter only when there is a need to fetch messages that match some value for that field.
        For a numeric filter field, you can also use the less than operator (`<`).

        If present, the filter value must have at least one clause, but it can contain a combination of clauses. Multiple clauses are separated with the `&` symbol.  Semantically, multiple clauses form a [logical conjunction](https://en.wikipedia.org/wiki/Logical_conjunction).

        For example, if you want to list all messages that were sent as part of a particular submission, your filter contains two clauses and will look something like this
        ```
        type%3DSENT&submission.id%3D1-00000000000522347562
        ```
        Because `filter` is a request parameter, it is important to note that the value for this parameter must be *URL encoded*. In particular, the `=` encodes to `%3D` and the `&` encodes to `%26`.  Note that you do not have to encode the `<` character.

        Using the previous example to illustrate; after encoding and encasing it, the clauses are transformed into a request that looks like this
        ```
        GET /v1/messages?filter=type%3DSENT%26submission.id%3D1-00000000000522347562
        ```
        If the field name or the field value of a clause is not valid, a [bad_request error](errors#bad-request) is returned instead of the usual result.  The `detail` field of this error provides more information about the problem.

        The table below lists the fields available for filtering

        | Field | Type   | Values | Note and example |
        |-------|------|--------------------|------|
        | id            | Integer  | Positive integer  | Use the `id` field with `<` (or with `>`) to fetch messages that are older (or newer) than those that are already fetched. `filter=id<123456` |
        | type          | String  | SENT, RECEIVED  | SENT are Mobile Terminating (MT) SMSs; RECEIVED are Mobile Originating (MO) SMSs. `filter=type%3DSENT` |
        | submission.id | String  |  | `filter=submission.id%3D1-00000000000522347562` |
        | status.type   | String  | ACCEPTED, SENT, DELIVERED, FAILED  | See the message `status.type` field for more information. `filter=status.type%3DDELIVERED` |
        | status.id| String  |  | See the message `status.id` field for more information. `filter=status.id%3DFAILED.EXPIRED`|
        | submission.date | String | Formatted Date | A fully specified date (e.g. 2017-01-01T10:00:00+01:00).  Use this field with `<=`, `<`, `>` or `>=` to limit the values. `filter=submission.date%3E%3D2017-01-01T10%3A00%3A00%2B01%3A00` |
        | userSuppliedId  | String | | Use a string value you specified in the `userSuppliedId` property when you sent the message. Only `SENT` messages will be retrieved. `filter=userSuppliedId%3Dacc009876` |
      parameters:
        - in: query
          name: filter
          description: See the message filtering for more information.
          schema:
            type: string
        - in: query
          name: sortOrder
          description: |
            The default value is DESCENDING

            If the `sortOrder` is DESCENDING, the newest messages be first in the result.
            ASCENDING sort is useful if you want to pull messages in the order in which they were created.
          schema:
            type: string
            enum:
              - ASCENDING
              - DESCENDING
        - in: query
          name: limit
          description: |
            The maximum number of messages that are returned.  The default is 1000.

            The value of `limit` is not a guarantee that a specific number of messages will be in the response, even if there are more messages available.  Consider the case where you have 150 messages and you specify `limit=50`.  It is possible that only 49 messages will be returned.  The  way to make sure that there are no more messages is to submit a new call using the `id` filter field with the `<` operator (described above).
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: Contains the requested array of messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Message'
              example:
                - id: "12345"
                  type: SENT
                  from: "+447911123456"
                  to: "+447700900000"
                  body: "Hi there!"
                  encoding: TEXT
                  protocolId: 0
                  messageClass: 1
                  numberOfParts: 1
                  creditCost: 1.0
                  submission:
                    id: "submission-98765"
                    date: "2026-04-02T15:51:37.623Z"
                  status:
                    id: "DELIVERED.null"
                    type: DELIVERED
                    subtype: null
                  relatedSentMessageId: null
                  userSuppliedId: null
        '400':
          $ref: '#/components/responses/bad-request'
      x-codeSamples:
        - lang: C#
          label: C# Complete Example
          source: |
            using System;
            using System.Net.Http;
            using System.Net.Http.Headers;
            using System.Text;
            using System.Threading.Tasks;

            class Program
            {
                static async Task Main()
                {
                    var tokenId = "your_token_id";
                    var tokenSecret = "your_token_secret";

                    using var client = new HttpClient();
                    var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{tokenId}:{tokenSecret}"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);

                    // Filter to get only SENT messages
                    var response = await client.GetAsync("https://api.bulksms.com/v1/messages?filter=type%3DSENT");
                    var body = await response.Content.ReadAsStringAsync();

                    if (response.IsSuccessStatusCode)
                        Console.WriteLine(body);
                    else
                        Console.WriteLine($"Error {(int)response.StatusCode}: {body}");
                }
            }
    post:
      tags: [Message]
      operationId: message.send
      summary: Send Messages
      description: |
        Send messages to one or more recipients.

        You can post up to `50,000` messages in a batch.
        But note that the `deduplication-id` is set per submission, so it is recommended that you use a smaller number, like `4000` per submission in order to make resubmissions on network failures more practical.

        #### Repliability

        When a sent message is _repliable_,  the BulkSMS system can process an SMS response sent by your recipient.

        The message sent by your customer is called a mobile originating (MO) message and would be available under `RECEIVED` messages.
        You can obtain a list of MOs using the [retrieve messages API call](#tag/message/GET/messages).
        In addition you can also get a list of the MOs that are associated with a specific sent message (see the [list related messages API call](#tag/message/GET/messages/{id}/relatedReceivedMessages)).

        If you use a specific _sender id_ in the `from` property of the send message, the message will not be repliable.
        If you want a message to be repliable, you need to specify `REPLIABLE` in the `from.type` property.

        If you do not set the `from` property, your account settings are considered to determine whether or not the message is repliable.
        If the _default repliable_ setting on your account is _yes_ then the message will be repliable.
        If this setting is _no_, the message will not be repliable.


        #### Body templates

        When sending a message you can use template fields to customise the message text.

        *Field based templates* allow you to create a message with place-holders for custom fields.  Fields are identified by a zero based index; the first field is `F0`, the second is `F1` and so on.

        For example, let's say you want to send a daily SMS message to all your clients that tell them what their current balance is.  The `body` of the message could look something like this

        ```
        Good morning {F0######}, your balance is {F1######}
        ```

        In this message, the first field, `F0`, is the name  of the customer and the second field `F1` is the balance for that customer.  The `#` used to specify the maximum length  of the field.  Note that the maximum length allowed for the value includes the space taken by the braces, template name and hash symbol.  For example, the value `{F0#}` specifies a maximum length of `5`.  If the data is longer than this length, the data will be truncated when the message body is constructed.

        The data fields are provided in the property named `fields` in the `to` element.  Here is a complete example of how this might look

        ```
        {
          "body": "Good morning {F0######}, your balance is {F1######}",
          "to":  [
              {"address": "27456789","fields": ["Harry", "$1345.23"] },
              {"address": "27456785","fields": ["Sally", "$2345.58"] }
          ]
        }
        ```

        If you are sending to contacts (or to groups) in your phonebook, you can use the *Phonebook based templates*.  These are similar to the templates described above, but they have specific names. The template for the contact's first name is identified by `fn` and the template for the contact's surname is identified by `sn`.  Below is an example that will work if the numbers are registered in your phonebook.

        ```
        {
          "body": "Hi {fn######} {sn######}, have a great day!",
          "to":  [
              {"address": "27456789" },
              {"address": "27456785" }
          ]
        }
        ```
      parameters:
        - in: query
          name: auto-unicode
          default: false
          description: |
            Specifies how to deal with message text that contains characters not present in the GSM 03.38 character set.

            Messages that contain only GSM 03.38 characters are not affected by this setting.
            If the value is `true` then a message containing non-GSM 03.38 characters will be transmitted as a Unicode SMS (which is most likely more costly).

            Please note: when `auto-unicode` is `true` and the value of the `encoding` property is specified as `UNICODE`, the message will always be sent as `UNICODE`.

            If the value is `false` and the `encoding` property is `TEXT` then non-GSM 03.38 characters will be replaced by the `?` character.

            When using this setting on the API, you should take care to ensure that your message is _clean_.

            Invisible unicode and unexpected characters could unintentionally convert a message to `UNICODE`.  A common mistake is to use the backtick character (\`) which is unicode and will turn your `TEXT` message into a `UNICODE` message.
          schema:
            type: boolean
        - in: query
          name: schedule-date
          description: |
            Allows you to send a message in the future.

            An example value is `2019-02-18T13:00:00+02:00`.  It encodes to `2019-02-18T13%3A00%3A00%2B02%3A00`.
            Credits are deducted from your account immediately. Once submitted, scheduled messages cannot be changed or cancelled.
            The date can be a maximum of two years in the future. If the value is in the past, the message will be sent immediately.
            The date format requires you to supply an offset from UTC. You can decide to use the offset of your timezone, or maybe the zone of the recipient's location is more appropriate.
            If the destination is a group, the group members are determined at the time that you submit the message; not the time the message is scheduled to be sent.
          schema:
            type: string
            format: date-time
        - in: query
          name: schedule-description
          description: |
            A note that is stored together with a scheduled submission, which could be used to more easily identify the scheduled submission at a later date.

            The value of this field is ignored if the `schedule-date` is not provided.
            A value that is longer than 256 characters is truncated.
          schema:
            type: string
        - in: query
          name: deduplication-id
          description: |
            Safeguards against the possibility of sending the same messages more than once.

            If a communication failure occurs during a submission, you cannot be sure that the submission was processed; therefore you would have to submit it again. When you post the retry, you must use the `deduplication-id` of the original post. The BulkSMS system uses this ID to check that the request was not previously processed. (If it was previously processed, the submission will succeed, and the behaviour will be indistinguishable to you from a non-duplicated submission). The ID expires after about 12 hours.
          schema:
            type: integer
            format: int32
      requestBody:
        required: true
        description: The messages to send.
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/SubmissionEntry'
                - type: array
                  items:
                    $ref: '#/components/schemas/SubmissionEntry'
            example:
              to: "+447700900000"
              body: "Hi there!"
      responses:
        '201':
          description: An array of Message objects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Message'
              example:
                - id: "12345"
                  type: SENT
                  from: "+447911123456"
                  to: "+447700900000"
                  body: "Hi there!"
                  encoding: TEXT
                  protocolId: 0
                  messageClass: 1
                  numberOfParts: 1
                  creditCost: 1.0
                  submission:
                    id: "submission-98765"
                    date: "2026-04-02T15:51:37.623Z"
                  status:
                    id: "ACCEPTED.null"
                    type: ACCEPTED
                    subtype: null
                  relatedSentMessageId: null
                  userSuppliedId: null
        '400':
          $ref: '#/components/responses/bad-request'
        '403':
          $ref: '#/components/responses/credits'
      x-codeSamples:
        - lang: PHP
          label: PHP Complete Example
          source: |
            <?php
            // Requires cURL extension (included in most PHP installations).
            // To run under docker:
            // docker run -v $PWD:/code php:8.3-alpine php /code/send.php

            $tokenId = 'your_token_id';
            $tokenSecret = 'your_token_secret';
            $messages = [
                ['to' => '+447700900000', 'body' => 'Hello World!'],
                ['to' => '+447700900001', 'body' => 'Hello World!'],
            ];

            $ch = curl_init('https://api.bulksms.com/v1/messages?auto-unicode=true');
            curl_setopt_array($ch, [
                CURLOPT_POST           => true,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => 20,
                CURLOPT_CONNECTTIMEOUT => 10,
                CURLOPT_POSTFIELDS     => json_encode($messages),
                CURLOPT_HTTPHEADER     => [
                    'Content-Type: application/json',
                    'Authorization: Basic ' . base64_encode("$tokenId:$tokenSecret"),
                ],
            ]);

            $response = curl_exec($ch);
            $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $error = curl_error($ch);
            curl_close($ch);

            if ($status === 201) {
                echo "Response: $response\n";
            } else {
                echo "Error (HTTP $status): " . ($error ?: $response) . "\n";
            }
        - lang: C#
          label: C# Complete Example
          source: |
            using System;
            using System.Net.Http;
            using System.Net.Http.Headers;
            using System.Text;
            using System.Threading.Tasks;

            class Program
            {
                static async Task Main()
                {
                    var tokenId = "your_token_id";
                    var tokenSecret = "your_token_secret";
                    var json = """{"to": "1111111", "body": "Hello World!"}""";

                    using var client = new HttpClient();
                    var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{tokenId}:{tokenSecret}"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);

                    var content = new StringContent(json, Encoding.UTF8, "application/json");
                    var response = await client.PostAsync("https://api.bulksms.com/v1/messages", content);
                    var body = await response.Content.ReadAsStringAsync();

                    if (response.IsSuccessStatusCode)
                        Console.WriteLine(body);
                    else
                        Console.WriteLine($"Error {(int)response.StatusCode}: {body}");
                }
            }
        - lang: Java
          label: Java Complete Example
          source: |
            import java.net.URI;
            import java.net.http.HttpClient;
            import java.net.http.HttpRequest;
            import java.net.http.HttpResponse;
            import java.util.Base64;

            public class SendMessage {
                public static void main(String[] args) throws Exception {
                    var tokenId = "your_token_id";
                    var tokenSecret = "your_token_secret";
                    var json = """
                        {"to": "1111111", "encoding": "UNICODE", "body": "Dobrá práce! Jak se máš?"}
                        """;

                    var auth = Base64.getEncoder().encodeToString((tokenId + ":" + tokenSecret).getBytes());

                    var request = HttpRequest.newBuilder()
                        .uri(URI.create("https://api.bulksms.com/v1/messages"))
                        .header("Content-Type", "application/json")
                        .header("Authorization", "Basic " + auth)
                        .POST(HttpRequest.BodyPublishers.ofString(json))
                        .build();

                    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());

                    System.out.println("Status: " + response.statusCode());
                    System.out.println(response.body());
                }
            }
        - lang: Node.js
          label: Node.js Complete Example
          source: |
            const tokenId = 'your_token_id';
            const tokenSecret = 'your_token_secret';

            const response = await fetch('https://api.bulksms.com/v1/messages', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Basic ' + btoa(tokenId + ':' + tokenSecret),
              },
              body: JSON.stringify({
                to: ['+447700900000', '+447700900001'],
                body: 'Hello World!',
              }),
            });

            const data = await response.json();
            console.log('Status:', response.status);
            console.log('Response:', JSON.stringify(data, null, 2));
        - lang: Python
          label: Python Complete Example
          source: |
            import requests

            token_id = "your_token_id"
            token_secret = "your_token_secret"

            response = requests.post(
                "https://api.bulksms.com/v1/messages",
                auth=(token_id, token_secret),
                json={
                    "to": ["1111111", "1111112"],
                    "body": "Hello World!",
                    "encoding": "UNICODE",
                },
            )

            if response.ok:
                print(response.json())
            else:
                print(f"Error {response.status_code}: {response.text}")

  /messages/{id}:
    get:
      tags: [Message]
      operationId: message.show
      summary: Show Message
      description: |
        Get a message by its `id`.
        ```http
        GET /v1/messages/4023457654
        ```
      parameters:
        - in: path
          name: id
          required: true
          description: The `id` of the message you want to retrieve
          schema:
            type: string
      responses:
        '200':
          description: The message detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
              example:
                id: "12345"
                type: SENT
                from: "+447911123456"
                to: "+447700900000"
                body: "Hi there!"
                encoding: TEXT
                protocolId: 0
                messageClass: 1
                numberOfParts: 1
                creditCost: 1.0
                submission:
                  id: "submission-98765"
                  date: "2026-04-02T15:51:37.623Z"
                status:
                  id: "DELIVERED.null"
                  type: DELIVERED
                  subtype: null
                relatedSentMessageId: null
                userSuppliedId: null
        '404':
          description: The message cannot be found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                type: https://developer.bulksms.com/json/v1/errors/#not-found
                title: Not Found
                status: 404
                detail: "Message with id '123' does not exist"
        '400':
          $ref: '#/components/responses/bad-request'

  /messages/{id}/relatedReceivedMessages:
    get:
      tags: [Message]
      operationId: message.related.received
      summary: List Related Messages
      description: |
        Get the messages related to a sent message identified by `id`.

        For more information on how this work, see the `relatedSentMessageId` field of the `Message` object.
      parameters:
        - in: path
          name: id
          required: true
          description: The `id` of the sent message
          schema:
            type: string
      responses:
        '200':
          description: An array of related messages.  If a sent message with this `id` could not be found, or there are no related, received messages, the array will be empty.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Message'
              example:
                - id: "67890"
                  type: RECEIVED
                  from: "+447700900000"
                  to: "+447911123456"
                  body: "Thanks, got it!"
                  encoding: TEXT
                  protocolId: 0
                  messageClass: 1
                  numberOfParts: 1
                  creditCost: 0
                  submission:
                    id: "submission-98765"
                    date: "2026-04-02T16:02:10.000Z"
                  status:
                    id: "DELIVERED.null"
                    type: DELIVERED
                    subtype: null
                  relatedSentMessageId: "12345"
                  userSuppliedId: null
        '400':
          $ref: '#/components/responses/bad-request'

  /messages/send:
    get:
      tags: [Message]
      operationId: message.send.simple.get
      summary: Send message by simple GET or POST
      description: |
        A simple interface for sending a single message via GET.

        This is a convenience method, recommended for debugging or quick tests. It allows you to send a message from the browser address bar.

        For example:
        ```http
        GET /v1/messages/send?to=%2b270000000&body=Hello%20World
        ```

        You can also POST the same parameters as form-encoded fields to the [Send Messages](#tag/message/POST/messages) endpoint:
        ```http
        POST /v1/messages
        Content-Type: application/x-www-form-urlencoded

        to=%2b27000000000&body=Hello+World
        ```

        For production use, the [Send Messages](#tag/message/POST/messages) operation is recommended as it supports batch sending, templates, and scheduling.
      parameters:
        - in: query
          name: to
          required: true
          description: The phone number of the recipient.
          schema:
            type: string
        - in: query
          name: body
          required: true
          description: The text you want to send.
          schema:
            type: string
        - in: query
          name: deduplication-id
          description: Refer to the `deduplication-id` parameter.
          schema:
            type: integer
            format: int32
      responses:
        '201':
          description: An array of Message objects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Message'
              example:
                - id: "12345"
                  type: SENT
                  from: "+447911123456"
                  to: "+447700900000"
                  body: "Hi there!"
                  encoding: TEXT
                  protocolId: 0
                  messageClass: 1
                  numberOfParts: 1
                  creditCost: 1.0
                  submission:
                    id: "submission-98765"
                    date: "2026-04-02T15:51:37.623Z"
                  status:
                    id: "ACCEPTED.null"
                    type: ACCEPTED
                    subtype: null
                  relatedSentMessageId: null
                  userSuppliedId: null
        '400':
          $ref: '#/components/responses/bad-request'
        '403':
          $ref: '#/components/responses/credits'

  /profile:
    get:
      tags: [Profile]
      operationId: profile.show
      summary: Get profile
      description: |
        Returns information about the account.
      responses:
        '200':
          description: A Profile object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Profile'

  /webhooks:
    get:
      tags: [Webhooks]
      operationId: webhook.list
      summary: List webhooks
      description: |
        Returns a list of webhooks that have been created for this account.  See the [Webhook Developer Guide](/developer/webhooks/) for event types, payload structure, and implementation details.
      responses:
        '200':
          description: An array of Webhook objects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Webhook'
    post:
      tags: [Webhooks]
      operationId: webhook.create
      summary: Create a webhook
      description: |
        A webhook is a URL that you can register when you want the BulkSMS system to notify you about your messages.
        You can register multiple webhooks, and each one will be called.  (Note: you can also use our [Web App](https://www.bulksms.com/account/ui/index.html#/account/dev/webhooks) to manage your webhooks interactively.)
        If you want to be notified of `SENT` messages and `RECEIVED` messages you need to create two webhooks.

        See the [Webhook Developer Guide](/developer/webhooks/) for event types, payload structure, implementation details, and testing instructions.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEntry'
      responses:
        '200':
          description: Contains the webhook
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        '400':
          $ref: '#/components/responses/bad-webhook-url'

  /webhooks/{id}:
    get:
      tags: [Webhooks]
      operationId: webhook.show
      summary: Read a webhook
      description: |
        Returns a specific webhook.  See the [Webhook Developer Guide](/developer/webhooks/) for event types, payload structure, and implementation details.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: A Webhook object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        '400':
          $ref: '#/components/responses/bad-webhook-url'
        '404':
          $ref: '#/components/responses/bad-webhook-id'
    post:
      tags: [Webhooks]
      operationId: webhook.update
      summary: Update a webhook
      description: |
        Updates the webhook.  See the [Webhook Developer Guide](/developer/webhooks/) for event types, payload structure, and implementation details.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEntry'
      responses:
        '200':
          description: Contains the updated webhook
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        '400':
          $ref: '#/components/responses/bad-webhook-url'
        '404':
          $ref: '#/components/responses/bad-webhook-id'
    delete:
      tags: [Webhooks]
      operationId: webhook.delete
      summary: Delete a webhook
      description: |
        Deletes a specific webhook.  See the [Webhook Developer Guide](/developer/webhooks/) for event types, payload structure, and implementation details.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The webhook has been deleted.
        '404':
          $ref: '#/components/responses/bad-webhook-id'

  /blocked-numbers:
    get:
      tags: [Blocked Numbers]
      operationId: blocked.list
      summary: List blocked numbers
      description: |
        Returns a list of phone numbers that are blocked for this account.
      parameters:
        - in: query
          name: min-id
          description: Records with an `id` that is greater or equal to min-id will be returned. The default value is `0`.  You can add 1 to an id that you previously retrieved, to return subsequent records.
          schema:
            type: integer
            format: int32
        - in: query
          name: limit
          description: The maximum number of records to return. The default value is `10000`. The value cannot be greater than 10000.
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A list of BlockedNumber objects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BlockedNumber'

    post:
      tags: [Blocked Numbers]
      operationId: blocked.create
      summary: Create a blocked number
      description: |
        Blocked numbers are phone numbers to which your account is not permitted to send messages.
        The numbers can be created via this API, by a recipient replying with a STOP message to one
        of your previous SENT messages, or by a BulkSMS administrator.

        Sending a message to a blocked number will result in the message being assigned a status of
        `FAILED.BLOCKED`. Messages sent to blocked numbers are billed to your account.
      requestBody:
        required: true
        description: "Maximum size: `1000` items"
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/PhoneNumber'
      responses:
        '200':
          description: Empty body upon success.

  /credit/transfer:
    post:
      tags: [Credits]
      operationId: credit.transfer
      summary: Transfer credits to another account
      description: |
        Before you can use the transfer credits endpoint, the _credit-transfer facility_ must be activated for your account.  You can request activation from `support@bulksms.com`.

        The recipient must be an enabled account that uses the same currency as your account.
      requestBody:
        required: true
        description: Contains details of the transfer request.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferEntry'
      responses:
        '200':
          description: An empty body when the credits were transferred OK.
        '403':
          description: When there are not enough credits in your account or the credit transfer facility is not activated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                type: https://developer.bulksms.com/json/v1/errors/#insufficient-credits
                title: Insufficient Credits
                status: 403
        '400':
          description: When the request fails validation checks.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                type: https://developer.bulksms.com/json/v1/errors/#bad-request
                title: Bad Request
                status: 400

  /rmm/pre-sign-attachment:
    post:
      tags: [Attachments]
      operationId: attachment.presign
      summary: Upload an attachment via a signed URL
      description: |
        When composing an SMS, you can add SMS attachments by adding a URL to your text. When the recipient clicks on the URL, the attached file will be downloaded and opened on their mobile device.

        The best way to do this is to store the file on a web server you own and use that URL in the SMS text. This URL will be easily recognisable to your message recipient and ties your message back to your brand or company.

        If that's not possible, you can use BulkSMS storage to keep the files that you want to attach to your SMS. These files will be deleted after 30 days as per our fair use policy.

        We recommend you keep this file size below 20 MB, as larger files may be deleted without warning.

        To use the BulkSMS storage, follow these three steps:

        **Step 1**: Use your API Token to request a pre-signed URL.  This is what this `pre-sign-attachment` method is for.  It returns a PreSignInfo object that you will use in the other two steps.

        **Step 2**: Upload the file using a standard HTTP `PUT` request. For your `PUT` request, use the value of `putURL` from the PreSignInfo object as the request address.  You also have to add the entries from the `fields` property (of the PreSignInfo object) to the headers of your 'PUT' request. You send the file content as the body of the `PUT` request.

        **Step 3**: Now you can use the value of `fetchURL` from the PreSignInfo object in the body of your SMS messages and send those using the usual messaging API (described elsewhere in this document).  If you send the same file to many recipients, it is safe to use the same URL for all of them.

        If you need to, take a closer look at the example program (on the right-hand side) to get a better idea of how to implement this process.
      requestBody:
        required: true
        description: Describes the file to upload
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreSignRequest'
      responses:
        '200':
          description: A PreSignInfo object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreSignInfo'
      x-codeSamples:
        - lang: C#
          label: C# Complete Example
          source: |
            using System;
            using System.IO;
            using System.Net.Http;
            using System.Net.Http.Headers;
            using System.Text;
            using System.Text.Json;
            using System.Threading.Tasks;

            class Program
            {
                const string TokenId = "your-token-id";
                const string TokenSecret = "your-token-secret";
                const string PhoneNumber = "1111111";
                const string FilePath = "path/to/file.pdf";

                static readonly HttpClient Client = new();

                static async Task Main()
                {
                    var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{TokenId}:{TokenSecret}"));
                    Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);

                    // Step 1: Get a pre-signed upload URL
                    var signJson = """{"fileExtension": "pdf", "mediaType": "application/pdf"}""";
                    var signResponse = await Client.PostAsync(
                        "https://api.bulksms.com/v1/rmm/pre-sign-attachment",
                        new StringContent(signJson, Encoding.UTF8, "application/json"));
                    var signInfo = JsonSerializer.Deserialize<PreSignInfo>(await signResponse.Content.ReadAsStringAsync());

                    // Step 2: Upload the file to the signed URL
                    var uploadRequest = new HttpRequestMessage(HttpMethod.Put, signInfo.putUrl);
                    uploadRequest.Content = new StreamContent(File.OpenRead(FilePath));
                    foreach (var field in signInfo.fields)
                        uploadRequest.Content.Headers.TryAddWithoutValidation(field.name, field.value);
                    await Client.SendAsync(uploadRequest);

                    // Step 3: Send a message with the attachment URL
                    var msgJson = $"""{{ "to": "{PhoneNumber}", "body": "See attachment: {signInfo.fetchUrl}" }}""";
                    var msgResponse = await Client.PostAsync(
                        "https://api.bulksms.com/v1/messages?auto-unicode=true",
                        new StringContent(msgJson, Encoding.UTF8, "application/json"));

                    Console.WriteLine(await msgResponse.Content.ReadAsStringAsync());
                }

                record PreSignInfo(string putUrl, string fetchUrl, Field[] fields);
                record Field(string name, string value);
            }

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        The API uses HTTP Basic Auth with an **API Token** for authentication.

        API tokens can be created by logging into your account and visiting _Settings > Developer Settings > API Tokens_.

        Supply the token ID as the username and the token secret as the password. The values must be [Base64 encoded](https://en.wikipedia.org/wiki/Base64) before using them in the `Authorization` header.

        For example, if the token ID is `BBDE1B476E03498AA768F66A286AABDC-01-B` and the token secret is `9jSbVDK20!MXdfRGiIIFu#ffUE8*S`, the unencoded header value is `BBDE1B476E03498AA768F66A286AABDC-01-B:9jSbVDK20!MXdfRGiIIFu#ffUE8*S`.  After encoding, the full header becomes

        ```
        Authorization: Basic QkJERTFCNDc2RTAzNDk4QUE3NjhGNjZBMjg2QUFCREMtMDEtQjo5alNiVkRLMjAhTVhkZlJHaUlJRnUjZmZVRTgqUw==
        ```

        You are requested to preemptively provide the `Authorization` header in your requests and not wait until the server has provided a `401 Unauthorized` response. Doing so will reduce the number of requests required to achieve your goal, which will improve overall performance.

        > **Username and password authentication is no longer supported.** Accounts created after 2026-04-29 can only authenticate with an API token. If you have a legacy integration that cannot be switched to token-based authentication, please [contact Support](https://www.bulksms.com/contact/) to request an exemption.

  schemas:
    PreSignRequest:
      type: object
      properties:
        fileExtension:
          type: string
          description: The extension of the file.  Usually related to the media type.
          example: pdf
        mediaType:
          type: string
          description: The media type of the file you would like to upload.  If you are not sure what value to use here, check out the standard [list of media types](https://www.iana.org/assignments/media-types/media-types.xhtml).
          example: application/pdf

    PreSignInfo:
      type: object
      properties:
        putUrl:
          type: string
          description: The address of the PUT request to upload the file.
          example: https://some.place/aethzd.pdf?Token=IQoJb3JpZ2luX2EP3
        fetchUrl:
          type: string
          description: The URL you use in your SMS text.  It is the file location.
          example: "https://smsattach.it/bedhkd.pdf"
        fields:
          type: array
          description: Name value objects to add to the headers of the PUT request.
          items:
            type: object
            properties:
              name:
                type: string
                description: The header name
                example: x-amz-acl
              value:
                type: string
                description: The header value
                example: public-read

    Profile:
      type: object
      required:
        - id
        - username
        - created
        - credits
        - quota
      properties:
        id:
          type: string
        username:
          type: string
        created:
          type: string
          format: date-time
        credits:
          type: object
          properties:
            balance:
              type: number
              format: float
            limit:
              type: integer
              format: int32
            isTransferAllowed:
              type: boolean
        quota:
          type: object
          required:
            - size
            - remaining
          properties:
            size:
              description: The setting that limits the number of messages you can send in a day.
              type: integer
              format: int32
            remaining:
              description: The number of messages you can still send today.
              type: integer
              format: int32
        originAddresses:
          type: object
          properties:
            isFullControlAllowed:
              type: boolean
            allowed:
              type: array
              items:
                type: string
        company:
          type: object
          properties:
            name:
              type: string
            taxReference:
              type: string
        commerce:
          type: object
          properties:
            bankPaymentReference:
              type: string
            address:
              type: object
              properties:
                street:
                  type: array
                  items:
                    type: string
                city:
                  type: string
                region:
                  type: string
                country:
                  type: string
                postalCode:
                  type: string

    SubmissionEntry:
      description: An object that you use when posting messages.
      type: object
      required:
        - to
        - body
      properties:
        from:
          description: |
            Identifies the sender.

            You can supply either a string value or a structured object.
            If you supply a string, the `type` of the sender is derived to be either INTERNATIONAL or ALPHANUMERIC.  If the value does not begin with a `+` and it contains at least one character that is not a digit, the type is detected as ALPHANUMERIC. Otherwise, the type is detected as INTERNATIONAL.
          oneOf:
            - type: string
              description: |
                A string sender ID (international number or alphanumeric).
                You can also supply a structured object with explicit `type` and `address` fields — use the selector above to see that format.
                If you supply a string, the `type` is derived to be either INTERNATIONAL or ALPHANUMERIC based on the value.
              example: "+447911123456"
            - type: object
              required:
                - type
              properties:
                type:
                  description: |
                    The type of the sender id.

                    If you want BulkSMS to collect replies to this message on your behalf, specify the type as REPLIABLE.  If the recipient is in a country where BulkSMS does not have a local reply number, the reply may incur costs that are more expensive than sending a local SMS in that country.
                    If you operate a service from a shortcode in a locale that allows messaging from such a shortcode, you can specify SHORTCODE for the type.
                  type: string
                  enum:
                    - INTERNATIONAL
                    - ALPHANUMERIC
                    - SHORTCODE
                    - REPLIABLE
                address:
                  type: string
                  example: "1111111"
                  description: |
                    The address of the sender id.

                    The validation for this field depends on the value of the `type`.
                    INTERNATIONAL can start with `+`. It has a maximum length of 15 digits, and has to be longer than 6 digits.
                    ALPHANUMERIC has a maximum length of 11 characters.
                    SHORTCODE has a maximum length of 6 digits.
                    REPLIABLE should not specify a value here.
        to:
          description: |
            Identifies the recipients.

            You can supply a single string (phone number), an array of strings, a single [Recipient](#model/Recipient) object, or an array of Recipient objects.
            If you supply a string, the `type` is taken as INTERNATIONAL.
          oneOf:
            - type: string
              description: |
                A single phone number.
                You can also supply an array of strings, a single [Recipient](#model/Recipient) object, or an array of Recipient objects — use the selector above to see each format.
                If you supply a string, the `type` is taken as INTERNATIONAL.
              example: "+447700900000"
            - type: array
              description: An array of phone numbers
              items:
                type: string
              example: ["+447700900000", "+447700900001"]
            - $ref: '#/components/schemas/Recipient'
            - type: array
              description: An array of recipient objects
              items:
                $ref: '#/components/schemas/Recipient'
        routingGroup:
          description: |
            Allows you to choose routing. The default is STANDARD.
          type: string
          enum:
            - STANDARD
            - ECONOMY
            - PREMIUM

        encoding:
          description: |
            Describes the content of the message body.

            Typically this is TEXT, which is the default if no value is provided.

            If you need to send characters that are not covered by the [GSM 03.38](https://en.wikipedia.org/wiki/GSM_03.38) character set you will need to specify UNICODE.

            If you want to send a sequence of bytes, you must use BINARY.

            You can also or use the `auto-unicode` parameter of the Send Messages Operation.

            If you supply the value of `TEXT` while `auto-unicode` is `true` then your message may be converted to `UNICODE`.

            If you supply a value other than `TEXT` for this property while `auto-unicode` is `true` then no automatic conversion will take place.

          type: string
          enum:
            - TEXT
            - UNICODE
            - BINARY

        longMessageMaxParts:
          description: |
            The maximum number of message parts that can be used for a [concatenated message](https://en.wikipedia.org/wiki/Concatenated_SMS).
            The default is `3`.
          type: integer
          format: int32
          example: 5
        body:
          description: |
            The message content as described in the `encoding`. If the `encoding` is BINARY, the body must contain only hexadecimal digits where one byte is represented as two digits. For example, if you want to send two bytes '0x05' and '0x1F', the message body must contain the text '051F'.

            The message content can also contain templates, read the [body templates section](#tag/message) for more information.
          type: string
          example: Hi there!
        userSuppliedId:
          description: |
            Correlate the messages created from this submission to your data.

            The value can contain no more than 20 characters.
          type: string
          example: submission-12765
        protocolId:
          description: |
            The TP-PID value from GSM 03.40[.750] §9.2.3.9.

            You can provide either an integer value, or a mnemonic string.

            If unspecified, this property defaults to `0`, representing the IMPLICIT value.
            Numeric values are listed below
            | Name | Value|
            |----- |------|
            | IMPLICIT              | 00 |
            | SHORT_MESSAGE_TYPE_0  | 64 |
            | REPLACE_MESSAGE_1     | 65 |
            | REPLACE_MESSAGE_2     | 66 |
            | REPLACE_MESSAGE_3     | 67 |
            | REPLACE_MESSAGE_4     | 68 |
            | REPLACE_MESSAGE_5     | 69 |
            | REPLACE_MESSAGE_6     | 70 |
            | REPLACE_MESSAGE_7     | 71 |
            | RETURN_CALL           | 95 |
            | ME_DOWNLOAD           | 125 |
            | ME_DEPERSONALIZE      | 126 |
            | SIM_DOWNLOAD          | 127 |
          type: string
          enum:
            - IMPLICIT
            - SHORT_MESSAGE_TYPE_0
            - REPLACE_MESSAGE_1
            - REPLACE_MESSAGE_2
            - REPLACE_MESSAGE_3
            - REPLACE_MESSAGE_4
            - REPLACE_MESSAGE_5
            - REPLACE_MESSAGE_6
            - REPLACE_MESSAGE_7
            - RETURN_CALL
            - ME_DOWNLOAD
            - ME_DEPERSONALIZE
            - SIM_DOWNLOAD
        messageClass:
          description: |
            The class of the message, as specified by §4 of the GSM 03.38 specification.

            You can provide either an integer value, or a mnemonic string.

            The default value is SIM_SPECIFIC.
            Numeric values are
            | Name | Value|
            |------|------|
            | FLASH_SMS | 0      |
            | ME_SPECIFIC | 1    |
            | SIM_SPECIFIC | 2   |
            | TE_SPECIFIC | 3   |
          type: string
          enum:
            - FLASH_SMS
            - ME_SPECIFIC
            - SIM_SPECIFIC
            - TE_SPECIFIC
        deliveryReports:
          description: |
            The type of delivery reports to request from the delivering network.

            The default value  is `ALL`.
            Please note that not all networks support delivery reports.

            ALL. All possible delivery reports

            ERRORS. Only error delivery reports

            NONE. No delivery reports
          type: string
          enum:
            - ALL
            - ERRORS
            - NONE

    Recipient:
      type: object
      description: A structured recipient object
      properties:
        type:
          description: Type of the recipient. The default value is INTERNATIONAL.
          type: string
          enum:
            - INTERNATIONAL
            - GROUP
        address:
          description: The phone number of the recipient.  It must be supplied if the `type` is INTERNATIONAL
          type: string
        name:
          description: The name of a group in your phonebook. A value can be given if the `type` is GROUP.
          type: string
        id:
          description: The id of a group in your phonebook.  A value can be given if the `type` is GROUP.
          type: string
        fields:
          type: array
          description: |
            Custom fields that can be used in the message body. A value can be given if the `type` is INTERNATIONAL

            Read the [body templates section](#tag/message) for more information.
          items:
            type: string
      example:
        type: INTERNATIONAL
        address: "1111111"
        fields: ["Jack", "$200.00"]

    TransferEntry:
      type: object
      required:
        - toUsername
        - toUserId
        - credits
      properties:
        toUsername:
          type: string
          example: roboto
          description: |
            The username of the account that will receive the credits.
        toUserId:
          type: number
          example: 2345
          description: |
            The numeric user ID of the account that will receive the credits.
            The ID must match the username.
        credits:
          type: number
          example: 2345
          description: |
            The amount of credits to transfer.
        commentOnFrom:
          type: string
          example: Transfer to Bobby
          description: |
            An optional note that will be shown on the credit history of your account.
            The maximum length of the comment is 100.
        commentOnTo:
          type: string
          example: Transfer from Danny
          description: |
            An optional note that will be shown on the credit history of the recipient's account.
            The maximum length of the comment is 100.

    WebhookEntry:
      type: object
      required:
        - name
        - url
        - triggerScope
      properties:
        name:
          type: string
          example: My MT Webhook
          description: |
            A text identifier for the webhook.
            More than one webhook cannot have the same name.
        url:
          type: string
          example: https://www.example.com
          description: |
            The location of the webhook.

            In addition to being a [valid URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax), the url must also start with `http` or `https`.
        contactEmailAddress:
          type: string
          example: tech_team@example.com
          description: |
            The email address to which emails will be sent if there are problem with invoking the webhook.

            The value must be a valid email address.
            If this value is `null`, no email will be sent.

            It is `null` by default.
        triggerScope:
          type: string
          enum:
            - SENT
            - RECEIVED
          description: |
            Specifies when the webhook will be triggered.

            Please note the values are case sensitive.

            If the value is `SENT`, the webhook will be called when a status update becomes available for a message you sent (i.e. a mobile terminating (MT) message).

            If the value is `RECEIVED`, the webhook will be called when a message is received (i.e. a mobile originating (MO) message).

            Note that this field forces you to create two separate webhook entries if you want to collect all messages.  However,  you can use the same `url` for both webhooks if you want.
          example: SENT
        invokeOption:
          type: string
          enum:
            - ONE
            - MANY
          description: |
            Specifies how to invoke your webhook.

            If the value is `ONE` the array POSTed to your webhook will contain no more than a single message.  Use this option if your webhook logic is unable to handle more than one messages at a time.

            If the value is `MANY` the array POSTed to your webhook can contain up to 10 messages.  This is the recommended option.  The number of calls made to your webhook would be less and this will speed up your total processing time.
            If your webhook fails for an invoke that has more than one message, each message in the array will automatically be retried one at a time.

            This value defaults to `ONE` - but it is recommended that you set this property to `MANY`.
          example: MANY
        active:
          type: boolean
          description: |
            Indicates whether you want the webhook activated.

            If the value is `true`, the webhook at the given `url` will be invoked with an empty array (`[]`) as part of the validation process.
            If the webhook responds with a `2xx` status code, the submission is accepted; if not the webhook is not created (or updated).

            If the value is `false` the webhook will be inactive, and it will not be invoked when messages are `SENT` or `RECEIVED`.

            The default value is `true`.
          example: true
        onWebApp:
          type: boolean
          example: true
          description: |
            Indicates whether you want to show this webhook on the Web App.

            Webhooks shown there can be updated by the user that use the public Web site.

            The default value is `true`.

    Webhook:
      type: object
      properties:
        id:
          type: number
          example: 234
        name:
          type: string
          example: My MT Webhook
        url:
          type: string
          example: https://www.example.com
        contactEmailAddress:
          type: string
          example: tech_team@example.com
        triggerScope:
          type: string
          example: SENT
        active:
          type: boolean
          example: true
        onWebApp:
          type: boolean
          example: true

    Message:
      type: object
      required:
        - id
        - type
        - to
        - body
        - status
      properties:
        id:
          description: A unique identifier that is assigned when the message is created.
          type: string
          example: "12345"
        type:
          description: The message direction
          type: string
          enum:
            - SENT
            - RECEIVED
        from:
          description: The address part of the sender id
          type: string
          example: "+447911123456"
        to:
          description: The phone number of the recipient
          type: string
          example: "+447700900000"
        body:
          description: The content of the message
          type: string
          example: "Hi there!"
        encoding:
          description: The type of the content.  See the `encoding` field for more information.
          type: string
          enum:
            - TEXT
            - UNICODE
            - BINARY
        protocolId:
          description: See the `protocolId` field for more information.
          type: integer
          format: int32
        messageClass:
          description: See the `messageClass` field for more information.
          type: integer
          format: int32
        numberOfParts:
          description: The number of parts.  If this is a concatenated message, the number of parts will be more than 1.  Note that this field does not have a value in the submission response.
          type: integer
          format: int32
        creditCost:
          description: The cost of the message (in credits).   Note that this field does not have a value in the submission response.
          type: number
          format: float
        submission:
          description: |
            Identifies the submission.
          type: object
          required:
            - id
            - date
          properties:
            id:
              description: A unique identity shared by all messages that were created from the same submission. This field should be ignored if the `type` is not SENT.
              type: string
              example: "submission-98765"
            date:
              description: The date and time the submission was processed. If the `type` is RECEIVED, this field reflects the date and time the received message was processed.
              type: string
              format: date-time
        status:
          description: The status of the message
          type: object
          required:
            - id
            - type
          properties:
            id:
              description: |
                A concatenated value A.B where A is the `status.type` and B is the `status.subtype`.
                It there is no value for `subtype` then B takes string value `"null"` (e.g. `"SENT.null"`).
              type: string
              example: "ACCEPTED.null"
            type:
              description: |

                ACCEPTED  Message accepted for delivery. Only returned for initial message submissions.

                SCHEDULED  Message accepted for delivery at a later date. Only returned for initial message  submissions.

                SENT  Message has been relayed away from our systems.

                DELIVERED  Successfully delivered to phone.

                UNKNOWN  Message is in an unknown state.

                FAILED  Delivery failed.
              type: string
              enum:
                - ACCEPTED
                - SCHEDULED
                - SENT
                - DELIVERED
                - UNKNOWN
                - FAILED
            subtype:
              description: |
                Has a value only if the `type` is FAILED.

                EXPIRED  Delivery failed because message expired before delivery was possible.

                HANDSET_ERROR  Delivery failed because of a problem related to the phone (e.g. message storage area full).

                BLOCKED  Your account has been blocked from sending to this phone (e.g. recipient replied STOP to block communication).

                NOT_SENT  Message delivery was not attempted (e.g. because we were not able to find a route for the supplied phone number).
              type: string
              nullable: true
              enum:
                - EXPIRED
                - HANDSET_ERROR
                - BLOCKED
                - NOT_SENT
              example: null
        relatedSentMessageId:
          description: |
            This field has a value only if the type is RECEIVED.
            With SMS messages, it is not possible to link a reply directly with a specific sent message.  However, if you specified `REPLIABLE` in the `from` property, BulkSMS will link any reply to the most recent message sent to a given phone number.

            The `relatedSentMessageId` property keeps the information about this link.

            You can use this property to derive an implicit conversation from a set of messages.
              - If a received reply message has a `relatedSentMessageId`, you can use it to retrieve the last message that was sent before the reply was received.
              - If you have the `id` of the sent message and you want all the received messages that relate to it, you can use the List Related Messages Operation.
          type: string
          nullable: true
          example: null
        userSuppliedId:
          description: |
            This is the value you supplied in the `userSuppliedId` field.
            Has a value only if the `type` is SENT.
          type: string
          nullable: true
          example: null

    Error:
      description: See the errors page at https://www.bulksms.com/developer/json/v1/errors/ for more detail on what kind of errors you can get.
      type: object
      required:
        - type
        - title
        - status
      properties:
        type:
          description: A URL to one of the error types listed at https://www.bulksms.com/developer/json/v1/errors/
          type: string
        title:
          description: A short description of the type
          type: string
        status:
          description: The HTTP status code
          type: integer
          format: int32
          example: 400
        detail:
          description: More information about why the error occurred.
          type: string

    PhoneNumber:
      type: string
      example: "44123456789"
      description: A phone number in E.164 format

    BlockedNumber:
      type: object
      required:
        - id
        - phoneNumber
      properties:
        id:
          type: number
          format: int32
          example: 234
          description: A unique identifier that is assigned when the BlockedNumber is created.
        phoneNumber:
          allOf:
            - $ref: '#/components/schemas/PhoneNumber'
        created:
          type: string
          format: date-time
          description: The date-time when the blocked number was created.
        description:
          type: string
          description: A note describing why the number is blocked.

  responses:
    bad-request:
      description: Bad Request. The content or structure of your submission, or a parameter, was found to be invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            type: https://developer.bulksms.com/json/v1/errors/#bad-request
            title: Bad Request
            status: 400
            detail: "Request body is not valid: ..."
    credits:
      description: Forbidden.  Inspect the body of the response for further details - for example, you may have insufficient credits remaining
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            type: https://developer.bulksms.com/json/v1/errors#insufficient-credits
            title: Insufficient Credits
            status: 403
    bad-webhook-url:
      description: The url given for the webhook is not callable
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            type: https://developer.bulksms.com/json/v1/errors/#bad-request
            title: Bad Request
            status: 400
            detail: |
              Invoking the Webhook URL caused an error: 403 - Forbidden
    bad-webhook-id:
      description: A webhook with the given id does not exist
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            type: https://developer.bulksms.com/json/v1/errors/#not-found
            title: Not Found
            status: 404
            detail: |
              Webhook with id '1' does not exist
