openapi: 3.0.3
info:
  title: 160.com.au API
  version: "2025-12-24"
  description: |
    The JSON API allows you to remotely manage your SMS and related stuff on 160.com.au.

    ## Authentication

    The API uses standard HTTP Basic Auth. The username is your **account username**, the password - **SMS secret**. Both values are available on the profile page on https://www.160.com.au. In case of error the **HTTP 401 Unauthorized** error raises.

    ## Rate limits

    The API has rate limits 2 request per second. In case of error the **HTTP 429 Too Many Requests** error raises.

    ## Error handling

    In case of error the **HTTP 4xx** error raises. **object** defined only in case of broken object, and contains this broken object.

    ```json
    {
      "errors": [
        {
          "statusCode": 400,
          "status": "Bad Request",
          "object": {}
        }
      ]
    }
    ```

servers:
  - url: https://api.160.com.au
    description: Live

tags:
  - name: Account
    description: method(s) to get account resources
  - name: Messages
    description: methods to manage outgoing SMS
  - name: Contacts
    description: methods to manage contacts
  - name: Groups
    description: methods to manage groups
  - name: Incoming
    description: methods to manage incoming SMS
  - name: OTP
    description: methods to work with OTP verifications

security:
  - basicAuth: []

paths:
  /v1/info:
    get:
      tags: [Account]
      summary: Get account info (balance, registered phone number, sender IDs, virtual phone numbers)
      operationId: getInfo
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InfoResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/messages:
    get:
      tags: [Messages]
      summary: List messages (descending posting order)
      operationId: listMessages
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/limit"
        - name: dateFrom
          in: query
          description: Posting from date ("YYYY-MM-DD hh:mm:ss")
          schema: { type: string, example: "2025-12-01 00:00:00" }
        - name: dateTo
          in: query
          description: Posting to date ("YYYY-MM-DD hh:mm:ss")
          schema: { type: string, example: "2025-12-31 23:59:59" }
        - name: statusCode
          in: query
          description: Filter by message statusCode
          schema:
            $ref: "#/components/schemas/EnumStatusCodeMessage"
        - name: delayed
          in: query
          description: "1 to get scheduled SMS"
          schema: { type: boolean, example: true }
        - name: messageIdFrom
          in: query
          description: From messageId (inclusive)
          schema: { type: integer, example: 1 }
        - name: messageIdTo
          in: query
          description: To messageId (inclusive)
          schema: { type: integer, example: 100 }
        - name: batchId
          in: query
          description: Batch ID of sent bulk transmission (applicable to /v1/groups/{groupId}/send)
          schema: { type: integer, example: 123456789 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    post:
      tags: [Messages]
      summary: Send one or more SMS messages (max 100 per request)
      operationId: createMessages
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessageCreateRequest"
            examples:
              simple:
                summary: send a message to 61412345678 from the registered phone number
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
              virtual:
                summary: send a message to 61412345678 from the virtual phone number
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
                      phone: "614800000031"
              senderId:
                summary: send a message to 61412345678 from the approved sender ID 'My Brand'
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
                      senderId: "My Brand"
              twoWay:
                summary: send a 2-way message to 61412345678 and receive response to email 'user@example.com'
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
                      email: "user@example.com"
              delayed:
                summary: send a message to 61412345678 from the registered phone number in 10 minutes
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
                      delay: 10
              callback:
                summary: send a message to 61412345678 from the registered phone number and receive a delivery report to the callbackURL
                value:
                  messages:
                    - recipient: "61412345678"
                      message: "hello world"
                      callbackURL: "https://example.com/get_delivery_report"
      responses:
        "201":
          description: Created (messages accepted / queued)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageCreateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

    delete:
      tags: [Messages]
      summary: Delete messages (by batchId OR by messageId range)
      operationId: deleteMessages
      description: |
        Provide either:
        - batchId, OR
        - messageIdFrom and/or messageIdTo.
      parameters:
        - name: batchId
          in: query
          schema: { type: integer }
        - name: messageIdFrom
          in: query
          schema: { type: integer }
        - name: messageIdTo
          in: query
          schema: { type: integer }
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/messages/{messageId}:
    get:
      tags: [Messages]
      summary: Get a message by ID
      operationId: getMessage
      parameters:
        - $ref: "#/components/parameters/messageIdPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    delete:
      tags: [Messages]
      summary: Delete a message by ID
      operationId: deleteMessage
      parameters:
        - $ref: "#/components/parameters/messageIdPath"
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/contacts:
    get:
      tags: [Contacts]
      summary: List contacts
      operationId: listContacts
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/limit"
        - name: groupId
          in: query
          schema: { type: integer, example: 1 }
        - name: firstName
          in: query
          description: "At least 5 characters to search by partial first name"
          schema: { type: string, example: "John" }
        - name: lastName
          in: query
          description: "At least 5 characters to search by partial last name"
          schema: { type: string, example: "Doe" }
        - name: phone
          in: query
          description: "At least 5 characters to search by partial phone number"
          schema: { type: string, example: "61412" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactListResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    post:
      tags: [Contacts]
      summary: Create contacts (max 100)
      operationId: createContacts
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ContactInstertRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactUpsertResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

    patch:
      tags: [Contacts]
      summary: Modify contacts (max 100)
      operationId: patchContacts
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ContactUpdateRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactUpsertResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

  /v1/contacts/{contactId}:
    get:
      tags: [Contacts]
      summary: Get a contact by ID
      operationId: getContact
      parameters:
        - $ref: "#/components/parameters/contactIdPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ContactResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    delete:
      tags: [Contacts]
      summary: Delete a contact by ID
      operationId: deleteContact
      parameters:
        - $ref: "#/components/parameters/contactIdPath"
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/groups:
    get:
      tags: [Groups]
      summary: List groups
      operationId: listGroups
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/limit"
        - name: groupType
          in: query
          description: Filter by groupType
          schema: { $ref: "#/components/schemas/EnumGroupType" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GroupListResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    post:
      tags: [Groups]
      summary: Create groups (max 100)
      operationId: createGroups
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GroupInsertRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GroupUpsertResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

    patch:
      tags: [Groups]
      summary: Modify groups (max 100)
      operationId: patchGroups
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GroupUpdateRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GroupUpsertResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

  /v1/groups/{groupId}:
    get:
      tags: [Groups]
      summary: Get a group by ID
      operationId: getGroup
      parameters:
        - $ref: "#/components/parameters/groupIdPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GroupResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    delete:
      tags: [Groups]
      summary: Delete a group by ID
      operationId: deleteGroup
      parameters:
        - $ref: "#/components/parameters/groupIdPath"
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/groups/{groupId}/send:
    post:
      tags: [Groups]
      summary: Send a bulk message to all contacts in a group (max 100 SMS per request)
      operationId: sendToGroup
      parameters:
        - $ref: "#/components/parameters/groupIdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/GroupSendRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageListResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/LongBadRequest"

  /v1/incoming:
    get:
      tags: [Incoming]
      summary: List incoming messages (descending receiving order)
      operationId: listIncoming
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/limit"
        - name: recipient
          in: query
          description: Recipient phone number
          schema: { type: string, example: "61480000003" }
        - name: dateFrom
          in: query
          description: Receiving from date ("YYYY-MM-DD hh:mm:ss")
          schema: { type: string, example: "2025-12-01 00:00:00" }
        - name: dateTo
          in: query
          description: Receiving to date ("YYYY-MM-DD hh:mm:ss")
          schema: { type: string, example: "2025-12-31 23:59:59" }
        - name: messageIdFrom
          in: query
          description: From messageId (inclusive)
          schema: { type: integer, example: 1 }
        - name: messageIdTo
          in: query
          description: To messageId (inclusive)
          schema: { type: integer, example: 100 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/IncomingListResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

    delete:
      tags: [Incoming]
      summary: Delete incoming messages (by messageId range)
      operationId: deleteIncoming
      parameters:
        - name: messageIdFrom
          in: query
          required: false
          schema: { type: integer }
        - name: messageIdTo
          in: query
          required: false
          schema: { type: integer }
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/incoming/{messageId}:
    delete:
      tags: [Incoming]
      summary: Delete an incoming message by ID
      operationId: deleteIncomingObject
      parameters:
        - $ref: "#/components/parameters/messageIdPath"
      responses:
        "204":
          description: No Content
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/otp:
    get:
      tags: [OTP]
      summary: List OTP configurations
      operationId: listOtpResponses
      parameters:
        - $ref: "#/components/parameters/page"
        - $ref: "#/components/parameters/limit"
        - name: title
          in: query
          description: Part of OTP configuration title
          schema: { type: string, example: "Login" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OtpListResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/otp/{otpId}:
    get:
      tags: [OTP]
      summary: Get an OTP configuration by ID
      operationId: getOtpResponse
      parameters:
        - $ref: "#/components/parameters/otpIdPath"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OtpResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/otp/{otpId}/send:
    post:
      tags: [OTP]
      summary: Send an OTP SMS using a configuration
      operationId: sendOtp
      parameters:
        - $ref: "#/components/parameters/otpIdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OtpSendRequest" }
            examples:
              example:
                value: { phone: "61412345678" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OtpSendResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

  /v1/otp/{otpId}/verify:
    post:
      tags: [OTP]
      summary: Verify an OTP code
      operationId: verifyOtp
      parameters:
        - $ref: "#/components/parameters/otpIdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OtpVerifyRequest" }
            examples:
              example:
                value:
                  phone: "61412345678"
                  code: "12345"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OtpVerifyResponse" }
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "400":
          $ref: "#/components/responses/BadRequest"

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: "username: account username, password: SMS secret"

  parameters:
    page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
    limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    messageIdPath:
      name: messageId
      in: path
      required: true
      schema: { type: integer, example: 1 }
    contactIdPath:
      name: contactId
      in: path
      required: true
      schema: { type: integer, example: 1 }
    groupIdPath:
      name: groupId
      in: path
      required: true
      schema: { type: integer, example: 1 }
    otpIdPath:
      name: otpId
      in: path
      required: true
      schema: { type: integer, example: 1 }

  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    TooManyRequests:
      description: Too Many Requests
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    BadRequest:
      description: Bad Request (validation or other client error)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorResponse" }
    LongBadRequest:
      description: Bad Request (validation or other client error)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorLongResponse" }

  schemas:
    LinkObject:
      type: object
      properties:
        rel:
          type: string
          enum: [prev, next]
          x-enumDescriptions:
            prev: type of URL to the previous page
            next: type of URL to the next page
          description: >
            `prev`: type of URL to the previous page
            `next`: type of URL to the next page
        href:
          type: string
          format: url
          description: "URL to a sibling page"
          example: "https://api.160.com.au/url?with=parameters"

    ErrorObject:
      type: object
      properties:
        statusCode:
          type: integer
          description: HTTP status code
        status:
          type: string
          description: Human readable error message
      required: [statusCode, status]

    ErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items: { $ref: "#/components/schemas/ErrorObject" }
      required: [errors]

    ErrorLongObject:
      allOf:
        - $ref: "#/components/schemas/ErrorObject"
        - type: object
          properties:
            object:
              type: object
              nullable: true
              description: The broken object (only present when an object is broken)
      required: [statusCode, status]

    ErrorLongResponse:
      type: object
      properties:
        errors:
          type: array
          items: { $ref: "#/components/schemas/ErrorLongObject" }
      required: [errors]

    EnumStatusCodeSenderId:
      type: integer
      enum: [0, 1, 2]
      x-enumDescriptions:
        0: Under review
        1: Active
        2: Rejected
      description: >
        status code of sender ID:
        `0`: Under review
        `1`: Active
        `2`: Rejected

    EnumStatusCodeVirtualNumber:
      type: integer
      enum: [0, 1, 2]
      x-enumDescriptions:
        0: Cancelled
        1: Active
        2: Blocked
      description: >
        status code of virtual phone number:
        `0`: Cancelled
        `1`: Active
        `2`: Blocked

    EnumStatusCodeMessage:
      type: integer
      enum: [-1, 0, 1, 2]
      description: >
        status code of SMS:
        `-1`: Queued
        `0`: Sent
        `1`: Delivered,
        `2`: Not delivered

    EnumStatusCodeOtp:
      type: integer
      enum: [-1, 0, 1, 2, 3, 4, 5, 6]
      description: >
        status code of OTP:
        `-1`: Queued
        `0`: Sent
        `1`: Delivered
        `2`: Not delivered
        `3`: Matched code (first match)
        `4`: Failed code
        `5`: Expired code
        `6`: Used code (second+ match)

    EnumGroupType:
      type: integer
      enum: [0, 1, 2]
      description: >
        status code of group type:
        `0`: Regular
        `1`: Messaging
        `2`: Offers

    InfoSenderIdObject:
      type: object
      properties:
        senderId:
          type: string
          description: "sender ID"
        statusCode: { $ref: "#/components/schemas/EnumStatusCodeSenderId" }
        status: { type: string }
      required: [senderId, statusCode, status]

    InfoVirtualNumberObject:
      type: object
      properties:
        phone:
          type: string
          description: "virtual phone number"
          example: "61480000003"
        expirationDate:
          type: string
          description: "virtual phone number expiration date"
          example: "2026-12-31 12:00:00"
        callbackURL:
          type: string
          format: url
          description: "callback URL to receive messages send to virtual phone number"
          example: "https://example.com/get_incoming_message"
        countryCode:
          type: string
          description: "ISO 3166 Alpha-2 code of virtual phone number country"
          example: "AU"
        statusCode: { $ref: "#/components/schemas/EnumStatusCodeVirtualNumber" }
        status: { type: string }
      required: [phone, expirationDate, countryCode, statusCode, status]

    InfoResponse:
      type: object
      properties:
        phone:
          type: string
          description: "registered phone number"
          example: "61000000003"
        balance:
          type: number
          description: "available credits"
          format: double
          example: 99.99
        senderIds:
          type: array
          items: { $ref: "#/components/schemas/InfoSenderIdObject" }
        virtualNumbers:
          type: array
          items: { $ref: "#/components/schemas/InfoVirtualNumberObject" }
      required: [phone, balance]

    MessageResponse:
      type: object
      properties:
        messageId: { type: integer, description: "unique SMS ID", example: 1 }
        datePosted:
          type: string
          description: "date when SMS posted"
          example: "2025-12-24 10:00:00"
        dateSent:
          type: string
          nullable: true
          description: "date when SMS sent"
          example: "2025-12-24 10:00:05"
        dateScheduled:
          type: string
          nullable: true
          description: "date when SMS scheduled"
          example: "2025-12-24 10:10:00"
        delayFor:
          type: integer
          nullable: true
          description: "number of minutes SMS was delayed"
          example: 10
        sender:
          type: string
          description: "phone number, sender ID or email"
        recipient:
          type: string
          description: "recipient phone number"
          example: "61412345678"
        message:
          type: string
          description: "text of SMS"
          example: "hello world"
        callbackURL:
          type: string
          format: url
          nullable: true
          description: "Callback URL for delivery report"
          example: "https://example.com/get_delivery_report"
        cost:
          type: string
          description: "SMS price in credits"
          example: "1.00"
        batchId:
          type: integer
          description: "ID of bulk messages"
          example: 123456789
        statusCode: { $ref: "#/components/schemas/EnumStatusCodeMessage" }
        status: { type: string, example: "Delivered" }
      required:
        [messageId, datePosted, sender, recipient, cost, statusCode, status]

    MessageListResponse:
      type: object
      properties:
        messages:
          type: array
          items: { $ref: "#/components/schemas/MessageResponse" }
        links:
          type: array
          items: { $ref: "#/components/schemas/LinkObject" }
      required: [messages, links]

    MessageCreateObject:
      type: object
      properties:
        delay:
          type: integer
          nullable: true
          description: Delay, 1..44640 minutes
          minimum: 1
          maximum: 44640
        recipient:
          type: string
          description: recipient phone number
        message:
          type: string
          description: text of SMS
        callbackURL:
          type: string
          format: url
          nullable: true
          description: Callback URL for delivery report
      required: [recipient, message]

    MessageCreatePhoneObject:
      allOf:
        - type: object
          properties:
            phone:
              type: string
              nullable: true
              description: Virtual phone number; leave empty for registered phone number
        - $ref: "#/components/schemas/MessageCreateObject"
      required: [recipient, message]

    MessageCreateSenderIDObject:
      allOf:
        - type: object
          properties:
            senderId:
              type: string
              description: Approved sender ID
        - $ref: "#/components/schemas/MessageCreateObject"
      required: [senderId, recipient, message]

    MessageCreateTwoWayObject:
      allOf:
        - type: object
          properties:
            email:
              type: string
              format: email
              description: Email for 2-way SMS
        - $ref: "#/components/schemas/MessageCreateObject"
      required: [email, recipient, message]

    MessageCreateRequest:
      type: object
      properties:
        messages:
          type: array
          maxItems: 100
          items:
            oneOf:
              - $ref: "#/components/schemas/MessageCreatePhoneObject"
              - $ref: "#/components/schemas/MessageCreateSenderIDObject"
              - $ref: "#/components/schemas/MessageCreateTwoWayObject"
      required: [messages]

    MessageCreateResponse:
      type: object
      properties:
        messages:
          type: array
          items: { $ref: "#/components/schemas/MessageResponse" }
      required: [messages]

    ContactGroupObject:
      type: object
      properties:
        groupId:
          type: integer
          description: "unique group ID"
          example: 1
        isDisabled:
          type: integer
          description: "1/0 = disabled/enabled"
          example: 1
      required: [groupId, isDisabled]

    ContactInsertObject:
      type: object
      properties:
        firstName:
          type: string
          description: "contact first name"
          example: "John"
        lastName:
          type: string
          description: "contact last name"
          example: "Doe"
        note:
          type: string
          description: "contact note"
          example: "My note"
        phone:
          type: string
          description: "contact phone number"
          example: "61412345678"
        groups:
          type: array
          items: { $ref: "#/components/schemas/ContactGroupObject" }
      required: [firstName, lastName, note, phone, groups]

    ContactUpdateObject:
      allOf:
        - type: object
          properties:
            contactId:
              type: integer
              description: "unique contact ID"
              example: 1
        - $ref: "#/components/schemas/ContactInsertObject"
      required: [contactId, firstName, lastName, note, phone, groups]

    ContactResponse:
      $ref: "#/components/schemas/ContactUpdateObject"

    ContactListResponse:
      type: object
      properties:
        contacts:
          type: array
          items: { $ref: "#/components/schemas/ContactResponse" }
        links:
          type: array
          items: { $ref: "#/components/schemas/LinkObject" }
      required: [contacts, links]

    ContactInstertRequest:
      type: object
      properties:
        contacts:
          type: array
          maxItems: 100
          items: { $ref: "#/components/schemas/ContactInsertObject" }
      required: [contacts]

    ContactUpdateRequest:
      type: object
      properties:
        contacts:
          type: array
          maxItems: 100
          items: { $ref: "#/components/schemas/ContactUpdateObject" }
      required: [contacts]

    ContactUpsertResponse:
      type: object
      properties:
        contacts:
          type: array
          items: { $ref: "#/components/schemas/ContactResponse" }
      required: [contacts]

    GroupInsertObject:
      type: object
      properties:
        group:
          type: string
          description: "group title"
          example: "My group"
        groupType: { $ref: "#/components/schemas/EnumGroupType" }
        keyword:
          type: string
          description: "Keyword if groupType is 1 or 2"
          nullable: true
          example: "XYZ"
      required: [group, groupType]

    GroupUpdateObject:
      allOf:
        - type: object
          properties:
            groupId:
              type: integer
              description: "unique group ID"
              example: 1
        - $ref: "#/components/schemas/GroupInsertObject"
      required: [groupId, group, groupType]

    GroupResponse:
      allOf:
        - $ref: "#/components/schemas/GroupUpdateObject"
        - type: object
          properties:
            userCount:
              type: integer
              description: "number of contacts in the group"
              example: 1
      required: [groupId, group, groupType, userCount]

    GroupListResponse:
      type: object
      properties:
        groups:
          type: array
          items: { $ref: "#/components/schemas/GroupResponse" }
        links:
          type: array
          items: { $ref: "#/components/schemas/LinkObject" }
      required: [groups, links]

    GroupInsertRequest:
      type: object
      properties:
        groups:
          type: array
          maxItems: 100
          items: { $ref: "#/components/schemas/GroupInsertObject" }
      required: [groups]

    GroupUpdateRequest:
      type: object
      properties:
        groups:
          type: array
          maxItems: 100
          items: { $ref: "#/components/schemas/GroupUpdateObject" }
      required: [groups]

    GroupUpsertResponse:
      type: object
      properties:
        groups:
          type: array
          items: { $ref: "#/components/schemas/GroupResponse" }
      required: [groups]

    GroupSendObject:
      type: object
      properties:
        delay:
          type: integer
          nullable: true
          description: Delay, 1..44640 minutes
          minimum: 1
          maximum: 44640
        message:
          type: string
          description: text of SMS
        callbackURL:
          type: string
          format: url
          nullable: true
          description: Callback URL for delivery report
      required: [message]

    GroupSendPhoneObject:
      allOf:
        - type: object
          properties:
            phone:
              type: string
              nullable: true
              description: Virtual phone number; leave empty for registered phone number
        - $ref: "#/components/schemas/GroupSendObject"
      required: [message]

    GroupSendSenderIDObject:
      allOf:
        - type: object
          properties:
            senderId:
              type: string
              description: Approved sender ID
        - $ref: "#/components/schemas/GroupSendObject"
      required: [senderId, message]

    GroupSendTwoWayObject:
      allOf:
        - type: object
          properties:
            email:
              type: string
              format: email
              description: Email for 2-way SMS
        - $ref: "#/components/schemas/GroupSendObject"
      required: [email, message]

    GroupSendRequest:
      type: object
      description: Send SMS to all recipients in the groupId path.
      oneOf:
        - $ref: "#/components/schemas/GroupSendPhoneObject"
        - $ref: "#/components/schemas/GroupSendSenderIDObject"
        - $ref: "#/components/schemas/GroupSendTwoWayObject"

    IncomingObject:
      type: object
      properties:
        messageId:
          type: integer
          description: "unique ID of incoming message"
          example: 1
        dateReceived:
          type: string
          description: "date when incoming message received"
          example: "2025-12-24 10:00:00"
        sender:
          type: string
          description: "phone number or sender ID"
        recipient:
          type: string
          description: "2-way SMS or virtual phone number"
        message:
          type: string
          description: "text of SMS"
      required: [messageId, dateReceived, sender, recipient, message]

    IncomingListResponse:
      type: object
      properties:
        incoming:
          type: array
          items: { $ref: "#/components/schemas/IncomingObject" }
        links:
          type: array
          items: { $ref: "#/components/schemas/LinkObject" }
      required: [incoming, links]

    OtpVerifyObject:
      type: object
      properties:
        messageId:
          type: integer
          description: "unique SMS ID"
          example: 1
        otpId:
          type: integer
          description: "used OTP config ID"
        datePosted:
          type: string
          description: "date when SMS posted"
        dateSent:
          type: string
          description: "date when SMS sent"
        sender:
          type: string
          description: "phone number, sender ID or email"
        recipient:
          type: string
          description: "recipient phone number"
        cost:
          type: string
          description: "SMS price in credits"
        statusCode: { $ref: "#/components/schemas/EnumStatusCodeOtp" }
        status: { type: string, example: "Delivered" }
      required: [messageId, otpId, sender, recipient, cost, statusCode, status]

    OtpResponse:
      type: object
      properties:
        otpId:
          type: integer
          description: "unique OTP config ID"
          example: 1
        title:
          type: string
          description: "title of OTP config"
          example: "My OTP config"
        length:
          type: integer
          description: "length of OTP code"
          example: 6
        ttl:
          type: integer
          description: "time how long a sent OTP code stays active"
          example: 10
        sent:
          type: integer
          description: "number of sent SMS for this OTP config"
          example: 4
        matched:
          type: integer
          description: "number of matched OTP codes"
          example: 2
        failed:
          type: integer
          description: "number of failed or expired OTP codes"
          example: 1
      required: [otpId, title, length, ttl, sent, matched, failed]

    OtpListResponse:
      type: object
      properties:
        otps:
          type: array
          items: { $ref: "#/components/schemas/OtpResponse" }
        links:
          type: array
          items: { $ref: "#/components/schemas/LinkObject" }
      required: [otps, links]

    OtpSendRequest:
      type: object
      properties:
        phone:
          type: string
          description: "recipient phone number to send OTP code to"
          example: "61412345678"
      required: [phone]

    OtpSendResponse:
      type: object
      properties:
        verify: { $ref: "#/components/schemas/OtpVerifyObject" }
      required: [verify]

    OtpVerifyRequest:
      type: object
      properties:
        phone:
          type: string
          description: "recipient phone number what OTP code was sent to"
          example: "61412345678"
        code:
          type: string
          description: "entered OTP code to verify"
          example: "12345"
      required: [phone, code]

    OtpVerifyResponse:
      type: object
      properties:
        verify: { $ref: "#/components/schemas/OtpVerifyObject" }
      required: [verify]
