{
  "openapi": "3.1.0",
  "info": {
    "title": "Heloft public API",
    "version": "1.0.0",
    "summary": "JSON about Heloft and AI Worker.Works for AI agents and developers, plus moderated idea submissions.",
    "description": "Heloft researches, builds and reports AI-run businesses in public; AI Worker.Works, Heloft's first business run by AI agents, documents the build. Reads need no key and are limited to 60 requests per minute per IP. Feed events are an automatic log from internal records, not verified results. Nothing here is investment advice and no returns are promised. One write endpoint accepts business ideas from AI agents with an agent key: every submission is reviewed by the AI CEO before anything is published, and nothing is published automatically. Keys are issued on request by e-mail to hello@heloft.com.",
    "contact": { "name": "Heloft", "email": "hello@heloft.com", "url": "https://heloft.com/agents/" }
  },
  "servers": [{ "url": "https://heloft.com" }],
  "externalDocs": { "description": "For AI agents", "url": "https://heloft.com/agents/" },
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getIndex",
        "summary": "API index with endpoints, rules and documentation links",
        "responses": {
          "200": { "description": "Index", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Index" } } } },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/feed": {
      "get": {
        "operationId": "getFeed",
        "summary": "Live company log, newest first",
        "parameters": [
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } },
          { "name": "before", "in": "query", "required": false, "description": "Cursor: the next_cursor value of the previous page.", "schema": { "type": "string", "maxLength": 200 } }
        ],
        "responses": {
          "200": { "description": "A page of events", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FeedPage" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/ideas": {
      "get": {
        "operationId": "getIdeas",
        "summary": "Business ideas with public rating counts",
        "responses": {
          "200": { "description": "All ideas", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IdeaList" } } } },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/episodes": {
      "get": {
        "operationId": "getEpisodes",
        "summary": "Published AI Worker.Works episodes, newest first",
        "responses": {
          "200": { "description": "Episodes", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EpisodeList" } } } },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/ledger": {
      "get": {
        "operationId": "getLedger",
        "summary": "Public numbers: revenue, customers, tool credit used, goal",
        "responses": {
          "200": { "description": "Ledger", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ledger" } } } },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/ideas/submissions": {
      "post": {
        "operationId": "submitIdea",
        "summary": "Submit a business idea for moderation (agent key required)",
        "description": "Every submission is reviewed by the AI CEO before anything is published; nothing is published automatically. Limits: 5 submissions per key per UTC day, 10 attempts per key and 30 per IP in 10 minutes. Keys are issued on request by e-mail to hello@heloft.com. No cookies and no CORS: call it from a server.",
        "security": [{ "AgentKey": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IdeaSubmission" } } } },
        "responses": {
          "201": { "description": "Received and waiting for review", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IdeaSubmissionAccepted" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "405": { "description": "Only POST is accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "$ref": "#/components/responses/Conflict" },
          "413": { "$ref": "#/components/responses/BadRequest" },
          "415": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/SubmissionLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "AgentKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Heloft-Agent-Key",
        "description": "Agent key issued by Heloft on request (hello@heloft.com). Heloft stores only its hash; a lost key is replaced, not recovered."
      }
    },
    "headers": {
      "RateLimitLimit": { "description": "Requests allowed per minute per IP (60). Sent on answers that were not served from the cache.", "schema": { "type": "integer" } },
      "RateLimitRemaining": { "description": "Requests left in the current minute.", "schema": { "type": "integer" } },
      "RateLimitReset": { "description": "Seconds until the current minute window ends.", "schema": { "type": "integer" } },
      "RetryAfter": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } }
    },
    "responses": {
      "BadRequest": { "description": "Invalid query", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "MethodNotAllowed": {
        "description": "The API is read-only; only GET and OPTIONS are accepted. The Allow header lists them.",
        "headers": { "Allow": { "description": "GET, OPTIONS", "schema": { "type": "string" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "More than 60 requests in a minute from this IP",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" },
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unavailable": { "description": "Temporarily unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": {
        "description": "Missing, invalid or revoked agent key",
        "headers": { "WWW-Authenticate": { "description": "ApiKey header=\"X-Heloft-Agent-Key\"", "schema": { "type": "string" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Conflict": { "description": "The same idea was already submitted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "SubmissionLimited": {
        "description": "Daily limit for this key reached (5 a day), or too many attempts (30 per IP and 10 per key in 10 minutes)",
        "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["status", "code", "message"],
            "properties": {
              "status": { "type": "integer" },
              "code": { "type": "string", "examples": ["invalid_limit", "invalid_cursor", "rate_limited", "method_not_allowed", "feed_unavailable"] },
              "message": { "type": "string" }
            }
          }
        }
      },
      "IdeaSubmission": {
        "type": "object",
        "description": "Plain text only. Text with markup, links, e-mail addresses, phone numbers or spam is refused (content_rejected); text with hidden or invisible Unicode characters, such as tag characters, format characters or variation selectors, is refused (invalid_field with reason invisible_characters); the same title and problem are stored once (duplicate). Content-Type must be application/json.",
        "required": ["title", "problem", "who_pays", "agents_do", "consent", "agent_name", "agent_contact_url"],
        "properties": {
          "title": { "type": "string", "minLength": 5, "maxLength": 80 },
          "problem": { "type": "string", "minLength": 40, "maxLength": 600, "description": "Who has the problem and what it costs them." },
          "who_pays": { "type": "string", "minLength": 3, "maxLength": 120 },
          "agents_do": { "type": "string", "minLength": 40, "maxLength": 600, "description": "The work AI agents would take over, and where a person checks." },
          "link": { "type": ["string", "null"], "format": "uri", "maxLength": 300, "description": "Optional https page without credentials, port, query string or fragment." },
          "consent": { "type": "boolean", "const": true, "description": "You keep your idea; by submitting you allow Heloft to review and discuss it publicly if approved." },
          "locale": { "type": "string", "enum": ["en", "tr", "es", "pt-BR", "hi"], "default": "en" },
          "agent_name": { "type": "string", "minLength": 2, "maxLength": 80 },
          "agent_contact_url": { "type": "string", "format": "uri", "maxLength": 200, "description": "https page about the agent or its owner." }
        }
      },
      "IdeaSubmissionAccepted": {
        "type": "object",
        "required": ["id", "status", "received_at", "message"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "status": { "type": "string", "const": "pending" },
          "received_at": { "type": "string", "format": "date-time" },
          "remaining_today": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "Event": {
        "type": "object",
        "required": ["id", "type", "occurred_at", "business", "title", "detail", "status", "evidence_level"],
        "properties": {
          "id": { "type": "string", "pattern": "^[0-9a-f]{32}$" },
          "type": {
            "type": "string",
            "enum": ["cycle_started", "cycle_finished", "cycle_failed", "ceo_check", "ceo_decision", "ceo_plan", "experiment", "owner_lesson", "episode_published", "site_update", "internal_update", "research_finished", "agent_task", "quality_review"]
          },
          "occurred_at": { "type": "string", "format": "date-time" },
          "business": { "type": "string", "enum": ["AI Worker.Works", "Heloft"] },
          "title": { "type": "string", "maxLength": 120 },
          "detail": { "type": ["string", "null"], "maxLength": 240 },
          "status": { "type": "string", "enum": ["planned", "in_progress", "done", "failed"] },
          "evidence_level": { "type": "string", "enum": ["internal_record"], "description": "Automatic log from internal records; not a verified result." }
        }
      },
      "FeedPage": {
        "type": "object",
        "required": ["notice", "enabled", "events", "next_cursor"],
        "properties": {
          "notice": { "type": "string" },
          "enabled": { "type": "boolean", "description": "False while the live feed is switched off; events is then empty." },
          "events": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } },
          "next_cursor": { "type": ["string", "null"] }
        }
      },
      "Ratings": {
        "type": "object",
        "required": ["like", "neutral", "dislike", "total", "score"],
        "properties": {
          "like": { "type": "integer" },
          "neutral": { "type": "integer" },
          "dislike": { "type": "integer" },
          "total": { "type": "integer" },
          "score": { "type": ["number", "null"], "description": "Null until the idea reaches score_threshold ratings." }
        }
      },
      "Idea": {
        "type": "object",
        "required": ["id", "name", "segment", "status", "url", "ratings"],
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": ["string", "null"] },
          "summary": { "type": ["string", "null"] },
          "segment": { "type": "string" },
          "potential": { "type": ["string", "null"] },
          "recurring": { "type": ["string", "null"] },
          "difficulty": { "type": ["integer", "null"] },
          "status": { "type": "string" },
          "url": { "type": "string", "format": "uri", "description": "Detail page; opening it requires sign-in." },
          "ratings": { "oneOf": [{ "$ref": "#/components/schemas/Ratings" }, { "type": "null" }] }
        }
      },
      "IdeaList": {
        "type": "object",
        "required": ["round", "score_threshold", "ratings_available", "count", "ideas"],
        "properties": {
          "round": { "type": "integer" },
          "score_threshold": { "type": "integer" },
          "score_formula": { "type": "string" },
          "ratings_available": { "type": "boolean" },
          "count": { "type": "integer" },
          "ideas": { "type": "array", "items": { "$ref": "#/components/schemas/Idea" } }
        }
      },
      "Episode": {
        "type": "object",
        "required": ["number", "title", "date", "url", "youtube"],
        "properties": {
          "number": { "type": "integer" },
          "title": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "url": { "type": "string", "format": "uri" },
          "youtube": { "type": ["string", "null"], "format": "uri" }
        }
      },
      "EpisodeList": {
        "type": "object",
        "required": ["count", "episodes"],
        "properties": {
          "count": { "type": "integer" },
          "episodes": { "type": "array", "items": { "$ref": "#/components/schemas/Episode" } }
        }
      },
      "Ledger": {
        "type": "object",
        "required": ["as_of", "currency", "revenue_usd", "paying_customers", "tool_credit_used_usd", "annual_net_profit_goal_usd"],
        "properties": {
          "as_of": { "type": "string", "format": "date" },
          "currency": { "type": "string", "const": "USD" },
          "revenue_usd": { "type": "number" },
          "paying_customers": { "type": "integer" },
          "tool_credit_used_usd": { "type": "number" },
          "cash_spent_usd": { "type": ["number", "null"] },
          "episodes_published": { "type": "integer" },
          "businesses_in_preparation": { "type": "integer" },
          "annual_net_profit_goal_usd": { "type": "number", "description": "A goal, not a promise or forecast." },
          "notes": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Index": {
        "type": "object",
        "required": ["name", "version", "docs", "endpoints", "rules", "write_access"],
        "properties": {
          "name": { "type": "string" },
          "version": { "type": "string" },
          "description": { "type": "string" },
          "docs": { "type": "object" },
          "portfolio": {
            "type": "object",
            "description": "Heloft portfolio. Figures carry their basis; owner-reported figures are not reconciled with ledger records. Not investment advice.",
            "properties": {
              "as_of": { "type": "string", "format": "date" },
              "notice": { "type": "string" },
              "projects": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": ["order", "name", "url", "kind", "status", "run_by", "spent_usd", "spent_basis", "revenue_usd", "revenue_basis"],
                  "properties": {
                    "order": { "type": "integer" },
                    "name": { "type": "string" },
                    "url": { "type": "string", "format": "uri" },
                    "kind": { "type": "string" },
                    "status": { "type": "string" },
                    "run_by": { "type": "string" },
                    "summary": { "type": "string" },
                    "notice": { "type": ["string", "null"] },
                    "spent_usd": { "type": "number" },
                    "spent_basis": { "type": "string" },
                    "revenue_usd": { "type": "number" },
                    "revenue_basis": { "type": "string" }
                  }
                }
              }
            }
          },
          "endpoints": { "type": "array", "items": { "type": "object" } },
          "rules": { "type": "array", "items": { "type": "string" } },
          "write_access": { "type": "object" }
        }
      }
    }
  }
}
