{
  "openapi": "3.0.3",
  "info": {
    "title": "BulkSMS API",
    "version": "1.0.0",
    "description": "Multi-tenant SMS gateway. All endpoints require a Bearer API key issued from the dashboard, except the public ones marked otherwise.",
    "contact": { "name": "BulkSMS support", "email": "support@bulksms.local" }
  },
  "servers": [
    { "url": "https://smsapi.test", "description": "Local dev (Herd)" },
    { "url": "https://api.bulksms.local", "description": "Production" }
  ],
  "tags": [
    { "name": "Messages",   "description": "Send + read SMS" },
    { "name": "Sender IDs", "description": "Request and manage approved senders" },
    { "name": "Billing",    "description": "Plans, credits, top-ups" },
    { "name": "Auth",       "description": "Login, signup, password reset, magic-link" },
    { "name": "Webhooks",   "description": "Subscribe to delivery events" },
    { "name": "Contacts",   "description": "Contacts and lists, opt-outs" },
    { "name": "Public",     "description": "No-auth public endpoints" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "API key" }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error":  { "type": "string", "description": "Machine-readable error message" },
          "detail": { "type": "string" }
        },
        "required": ["error"]
      },
      "SmsSendRequest": {
        "type": "object",
        "required": ["to", "sender", "message"],
        "properties": {
          "to":      { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }], "description": "E.164 phone, comma-separated, or array of phones" },
          "sender":  { "type": "string", "minLength": 3, "maxLength": 11, "description": "Approved alphanumeric sender ID for this tenant" },
          "message": { "type": "string", "description": "Body. Use {key} placeholders for templating" },
          "params":  { "type": "object", "additionalProperties": { "type": "string" }, "description": "Global template parameters" },
          "idempotency_key": { "type": "string", "description": "Replays with the same key are rejected with HTTP 409" }
        }
      },
      "SmsSendResult": {
        "type": "object",
        "properties": {
          "status":      { "type": "string", "enum": ["sent","failed","partial"] },
          "credits_used":{ "type": "integer" },
          "sms_log_id":  { "type": "integer" },
          "credit_info": {
            "type": "object",
            "properties": {
              "total_recipients": { "type": "integer" },
              "credits_charged":  { "type": "integer" },
              "balance_after":    { "type": "integer" },
              "sandbox":          { "type": "boolean" }
            }
          }
        }
      },
      "Plan": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "name": { "type": "string" },
          "monthly_fee_cents": { "type": "integer" },
          "currency": { "type": "string" },
          "included_credits": { "type": "integer" },
          "overage_rate_cents": { "type": "string" }
        }
      },
      "CreditPack": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "name": { "type": "string" },
          "credits": { "type": "integer" },
          "price_amount": { "type": "integer" },
          "currency": { "type": "string" },
          "per_credit": { "type": "number" }
        }
      }
    }
  },
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/api/sms": {
      "post": {
        "tags": ["Messages"],
        "summary": "Send SMS to one or many recipients",
        "description": "Recipients on the tenant's opt-out list or the global DNC blocklist are filtered before send. Sandbox tokens (issued in the dashboard) simulate success without charging credits.",
        "parameters": [
          {
            "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" },
            "description": "Repeat-safe send: same key returns 409 Conflict instead of charging twice."
          }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmsSendRequest" } } }
        },
        "responses": {
          "200": { "description": "Sent (or partial)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SmsSendResult" } } } },
          "402": { "description": "Insufficient credits / trial expired", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "KYC not approved, sender not approved, or scope missing", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Idempotency key already used" },
          "422": { "description": "All recipients are blocked (opt-out / DNC)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "description": "Platform provider not configured" }
        }
      }
    },
    "/api/logs": {
      "get": {
        "tags": ["Messages"],
        "summary": "Paginated SMS logs (tenant-scoped)",
        "parameters": [
          { "name": "page",   "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit",  "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 100 } },
          { "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Match recipient, sender, or message body" },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["sent","failed","pending","delivered","undelivered"] } },
          { "name": "type",   "in": "query", "schema": { "type": "string", "enum": ["sms","access"], "default": "sms" } }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/balance": {
      "get": {
        "tags": ["Billing"], "summary": "Current credit balance",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/sender-ids": {
      "get": {
        "tags": ["Sender IDs"], "summary": "List your sender IDs",
        "responses": { "200": { "description": "OK" } }
      },
      "post": {
        "tags": ["Sender IDs"], "summary": "Request a new sender ID for admin approval",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["sender","use_case"],
            "properties": {
              "sender": { "type": "string", "minLength": 3, "maxLength": 11 },
              "country": { "type": "string", "maxLength": 2 },
              "use_case": { "type": "string", "minLength": 5 },
              "sample_message": { "type": "string" }
            }
          } } }
        },
        "responses": { "201": { "description": "Pending approval" } }
      }
    },
    "/api/plans": {
      "get": {
        "tags": ["Public"], "summary": "List public plans (no auth)", "security": [],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object", "properties": { "plans": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } } }
        }}}}}
      }
    },
    "/api/credit-packs": {
      "get": {
        "tags": ["Public"], "summary": "List public credit packs (no auth)", "security": [],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": {
          "type": "object", "properties": { "packs": { "type": "array", "items": { "$ref": "#/components/schemas/CreditPack" } } }
        }}}}}
      }
    },
    "/api/auth": {
      "post": {
        "tags": ["Auth"], "summary": "Email + password login (returns Bearer token)", "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["email","password"],
            "properties": {
              "email":     { "type": "string", "format": "email" },
              "password":  { "type": "string" },
              "totp_code": { "type": "string", "description": "Required if 2FA is enabled" },
              "tenant_id": { "type": "integer", "description": "Choose tenant when user belongs to many" }
            }
          } } }
        },
        "responses": {
          "200": { "description": "OK" },
          "401": { "description": "Invalid credentials" },
          "429": { "description": "Account locked due to repeated failures" }
        }
      }
    },
    "/api/magic-link": {
      "post": {
        "tags": ["Auth"], "summary": "Request or consume a one-time login link", "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "action": { "type": "string", "enum": ["request","consume"] },
              "email":  { "type": "string", "format": "email" },
              "token":  { "type": "string" }
            }
          } } }
        },
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/webhooks": {
      "get":  { "tags": ["Webhooks"], "summary": "List your webhook endpoints",  "responses": { "200": { "description": "OK" } } }
    },
    "/api/webhooks?action=create": {
      "post": {
        "tags": ["Webhooks"], "summary": "Create a webhook endpoint",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["url"],
            "properties": {
              "url": { "type": "string", "format": "uri" },
              "events": { "type": "string", "description": "Comma-separated. Default: sms.delivered,sms.failed,credits.low" }
            }
          } } }
        },
        "responses": { "201": { "description": "Created — secret is returned only once" } }
      }
    },
    "/api/contacts": {
      "get": { "tags": ["Contacts"], "summary": "List contacts", "responses": { "200": { "description": "OK" } } }
    },
    "/api/csv?action=import-contacts": {
      "post": {
        "tags": ["Contacts"], "summary": "Bulk-upload contacts from CSV (multipart)",
        "requestBody": {
          "required": true,
          "content": { "multipart/form-data": { "schema": {
            "type": "object",
            "properties": {
              "file":    { "type": "string", "format": "binary", "description": "CSV with columns phone,name,email" },
              "list_id": { "type": "integer", "description": "Optional contact-list to assign imported contacts to" }
            }
          } } }
        },
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/csv?action=export-messages": {
      "get": {
        "tags": ["Messages"], "summary": "CSV download of your messages",
        "parameters": [
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "to",   "in": "query", "schema": { "type": "string", "format": "date" } }
        ],
        "responses": { "200": { "description": "CSV file", "content": { "text/csv": {} } } }
      }
    }
  }
}
