"openapi": "3.0.3"
"info":
  "title": "RafikiSMS API"
  "version": "1.0.0"
  "description": "REST API for sending SMS, generating and verifying OTP codes, managing sender names, checking balances, retrieving delivery reports, and configuring delivery webhooks."
  "contact":
    "name": "RafikiSMS Support"
    "url": "https://rafikisms.com"
"servers":
  - "url": "https://api.rafikisms.com"
    "description": "Production API"
"tags":
  - "name": "SMS"
    "description": "Send SMS messages and retrieve message logs."
  - "name": "OTP"
    "description": "Generate and verify one-time passwords sent by SMS."
  - "name": "Sender Names"
    "description": "Manage sender names and account balance."
  - "name": "Webhooks"
    "description": "Configure delivery status webhooks."
"paths":
  "/v1/vendor/send-sms":
    "post":
      "tags":
        - "SMS"
      "summary": "Send single SMS"
      "description": "Queues one SMS message for asynchronous delivery. Phone numbers must be in international format without the plus sign, for example 255712345678. If sender_id, sender, or source_address is omitted, the account default sender is used."
      "security":
        - "ApiKeyAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/SendSmsRequest"
            "example":
              "phone": "255712345678"
              "message": "Hello! This is a test message from RafikiSMS."
              "sender_id": "MYBRAND"
      "responses":
        "200":
          "description": "SMS accepted and queued for sending."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/SendSmsResponse"
              "example":
                "status": "success"
                "message": "SMS queued successfully"
                "data":
                  "message": "SMS has been queued for sending"
                  "note": "SMS will be processed asynchronously. Check SMS logs for status updates."
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/vendor/send-bulk-sms":
    "post":
      "tags":
        - "SMS"
      "summary": "Send bulk SMS"
      "description": "Queues one message for multiple recipients. Use recipients as an array of phone numbers or phone as a comma-separated string. Multipart uploads can also pass recipients_file in the dashboard flow."
      "security":
        - "ApiKeyAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/SendBulkSmsRequest"
            "example":
              "message": "Your bulk message content (max 160 characters)"
              "phone": "255712345678,255621728109,255620656604"
              "sender_id": "MYBRAND"
      "responses":
        "200":
          "description": "Bulk SMS accepted and queued for sending."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/SendBulkSmsResponse"
              "example":
                "status": "success"
                "message": "Bulk SMS queued successfully"
                "data":
                  "message": "Bulk SMS has been queued for sending"
                  "total_recipients": 3
                  "note": "SMS will be processed asynchronously."
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/sms-logs":
    "get":
      "tags":
        - "SMS"
      "summary": "Get SMS logs"
      "description": "Returns paginated SMS logs for the authenticated user."
      "security":
        - "BearerAuth": []
      "parameters":
        - "$ref": "#/components/parameters/Page"
        - "$ref": "#/components/parameters/PerPage"
      "responses":
        "200":
          "description": "Paginated SMS logs."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/SmsLogsResponse"
              "example":
                "status": "success"
                "message": "SMS logs retrieved successfully"
                "data":
                  "current_page": 1
                  "data":
                    - "id": 15
                      "user_id": 4
                      "recipient": "255712345678"
                      "message": "Hello! This is a test message"
                      "status": "success"
                      "transaction_id": "19725260"
                      "created_at": "2025-12-11T13:10:15.000000Z"
                      "updated_at": "2025-12-11T13:10:17.000000Z"
                  "per_page": 10
                  "total": 15
                  "last_page": 2
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/v1/otp/generate":
    "post":
      "tags":
        - "OTP"
      "summary": "Generate OTP"
      "description": "Sends a one-time password to a phone number. The OTP code is never returned in the API response. Use the returned reference_id when verifying the user-entered code. Rate limit: 3 generate calls per phone number per 5 minutes. If sender_id is omitted, the SMS will be sent using the RafikiSMS default company sender name instead of your brand."
      "security":
        - "ApiKeyAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/GenerateOtpRequest"
            "example":
              "phone_number": "255712345678"
              "sender_id": "MYSHOP-OTP"
      "responses":
        "200":
          "description": "OTP sent."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/GenerateOtpResponse"
              "example":
                "success": true
                "message": "OTP sent successfully."
                "data":
                  "reference_id": "550e8400-e29b-41d4-a716-446655440000"
                  "expires_in_seconds": 300
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
        "429":
          "$ref": "#/components/responses/RateLimited"
  "/v1/otp/verify":
    "post":
      "tags":
        - "OTP"
      "summary": "Verify OTP"
      "description": "Verifies the 6-digit code received by SMS. This public endpoint does not require an API key. Each reference_id allows a maximum of 5 wrong attempts before it is locked."
      "security": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/VerifyOtpRequest"
            "example":
              "phone_number": "255712345678"
              "otp_code": "482916"
              "reference_id": "550e8400-e29b-41d4-a716-446655440000"
      "responses":
        "200":
          "description": "OTP verified."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/VerifyOtpResponse"
              "example":
                "success": true
                "message": "Verification successful."
                "data":
                  "verified": true
        "422":
          "$ref": "#/components/responses/ValidationError"
        "429":
          "description": "Maximum verification attempts exceeded."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/OtpErrorResponse"
              "example":
                "success": false
                "message": "Too many attempts."
                "error_code": "ATTEMPTS_EXCEEDED"
  "/v1/vendor/sender-names":
    "get":
      "tags":
        - "Sender Names"
      "summary": "Get sender names"
      "description": "Retrieves sender names associated with the API key account."
      "security":
        - "ApiKeyAuth": []
      "parameters":
        - "$ref": "#/components/parameters/Page"
        - "$ref": "#/components/parameters/PerPage"
        - "name": "status"
          "in": "query"
          "required": false
          "description": "Optional sender-name status filter, for example active or pending."
          "schema":
            "type": "string"
      "responses":
        "200":
          "description": "Sender names for the account."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/SenderNamesResponse"
              "example":
                "success": true
                "message": "Sender names retrieved successfully"
                "data":
                  "sender_names":
                    - "id": "c893d701-e1d8-4b00-8634-8c464e56ba11"
                      "senderid": "STARSHINE"
                      "sample_content": "This sender name is used for order confirmations"
                      "status": "active"
                      "created": "2025-07-08T14:07:56.000Z"
                  "pagination":
                    "totalItems": 1
                    "currentPage": 1
                    "pageSize": 25
                    "totalPages": 1
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/v1/vendor/balance":
    "get":
      "tags":
        - "Sender Names"
      "summary": "Check balance"
      "description": "Returns the current SMS credit balance for the API key account."
      "security":
        - "ApiKeyAuth": []
      "responses":
        "200":
          "description": "Current SMS credit balance."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/BalanceResponse"
              "example":
                "status": "success"
                "message": "Balance retrieved successfully"
                "data":
                  "credit_balance": 1000
        "401":
          "$ref": "#/components/responses/Unauthorized"
  "/v1/vendor/delivery-reports":
    "get":
      "tags":
        - "Sender Names"
      "summary": "Get delivery report"
      "description": "Checks delivery status for a sent SMS. Query at least 5 minutes after sending to allow for network delays. Status values include DELIVERED, PENDING, and UNDELIVERED."
      "security":
        - "ApiKeyAuth": []
      "parameters":
        - "name": "dest_addr"
          "in": "query"
          "required": true
          "description": "Destination phone number in international format."
          "schema":
            "type": "string"
            "example": "255712345678"
        - "name": "request_id"
          "in": "query"
          "required": true
          "description": "SMS request or transaction ID returned by the send endpoint."
          "schema":
            "type": "string"
            "example": "19725260"
      "responses":
        "200":
          "description": "Delivery report for the message."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/DeliveryReportsResponse"
              "example":
                "status": "success"
                "message": "Delivery report retrieved successfully"
                "data":
                  "delivery_reports":
                    - "dest_addr": "255712345678"
                      "status": "DELIVERED"
                      "request_id": "19725260"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/sender-names/request":
    "post":
      "tags":
        - "Sender Names"
      "summary": "Request sender name"
      "description": "Requests a new sender name. The sender ID must be 11 characters or fewer and can contain letters, numbers, spaces, hyphens, and dots."
      "security":
        - "BearerAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/RequestSenderNameRequest"
            "example":
              "senderid": "STARSHINE"
              "sample_content": "This sender name will be used for sending order confirmations and delivery notifications to our customers"
      "responses":
        "200":
          "description": "Sender name request created."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/RequestSenderNameResponse"
              "example":
                "status": "success"
                "message": "Sender name requested successfully"
                "data":
                  "id": "24e1ffaf-e399-4b15-983a-53539e4970bd"
                  "senderid": "STARSHINE"
                  "sample_content": "This sender name will be used for sending order confirmations"
                  "status": "pending"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
  "/v1/vendors/delivery-webhook":
    "put":
      "tags":
        - "Webhooks"
      "summary": "Configure delivery webhook"
      "description": "Sets or disables the HTTPS URL that receives SMS delivery status events. Only the parent vendor account can set this URL; sub-accounts inherit it. Pass null to disable."
      "security":
        - "BearerAuth": []
      "requestBody":
        "required": true
        "content":
          "application/json":
            "schema":
              "$ref": "#/components/schemas/DeliveryWebhookRequest"
            "example":
              "delivery_webhook_url": "https://yourapp.com/webhooks/sms"
      "responses":
        "200":
          "description": "Webhook URL saved."
          "content":
            "application/json":
              "schema":
                "$ref": "#/components/schemas/DeliveryWebhookResponse"
              "example":
                "status": "success"
                "message": "Delivery webhook updated successfully"
                "data":
                  "delivery_webhook_url": "https://yourapp.com/webhooks/sms"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "422":
          "$ref": "#/components/responses/ValidationError"
