{
  "openapi": "3.1.0",
  "info": {
    "title": "OgForge",
    "version": "0.2.1",
    "contact": {
      "email": "digitalpromohub.support@gmail.com"
    },
    "description": "Open Graph image API: 1200x630 PNG social cards from 5 templates. Free, no key: about 120 image requests and 20 new renders per minute per IP, ~60 new renders/min service-wide. API keys are not available yet; a sent key is ignored."
  },
  "servers": [
    {
      "url": "https://ogforge.mike-tusa.workers.dev"
    }
  ],
  "security": [
    {},
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Reserved for the future Pro tier; ignored today."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Reserved for the future Pro tier; ignored today."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string"
              },
              "limit": {
                "type": "integer"
              },
              "window": {
                "type": "string"
              },
              "scope": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "McpCreateOgImageResult": {
        "type": "object",
        "properties": {
          "image_url": {
            "type": "string",
            "description": "Public PNG URL (GET /api/og?...). Use it as og:image / twitter:image."
          },
          "width": {
            "type": "integer"
          },
          "height": {
            "type": "integer"
          },
          "bytes": {
            "type": "integer"
          },
          "template": {
            "type": "string"
          },
          "cache": {
            "type": "string",
            "description": "MISS, HIT-MEMORY or HIT-EDGE"
          },
          "tier": {
            "type": "string"
          },
          "etag": {
            "type": "string"
          },
          "truncated": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fields that were shortened to fit."
          },
          "warning": {
            "type": "string",
            "description": "e.g. logo_fetch_failed: the image rendered without the logo."
          },
          "meta_tags": {
            "type": "string",
            "description": "Ready-to-paste og:image / twitter:card meta tags."
          }
        },
        "required": [
          "image_url",
          "width",
          "height",
          "template",
          "cache",
          "tier"
        ]
      }
    }
  },
  "paths": {
    "/api/og": {
      "get": {
        "operationId": "renderOgImage",
        "summary": "Render an Open Graph image",
        "parameters": [
          {
            "name": "template",
            "in": "query",
            "required": false,
            "description": "Layout (default blog). Alias: t",
            "schema": {
              "type": "string",
              "enum": [
                "blog",
                "launch",
                "docs",
                "minimal",
                "gradient"
              ]
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Headline (truncated to 120 characters)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "subtitle",
            "in": "query",
            "required": false,
            "description": "Secondary line (truncated to 200). Alias: description",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "label",
            "in": "query",
            "required": false,
            "description": "Pill / tag (truncated to 40). Alias: tag",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "site",
            "in": "query",
            "required": false,
            "description": "Site name (truncated to 60)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "description": "Author (truncated to 60)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "emoji",
            "in": "query",
            "required": false,
            "description": "One emoji",
            "schema": {
              "type": "string",
              "maxLength": 32
            }
          },
          {
            "name": "logo",
            "in": "query",
            "required": false,
            "description": "https:// PNG, JPEG or SVG URL (max 300 KB), fetched server-side",
            "schema": {
              "type": "string",
              "maxLength": 512,
              "format": "uri"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ]
            }
          },
          {
            "name": "bg",
            "in": "query",
            "required": false,
            "description": "Background hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          },
          {
            "name": "fg",
            "in": "query",
            "required": false,
            "description": "Text hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          },
          {
            "name": "accent",
            "in": "query",
            "required": false,
            "description": "Accent hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "1200x630 PNG",
            "headers": {
              "x-ogforge-cache": {
                "description": "MISS, HIT-MEMORY or HIT-EDGE",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-tier": {
                "description": "free",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-truncated": {
                "description": "Comma-separated fields that were shortened",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-warning": {
                "description": "e.g. logo_fetch_failed",
                "schema": {
                  "type": "string"
                }
              },
              "etag": {
                "description": "Strong ETag (cache-key hash)",
                "schema": {
                  "type": "string"
                }
              },
              "cache-control": {
                "description": "public, max-age=31536000, immutable (300 s when degraded)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)"
          },
          "400": {
            "description": "invalid_template, invalid_theme, invalid_color, invalid_emoji, invalid_logo or param_too_long (error.field)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "blocked_content (acceptable-use denylist)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "headers": {
              "allow": {
                "description": "GET, HEAD, OPTIONS",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "414": {
            "description": "query_too_long",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (per IP) or busy (global render cap)",
            "headers": {
              "retry-after": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "render_failed or internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/og.png": {
      "get": {
        "operationId": "renderOgImagePng",
        "summary": "Render an Open Graph image",
        "parameters": [
          {
            "name": "template",
            "in": "query",
            "required": false,
            "description": "Layout (default blog). Alias: t",
            "schema": {
              "type": "string",
              "enum": [
                "blog",
                "launch",
                "docs",
                "minimal",
                "gradient"
              ]
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Headline (truncated to 120 characters)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "subtitle",
            "in": "query",
            "required": false,
            "description": "Secondary line (truncated to 200). Alias: description",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "label",
            "in": "query",
            "required": false,
            "description": "Pill / tag (truncated to 40). Alias: tag",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "site",
            "in": "query",
            "required": false,
            "description": "Site name (truncated to 60)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "description": "Author (truncated to 60)",
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "emoji",
            "in": "query",
            "required": false,
            "description": "One emoji",
            "schema": {
              "type": "string",
              "maxLength": 32
            }
          },
          {
            "name": "logo",
            "in": "query",
            "required": false,
            "description": "https:// PNG, JPEG or SVG URL (max 300 KB), fetched server-side",
            "schema": {
              "type": "string",
              "maxLength": 512,
              "format": "uri"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ]
            }
          },
          {
            "name": "bg",
            "in": "query",
            "required": false,
            "description": "Background hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          },
          {
            "name": "fg",
            "in": "query",
            "required": false,
            "description": "Text hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          },
          {
            "name": "accent",
            "in": "query",
            "required": false,
            "description": "Accent hex color",
            "schema": {
              "type": "string",
              "maxLength": 7,
              "pattern": "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "1200x630 PNG",
            "headers": {
              "x-ogforge-cache": {
                "description": "MISS, HIT-MEMORY or HIT-EDGE",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-tier": {
                "description": "free",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-truncated": {
                "description": "Comma-separated fields that were shortened",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-warning": {
                "description": "e.g. logo_fetch_failed",
                "schema": {
                  "type": "string"
                }
              },
              "etag": {
                "description": "Strong ETag (cache-key hash)",
                "schema": {
                  "type": "string"
                }
              },
              "cache-control": {
                "description": "public, max-age=31536000, immutable (300 s when degraded)",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)"
          },
          "400": {
            "description": "invalid_template, invalid_theme, invalid_color, invalid_emoji, invalid_logo or param_too_long (error.field)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "blocked_content (acceptable-use denylist)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "method_not_allowed",
            "headers": {
              "allow": {
                "description": "GET, HEAD, OPTIONS",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "414": {
            "description": "query_too_long",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (per IP) or busy (global render cap)",
            "headers": {
              "retry-after": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "render_failed or internal_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "Templates and text limits",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Service status and version",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "MCP JSON-RPC 2.0 (Streamable HTTP, stateless). Protocol versions 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05. Max body 65536 bytes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (tool errors are results with isError and _meta[\"ogforge/error\"])",
            "headers": {
              "retry-after": {
                "description": "Seconds (on a rate-limited tools/call)",
                "schema": {
                  "type": "string"
                }
              },
              "x-ogforge-cache": {
                "description": "Cache status of the rendered image",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Notification or client response accepted"
          },
          "400": {
            "description": "Parse error, invalid request, header mismatch or unsupported version"
          },
          "401": {
            "description": "Reserved (no API keys yet; no WWW-Authenticate)"
          },
          "403": {
            "description": "Origin not allowed"
          },
          "404": {
            "description": "Unknown method (2026-07-28)"
          },
          "413": {
            "description": "Body too large"
          },
          "429": {
            "description": "Too many MCP requests",
            "headers": {
              "retry-after": {
                "description": "Seconds",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "Internal error"
          },
          "503": {
            "description": "Unavailable"
          }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "Not supported (stateless, no SSE)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "allow": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "POST, OPTIONS"
                  ]
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "mcpDelete",
        "summary": "Not supported (no sessions)",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "allow": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "POST, OPTIONS"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}