{
  "openapi": "3.0.3",
  "info": {
    "title": "PhoneValidation API",
    "version": "1.0.0",
    "description": "Honest phone number validation API — format, country, line type, carrier, disposable detection. No SMS sent. See https://phonevalidationapi.com/docs",
    "contact": {
      "name": "PhoneValidation API support",
      "url": "https://phonevalidationapi.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://phonevalidationapi.com/terms"
    }
  },
  "servers": [
    { "url": "https://phonevalidationapi.com/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/validate": {
      "post": {
        "summary": "Validate a single phone number",
        "description": "Synchronously validates a phone number. Consumes 1 credit per call (basic level). Hard cap on quota — returns 402 quota_exceeded when monthly quota is exhausted.",
        "tags": ["Validation"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ValidateRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidateResponse" } } }
          },
          "400": { "$ref": "#/components/responses/LevelNotAvailable" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/QuotaExceeded" },
          "422": { "$ref": "#/components/responses/InvalidInput" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/validate-async": {
      "post": {
        "summary": "Submit a bulk async batch (≤10000 numbers)",
        "description": "Queues a batch for background processing. Credits are debited upfront. Polled via /batch/{id} or webhook callback.",
        "tags": ["Bulk"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AsyncRequest" }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch queued",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AsyncResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/QuotaExceeded" },
          "422": { "$ref": "#/components/responses/InvalidInput" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/batch/{id}": {
      "get": {
        "summary": "Get async batch status & results",
        "tags": ["Bulk"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "pattern": "^bat_[a-f0-9]{32}$" }
          }
        ],
        "responses": {
          "200": {
            "description": "Batch status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchStatus" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/credits": {
      "get": {
        "summary": "Get current quota status",
        "description": "Returns the user's plan, monthly quota, used / remaining, and reset date. Free, no credit consumed.",
        "tags": ["Account"],
        "responses": {
          "200": {
            "description": "Plan + quota state",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreditsResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/demo": {
      "post": {
        "summary": "Public demo endpoint (no auth, 3/IP/24h)",
        "description": "Throttled by IP, no API key required. Use for landing-page demos and integration tests.",
        "tags": ["Public"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ValidateRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidateResponse" } } }
          },
          "422": { "$ref": "#/components/responses/InvalidInput" },
          "429": {
            "description": "Demo rate limit hit (3 / IP / 24h)",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "error":       { "type": "string", "example": "demo_limit_reached" },
                "message":     { "type": "string" },
                "signup_url":  { "type": "string", "format": "uri" },
                "retry_after": { "type": "integer" }
              }
            } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key (pvs_live_…)"
      }
    },
    "schemas": {
      "ValidateRequest": {
        "type": "object",
        "required": ["phone"],
        "properties": {
          "phone":   { "type": "string", "example": "+33612345678", "description": "E.164 (with leading +) or national format if `country` is provided." },
          "country": { "type": "string", "pattern": "^[A-Z]{2}$", "example": "FR", "description": "ISO-3166-1 alpha-2. Required if phone is not E.164." },
          "level":   { "type": "string", "enum": ["basic"], "default": "basic", "description": "`hlr` will be available later as a paid tier." }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "properties": {
          "valid":         { "type": "boolean" },
          "confidence":    { "type": "string", "enum": ["verified", "likely", "uncertain", "low", "invalid"] },
          "score":         { "type": "number", "format": "float", "minimum": 0, "maximum": 1 },
          "reason":        { "type": "string", "example": "valid" },
          "input":         { "type": "string" },
          "formatted": {
            "type": "object",
            "properties": {
              "e164":          { "type": "string", "example": "+33612345678" },
              "national":      { "type": "string", "example": "06 12 34 56 78" },
              "international": { "type": "string", "example": "+33 6 12 34 56 78" }
            }
          },
          "country": {
            "type": "object",
            "properties": {
              "iso2": { "type": "string", "example": "FR" },
              "code": { "type": "integer", "example": 33 }
            }
          },
          "region":        { "type": "string", "nullable": true },
          "line_type":     { "type": "string", "example": "mobile" },
          "carrier":       { "type": "string", "nullable": true, "example": "SFR" },
          "is_disposable": { "type": "boolean" },
          "is_possible":   { "type": "boolean" },
          "level":         { "type": "string", "example": "basic" },
          "checks": {
            "type": "object",
            "additionalProperties": { "type": "string", "enum": ["pass", "fail", "skipped", "unavailable", "pending"] }
          },
          "diagnostics":       { "type": "object", "additionalProperties": true },
          "quota_remaining":   { "type": "integer", "description": "Remaining quota in current cycle after this call." }
        }
      },
      "AsyncRequest": {
        "type": "object",
        "required": ["phones"],
        "properties": {
          "phones":         { "type": "array", "items": { "type": "string" }, "maxItems": 10000 },
          "country":        { "type": "string", "pattern": "^[A-Z]{2}$" },
          "level":          { "type": "string", "enum": ["basic"] },
          "webhook_url":    { "type": "string", "format": "uri" },
          "webhook_secret": { "type": "string", "maxLength": 256 }
        }
      },
      "AsyncResponse": {
        "type": "object",
        "properties": {
          "batch_id":           { "type": "string", "pattern": "^bat_[a-f0-9]{32}$" },
          "status":             { "type": "string", "enum": ["queued"] },
          "total":              { "type": "integer" },
          "level":              { "type": "string" },
          "credits_charged":    { "type": "integer" },
          "quota_remaining":    { "type": "integer" },
          "webhook_configured": { "type": "boolean" },
          "status_url":         { "type": "string", "format": "uri" }
        }
      },
      "BatchStatus": {
        "type": "object",
        "properties": {
          "batch_id":     { "type": "string" },
          "status":       { "type": "string", "enum": ["queued", "processing", "completed", "failed"] },
          "total":        { "type": "integer" },
          "processed":    { "type": "integer" },
          "progress":     { "type": "number", "format": "float", "minimum": 0, "maximum": 1 },
          "created_at":   { "type": "string", "format": "date-time" },
          "started_at":   { "type": "string", "format": "date-time", "nullable": true },
          "completed_at": { "type": "string", "format": "date-time", "nullable": true },
          "results":      { "type": "array", "items": { "$ref": "#/components/schemas/ValidateResponse" } },
          "has_webhook":  { "type": "boolean" }
        }
      },
      "CreditsResponse": {
        "type": "object",
        "properties": {
          "plan_code":      { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] },
          "plan_label":     { "type": "string" },
          "plan_quota":     { "type": "integer" },
          "plan_used":      { "type": "integer" },
          "plan_remaining": { "type": "integer" },
          "resets_at":      { "type": "string", "format": "date-time" },
          "plan_status":    { "type": "string" },
          "total_consumed": { "type": "integer" },
          "upgrade_url":    { "type": "string", "format": "uri" },
          "billing_url":    { "type": "string", "format": "uri" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error":   { "type": "string" },
          "message": { "type": "string" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "QuotaExceeded": {
        "description": "Monthly quota exhausted",
        "content": { "application/json": { "schema": {
          "allOf": [
            { "$ref": "#/components/schemas/Error" },
            {
              "type": "object",
              "properties": {
                "plan_code":      { "type": "string" },
                "plan_quota":     { "type": "integer" },
                "plan_used":      { "type": "integer" },
                "plan_remaining": { "type": "integer" },
                "credits_needed": { "type": "integer" },
                "resets_at":      { "type": "string", "format": "date-time" },
                "upgrade_url":    { "type": "string", "format": "uri" }
              }
            }
          ]
        } } }
      },
      "InvalidInput": {
        "description": "Validation error on input",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "LevelNotAvailable": {
        "description": "Requested level (e.g. hlr) is not enabled yet",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Per-key rate limit hit",
        "headers": {
          "Retry-After":           { "schema": { "type": "integer" }, "description": "Seconds to wait before retry" },
          "X-RateLimit-Limit":     { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset":     { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
