{
  "openapi": "3.0.3",
  "info": {
    "title": "SoftLinkSMS API",
    "version": "1.0.0 (v1)",
    "description": "\n    **Bulk Messaging API** - SMS, WhatsApp, and email management system.\n\n    ## Versioning\n    The API uses URL path versioning (e.g., `/api/v1/`, `/api/v2/`).\n    Current version: **v1**\n    ",
    "contact": {
      "email": "support@softlinksms.co.ke"
    }
  },
  "paths": {
    "/api/v1/email": {
      "get": {
        "operationId": "email_list",
        "description": "Returns a cursor-paginated list of emails belonging to the authenticated user, ordered by most recent first. Supports free-text search over the recipient address via the `search` query parameter.",
        "summary": "List sent emails",
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination cursor returned in `next`/`previous` links."
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "description": "Number of results to return per page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string"
            },
            "description": "Search term matched against `to_email`."
          }
        ],
        "tags": [
          "Email"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedEmailMessageList"
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "email_create",
        "description": "Queues a transactional email for delivery to up to 1000 recipients. Each recipient receives an individual `EmailMessage` record; delivery is dispatched asynchronously via a background task. Recipients on the suppression list (prior bounce, complaint, or unsubscribe) are rejected.",
        "summary": "Send an email to one or more recipients",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            },
            "description": "Makes retries safe. Send a unique value (a UUID works) per logical send; repeating the request with the same key within 24 hours returns the original response with `Idempotent-Replayed: true` instead of sending and charging again. Reusing a key with a different body is `422 idempotency_key_reused`. Max 255 characters."
          }
        ],
        "tags": [
          "Email"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailMessageRequest"
              },
              "examples": {
                "QueueRequest": {
                  "value": {
                    "recipients": [
                      "user1@example.com",
                      "user2@example.com"
                    ],
                    "subject": "Your order has shipped!",
                    "body": "<p>Your order has shipped!</p>"
                  },
                  "summary": "Queue request"
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/EmailMessageRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/EmailMessageRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailBatch"
                },
                "examples": {
                  "EmailsQueued": {
                    "value": {
                      "batch_id": "7b2e4c1d-0a9f-4e3b-8c6d-5f1a2b3c4d5e",
                      "recipients": 1,
                      "total_price": "0.25",
                      "currency": "KES",
                      "messages": [
                        {
                          "id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
                          "traffic_class": "transactional",
                          "recipient": "user1@example.com",
                          "price": "0.25",
                          "currency": "KES",
                          "batch_id": "7b2e4c1d-0a9f-4e3b-8c6d-5f1a2b3c4d5e",
                          "message_id": null,
                          "response_code": null,
                          "response_description": null,
                          "status": "pending",
                          "created_at": "2026-10-19T08:15:00.412000Z"
                        }
                      ]
                    },
                    "summary": "Emails queued"
                  }
                }
              }
            },
            "description": "Queued. One email per recipient, all sharing `batch_id`."
          },
          "400": {
            "description": "Invalid payload, e.g. malformed addresses or a suppressed recipient."
          },
          "403": {
            "description": "`traffic_class_not_permitted`: transactional email isn't approved for this account, or promotional email was requested (not offered). `details.status` says where the access request stands."
          }
        }
      }
    },
    "/api/v1/email/{id}": {
      "get": {
        "operationId": "email_retrieve",
        "description": "Returns the delivery status and details of a single email owned by the authenticated user.",
        "summary": "Retrieve a single email",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A UUID string identifying this Email Message.",
            "required": true
          }
        ],
        "tags": [
          "Email"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailMessage"
                }
              }
            },
            "description": "Email retrieved successfully."
          },
          "404": {
            "description": "Email not found or not owned by the requesting user."
          }
        }
      }
    },
    "/api/v1/sending-profile": {
      "get": {
        "operationId": "sending_profile_retrieve",
        "description": "Every channel × traffic class combination with its status: `active` (send now), `in_review`, `action_required` (finish or fix your access request), `available` (apply via `POST /access-requests`) or `unavailable` (not offered). Active entries include the sender ID messages go out as.",
        "summary": "What this account can send",
        "parameters": [],
        "tags": [
          "Sending access"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendingProfile"
                },
                "examples": {
                  "NewAccount": {
                    "value": {
                      "account_id": 1042,
                      "capabilities": [
                        {
                          "channel": "sms",
                          "traffic_class": "promotional",
                          "status": "active",
                          "can_send": true,
                          "source": "default",
                          "sender_id": "SOFTLINK",
                          "access_request_id": null,
                          "reason": ""
                        },
                        {
                          "channel": "sms",
                          "traffic_class": "transactional",
                          "status": "available",
                          "can_send": false,
                          "source": null,
                          "sender_id": null,
                          "access_request_id": null,
                          "reason": ""
                        },
                        {
                          "channel": "email",
                          "traffic_class": "promotional",
                          "status": "unavailable",
                          "can_send": false,
                          "source": null,
                          "sender_id": null,
                          "access_request_id": null,
                          "reason": "Promotional email isn't offered. Email is for transactional messages (receipts, codes, alerts) and needs approval."
                        },
                        {
                          "channel": "email",
                          "traffic_class": "transactional",
                          "status": "in_review",
                          "can_send": false,
                          "source": null,
                          "sender_id": null,
                          "access_request_id": "8f0c6a5e-2a5d-4c47-9d0b-2f1f8f0e4b11",
                          "reason": ""
                        }
                      ]
                    },
                    "summary": "New account"
                  }
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "404": {
            "description": "`not_found`: no such access request on your accounts."
          }
        }
      }
    },
    "/api/v1/sms": {
      "get": {
        "operationId": "sms_list",
        "description": "Returns a cursor-paginated list of SMS messages belonging to the authenticated user, ordered by most recent first. Supports free-text search over the recipient number, message body, and delivery response description via the `search` query parameter, plus exact filtering by `status` and `mobile`.",
        "summary": "List sent SMS messages",
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination cursor returned in `next`/`previous` links."
          },
          {
            "in": "query",
            "name": "mobile",
            "schema": {
              "type": "string"
            },
            "description": "Filter by recipient number (partial match)."
          },
          {
            "name": "page_size",
            "required": false,
            "in": "query",
            "description": "Number of results to return per page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "search",
            "schema": {
              "type": "string"
            },
            "description": "Search term matched against `to_number`, `message`, and `response_description`."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "error",
                "pending",
                "sent",
                "success"
              ]
            },
            "description": "Filter by simplified outcome category rather than the raw dispatch status: `pending`, `sent` (includes messages queued for retry), `success` (delivery confirmed), or `error` (failed or bounced)."
          }
        ],
        "tags": [
          "SMS"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedMessageList"
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "sms_create",
        "description": "Queues an SMS message for delivery to up to 1000 recipients. Each recipient receives an individual `Message` record; delivery is dispatched asynchronously via a background task and the cost is calculated per-segment based on message length.",
        "summary": "Send one or more SMS messages",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "schema": {
              "type": "string"
            },
            "description": "Makes retries safe. Send a unique value (a UUID works) per logical send; repeating the request with the same key within 24 hours returns the original response with `Idempotent-Replayed: true` instead of sending and charging again. Reusing a key with a different body is `422 idempotency_key_reused`. Max 255 characters."
          }
        ],
        "tags": [
          "SMS"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              },
              "examples": {
                "QueueRequest": {
                  "value": {
                    "recipients": [
                      "+254712345678",
                      "+254798765432"
                    ],
                    "body": "Your order has shipped!"
                  },
                  "summary": "Queue request"
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/MessageRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmsBatch"
                },
                "examples": {
                  "MessagesQueued": {
                    "value": {
                      "batch_id": "1c9d7f2a-55e3-4b8e-9a61-0f2d3c4b5a69",
                      "recipients": 1,
                      "total_price": "0.50",
                      "currency": "KES",
                      "messages": [
                        {
                          "id": "3fae8b8e-9c3a-4e9a-9d3a-7e8e9c3a4e9a",
                          "body": "Your order has shipped!",
                          "traffic_class": "promotional",
                          "sender_id": "SOFTLINK",
                          "price": "0.50",
                          "currency": "KES",
                          "batch_id": "1c9d7f2a-55e3-4b8e-9a61-0f2d3c4b5a69",
                          "message_id": null,
                          "recipient": "+254712345678",
                          "response_code": null,
                          "response_description": null,
                          "status": "pending",
                          "duration_ms": null,
                          "created_at": "2026-10-19T08:15:00.412000Z",
                          "updated_at": "2026-10-19T08:15:00.412000Z"
                        }
                      ]
                    },
                    "summary": "Messages queued"
                  }
                }
              }
            },
            "description": "Queued. One message per recipient, all sharing `batch_id`."
          },
          "400": {
            "description": "Invalid payload, e.g. malformed phone numbers or message too long."
          },
          "403": {
            "description": "`traffic_class_not_permitted`: transactional SMS isn't approved for this account. `details.status` says where the access request stands."
          }
        }
      }
    },
    "/api/v1/sms/{id}": {
      "get": {
        "operationId": "sms_retrieve",
        "description": "Returns the delivery status and details of a single SMS message owned by the authenticated user.",
        "summary": "Retrieve a single SMS message",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "A UUID string identifying this Message.",
            "required": true
          }
        ],
        "tags": [
          "SMS"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            },
            "description": "Message retrieved successfully."
          },
          "404": {
            "description": "Message not found or not owned by the requesting user."
          }
        }
      }
    },
    "/api/v1/webhook-endpoint": {
      "get": {
        "operationId": "webhook_endpoint_retrieve",
        "summary": "Retrieve the webhook endpoint",
        "parameters": [],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:read`."
          },
          "404": {
            "description": "`not_found`: no webhook endpoint is configured."
          }
        }
      },
      "put": {
        "operationId": "webhook_endpoint_set",
        "description": "Sets where events are POSTed. The URL must be `https://` on port 443 and resolve to a public address. Creating the endpoint returns its signing `secret` once; updating keeps the existing secret. `events` limits what you receive; omit it for every event.",
        "summary": "Create or update the webhook endpoint",
        "parameters": [],
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointWriteRequest"
              },
              "examples": {
                "DeliveryEventsOnly": {
                  "value": {
                    "url": "https://example.com/webhooks/softlinksms",
                    "events": [
                      "sms.delivered",
                      "sms.failed"
                    ]
                  },
                  "summary": "Delivery events only"
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointWriteRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointWriteRequest"
              }
            }
          },
          "required": true
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            },
            "description": ""
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointWithSecret"
                }
              }
            },
            "description": ""
          },
          "400": {
            "description": "`invalid_webhook_url` or `invalid_event_type`."
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:write`."
          }
        }
      },
      "delete": {
        "operationId": "webhook_endpoint_delete",
        "description": "Stops all event delivery. Pending retries are abandoned.",
        "summary": "Remove the webhook endpoint",
        "parameters": [],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "No response body"
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:write`."
          }
        }
      }
    },
    "/api/v1/webhook-endpoint/rotate-secret": {
      "post": {
        "operationId": "webhook_endpoint_rotate_secret",
        "description": "Issues a new signing secret and returns it once. Events are signed with the new secret from the next delivery on, so update your verifier first.",
        "summary": "Rotate the signing secret",
        "parameters": [],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Secret"
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:write`."
          },
          "404": {
            "description": "`not_found`: no webhook endpoint is configured."
          }
        }
      }
    },
    "/api/v1/webhook-endpoint/test": {
      "post": {
        "operationId": "webhook_endpoint_test",
        "description": "Queues a signed `webhook.test` event to the endpoint. Check its delivery with `GET /webhook-events/{id}`.",
        "summary": "Send a test event",
        "parameters": [],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEvent"
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:write`."
          },
          "404": {
            "description": "`not_found`: no webhook endpoint is configured."
          }
        }
      }
    },
    "/api/v1/webhook-events": {
      "get": {
        "operationId": "webhook_events_list",
        "description": "Every event generated for the account in the last 30 days, newest first, with its delivery status and attempts. Use it to debug your endpoint or to catch up after downtime.",
        "summary": "List webhook events",
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            },
            "description": "Pagination cursor from `next`."
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "delivered",
                "failed",
                "pending"
              ]
            },
            "description": "Delivery status."
          },
          {
            "in": "query",
            "name": "type",
            "schema": {
              "type": "string",
              "enum": [
                "email.bounced",
                "email.delivered",
                "email.failed",
                "sms.delivered",
                "sms.failed",
                "webhook.test"
              ]
            },
            "description": "Event type."
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookEvent"
                  }
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:read`."
          }
        }
      }
    },
    "/api/v1/webhook-events/{event_id}": {
      "get": {
        "operationId": "webhook_event_retrieve",
        "summary": "Retrieve a webhook event",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEvent"
                },
                "examples": {
                  "Payload": {
                    "value": {
                      "id": "evt_6a1f0c2e9b8d4f3aa1c27e5d9b0f4e21",
                      "type": "sms.delivered",
                      "created_at": "2026-10-19T08:15:02Z",
                      "account_id": 1042,
                      "data": {
                        "object": "sms",
                        "id": "3fae8b8e-9c3a-4e9a-9d3a-7e8e9c3a4e9a",
                        "recipient": "+254712345678",
                        "status": "delivered",
                        "traffic_class": "transactional",
                        "sender_id": "ACMEBANK",
                        "batch_id": "1c9d7f2a-55e3-4b8e-9a61-0f2d3c4b5a69",
                        "price": "0.50",
                        "currency": "KES",
                        "delivery_description": "DeliveredToTerminal",
                        "delivered_at": "2026-10-19T08:15:01Z",
                        "created_at": "2026-10-19T08:15:00Z"
                      }
                    }
                  }
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:read`."
          },
          "404": {
            "description": "`not_found`."
          }
        }
      }
    },
    "/api/v1/webhook-events/{event_id}/redeliver": {
      "post": {
        "operationId": "webhook_event_redeliver",
        "description": "Sends the event again now, whatever its status, with a fresh signature. The event id stays the same, so your handler's deduplication still works.",
        "summary": "Redeliver a webhook event",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEvent"
                }
              }
            },
            "description": ""
          },
          "401": {
            "description": "Missing or invalid credentials."
          },
          "403": {
            "description": "`insufficient_scope`: the key lacks `webhooks:write`."
          },
          "404": {
            "description": "`not_found`."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Capability": {
        "type": "object",
        "properties": {
          "channel": {
            "type": "string"
          },
          "traffic_class": {
            "type": "string"
          },
          "status": {
            "enum": [
              "active",
              "in_review",
              "action_required",
              "available",
              "unavailable"
            ],
            "type": "string",
            "description": "* `active` - active\n* `in_review` - in_review\n* `action_required` - action_required\n* `available` - available\n* `unavailable` - unavailable",
            "x-spec-enum-id": "292e724be10c8605"
          },
          "can_send": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "nullable": true
          },
          "sender_id": {
            "type": "string",
            "nullable": true
          },
          "access_request_id": {
            "type": "string",
            "nullable": true,
            "readOnly": true
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "access_request_id",
          "can_send",
          "channel",
          "reason",
          "sender_id",
          "source",
          "status",
          "traffic_class"
        ]
      },
      "EmailBatch": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "description": "Shared by every message in this send, and by their webhook events."
          },
          "recipients": {
            "type": "integer",
            "description": "Number of messages queued."
          },
          "total_price": {
            "type": "string",
            "description": "Total cost of the send as a decimal string, e.g. `1.20`. Charged up front."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code, e.g. `KES`."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailMessage"
            },
            "description": "One email per recipient."
          }
        },
        "required": [
          "batch_id",
          "currency",
          "messages",
          "recipients",
          "total_price"
        ]
      },
      "EmailMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "traffic_class": {
            "enum": [
              "promotional",
              "transactional"
            ],
            "type": "string",
            "x-spec-enum-id": "c27c684863ebe73f",
            "default": "transactional",
            "description": "Email is transactional only (receipts, codes, alerts) and needs approval. See GET /sending-profile.\n\n* `promotional` - Promotional\n* `transactional` - Transactional"
          },
          "recipient": {
            "type": "string",
            "readOnly": true
          },
          "price": {
            "type": "string",
            "readOnly": true
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The currency of the email"
          },
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "nullable": true,
            "description": "The send this email was part of"
          },
          "message_id": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The provider's request id for this send"
          },
          "response_code": {
            "type": "integer",
            "readOnly": true,
            "nullable": true,
            "description": "The response code from the delivery"
          },
          "response_description": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The response description from the delivery"
          },
          "status": {
            "enum": [
              "pending",
              "sent",
              "delivered",
              "bounced",
              "failed",
              "retrying"
            ],
            "type": "string",
            "x-spec-enum-id": "652583c0f365cf58",
            "readOnly": true,
            "description": "Dispatch status of the email\n\n* `pending` - Pending\n* `sent` - Sent\n* `delivered` - Delivered\n* `bounced` - Bounced\n* `failed` - Failed\n* `retrying` - Retrying"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "batch_id",
          "created_at",
          "currency",
          "id",
          "message_id",
          "price",
          "recipient",
          "response_code",
          "response_description",
          "status"
        ]
      },
      "EmailMessageRequest": {
        "type": "object",
        "properties": {
          "recipients": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email",
              "minLength": 1
            },
            "writeOnly": true,
            "maxItems": 1000,
            "minItems": 1
          },
          "subject": {
            "type": "string",
            "writeOnly": true,
            "minLength": 1,
            "maxLength": 255
          },
          "body": {
            "type": "string",
            "writeOnly": true,
            "minLength": 1
          },
          "traffic_class": {
            "enum": [
              "promotional",
              "transactional"
            ],
            "type": "string",
            "x-spec-enum-id": "c27c684863ebe73f",
            "default": "transactional",
            "description": "Email is transactional only (receipts, codes, alerts) and needs approval. See GET /sending-profile.\n\n* `promotional` - Promotional\n* `transactional` - Transactional"
          }
        },
        "required": [
          "body",
          "recipients",
          "subject"
        ]
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "body": {
            "type": "string",
            "description": "The text to send. `message` is accepted as an alias."
          },
          "traffic_class": {
            "enum": [
              "promotional",
              "transactional"
            ],
            "type": "string",
            "x-spec-enum-id": "c27c684863ebe73f",
            "default": "promotional",
            "description": "`promotional` (default, shared sender ID) or `transactional` (OTPs, receipts, alerts; needs approval, sent from your own sender ID). See GET /sending-profile.\n\n* `promotional` - Promotional\n* `transactional` - Transactional"
          },
          "sender_id": {
            "type": "string",
            "readOnly": true
          },
          "price": {
            "type": "string",
            "readOnly": true
          },
          "currency": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The currency of the message"
          },
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "nullable": true,
            "description": "The send this message was part of"
          },
          "message_id": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The id of the message"
          },
          "recipient": {
            "type": "string",
            "readOnly": true
          },
          "response_code": {
            "type": "integer",
            "readOnly": true,
            "nullable": true,
            "description": "The response code from the delivery"
          },
          "response_description": {
            "type": "string",
            "readOnly": true,
            "nullable": true,
            "description": "The response description from the delivery"
          },
          "status": {
            "enum": [
              "pending",
              "sent",
              "delivered",
              "bounced",
              "failed",
              "retrying"
            ],
            "type": "string",
            "x-spec-enum-id": "652583c0f365cf58",
            "readOnly": true,
            "description": "Dispatch status of the message\n\n* `pending` - Pending\n* `sent` - Sent\n* `delivered` - Delivered\n* `bounced` - Bounced\n* `failed` - Failed\n* `retrying` - Retrying"
          },
          "duration_ms": {
            "type": "integer",
            "readOnly": true,
            "nullable": true,
            "description": "Measured time, in milliseconds, the provider took to respond to this message's send request. Null until a result comes back (or forever, for messages that ran out of retries without ever getting one)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          }
        },
        "required": [
          "batch_id",
          "body",
          "created_at",
          "currency",
          "duration_ms",
          "id",
          "message_id",
          "price",
          "recipient",
          "response_code",
          "response_description",
          "sender_id",
          "status",
          "updated_at"
        ]
      },
      "MessageRequest": {
        "type": "object",
        "properties": {
          "recipients": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "writeOnly": true,
            "maxItems": 1000,
            "minItems": 1
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "description": "The text to send. `message` is accepted as an alias."
          },
          "traffic_class": {
            "enum": [
              "promotional",
              "transactional"
            ],
            "type": "string",
            "x-spec-enum-id": "c27c684863ebe73f",
            "default": "promotional",
            "description": "`promotional` (default, shared sender ID) or `transactional` (OTPs, receipts, alerts; needs approval, sent from your own sender ID). See GET /sending-profile.\n\n* `promotional` - Promotional\n* `transactional` - Transactional"
          }
        },
        "required": [
          "body",
          "recipients"
        ]
      },
      "PaginatedEmailMessageList": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "next": {
            "type": "string",
            "nullable": true,
            "format": "uri",
            "example": "http://api.example.org/accounts/?cursor=cD00ODY%3D\""
          },
          "previous": {
            "type": "string",
            "nullable": true,
            "format": "uri",
            "example": "http://api.example.org/accounts/?cursor=cj0xJnA9NDg3"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailMessage"
            }
          }
        }
      },
      "PaginatedMessageList": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "next": {
            "type": "string",
            "nullable": true,
            "format": "uri",
            "example": "http://api.example.org/accounts/?cursor=cD00ODY%3D\""
          },
          "previous": {
            "type": "string",
            "nullable": true,
            "format": "uri",
            "example": "http://api.example.org/accounts/?cursor=cj0xJnA9NDg3"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "Secret": {
        "type": "object",
        "properties": {
          "secret": {
            "type": "string"
          }
        },
        "required": [
          "secret"
        ]
      },
      "SendingProfile": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "integer"
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Capability"
            }
          }
        },
        "required": [
          "account_id",
          "capabilities"
        ]
      },
      "SmsBatch": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "description": "Shared by every message in this send, and by their webhook events."
          },
          "recipients": {
            "type": "integer",
            "description": "Number of messages queued."
          },
          "total_price": {
            "type": "string",
            "description": "Total cost of the send as a decimal string, e.g. `1.20`. Charged up front."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code, e.g. `KES`."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "One message per recipient."
          }
        },
        "required": [
          "batch_id",
          "currency",
          "messages",
          "recipients",
          "total_price"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "readOnly": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "secret_hint": {
            "type": "string",
            "readOnly": true,
            "description": "The last 4 characters of the signing secret, to tell secrets apart."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "created_at",
          "events",
          "object",
          "secret_hint",
          "updated_at",
          "url"
        ]
      },
      "WebhookEndpointWithSecret": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "readOnly": true
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "secret_hint": {
            "type": "string",
            "readOnly": true,
            "description": "The last 4 characters of the signing secret, to tell secrets apart."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret. Shown only when created or rotated: store it now."
          }
        },
        "required": [
          "created_at",
          "events",
          "object",
          "secret",
          "secret_hint",
          "updated_at",
          "url"
        ]
      },
      "WebhookEndpointWriteRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "minLength": 1,
            "maxLength": 500
          },
          "events": {
            "type": "array",
            "items": {
              "enum": [
                "sms.delivered",
                "sms.failed",
                "email.delivered",
                "email.bounced",
                "email.failed"
              ],
              "type": "string",
              "description": "* `sms.delivered` - sms.delivered\n* `sms.failed` - sms.failed\n* `email.delivered` - email.delivered\n* `email.bounced` - email.bounced\n* `email.failed` - email.failed",
              "x-spec-enum-id": "fb8b35852e5a9649"
            },
            "description": "Event types to receive. Empty or omitted means all of them."
          }
        },
        "required": [
          "url"
        ]
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "readOnly": true
          },
          "type": {
            "enum": [
              "sms.delivered",
              "sms.failed",
              "email.delivered",
              "email.bounced",
              "email.failed",
              "webhook.test"
            ],
            "type": "string",
            "description": "* `sms.delivered` - SMS delivered\n* `sms.failed` - SMS failed\n* `email.delivered` - Email delivered\n* `email.bounced` - Email bounced\n* `email.failed` - Email failed\n* `webhook.test` - Test event",
            "x-spec-enum-id": "4a278caf21d4f647"
          },
          "status": {
            "enum": [
              "pending",
              "delivered",
              "failed"
            ],
            "type": "string",
            "description": "* `pending` - Pending\n* `delivered` - Delivered\n* `failed` - Failed",
            "x-spec-enum-id": "99733768b07b9048"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "attempts": {
            "type": "integer",
            "maximum": 32767,
            "minimum": 0
          },
          "last_status_code": {
            "type": "integer",
            "maximum": 32767,
            "minimum": 0,
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "maxLength": 300
          },
          "last_attempt_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "next_attempt_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "payload": {}
        },
        "required": [
          "created_at",
          "id",
          "object",
          "payload",
          "status",
          "type",
          "url"
        ]
      }
    },
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Your API key, sent as `X-Api-Key`. Format `bsk_<key_id>.<secret>`. Create one in the dashboard under Settings → Developer tools."
      }
    }
  },
  "servers": [
    {
      "url": "https://api.softlinksms.co.ke",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKeyAuth": []
    }
  ]
}
