{
  "openapi": "3.0.3",
  "info": {
    "title": "Terradium public machine surface",
    "description": "Honest OpenAPI for the live Terradium product (v1.0.7). The API host is https://api.terradium.io. The marketing site https://terradium.io serves this document, llms.txt, sitemap.xml, and docs. It is not the API host and it is not the MCP server.\n\nMCP writes need a project-scoped token with prefix td_mcp_ (Authorization: Bearer or X-API-Key). The public content API is read-only and gated by a per-project td_live_ key, except the keyless AI discoverability files and Accept-negotiated post markdown/html.\n\nDo not invent unauthenticated write endpoints on terradium.io.",
    "version": "1.0.7",
    "contact": {
      "name": "PT Semesta Solusi Digital",
      "email": "company@kugie.app",
      "url": "https://terradium.io/contact"
    }
  },
  "servers": [
    {
      "url": "https://api.terradium.io",
      "description": "Terradium product API"
    }
  ],
  "tags": [
    { "name": "Health", "description": "Unauthenticated liveness and readiness." },
    { "name": "MCP", "description": "Project-scoped Model Context Protocol endpoint. Auth required." },
    { "name": "Content", "description": "Published-content REST API for customer sites." },
    { "name": "Landing", "description": "Machine files on https://terradium.io." }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "getHealthz",
        "tags": ["Health"],
        "summary": "Liveness",
        "description": "Unauthenticated liveness probe. Returns status=ok and the release version stamped into the binary (currently v1.0.7 in production). No request body. No API key.",
        "responses": {
          "200": {
            "description": "Process is alive.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Healthz" },
                "example": { "status": "ok", "version": "v1.0.7" }
              }
            }
          }
        }
      }
    },
    "/readyz": {
      "get": {
        "operationId": "getReadyz",
        "tags": ["Health"],
        "summary": "Readiness",
        "description": "Unauthenticated readiness probe. Pings Postgres. Returns 503 when a dependency is down or unconfigured.",
        "responses": {
          "200": {
            "description": "Ready to serve traffic.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Readyz" }
              }
            }
          },
          "503": {
            "description": "A named dependency is not ready.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Readyz" }
              }
            }
          }
        }
      }
    },
    "/api/v1/mcp": {
      "get": {
        "operationId": "getMcp",
        "tags": ["MCP"],
        "summary": "MCP Streamable HTTP (GET)",
        "description": "Streamable HTTP MCP transport on the product API. Requires a project-scoped token with prefix td_mcp_. Send Authorization: Bearer td_mcp_... or X-API-Key: td_mcp_.... Without a token the response is 401 with WWW-Authenticate: Bearer realm=\"terradium\". This path does not exist on terradium.io. Writes (generate, publish, sample, settings) need this token and an active subscription. A td_live_ content key is rejected.",
        "security": [{ "McpBearer": [] }, { "McpApiKey": [] }],
        "responses": {
          "200": {
            "description": "MCP session or event stream for an authenticated client.",
            "content": {
              "text/event-stream": {
                "schema": { "type": "string" }
              },
              "application/json": {
                "schema": { "$ref": "#/components/schemas/McpJsonRpc" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid td_mcp_ token.",
            "headers": {
              "WWW-Authenticate": {
                "schema": { "type": "string", "example": "Bearer realm=\"terradium\"" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Unauthorized" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postMcp",
        "tags": ["MCP"],
        "summary": "MCP Streamable HTTP (POST)",
        "description": "JSON-RPC 2.0 MCP messages (initialize, tools/list, tools/call, ...). Same auth as GET. Tools are project-scoped by the token: no projectId argument. Writes consume the normal generation and sample-run quotas. See https://terradium.io/docs/mcp for the tool list.",
        "security": [{ "McpBearer": [] }, { "McpApiKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/McpJsonRpc" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or MCP stream.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/McpJsonRpc" }
              },
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid td_mcp_ token.",
            "headers": {
              "WWW-Authenticate": {
                "schema": { "type": "string", "example": "Bearer realm=\"terradium\"" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Unauthorized" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/posts": {
      "get": {
        "operationId": "listPublicPosts",
        "tags": ["Content"],
        "summary": "List published posts",
        "description": "Newest-first list of published posts for a project. Requires a project content API key (td_live_...) via X-API-Key or Authorization: Bearer. Rate limit: 1200 requests per minute per client IP.",
        "security": [{ "ContentApiKey": [] }, { "ContentBearer": [] }],
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "description": "Project UUID from the dashboard.",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Items per page. Max 100.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Only posts in this category name.",
            "schema": { "type": "string" }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Only posts with this tag.",
            "schema": { "type": "string" }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "ISO language code (for example en or id).",
            "schema": { "type": "string" }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Set to body to return the full post object (htmlContent, markdownContent, metadata) per item.",
            "schema": { "type": "string", "enum": ["body"] }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Defaults to published.",
            "schema": { "type": "string", "default": "published" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated post list.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostListResponse" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Project not found, or the key does not match the project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honor Retry-After.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/posts/{slug}": {
      "get": {
        "operationId": "getPublicPost",
        "tags": ["Content"],
        "summary": "Get one published post",
        "description": "Fetch one published post by slug. JSON requires a project API key. Accept: text/markdown or text/html returns the clean body and is keyless (AI crawlers). The cache key includes the negotiated format so representations never alias.",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "description": "Project UUID.",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Published post slug.",
            "schema": { "type": "string" }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "text/markdown, text/html, or application/json (default when a key is sent).",
            "schema": {
              "type": "string",
              "enum": ["application/json", "text/markdown", "text/html"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post in the negotiated format.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostResponse" }
              },
              "text/markdown": {
                "schema": { "type": "string" }
              },
              "text/html": {
                "schema": { "type": "string" }
              }
            }
          },
          "401": {
            "description": "JSON requested without a valid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Unknown or unpublished slug.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/categories": {
      "get": {
        "operationId": "listPublicCategories",
        "tags": ["Content"],
        "summary": "List categories",
        "description": "Categories used across published posts, with counts. Requires a project content API key.",
        "security": [{ "ContentApiKey": [] }, { "ContentBearer": [] }],
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "description": "Project UUID.",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Category list.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CategoryListResponse" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Project not found, or the key does not match the project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/llms.txt": {
      "get": {
        "operationId": "getProjectLlmsTxt",
        "tags": ["Content"],
        "summary": "Project llms.txt",
        "description": "Keyless link-first index of the project's published posts. Intended for the customer site to host at /llms.txt. Not the marketing-site llms.txt.",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Plain-text llms.txt index.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "404": {
            "description": "Unknown project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/llms-full.txt": {
      "get": {
        "operationId": "getProjectLlmsFullTxt",
        "tags": ["Content"],
        "summary": "Project llms-full.txt",
        "description": "Keyless full markdown corpus of the project's published posts.",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Plain-text corpus.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "404": {
            "description": "Unknown project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/sitemap.xml": {
      "get": {
        "operationId": "getProjectSitemap",
        "tags": ["Content"],
        "summary": "Project sitemap.xml",
        "description": "Keyless sitemap of the project's published posts. URLs follow the project's blog URL pattern.",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "XML sitemap.",
            "content": {
              "application/xml": { "schema": { "type": "string" } }
            }
          },
          "404": {
            "description": "Unknown project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/content/{projectId}/robots.txt": {
      "get": {
        "operationId": "getProjectRobotsTxt",
        "tags": ["Content"],
        "summary": "Project robots.txt",
        "description": "Keyless robots.txt that welcomes AI crawlers and advertises the project's sitemap and llms files.",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "robots.txt body.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "404": {
            "description": "Unknown project.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "servers": [{ "url": "https://terradium.io" }],
      "get": {
        "operationId": "getLandingOpenapi",
        "tags": ["Landing"],
        "summary": "This OpenAPI document",
        "description": "The document you are reading, served as application/json from the marketing origin so auditors and agents can parse it. The API host may not serve /openapi.json yet.",
        "responses": {
          "200": {
            "description": "OpenAPI 3 document.",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "servers": [{ "url": "https://terradium.io" }],
      "get": {
        "operationId": "getLandingLlmsTxt",
        "tags": ["Landing"],
        "summary": "Marketing-site llms.txt",
        "description": "Curated agent brief (when to use, how to call, pricing lock, MCP pointer) plus an optional blog-post index. Not a second MCP server.",
        "responses": {
          "200": {
            "description": "text/plain llms.txt.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/agents.md": {
      "servers": [{ "url": "https://terradium.io" }],
      "get": {
        "operationId": "getLandingAgentsMd",
        "tags": ["Landing"],
        "summary": "Agent instructions",
        "description": "Short when-to-use brief without the blog dump. Same facts as the llms.txt header.",
        "responses": {
          "200": {
            "description": "Markdown brief.",
            "content": {
              "text/markdown": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "servers": [{ "url": "https://terradium.io" }],
      "get": {
        "operationId": "getLandingSitemap",
        "tags": ["Landing"],
        "summary": "Marketing-site sitemap",
        "description": "Sitemap of prerendered marketing, docs, and blog URLs on terradium.io.",
        "responses": {
          "200": {
            "description": "XML sitemap.",
            "content": {
              "application/xml": { "schema": { "type": "string" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "McpBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "td_mcp_",
        "description": "Project-scoped MCP token. Prefix td_mcp_. Required for MCP writes."
      },
      "McpApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Same td_mcp_ token for clients that cannot send Authorization."
      },
      "ContentApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Read-only project content key. Prefix td_live_. Cannot call MCP writes."
      },
      "ContentBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same td_live_ content key sent as Authorization: Bearer."
      }
    },
    "schemas": {
      "Healthz": {
        "type": "object",
        "required": ["status", "version"],
        "properties": {
          "status": { "type": "string", "example": "ok" },
          "version": { "type": "string", "example": "v1.0.7" }
        }
      },
      "Readyz": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "string", "enum": ["ready", "not_ready"] },
          "checks": {
            "type": "object",
            "additionalProperties": { "type": "string" }
          }
        }
      },
      "Unauthorized": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "unauthorized" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" }
        }
      },
      "McpJsonRpc": {
        "type": "object",
        "description": "JSON-RPC 2.0 envelope used by the MCP SDK. method examples: initialize, tools/list, tools/call.",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "enum": ["2.0"] },
          "id": {
            "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }]
          },
          "method": { "type": "string" },
          "params": { "type": "object", "additionalProperties": true },
          "result": { "type": "object", "additionalProperties": true },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["page", "limit", "total", "totalPages"],
        "properties": {
          "page": { "type": "integer" },
          "limit": { "type": "integer" },
          "total": { "type": "integer" },
          "totalPages": { "type": "integer" }
        }
      },
      "PostListItem": {
        "type": "object",
        "required": ["slug", "title"],
        "properties": {
          "slug": { "type": "string" },
          "title": { "type": "string" },
          "excerpt": { "type": "string" },
          "categories": { "type": "array", "items": { "type": "string" } },
          "tags": { "type": "array", "items": { "type": "string" } },
          "keywords": { "type": "array", "items": { "type": "string" } },
          "language": { "type": "string" },
          "readingTime": { "type": "integer", "nullable": true },
          "featuredImage": { "type": "string" },
          "publishedAt": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "Post": {
        "type": "object",
        "required": ["slug", "title"],
        "properties": {
          "slug": { "type": "string" },
          "title": { "type": "string" },
          "htmlContent": { "type": "string" },
          "markdownContent": { "type": "string" },
          "excerpt": { "type": "string" },
          "metaDescription": { "type": "string" },
          "featuredImage": { "type": "string" },
          "categories": { "type": "array", "items": { "type": "string" } },
          "tags": { "type": "array", "items": { "type": "string" } },
          "keywords": { "type": "array", "items": { "type": "string" } },
          "author": { "nullable": true },
          "seoMetadata": { "type": "object", "additionalProperties": true },
          "readingTime": { "type": "integer", "nullable": true },
          "wordCount": { "type": "integer", "nullable": true },
          "language": { "type": "string" },
          "publishedAt": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "PostListResponse": {
        "type": "object",
        "required": ["data", "pagination"],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/PostListItem" },
                { "$ref": "#/components/schemas/Post" }
              ]
            }
          },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        }
      },
      "PostResponse": {
        "type": "object",
        "required": ["data"],
        "properties": {
          "data": { "$ref": "#/components/schemas/Post" }
        }
      },
      "Category": {
        "type": "object",
        "required": ["name", "count"],
        "properties": {
          "name": { "type": "string" },
          "count": { "type": "integer" }
        }
      },
      "CategoryListResponse": {
        "type": "object",
        "required": ["data"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Category" }
          }
        }
      }
    }
  }
}
