{
  "openapi": "3.1.0",
  "info": {
    "title": "EaseClaw API",
    "version": "1.0.0",
    "description": "EaseClaw is an AI lead-finder: it monitors the web for people describing the problem your business solves, scores each post by buying intent (40-100 surface as leads), and drafts replies in your voice. This API is read-and-draft only — there is no endpoint that posts or sends anything; a human sends every reply. Same key works on the MCP server at https://www.easeclaw.com/api/mcp (streamable HTTP). Get a key at https://www.easeclaw.com/leads/api.",
    "contact": { "email": "support@easeclaw.com" }
  },
  "servers": [{ "url": "https://www.easeclaw.com" }],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/api/v1/projects": {
      "get": {
        "summary": "List monitored projects",
        "operationId": "listProjects",
        "responses": {
          "200": {
            "description": "The account's projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "projects": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/leads": {
      "get": {
        "summary": "List warm leads, best first",
        "description": "Leads scored >= 40 by buying intent, ordered by score descending. Post bodies are truncated to 500 chars in list results — fetch a single lead for the full post.",
        "operationId": "listLeads",
        "parameters": [
          { "name": "project_id", "in": "query", "schema": { "type": "string", "format": "uuid" } },
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/LeadStatus" } },
          { "name": "min_score", "in": "query", "schema": { "type": "integer", "minimum": 40, "maximum": 100 } },
          { "name": "since", "in": "query", "description": "ISO 8601 timestamp — only leads found after this moment", "schema": { "type": "string", "format": "date-time" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } }
        ],
        "responses": {
          "200": {
            "description": "Scored leads",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/Lead" } } } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/leads/{id}": {
      "get": {
        "summary": "One lead in full (complete post body + draft)",
        "operationId": "getLead",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": {
            "description": "The lead",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "lead": { "$ref": "#/components/schemas/Lead" } } } } }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "patch": {
        "summary": "Update a lead's pipeline status and/or write back an edited draft",
        "description": "Marking a lead 'contacted' records that the human sent the reply — this endpoint never posts anything itself.",
        "operationId": "updateLead",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": { "$ref": "#/components/schemas/LeadStatus" },
                  "draft_reply": { "type": "string", "maxLength": 4000 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The applied patch",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "lead": { "type": "object" } } } } }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/leads/{id}/draft": {
      "post": {
        "summary": "Generate a reply draft in the owner's voice",
        "description": "Costs AI budget: drafting is capped by the plan's per-account daily budget and rate-limited to 30/hour. On 429 with code budget_reached, the Retry-After header says when the budget resets (midnight UTC).",
        "operationId": "draftLead",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tone": { "type": "string", "maxLength": 60, "description": "e.g. \"friendly\", \"concise and technical\"" },
                  "length": { "type": "string", "enum": ["short", "medium", "long"] },
                  "note": { "type": "string", "maxLength": 300, "description": "revision instruction, e.g. \"mention pricing\"" },
                  "n": { "type": "integer", "minimum": 1, "maximum": 3, "default": 1, "description": "number of variants; each costs budget" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft (first variant is persisted to the lead)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "draft_reply": { "type": "string" },
                    "replies": { "type": "array", "items": { "type": "string" } },
                    "draft_status": { "type": "string", "enum": ["ready"] }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/Error" },
          "503": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/api/v1/activity": {
      "get": {
        "summary": "Recent monitoring runs",
        "operationId": "getActivity",
        "parameters": [
          { "name": "project_id", "in": "query", "schema": { "type": "string", "format": "uuid" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 } }
        ],
        "responses": {
          "200": {
            "description": "Scan runs, newest first",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "runs": { "type": "array", "items": { "$ref": "#/components/schemas/ScanRun" } } } } } }
          },
          "401": { "$ref": "#/components/responses/Error" },
          "402": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from https://www.easeclaw.com/leads/api — format ec_live_ + 64 hex chars. 300 requests/hour per account."
      }
    },
    "schemas": {
      "LeadStatus": { "type": "string", "enum": ["new", "saved", "dismissed", "contacted", "won", "lost"] },
      "Lead": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "project_id": { "type": "string", "format": "uuid" },
          "score": { "type": "integer", "minimum": 40, "maximum": 100, "description": "buying-intent score; higher = hotter" },
          "reason": { "type": ["string", "null"], "description": "why the AI scored it this way" },
          "draft_reply": { "type": ["string", "null"] },
          "draft_status": { "type": "string", "enum": ["none", "generating", "ready"] },
          "status": { "$ref": "#/components/schemas/LeadStatus" },
          "created_at": { "type": "string", "format": "date-time" },
          "match": {
            "type": ["object", "null"],
            "description": "the source post",
            "properties": {
              "source": { "type": ["string", "null"] },
              "permalink": { "type": ["string", "null"] },
              "title": { "type": ["string", "null"] },
              "body": { "type": ["string", "null"] },
              "author": { "type": ["string", "null"] },
              "posted_at": { "type": ["string", "null"], "format": "date-time" }
            }
          }
        }
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": ["string", "null"] },
          "website": { "type": ["string", "null"] },
          "business_summary": { "type": ["string", "null"] },
          "icp": { "type": ["string", "null"] },
          "keywords": { "type": "array", "items": { "type": "string" } },
          "sources": { "type": "array", "items": { "type": "string" } },
          "last_scanned_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ScanRun": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "project_id": { "type": "string", "format": "uuid" },
          "ran_at": { "type": "string", "format": "date-time" },
          "lane": { "type": "string", "enum": ["wave", "fast"] },
          "status": { "type": "string", "enum": ["ok", "error", "skipped_spend", "skipped_budget"] },
          "fetched": { "type": "integer" },
          "claimed": { "type": "integer" },
          "survivors": { "type": "integer" },
          "qualified": { "type": "integer" },
          "drafted": { "type": "integer" },
          "sources": { "type": "array", "items": { "type": "string" } }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" }, "code": { "type": "string" } },
              "required": ["error"]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited (300/hour per account; drafting also 30/hour + daily AI budget). Retry-After header set when known.",
        "headers": { "Retry-After": { "schema": { "type": "integer" }, "description": "seconds until the limit resets" } },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" }, "code": { "type": "string" } }
            }
          }
        }
      }
    }
  }
}
