{
  "openapi": "3.0.3",
  "info": {
    "title": "KaijuVerifier API",
    "version": "1.0.0",
    "description": "Email verification API: real-time single & batch validation, asynchronous bulk jobs (submit → poll/webhook → CSV), account/quota, webhooks and API-key management.\n\n## Authentication\nAll `/api/v1/*` and `/api/dashboard/*` endpoints require authentication. Three equivalent methods are accepted:\n- `Authorization: Bearer YOUR_API_KEY`\n- `X-API-Key: YOUR_API_KEY`\n- `?api_key=YOUR_API_KEY` (legacy)\n\nGenerate keys in Dashboard → Settings.\n\n## Webhooks\nPaid plans can register webhook endpoints (Dashboard → Settings or `POST /api/dashboard/webhooks`). KaijuVerifier delivers a signed `POST` for events `batch.completed`, `job.completed`, `job.failed`. Each delivery carries `X-Kaiju-Signature: sha256=<hmac>` — the HMAC-SHA256 of the raw request body keyed with your endpoint's signing secret. Verify it with a constant-time comparison before trusting the payload.",
    "contact": { "name": "KaijuVerifier Support", "email": "control@kaijuverifier.com", "url": "https://kaijuverifier.com/api-docs" }
  },
  "servers": [{ "url": "https://kaijuverifier.com", "description": "Production" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }, { "apiKeyQuery": [] }],
  "tags": [
    { "name": "Verification", "description": "Real-time single and batch verification" },
    { "name": "Async Jobs", "description": "Asynchronous bulk verification for large lists" },
    { "name": "Account", "description": "Plan, quota and feature flags" },
    { "name": "Webhooks", "description": "Manage signed event callbacks" },
    { "name": "API Keys", "description": "Generate and revoke API keys" },
    { "name": "Public", "description": "No-auth, IP-rate-limited endpoint for testing" }
  ],
  "paths": {
    "/api/validate-email": {
      "post": {
        "tags": ["Public"],
        "summary": "Verify a single email (public, no auth)",
        "description": "No-auth, IP-rate-limited single-email check for testing and client-side widgets (20 req/min, 50 SMTP probes/day per IP). For production use the authenticated `/api/v1/verify-single`.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SingleRequest" } } } },
        "responses": {
          "200": { "description": "Verification result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerificationResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/verify-single": {
      "post": {
        "tags": ["Verification"],
        "summary": "Verify a single email (authenticated)",
        "description": "Counts as one verification against your monthly quota. `use_smtp` requires a plan with the SMTP feature.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SingleRequest" } } } },
        "responses": {
          "200": { "description": "Verification result with live quota", "content": { "application/json": { "schema": { "allOf": [ { "$ref": "#/components/schemas/VerificationResult" }, { "type": "object", "properties": { "quota": { "$ref": "#/components/schemas/Quota" } } } ] } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/verify-batch": {
      "post": {
        "tags": ["Verification"],
        "summary": "Verify a batch synchronously",
        "description": "Verifies up to 10,000 emails in one request and returns when finished. Each email counts against quota. For larger lists or to avoid HTTP timeouts, use the async jobs API. Fires the `batch.completed` webhook.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchRequest" } } } },
        "responses": {
          "200": { "description": "Batch summary + per-email results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchResult" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "description": "Quota exceeded or rate limited" }
        }
      }
    },
    "/api/v1/jobs": {
      "get": {
        "tags": ["Async Jobs"],
        "summary": "List your recent async jobs",
        "responses": {
          "200": { "description": "Up to 50 most recent jobs", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "jobs": { "type": "array", "items": { "$ref": "#/components/schemas/Job" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Async Jobs"],
        "summary": "Submit an async bulk job",
        "description": "Submit up to 1,000,000 emails for background processing. Quota is reserved up front. Set `dedupe:true` to remove case-insensitive duplicates and blanks before reserving quota (you are not charged for repeats). Poll `GET /api/v1/jobs/{job_id}` or receive a `job.completed` webhook.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobRequest" } } } },
        "responses": {
          "202": { "description": "Job accepted and queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobSubmitResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "Insufficient quota for the whole list", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "example": "insufficient_quota" }, "remaining": { "type": "integer" }, "required": { "type": "integer" } } } } } }
        }
      }
    },
    "/api/v1/jobs/{job_id}": {
      "parameters": [{ "name": "job_id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "job_ef2eb1f225da9e1c926c607a" }],
      "get": {
        "tags": ["Async Jobs"],
        "summary": "Get async job status / progress",
        "responses": {
          "200": { "description": "Job status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Not found or not owned by the caller" }
        }
      },
      "delete": {
        "tags": ["Async Jobs"],
        "summary": "Cancel a job and refund unprocessed quota",
        "description": "Cancels a `queued` or `processing` job and refunds the unprocessed remainder of the reserved quota. Completed/failed/already-cancelled jobs return 409.",
        "responses": {
          "200": { "description": "Job cancelled", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "job_id": { "type": "string" }, "status": { "type": "string", "example": "cancelled" }, "processed": { "type": "integer" }, "refunded": { "type": "integer" } } } } } },
          "404": { "description": "Not found or not owned" },
          "409": { "description": "Job already completed/failed/cancelled" }
        }
      }
    },
    "/api/v1/jobs/{job_id}/results": {
      "get": {
        "tags": ["Async Jobs"],
        "summary": "Fetch async job results (JSON or CSV)",
        "description": "Returns per-email verdicts once the job is `completed` (otherwise 409). JSON is paginated; `?format=csv` streams the entire job as a downloadable CSV (`email,is_valid,status,reason,score,suggestion`).",
        "parameters": [
          { "name": "job_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "format", "in": "query", "required": false, "schema": { "type": "string", "enum": ["json", "csv"], "default": "json" } },
          { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "default": 1 } },
          { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer", "default": 1000, "maximum": 5000 } }
        ],
        "responses": {
          "200": { "description": "Results (JSON) or CSV download", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobResults" } }, "text/csv": { "schema": { "type": "string", "format": "binary" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Not found or not owned" },
          "409": { "description": "Job not completed yet", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string", "example": "job_not_completed" }, "status": { "type": "string" }, "progress_pct": { "type": "integer" } } } } } }
        }
      }
    },
    "/api/v1/account": {
      "get": {
        "tags": ["Account"],
        "summary": "Plan, quota and feature flags",
        "description": "Check plan, remaining quota and enabled features without spending a verification.",
        "responses": {
          "200": { "description": "Account info", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Account" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/dashboard/webhooks": {
      "get": { "tags": ["Webhooks"], "summary": "List webhooks", "responses": { "200": { "description": "Active webhooks", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "webhooks": { "type": "array", "items": { "$ref": "#/components/schemas/Webhook" } } } } } } }, "403": { "$ref": "#/components/responses/FeatureRequiresUpgrade" } } },
      "post": {
        "tags": ["Webhooks"],
        "summary": "Create a webhook",
        "description": "Registers a signed webhook endpoint. The signing `secret` is returned ONCE at creation. Supported events: `batch.completed`, `job.completed`, `job.failed`. Max 10 active.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["url"], "properties": { "url": { "type": "string", "format": "uri", "example": "https://example.com/hooks/kaiju" }, "events": { "type": "string", "example": "batch.completed,job.completed,job.failed" } } } } } },
        "responses": {
          "200": { "description": "Created (secret shown once)", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "webhook": { "allOf": [ { "$ref": "#/components/schemas/Webhook" }, { "type": "object", "properties": { "secret": { "type": "string", "example": "whsec_..." } } } ] } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/FeatureRequiresUpgrade" },
          "409": { "description": "Webhook limit reached (10)" }
        }
      },
      "delete": {
        "tags": ["Webhooks"],
        "summary": "Delete a webhook",
        "parameters": [{ "name": "id", "in": "query", "required": true, "schema": { "type": "string" }, "description": "webhook_id" }],
        "responses": { "200": { "description": "Deleted" }, "404": { "description": "Not found" } }
      }
    },
    "/api/dashboard/api-keys": {
      "get": { "tags": ["API Keys"], "summary": "List API keys (masked)", "responses": { "200": { "description": "Keys", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "keys": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "key_preview": { "type": "string" }, "created_at": { "type": "string" }, "last_used": { "type": "string", "nullable": true } } } } } } } } } } },
      "post": { "tags": ["API Keys"], "summary": "Generate an API key (full key shown once)", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } } } } }, "responses": { "200": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "api_key": { "type": "string", "example": "sk_..." } } } } } } } },
      "delete": { "tags": ["API Keys"], "summary": "Revoke an API key", "parameters": [{ "name": "id", "in": "query", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Revoked" } } }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Authorization: Bearer YOUR_API_KEY" },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key" },
      "apiKeyQuery": { "type": "apiKey", "in": "query", "name": "api_key" }
    },
    "responses": {
      "BadRequest": { "description": "Missing or invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "FeatureRequiresUpgrade": { "description": "Your plan does not include this feature", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "SingleRequest": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "example": "user@example.com" }, "use_smtp": { "type": "boolean", "default": false, "description": "Run the SMTP mailbox probe (paid feature)" } } },
      "BatchRequest": { "type": "object", "required": ["emails"], "properties": { "emails": { "type": "array", "items": { "type": "string" }, "maxItems": 10000 }, "use_smtp": { "type": "boolean", "default": false } } },
      "JobRequest": { "type": "object", "required": ["emails"], "properties": { "emails": { "type": "array", "items": { "type": "string" }, "maxItems": 1000000 }, "use_smtp": { "type": "boolean", "default": false }, "dedupe": { "type": "boolean", "default": false } } },
      "Checks": { "type": "object", "properties": { "syntax": { "type": "boolean" }, "dns": { "type": "boolean" }, "smtp": { "type": "boolean", "nullable": true }, "catch_all": { "type": "boolean", "nullable": true }, "is_disposable": { "type": "boolean" }, "disposable": { "type": "boolean", "description": "Alias of is_disposable" }, "role_based": { "type": "boolean" }, "free_provider": { "type": "boolean" }, "mx": { "type": "object", "properties": { "count": { "type": "integer" }, "providers": { "type": "array", "items": { "type": "string" } }, "grade": { "type": "string" } } } } },
      "VerificationResult": { "type": "object", "properties": { "email": { "type": "string" }, "normalized": { "type": "string" }, "is_valid": { "type": "boolean" }, "status": { "type": "string", "enum": ["valid", "invalid", "risky", "unknown"] }, "reason": { "type": "string", "nullable": true }, "confidence": { "type": "integer" }, "score": { "type": "integer", "description": "0-100 deliverability score" }, "grade": { "type": "string", "description": "A-F" }, "suggestion": { "type": "string", "nullable": true, "description": "Did-you-mean typo correction" }, "checks": { "$ref": "#/components/schemas/Checks" } } },
      "BatchResult": { "type": "object", "properties": { "success": { "type": "boolean" }, "batch_id": { "type": "string" }, "status": { "type": "string", "example": "completed" }, "summary": { "type": "object", "properties": { "total": { "type": "integer" }, "valid": { "type": "integer" }, "invalid": { "type": "integer" }, "accuracy": { "type": "number" } } }, "results": { "type": "array", "items": { "$ref": "#/components/schemas/VerificationResult" } }, "quota": { "$ref": "#/components/schemas/Quota" } } },
      "Job": { "type": "object", "properties": { "job_id": { "type": "string" }, "status": { "type": "string", "enum": ["queued", "processing", "completed", "failed", "cancelled"] }, "total": { "type": "integer" }, "processed": { "type": "integer" }, "valid_count": { "type": "integer" }, "invalid_count": { "type": "integer" }, "created_at": { "type": "string" }, "completed_at": { "type": "string", "nullable": true } } },
      "JobSubmitResponse": { "type": "object", "properties": { "success": { "type": "boolean" }, "job_id": { "type": "string" }, "status": { "type": "string", "example": "queued" }, "submitted": { "type": "integer" }, "duplicates_removed": { "type": "integer" }, "total": { "type": "integer" }, "status_url": { "type": "string" } } },
      "JobStatus": { "type": "object", "properties": { "success": { "type": "boolean" }, "job_id": { "type": "string" }, "status": { "type": "string", "enum": ["queued", "processing", "completed", "failed", "cancelled"] }, "total": { "type": "integer" }, "processed": { "type": "integer" }, "valid": { "type": "integer" }, "invalid": { "type": "integer" }, "risky": { "type": "integer" }, "progress_pct": { "type": "integer" }, "created_at": { "type": "string" }, "completed_at": { "type": "string", "nullable": true } } },
      "JobResults": { "type": "object", "properties": { "success": { "type": "boolean" }, "job_id": { "type": "string" }, "total": { "type": "integer" }, "page": { "type": "integer" }, "per_page": { "type": "integer" }, "results": { "type": "array", "items": { "type": "object", "properties": { "email": { "type": "string" }, "is_valid": { "type": "boolean" }, "status": { "type": "string" }, "reason": { "type": "string", "nullable": true }, "score": { "type": "integer", "nullable": true }, "suggestion": { "type": "string", "nullable": true } } } } } },
      "Account": { "type": "object", "properties": { "success": { "type": "boolean" }, "account": { "type": "object", "properties": { "email": { "type": "string" }, "plan": { "type": "string" }, "plan_name": { "type": "string" } } }, "quota": { "type": "object", "properties": { "limit": { "type": "integer" }, "used": { "type": "integer" }, "remaining": { "type": "integer" }, "reset_date": { "type": "string" } } }, "rate_limit_rpm": { "type": "integer" }, "features": { "type": "object", "properties": { "smtp": { "type": "boolean" }, "webhooks": { "type": "boolean" }, "catch_all": { "type": "boolean" } } } } },
      "Quota": { "type": "object", "properties": { "limit": { "type": "integer" }, "used_this_month": { "type": "integer" }, "remaining": { "type": "integer" } } },
      "Webhook": { "type": "object", "properties": { "webhook_id": { "type": "string" }, "url": { "type": "string" }, "events": { "type": "string", "example": "batch.completed,job.completed,job.failed" }, "active": { "type": "boolean" }, "last_triggered": { "type": "string", "nullable": true }, "created_at": { "type": "string" } } },
      "Error": { "type": "object", "properties": { "error": { "type": "string" }, "message": { "type": "string" } } }
    }
  }
}