"components":
  "securitySchemes":
    "ApiKeyAuth":
      "type": "apiKey"
      "in": "header"
      "name": "X-API-Key"
      "description": "Vendor API key generated from Profile > Integration."
    "BearerAuth":
      "type": "http"
      "scheme": "bearer"
      "bearerFormat": "JWT"
      "description": "Dashboard access token for account-authenticated endpoints."
  "parameters":
    "Page":
      "name": "page"
      "in": "query"
      "required": false
      "description": "Page number. Defaults to 1."
      "schema":
        "type": "integer"
        "minimum": 1
        "default": 1
    "PerPage":
      "name": "per_page"
      "in": "query"
      "required": false
      "description": "Items per page. Defaults to 10, maximum 100."
      "schema":
        "type": "integer"
        "minimum": 1
        "maximum": 100
        "default": 10
  "responses":
    "Unauthorized":
      "description": "Authentication failed or credentials are missing."
      "content":
        "application/json":
          "schema":
            "$ref": "#/components/schemas/ErrorResponse"
          "example":
            "status": "error"
            "message": "Unauthenticated."
    "ValidationError":
      "description": "Request validation failed."
      "content":
        "application/json":
          "schema":
            "$ref": "#/components/schemas/ValidationErrorResponse"
          "example":
            "status": "error"
            "message": "Validation failed"
            "errors":
              "phone":
                - "The phone field is required."
              "message":
                - "The message must not exceed 160 characters."
    "RateLimited":
      "description": "Too many requests."
      "content":
        "application/json":
          "schema":
            "$ref": "#/components/schemas/ErrorResponse"
          "example":
            "status": "error"
            "message": "Too Many Requests"
  "schemas":
    "ErrorResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
          "example": "error"
        "success":
          "type": "boolean"
          "example": false
        "message":
          "type": "string"
        "error_code":
          "type": "string"
    "ValidationErrorResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
          "example": "error"
        "message":
          "type": "string"
          "example": "Validation failed"
        "errors":
          "type": "object"
          "additionalProperties":
            "type": "array"
            "items":
              "type": "string"
    "SendSmsRequest":
      "type": "object"
      "required":
        - "phone"
        - "message"
      "properties":
        "phone":
          "type": "string"
          "example": "255712345678"
        "message":
          "type": "string"
          "maxLength": 160
        "sender_id":
          "type": "string"
          "maxLength": 11
          "description": "Approved sender name."
        "sender":
          "type": "string"
          "maxLength": 11
        "source_address":
          "type": "string"
          "maxLength": 11
    "SendSmsResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
          "example": "success"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "message":
              "type": "string"
            "note":
              "type": "string"
    "SendBulkSmsRequest":
      "type": "object"
      "required":
        - "message"
      "properties":
        "message":
          "type": "string"
          "maxLength": 160
        "phone":
          "type": "string"
          "description": "Comma-separated recipient phone numbers."
          "example": "255712345678,255621728109"
        "recipients":
          "type": "array"
          "items":
            "type": "string"
          "description": "Recipient phone numbers."
        "sender_id":
          "type": "string"
          "maxLength": 11
        "sender":
          "type": "string"
          "maxLength": 11
        "source_address":
          "type": "string"
          "maxLength": 11
    "SendBulkSmsResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
          "example": "success"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "message":
              "type": "string"
            "total_recipients":
              "type": "integer"
            "note":
              "type": "string"
    "SmsLog":
      "type": "object"
      "properties":
        "id":
          "type": "integer"
        "user_id":
          "type": "integer"
        "recipient":
          "type": "string"
        "message":
          "type": "string"
        "status":
          "type": "string"
        "transaction_id":
          "type": "string"
        "created_at":
          "type": "string"
          "format": "date-time"
        "updated_at":
          "type": "string"
          "format": "date-time"
    "SmsLogsResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "current_page":
              "type": "integer"
            "data":
              "type": "array"
              "items":
                "$ref": "#/components/schemas/SmsLog"
            "per_page":
              "type": "integer"
            "total":
              "type": "integer"
            "last_page":
              "type": "integer"
    "GenerateOtpRequest":
      "type": "object"
      "required":
        - "phone_number"
      "properties":
        "phone_number":
          "type": "string"
          "example": "255712345678"
        "phone":
          "type": "string"
          "description": "Alias for phone_number."
        "sender_id":
          "type": "string"
          "maxLength": 11
    "GenerateOtpResponse":
      "type": "object"
      "properties":
        "success":
          "type": "boolean"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "reference_id":
              "type": "string"
              "format": "uuid"
            "expires_in_seconds":
              "type": "integer"
              "example": 300
    "VerifyOtpRequest":
      "type": "object"
      "required":
        - "phone_number"
        - "otp_code"
        - "reference_id"
      "properties":
        "phone_number":
          "type": "string"
          "example": "255712345678"
        "phone":
          "type": "string"
          "description": "Alias for phone_number."
        "otp_code":
          "type": "string"
          "minLength": 6
          "maxLength": 6
          "example": "482916"
        "code":
          "type": "string"
          "description": "Alias for otp_code."
        "otp":
          "type": "string"
          "description": "Alias for otp_code."
        "reference_id":
          "type": "string"
          "format": "uuid"
        "referenceId":
          "type": "string"
          "description": "Alias for reference_id."
        "ref_id":
          "type": "string"
          "description": "Alias for reference_id."
    "VerifyOtpResponse":
      "type": "object"
      "properties":
        "success":
          "type": "boolean"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "verified":
              "type": "boolean"
    "OtpErrorResponse":
      "type": "object"
      "properties":
        "success":
          "type": "boolean"
          "example": false
        "message":
          "type": "string"
        "error_code":
          "type": "string"
          "enum":
            - "INVALID_REFERENCE"
            - "CODE_ALREADY_USED"
            - "CODE_EXPIRED"
            - "INVALID_CODE"
            - "ATTEMPTS_EXCEEDED"
            - "OTP_RATE_LIMIT_EXCEEDED"
        "attempts_remaining":
          "type": "integer"
    "SenderName":
      "type": "object"
      "properties":
        "id":
          "type": "string"
          "format": "uuid"
        "senderid":
          "type": "string"
        "sample_content":
          "type": "string"
        "status":
          "type": "string"
          "enum":
            - "active"
            - "pending"
            - "rejected"
        "created":
          "type": "string"
          "format": "date-time"
    "SenderNamesResponse":
      "type": "object"
      "properties":
        "success":
          "type": "boolean"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "sender_names":
              "type": "array"
              "items":
                "$ref": "#/components/schemas/SenderName"
            "pagination":
              "$ref": "#/components/schemas/PaginationMeta"
    "PaginationMeta":
      "type": "object"
      "properties":
        "totalItems":
          "type": "integer"
        "currentPage":
          "type": "integer"
        "pageSize":
          "type": "integer"
        "totalPages":
          "type": "integer"
    "BalanceResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "credit_balance":
              "type": "number"
    "DeliveryReport":
      "type": "object"
      "properties":
        "dest_addr":
          "type": "string"
        "status":
          "type": "string"
          "enum":
            - "DELIVERED"
            - "PENDING"
            - "UNDELIVERED"
        "request_id":
          "type": "string"
    "DeliveryReportsResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "delivery_reports":
              "type": "array"
              "items":
                "$ref": "#/components/schemas/DeliveryReport"
    "RequestSenderNameRequest":
      "type": "object"
      "required":
        - "senderid"
        - "sample_content"
      "properties":
        "senderid":
          "type": "string"
          "maxLength": 11
          "pattern": "^[A-Za-z0-9 .-]+$"
        "sample_content":
          "type": "string"
          "minLength": 15
    "RequestSenderNameResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
        "message":
          "type": "string"
        "data":
          "$ref": "#/components/schemas/SenderName"
    "DeliveryWebhookRequest":
      "type": "object"
      "required":
        - "delivery_webhook_url"
      "properties":
        "delivery_webhook_url":
          "type": "string"
          "format": "uri"
          "nullable": true
          "description": "HTTPS endpoint URL. Set to null to disable."
    "DeliveryWebhookResponse":
      "type": "object"
      "properties":
        "status":
          "type": "string"
        "message":
          "type": "string"
        "data":
          "type": "object"
          "properties":
            "delivery_webhook_url":
              "type": "string"
              "format": "uri"
              "nullable": true
    "WebhookDeliveryPayload":
      "type": "object"
      "description": "Payload sent by RafikiSMS to the configured delivery webhook URL."
      "properties":
        "event":
          "type": "string"
          "example": "sms.delivery_status"
        "event_time":
          "type": "string"
          "format": "date-time"
        "vendor":
          "type": "object"
          "properties":
            "id":
              "type": "integer"
            "uuid":
              "type": "string"
              "format": "uuid"
            "email":
              "type": "string"
              "format": "email"
        "data":
          "type": "object"
          "properties":
            "sms_log_id":
              "type": "integer"
            "status":
              "type": "string"
              "enum":
                - "delivered"
                - "pending"
                - "failed"
                - "undelivered"
            "recipient":
              "type": "string"
            "sender_id":
              "type": "string"
            "transaction_id":
              "type": "string"
            "message_id":
              "type": "string"
            "message":
              "type": "string"
            "delivered_at":
              "type": "string"
              "format": "date-time"
              "nullable": true
            "failure_reason":
              "type": "string"
              "nullable": true
            "source_addr":
              "type": "string"
            "dest_addr":
              "type": "string"
            "processing_time_ms":
              "type": "integer"
            "created_at":
              "type": "string"
              "format": "date-time"
            "updated_at":
              "type": "string"
              "format": "date-time"
