{
  "openapi": "3.1.0",
  "info": {
    "title": "OGImagen API",
    "version": "1.0.0",
    "summary": "Generate Open Graph, Twitter and LinkedIn images and host them permanently.",
    "description": "OGImagen creates the social preview cards that appear when a link is shared,\nhosts them permanently on Cloudflare CDN, and returns copy-ready meta tags.\n\nTwo generation modes:\n- AI generations (`/api/v1/generations`) consume credits and produce an\n  AI-rendered background with your headline composited on top.\n- Template renders (`/api/v1/templates` and the public `/api/template/preview`)\n  use a deterministic Satori renderer, consume no credits, and need no AI.\n\nThe same capabilities are exposed to AI agents over the Model Context Protocol\nat `/api/mcp`, which authenticates with OAuth 2.1 instead of a static key.",
    "contact": {
      "name": "OGImagen support",
      "email": "info@9thavenue.dev",
      "url": "https://ogimagen.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://ogimagen.com/terms"
    },
    "termsOfService": "https://ogimagen.com/terms"
  },
  "servers": [
    {
      "url": "https://ogimagen.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "REST API guide",
    "url": "https://ogimagen.com/docs/api"
  },
  "tags": [
    {
      "name": "Generations",
      "description": "AI-generated social cards. Consumes credits."
    },
    {
      "name": "Templates",
      "description": "Deterministic template renders. No AI, no credits."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/generations": {
      "post": {
        "operationId": "createGeneration",
        "tags": [
          "Generations"
        ],
        "summary": "Create an AI generation",
        "description": "Queues an AI generation and returns immediately with a generation id and\nstatus `pending`. Poll `getGeneration` until status is `done`.\n\nCost in credits is the per-variant cost of `quality` multiplied by `variants`:\nflash costs 1 per variant, max costs 3 per variant.\n\nPass `idempotencyKey` (or the `Idempotency-Key` header) to make retries safe:\na replay returns the original generation, uncharged, with `deduplicated: true`.\nPass `webhookUrl` to receive a signed `generation.completed` callback instead\nof polling.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Alternative to the idempotencyKey body field. Unique per account.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGenerationRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Smallest valid request",
                  "value": {
                    "title": "Launch week"
                  }
                },
                "branded": {
                  "summary": "Branded card with a headline baked in",
                  "value": {
                    "title": "Abstract gradient mesh, calm and technical",
                    "displayText": "Ship links that look designed",
                    "displaySubtitle": "OG, Twitter and LinkedIn in one call",
                    "brandColor": "#0689CA",
                    "style": "minimal",
                    "quality": "flash",
                    "variants": 2
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay of an earlier generation. Not charged again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "202": {
            "description": "Generation queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerationQueued"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/generations/{id}": {
      "get": {
        "operationId": "getGeneration",
        "tags": [
          "Generations"
        ],
        "summary": "Poll a generation",
        "description": "Returns the current status and, once done, the permanent CDN URLs for every format plus copy-ready meta tag snippets.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Generation id returned by createGeneration.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current state of the generation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Generation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/templates": {
      "post": {
        "operationId": "createTemplateImage",
        "tags": [
          "Templates"
        ],
        "summary": "Render and host a template image",
        "description": "Renders one of the built-in templates and hosts it on the CDN. Uses no AI and consumes no credits. Free accounts may host a limited number; paid plans are unlimited.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTemplateRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Smallest valid request",
                  "value": {
                    "template": "minimal",
                    "title": "Changelog 2.4"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Image rendered and hosted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateImage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "operationId": "listTemplateImages",
        "tags": [
          "Templates"
        ],
        "summary": "List hosted template images",
        "description": "Returns the template images this account currently hosts on the CDN.",
        "responses": {
          "200": {
            "description": "Hosted template images.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "images"
                  ],
                  "properties": {
                    "images": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TemplateImage"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/templates/{id}": {
      "delete": {
        "operationId": "deleteTemplateImage",
        "tags": [
          "Templates"
        ],
        "summary": "Delete a hosted template image",
        "description": "Removes the image from the CDN and frees a hosting slot. The URL stops resolving.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Template image id returned by createTemplateImage.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Image deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted"
                  ],
                  "properties": {
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/template/preview": {
      "get": {
        "operationId": "renderTemplatePreview",
        "tags": [
          "Templates"
        ],
        "summary": "Render a template image without an account",
        "description": "Public, unauthenticated PNG renderer. Rate limited per IP. Returns the image bytes directly rather than a hosted URL, so nothing is stored.",
        "security": [],
        "parameters": [
          {
            "name": "template",
            "in": "query",
            "required": true,
            "description": "Template id.",
            "schema": {
              "type": "string",
              "enum": [
                "minimal",
                "gradient",
                "split",
                "card",
                "dots",
                "quote",
                "banner",
                "terminal",
                "blog",
                "changelog",
                "stat",
                "profile",
                "event",
                "photo",
                "product",
                "showcase"
              ]
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": true,
            "description": "Headline rendered on the card.",
            "schema": {
              "type": "string",
              "maxLength": 140
            }
          },
          {
            "name": "subtitle",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "brandColor",
            "in": "query",
            "required": false,
            "description": "Accent color as #rrggbb.",
            "schema": {
              "type": "string",
              "pattern": "^#[0-9A-Fa-f]{6}$"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "default": "dark"
            }
          },
          {
            "name": "font",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "inter",
                "serif",
                "mono"
              ]
            }
          },
          {
            "name": "pattern",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "none",
                "dots",
                "grid",
                "lines"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rendered PNG, 1200x630.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ogim_*",
        "description": "Static API key issued on every paid plan, sent as `Authorization: Bearer ogim_...`.\nA key carries the full permission set of its account and is scoped to that\naccount only: it can never read or modify another account’s data.\n\nFor agent access prefer OAuth over MCP at `/api/mcp`, where access is granted\nper client with explicit scopes rather than one long-lived secret."
      },
      "mcpOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 used by MCP clients. Discovery metadata is published at /.well-known/oauth-authorization-server.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://ogimagen.com/api/auth/mcp/authorize",
            "tokenUrl": "https://ogimagen.com/api/auth/mcp/token",
            "refreshUrl": "https://ogimagen.com/api/auth/mcp/token",
            "scopes": {
              "openid": "Identify the account the token belongs to.",
              "profile": "Read the account name and avatar.",
              "email": "Read the account email address.",
              "offline_access": "Refresh the access token without user interaction."
            }
          }
        }
      }
    },
    "schemas": {
      "CreateGenerationRequest": {
        "type": "object",
        "required": [
          "title"
        ],
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000,
            "description": "Creative brief describing the imagery. Never rendered as text on the card."
          },
          "description": {
            "type": "string",
            "maxLength": 2000,
            "description": "Secondary creative direction. Never rendered as text."
          },
          "displayText": {
            "type": "string",
            "maxLength": 120,
            "description": "Headline composited on top of the artwork with real typography. Omit for a textless card."
          },
          "displaySubtitle": {
            "type": "string",
            "maxLength": 180,
            "description": "Subtitle under the headline. Ignored when displayText is absent."
          },
          "brandColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$",
            "description": "Accent color. Defaults to the account brand kit when omitted."
          },
          "style": {
            "type": "string",
            "enum": [
              "minimal",
              "editorial",
              "bold",
              "retro",
              "photographic",
              "illustrated",
              "gradient",
              "dark"
            ],
            "description": "Named visual preset that locks the mood."
          },
          "quality": {
            "type": "string",
            "enum": [
              "flash",
              "max"
            ],
            "default": "flash",
            "description": "flash costs 1 credit per variant, max costs 3."
          },
          "variants": {
            "type": "integer",
            "enum": [
              1,
              2,
              3,
              4
            ],
            "default": 1,
            "description": "How many distinct variants to generate."
          },
          "brandKitId": {
            "type": "string",
            "format": "uuid",
            "description": "Apply a specific saved brand kit instead of the default."
          },
          "referenceImageUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Reference image. Must already be hosted on the OGImagen CDN."
          },
          "idempotencyKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Replays of the same key return the original generation, uncharged."
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Receives a signed generation.completed POST on success and failure."
          },
          "webhookSecret": {
            "type": "string",
            "minLength": 8,
            "maxLength": 200,
            "description": "HMAC-SHA256 key used to sign the webhook payload."
          }
        }
      },
      "GenerationQueued": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "pending"
          },
          "creditsCharged": {
            "type": "integer",
            "minimum": 0
          },
          "creditsRemaining": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Generation": {
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "done",
              "failed"
            ],
            "description": "Poll until done or failed. Failed generations refund their credits."
          },
          "deduplicated": {
            "type": "boolean",
            "description": "True when an idempotency key replayed an earlier generation."
          },
          "urls": {
            "type": "object",
            "description": "Permanent CDN URLs, present once status is done.",
            "properties": {
              "og": {
                "type": "string",
                "format": "uri",
                "description": "1200x630."
              },
              "twitter": {
                "type": "string",
                "format": "uri",
                "description": "1200x600."
              },
              "linkedin": {
                "type": "string",
                "format": "uri",
                "description": "1200x627."
              }
            }
          },
          "snippets": {
            "type": "object",
            "description": "Copy-ready meta tags per framework.",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "CreateTemplateRequest": {
        "type": "object",
        "required": [
          "template",
          "title"
        ],
        "properties": {
          "template": {
            "type": "string",
            "enum": [
              "minimal",
              "gradient",
              "split",
              "card",
              "dots",
              "quote",
              "banner",
              "terminal",
              "blog",
              "changelog",
              "stat",
              "profile",
              "event",
              "photo",
              "product",
              "showcase"
            ]
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 140
          },
          "subtitle": {
            "type": "string",
            "maxLength": 200
          },
          "brandColor": {
            "type": "string",
            "pattern": "^#[0-9A-Fa-f]{6}$"
          },
          "theme": {
            "type": "string",
            "enum": [
              "light",
              "dark"
            ],
            "default": "dark"
          },
          "font": {
            "type": "string",
            "enum": [
              "inter",
              "serif",
              "mono"
            ]
          },
          "pattern": {
            "type": "string",
            "enum": [
              "none",
              "dots",
              "grid",
              "lines"
            ]
          },
          "brandKitId": {
            "type": "string",
            "format": "uuid",
            "description": "Fills brandColor and logoUrl from a saved kit when omitted here."
          }
        }
      },
      "TemplateImage": {
        "type": "object",
        "required": [
          "id",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Permanent CDN URL."
          },
          "template": {
            "type": "string",
            "enum": [
              "minimal",
              "gradient",
              "split",
              "card",
              "dots",
              "quote",
              "banner",
              "terminal",
              "blog",
              "changelog",
              "stat",
              "profile",
              "event",
              "photo",
              "product",
              "showcase"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code",
          "message",
          "resolution",
          "docs"
        ],
        "description": "Every non-2xx response uses this shape. Branch on `code`; `resolution` says what to do next.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Same text as message. Kept for compatibility."
          },
          "code": {
            "type": "string",
            "enum": [
              "unauthorized",
              "plan_required",
              "rate_limited",
              "invalid_json",
              "invalid_parameters",
              "not_found",
              "insufficient_credits",
              "moderation_blocked",
              "payload_too_large",
              "generation_failed",
              "internal_error"
            ]
          },
          "message": {
            "type": "string"
          },
          "resolution": {
            "type": "string",
            "description": "What the caller should do next."
          },
          "docs": {
            "type": "string",
            "format": "uri"
          },
          "issues": {
            "type": "object",
            "description": "Field-level validation errors, present on invalid_parameters.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request body or parameters were rejected.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Not enough credits for this generation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The account plan does not include API access.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource on this account.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Retry after the number of seconds in Retry-After.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait."
          },
          "X-RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}