{
  "openapi": "3.1.0",
  "info": {
    "title": "Lead Radar API",
    "version": "1.0.0",
    "description": "B2B lead generation from Google Maps: search businesses, deduplicate, enrich emails. Fully usable by AI agents: create an account and get an API key in a single call. Human-readable docs: https://lead-radar.fr/llms.txt — MCP server: https://lead-radar.fr/api/mcp (OAuth or Bearer key).",
    "contact": { "email": "contact@lead-radar.fr", "url": "https://lead-radar.fr" }
  },
  "servers": [{ "url": "https://lead-radar.fr" }],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key (lr_...) from POST /api/auth/register with createApiKey: true, from the dashboard settings, or issued via OAuth 2.1 (see /.well-known/oauth-authorization-server). 120 requests/minute per key."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      },
      "Search": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "projectName": { "type": "string" },
          "searchType": { "type": "string", "enum": ["KEYWORD", "MAPS_URL"] },
          "status": { "type": "string", "enum": ["PENDING", "PROCESSING", "PAUSED", "COMPLETED", "FAILED"] },
          "totalFound": { "type": "integer" },
          "totalNew": { "type": "integer" },
          "cellsDone": { "type": "integer" },
          "cellsTotal": { "type": "integer" }
        }
      },
      "Lead": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "address": { "type": "string", "nullable": true },
          "phone": { "type": "string", "nullable": true },
          "website": { "type": "string", "nullable": true },
          "email": { "type": "string", "nullable": true },
          "emailStatus": { "type": "string", "enum": ["NOT_ATTEMPTED", "PROCESSING", "FOUND", "NOT_FOUND", "ERROR"] },
          "rating": { "type": "number", "nullable": true },
          "reviewCount": { "type": "integer", "nullable": true }
        }
      }
    }
  },
  "paths": {
    "/api/auth/register": {
      "post": {
        "operationId": "register",
        "summary": "Create an account (optionally with an API key, for agents)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "password"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "password": { "type": "string", "minLength": 8 },
                  "name": { "type": "string" },
                  "createApiKey": { "type": "boolean", "description": "true = return an API key in the response (agent flow)" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Account created. welcomeCredits is 5 or 0 (API signups share a global daily bonus quota).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "apiKey": { "type": "string", "description": "Only when createApiKey: true. Shown once — store it." },
                    "welcomeCredits": { "type": "integer" }
                  }
                }
              }
            }
          },
          "409": { "description": "Email already registered" },
          "429": { "description": "Rate limited (5 signups/hour/IP)" }
        }
      }
    },
    "/api/keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List active API keys",
        "responses": { "200": { "description": "Keys (id, name, keyPrefix, lastUsedAt, createdAt) — never the key itself" } }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create an API key (max 10 active; plaintext returned once)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 100 } } }
            }
          }
        },
        "responses": { "201": { "description": "Created — response includes the plaintext key" }, "409": { "description": "Active key limit reached" } }
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Revoked" }, "404": { "description": "Key not found" } }
      }
    },
    "/api/credits/balance": {
      "get": {
        "operationId": "getBalance",
        "summary": "Current credit balance",
        "responses": {
          "200": {
            "description": "Balance",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "balance": { "type": "integer" } } } } }
          }
        }
      }
    },
    "/api/credits/history": {
      "get": { "operationId": "getCreditHistory", "summary": "Credit ledger history", "responses": { "200": { "description": "Ledger entries" } } }
    },
    "/api/searches": {
      "post": {
        "operationId": "createSearch",
        "summary": "Launch a Google Maps business search (async; debits credits)",
        "description": "Cost: 1 credit by default; a KEYWORD search with radiusKm costs more as the radius grows. Refunded on failure. Poll GET /api/searches/{id} until COMPLETED.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": ["type", "keyword", "location"],
                    "properties": {
                      "type": { "const": "KEYWORD" },
                      "keyword": { "type": "string", "example": "plumber" },
                      "location": { "type": "string", "example": "Lyon, France" },
                      "radiusKm": { "type": "integer", "minimum": 1, "maximum": 50 }
                    }
                  },
                  {
                    "type": "object",
                    "required": ["type", "mapsUrl"],
                    "properties": {
                      "type": { "const": "MAPS_URL" },
                      "mapsUrl": { "type": "string", "format": "uri" }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Search created (status PENDING)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Search" } } } },
          "402": { "description": "Insufficient credits" }
        }
      },
      "get": { "operationId": "listSearches", "summary": "List searches", "responses": { "200": { "description": "Searches" } } }
    },
    "/api/searches/{id}": {
      "get": {
        "operationId": "getSearch",
        "summary": "Search status and progress",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Search", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Search" } } } } }
      }
    },
    "/api/leads": {
      "get": {
        "operationId": "listLeads",
        "summary": "List leads with optional filters",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "maximum": 100 } },
          { "name": "searchId", "in": "query", "schema": { "type": "string" } },
          { "name": "hasEmail", "in": "query", "schema": { "type": "boolean" } },
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Free-text search" },
          { "name": "tagId", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Paginated leads", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Lead" } } } } } } } }
      }
    },
    "/api/leads/{id}": {
      "get": {
        "operationId": "getLead",
        "summary": "Lead detail (tags, notes)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Lead" }, "404": { "description": "Not found" } }
      },
      "patch": {
        "operationId": "updateLead",
        "summary": "Fix a lead's phone/email",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "phone": { "type": "string", "nullable": true }, "email": { "type": "string", "nullable": true } } } } } },
        "responses": { "200": { "description": "Updated" } }
      }
    },
    "/api/leads/enrich": {
      "post": {
        "operationId": "enrichLeads",
        "summary": "Enrich lead emails from their websites (async; 1 credit per email found)",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["leadIds"], "properties": { "leadIds": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 100 } } } } }
        },
        "responses": { "200": { "description": "Enrichment job queued" } }
      }
    },
    "/api/leads/export": {
      "get": { "operationId": "exportLeads", "summary": "CSV export of leads", "responses": { "200": { "description": "CSV file" } } }
    },
    "/api/stripe/checkout": {
      "post": {
        "operationId": "createCheckout",
        "summary": "Create a Stripe Checkout session (hand the URL to a human to pay)",
        "description": "Credits are added automatically after payment (webhook). One of packId or planId. Limit: 3 sessions/minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "type": "object", "required": ["packId"], "properties": { "packId": { "type": "string", "enum": ["pack_10", "pack_50", "pack_100"] } } },
                  { "type": "object", "required": ["planId"], "properties": { "planId": { "type": "string", "enum": ["starter", "pro"] } } }
                ]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Checkout URL", "content": { "application/json": { "schema": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" } } } } } }
        }
      }
    }
  }
}
