{
  "openapi": "3.1.0",
  "info": {
    "title": "Liftli Public API",
    "version": "1.4.6",
    "summary": "The publicly callable surface of Liftli: the free micro-tools API, service metadata, and the authenticated MCP endpoint.",
    "description": "Liftli is an AI content strategist for LinkedIn, X and Substack that runs inside an AI agent (Claude, Claude Code, ChatGPT, Codex, Cursor) over the Model Context Protocol.\n\nThis document describes the surface an agent can call directly.\n\n**No key required** — `/health`, `/version` and everything under `/api/v1/tools/` are open. The micro-tools endpoint is rate limited per caller per day; every successful response reports the caller's `remaining_today`.\n\n**OAuth required** — the MCP endpoint at `https://mcp.liftli.ai/mcp` speaks Streamable HTTP MCP and is protected by OAuth 2.1 with PKCE. Discovery metadata is published as RFC 9728 protected-resource metadata at `https://mcp.liftli.ai/.well-known/oauth-protected-resource`, which points at the authorization server `https://auth.liftli.ai`. Dynamic client registration is supported, so an agent can onboard without a human creating credentials first.\n\nErrors are always JSON. Validation failures return HTTP 422 with a `detail` array naming the offending field.",
    "contact": {
      "name": "Liftli support",
      "email": "support@liftli.ai",
      "url": "https://liftli.ai/contact"
    },
    "license": { "name": "Proprietary", "url": "https://liftli.ai/terms" }
  },
  "servers": [
    { "url": "https://app.liftli.ai", "description": "Liftli API" }
  ],
  "externalDocs": {
    "description": "Developer documentation, MCP setup and agent instructions",
    "url": "https://liftli.ai/developers"
  },
  "tags": [
    { "name": "micro-tools", "description": "Free, unauthenticated single-shot content generators. Rate limited per caller per day." },
    { "name": "service", "description": "Service metadata used for health and client-compatibility checks." },
    { "name": "mcp", "description": "The Model Context Protocol endpoint. This is the main way an agent uses Liftli." }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["service"],
        "operationId": "getHealth",
        "summary": "Service health",
        "description": "Liveness probe. Returns the service status and the server's current UTC timestamp. Requires no authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "The service is reachable.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" },
                "example": { "status": "healthy", "timestamp": "2026-08-25T22:14:59.845933+00:00" }
              }
            }
          }
        }
      }
    },
    "/version": {
      "get": {
        "tags": ["service"],
        "operationId": "getVersion",
        "summary": "API version and client compatibility",
        "description": "Returns the running API version, the minimum client version still accepted, and any endpoints that are deprecated. Clients should check `min_client_version` before relying on newer behaviour. Requires no authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "Version metadata.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/VersionResponse" },
                "example": {
                  "version": "1.4.6",
                  "min_client_version": "1.0.0",
                  "deprecated_endpoints": ["/log-reply"],
                  "status": "stable"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/health": {
      "get": {
        "tags": ["micro-tools"],
        "operationId": "getToolsHealth",
        "summary": "List available micro-tools",
        "description": "Reports whether micro-tool generation is currently enabled and lists the tool identifiers accepted by `generateWithTool`. Call this first to discover valid values for the `tool` field. Requires no authentication.",
        "security": [],
        "responses": {
          "200": {
            "description": "Availability and the current tool list.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ToolsHealthResponse" },
                "example": {
                  "enabled": true,
                  "tools": [
                    "about-generator", "carousel-outline", "comment-generator", "content-ideas",
                    "contrarian-angles", "devlog-to-post", "headline-generator", "hook-generator",
                    "hot-take-check", "linkedin-to-x", "meeting-to-post", "poll-generator",
                    "post-generator", "post-rewriter"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/generate": {
      "post": {
        "tags": ["micro-tools"],
        "operationId": "generateWithTool",
        "summary": "Run one micro-tool generation",
        "description": "Runs a single free micro-tool and returns the generated text. `tool` must be one of the identifiers returned by `getToolsHealth`. `inputs` is a flat map of string values whose keys depend on the tool — for example `hook-generator` takes `topic` and `audience`.\n\nRate limited per caller per day; the response reports how many generations remain. Requires no authentication.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/GenerateRequest" },
              "example": { "tool": "hook-generator", "inputs": { "topic": "why most AI posts sound the same", "audience": "B2B founders" } }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated text and the caller's remaining daily quota.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GenerateResponse" },
                "example": { "result": "Everyone wants the AI. Nobody wants the cleanup.", "remaining_today": 4 }
              }
            }
          },
          "422": {
            "description": "The request body failed validation. `detail` names the offending field.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HTTPValidationError" },
                "example": { "detail": [{ "type": "missing", "loc": ["body", "tool"], "msg": "Field required" }] }
              }
            }
          },
          "429": {
            "description": "The caller's daily generation quota is exhausted. Retry after the quota resets.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          }
        }
      }
    },
    "/api/v1/tools/lead-magnet": {
      "post": {
        "tags": ["micro-tools"],
        "operationId": "requestLeadMagnet",
        "summary": "Email a library resource to a visitor",
        "description": "Emails one of the unlisted library resources to the supplied address. `magnet` is the resource slug. Intended for human visitors of the free tool pages. Requires no authentication.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LeadMagnetRequest" },
              "example": { "email": "someone@example.com", "magnet": "hook-patterns" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resource was queued for delivery.",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": { "type": "string" } },
                "example": { "status": "sent" }
              }
            }
          },
          "422": {
            "description": "The request body failed validation.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
          }
        }
      }
    },
    "/mcp": {
      "servers": [{ "url": "https://mcp.liftli.ai", "description": "Liftli MCP server" }],
      "post": {
        "tags": ["mcp"],
        "operationId": "callMcpEndpoint",
        "summary": "Model Context Protocol endpoint (Streamable HTTP)",
        "description": "The primary way an agent uses Liftli. Speaks the Model Context Protocol over Streamable HTTP and exposes Liftli's strategist tools — briefing, planning, drafting, critique, publishing and results analysis — as callable MCP tools.\n\nProtected by OAuth 2.1 with PKCE. An unauthenticated `initialize` returns HTTP 401 with a `WWW-Authenticate` challenge pointing at the protected-resource metadata at `/.well-known/oauth-protected-resource`, which names `https://auth.liftli.ai` as the authorization server. That server supports dynamic client registration, so an agent can register itself and complete the authorization-code or device-code flow without a human provisioning credentials first.\n\nThe request and response bodies are JSON-RPC 2.0 messages as defined by the MCP specification.",
        "security": [{ "liftliOAuth": ["openid", "profile", "email", "offline_access"] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" },
              "example": { "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC 2.0 result, or an SSE stream when the client accepts `text/event-stream`.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } } }
          },
          "401": {
            "description": "Authentication required. The `WWW-Authenticate` header points at the protected-resource metadata.",
            "headers": {
              "WWW-Authenticate": {
                "description": "Bearer challenge naming the resource metadata URL.",
                "schema": { "type": "string" }
              }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "liftliOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE (S256), issued by https://auth.liftli.ai. Dynamic client registration is supported at the registration endpoint, so agents can self-onboard. Request the least privilege you need: `openid` alone is enough to identify the account; add `offline_access` only if you need to refresh without the user present.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.liftli.ai/oauth2/authorize",
            "tokenUrl": "https://auth.liftli.ai/oauth2/token",
            "refreshUrl": "https://auth.liftli.ai/oauth2/token",
            "scopes": {
              "openid": "Identify the signed-in Liftli account.",
              "profile": "Read basic profile details for the signed-in account.",
              "email": "Read the email address of the signed-in account.",
              "offline_access": "Obtain a refresh token so the agent can act after the interactive session ends."
            }
          },
          "deviceAuthorization": {
            "deviceAuthorizationUrl": "https://auth.liftli.ai/oauth2/device_authorization",
            "tokenUrl": "https://auth.liftli.ai/oauth2/token",
            "refreshUrl": "https://auth.liftli.ai/oauth2/token",
            "scopes": {
              "openid": "Identify the signed-in Liftli account.",
              "profile": "Read basic profile details for the signed-in account.",
              "email": "Read the email address of the signed-in account.",
              "offline_access": "Obtain a refresh token so the agent can act after the interactive session ends."
            }
          }
        }
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "title": "HealthResponse",
        "description": "Liveness result.",
        "required": ["status", "timestamp"],
        "properties": {
          "status": { "type": "string", "description": "Service status, e.g. \"healthy\"." },
          "timestamp": { "type": "string", "format": "date-time", "description": "Server time in ISO 8601, UTC." }
        }
      },
      "VersionResponse": {
        "type": "object",
        "title": "VersionResponse",
        "description": "API version and client-compatibility metadata.",
        "required": ["version", "min_client_version", "status"],
        "properties": {
          "version": { "type": "string", "description": "Running API version." },
          "min_client_version": { "type": "string", "description": "Oldest client version still accepted." },
          "deprecated_endpoints": { "type": "array", "items": { "type": "string" }, "description": "Paths that still respond but are scheduled for removal." },
          "status": { "type": "string", "description": "Release channel, e.g. \"stable\"." }
        }
      },
      "ToolsHealthResponse": {
        "type": "object",
        "title": "ToolsHealthResponse",
        "description": "Whether micro-tool generation is enabled, and which tools are available.",
        "required": ["enabled", "tools"],
        "properties": {
          "enabled": { "type": "boolean", "description": "False when generation is switched off; calls to generateWithTool will not succeed." },
          "tools": { "type": "array", "items": { "type": "string" }, "description": "Valid identifiers for the `tool` field of generateWithTool." }
        }
      },
      "GenerateRequest": {
        "type": "object",
        "title": "GenerateRequest",
        "description": "One micro-tool generation request.",
        "required": ["tool"],
        "properties": {
          "tool": { "type": "string", "description": "A tool identifier returned by getToolsHealth, e.g. \"hook-generator\"." },
          "inputs": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Flat map of string inputs. Accepted keys depend on the tool." }
        }
      },
      "GenerateResponse": {
        "type": "object",
        "title": "GenerateResponse",
        "description": "Generated text plus the caller's remaining daily quota.",
        "required": ["result", "remaining_today"],
        "properties": {
          "result": { "type": "string", "description": "The generated text." },
          "remaining_today": { "type": "integer", "description": "Generations left for this caller before the daily quota resets." }
        }
      },
      "LeadMagnetRequest": {
        "type": "object",
        "title": "LeadMagnetRequest",
        "description": "One lead-magnet delivery request.",
        "required": ["email", "magnet"],
        "properties": {
          "email": { "type": "string", "format": "email", "description": "Address to deliver the resource to." },
          "magnet": { "type": "string", "maxLength": 64, "description": "Slug of the library resource." }
        }
      },
      "ValidationError": {
        "type": "object",
        "title": "ValidationError",
        "description": "One field-level validation failure.",
        "required": ["loc", "msg", "type"],
        "properties": {
          "loc": { "type": "array", "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }, "description": "Path to the offending field, e.g. [\"body\", \"tool\"]." },
          "msg": { "type": "string", "description": "Human-readable explanation." },
          "type": { "type": "string", "description": "Machine-readable error code, e.g. \"missing\"." }
        }
      },
      "HTTPValidationError": {
        "type": "object",
        "title": "HTTPValidationError",
        "description": "Returned with HTTP 422 when the request body fails validation.",
        "properties": {
          "detail": { "type": "array", "items": { "$ref": "#/components/schemas/ValidationError" } }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "title": "ErrorResponse",
        "description": "Generic JSON error body.",
        "required": ["detail"],
        "properties": {
          "detail": { "type": "string", "description": "Human-readable explanation of the failure." }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "title": "JsonRpcRequest",
        "description": "A JSON-RPC 2.0 request as used by the Model Context Protocol.",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "anyOf": [{ "type": "string" }, { "type": "integer" }], "description": "Omit for notifications." },
          "method": { "type": "string", "description": "MCP method, e.g. \"initialize\", \"tools/list\", \"tools/call\"." },
          "params": { "type": "object", "additionalProperties": true, "description": "Method parameters as defined by the MCP specification." }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "title": "JsonRpcResponse",
        "description": "A JSON-RPC 2.0 response.",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "anyOf": [{ "type": "string" }, { "type": "integer" }] },
          "result": { "type": "object", "additionalProperties": true },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" },
              "data": { "type": "object", "additionalProperties": true }
            }
          }
        }
      }
    }
  }
}
