API Reference

Open Graph

Screenshot any URL, render a Chart.js config, render from a saved template, or render inline from a full template definition in the request body. Cached endpoints respond in under 200 ms on repeat requests.

GET /render

Navigates a headless Chrome to any URL and returns a screenshot. Results are cached in KV for 30 days. The cache key rolls over weekly so images stay fresh without manual cache-busting.

GET/render?url=https://example.com
GET/render?url=https://example.com&format=png&w=1200&h=630
GET/render?url=https://example.com&selector=.og-card&api_key=ogsk_…
ParamDefaultDescription
url Target URL to screenshot. Must be a fully-qualified URL including scheme.
w 1440 Viewport width in pixels. Clamped to 1–4096.
h 788 Viewport height in pixels. Clamped to 1–4096.
dpr 2 Device pixel ratio — 1, 2, or 3. Higher values produce sharper images on HiDPI screens but increase file size.
format jpeg Output image format: jpeg, png, or webp. JPEG quality is fixed at 85. PNG and WebP use a transparent background.
selector CSS selector to clip the screenshot to a single element. Falls back to full-page if the selector isn't found within 5 s.
scheme Override the URL scheme before navigating — e.g. https. Useful when the url param omits the scheme.
digest ISO week Cache-key suffix. Defaults to YYYY-Www (rolls weekly). Pass any string to pin a specific render version.
force Set force=1 to bypass the KV cache and always re-render.
api_key API key for authenticated access. Also accepted as Authorization: Bearer <key>. See Authentication.
…extra params Any other query params are forwarded to the target URL and baked into the cache key. Useful for auth tokens, feature flags, preview modes, etc.
Host allowlist: if ALLOWED_HOSTS is configured (comma-separated exact hostnames), requests to unlisted hosts return 403 host_not_allowed:<host>. Empty means allow-all.

GET /chart

Renders a QuickChart-style Chart.js config to a PNG image using the browser renderer. This is a focused image API for static charts: v1 supports bar and line charts, multiple datasets, labels, and Chart.js color fields.

# Copy-paste curl: --data-urlencode handles the JSON query param
curl -G "https://open-graph.com/chart" \
  --data-urlencode 'c={"type":"bar","data":{"labels":["A","B"],"datasets":[{"label":"Sales","data":[10,20],"backgroundColor":"#4ade80"}]}}' \
  --data-urlencode "w=1200" \
  --data-urlencode "h=630" \
  --output chart.png
GET/chart?c={"type":"bar","data":{"labels":["A","B"],"datasets":[{"label":"Sales","data":[10,20]}]}}
GET/chart?c={"type":"line","data":{"labels":["Jan","Feb"],"datasets":[{"label":"MRR","data":[1200,1800],"borderColor":"#4ade80"}]}}&w=1200&h=630
GET/chart?c={…}&api_key=ogsk_…
ParamDefaultDescription
c URL-encoded JSON chart config. The accepted shape mirrors Chart.js: type, data.labels, and data.datasets.
type inside c Supported values are bar and line. Other Chart.js chart types are rejected in v1.
w / width 600 Output width in pixels. Clamped to 1–4096. w wins when both aliases are present.
h / height 300 Output height in pixels. Clamped to 1–4096. h wins when both aliases are present.
dpr 2 Device pixel ratio — 1, 2, or 3. Higher values produce sharper images.
digest ISO week Cache-key suffix. Defaults to YYYY-Www (rolls weekly). Pass any string to pin a specific chart version.
force Set force=1 to bypass the KV cache and re-render the chart.
api_key API key for authenticated access. Also accepted as Authorization: Bearer <key>. See Authentication.
Static output: animations and responsive layout are disabled before rendering. Root-level Chart.js plugins are not supported; send plain JSON configs only.

GET /template/:name

Renders a saved Satori template to a PNG image. No browser needed — renders from a JSON tree in ~200 ms. Always returns image/png. Missing a required param returns 400 with usage instructions.

GET/template/hero?title=Hello+World
GET/template/hero?title=Hello&subtitle=World&eyebrow=My+Site
GET/template/card?title=Post+Title&api_key=ogsk_…
ParamDefaultDescription
w template default Output width in pixels. Clamped to 1–4096.
h template default Output height in pixels. Clamped to 1–4096.
dpr 2 Resvg zoom factor 1–3. Higher values produce sharper images.
digest week + updatedAt Cache-key suffix. Defaults to ISO week combined with the template's updatedAt timestamp — so edits in the admin UI automatically bust the cache.
force Set force=1 to bypass the KV cache.
api_key API key for authenticated access. Also accepted as Authorization: Bearer <key>.
…template params All other query params are passed as template variables (e.g. title, subtitle). Required params are declared per-template; missing ones return 400.
Shipped templates: hero — dark bg, title + optional eyebrow & subtitle (1200×630, requires title) · card — light bg, title + subtitle + byline (1200×630, requires title) · promo — dynamic colors, feature bullets, CTA (requires title). Manage all templates in the admin UI.

POST /render-inline

Renders a full template definition from the request body to a PNG image. No saved template needed — send the complete template JSON and params in one request. Always returns image/png. No authentication required.

POST/render-inline
Content-Type: application/json

{
  "template": { /* full TemplateDef object */ },
  "params":   { "title": "Hello World", "subtitle": "..." }
}
# Render an inline template and save the PNG to disk
curl -X POST https://open-graph.com/render-inline \
  -H "Content-Type: application/json" \
  -d '{
    "template": {
      "name": "agency",
      "defaultSize": { "w": 1200, "h": 630 },
      "required": ["title"],
      "defaults": {
        "eyebrow": "Amazon Optimization Agency",
        "site": "bigdetailpage.com"
      },
      "tree": {
        "type": "div",
        "tw": "flex h-full w-full flex-col justify-between",
        "style": {
          "backgroundColor": "#030712",
          "padding": "56px 64px",
          "backgroundImage": "radial-gradient(ellipse at 90% 10%, rgba(245,158,11,0.14) 0%, transparent 60%)"
        },
        "children": [
          {
            "type": "div",
            "tw": "flex flex-col",
            "style": { "flex": "1", "justifyContent": "center" },
            "children": [
              {
                "type": "div",
                "tw": "flex items-center",
                "style": { "marginBottom": "24px" },
                "children": [
                  { "type": "div", "style": { "width": "40px", "height": "4px", "backgroundColor": "#f59e0b", "marginRight": "16px" } },
                  { "type": "span", "style": { "color": "#f59e0b", "fontSize": "36px", "fontWeight": "700", "letterSpacing": "0.08em", "textTransform": "uppercase" }, "text": "{{eyebrow}}" }
                ]
              },
              { "type": "div", "style": { "color": "#ffffff", "fontSize": "92px", "fontWeight": "900", "lineHeight": "1.05", "letterSpacing": "-0.02em", "marginBottom": "28px" }, "text": "{{title}}" },
              { "type": "div", "style": { "color": "#9ca3af", "fontSize": "54px", "lineHeight": "1.35", "maxWidth": "1000px" }, "text": "{{subtitle}}", "when": "subtitle" }
            ]
          },
          {
            "type": "div",
            "tw": "flex items-center justify-between",
            "style": { "borderTopWidth": "1px", "borderTopStyle": "solid", "borderTopColor": "#1f2937", "paddingTop": "24px" },
            "children": [
              { "type": "div", "style": { "color": "#6b7280", "fontSize": "36px", "fontWeight": "600" }, "text": "{{site}}" },
              {
                "type": "div",
                "tw": "flex items-center",
                "children": [
                  { "type": "div", "style": { "width": "14px", "height": "14px", "borderRadius": "50%", "backgroundColor": "#f59e0b", "marginRight": "12px" } },
                  { "type": "span", "style": { "color": "#f59e0b", "fontSize": "34px", "fontWeight": "700" }, "text": "White-Glove Agency" }
                ]
              }
            ]
          }
        ]
      }
    },
    "params": {
      "title": "Your Listings. Expertly Managed.",
      "eyebrow": "Amazon Optimization Agency",
      "subtitle": "Powered by AI, run by humans who know Amazon.",
      "site": "bigdetailpage.com"
    }
  }' \
  --output og.png
Body fieldRequiredDescription
template yes A complete TemplateDef object — same structure as a saved template, including name, defaultSize, tree, required, etc.
params Template variable values as a flat string map (e.g. {"title":"Hello"}). Merged with template.defaults; required params must be present.
Query paramDefaultDescription
w template default Output width in pixels. Clamped to 1–4096.
h template default Output height in pixels. Clamped to 1–4096.
dpr 2 Resvg zoom factor 1–3. Higher values produce sharper images.
No caching. Responses are returned with Cache-Control: no-store. For cacheable production rendering, save the template via the admin UI and use GET /template/:name instead.

Template structure

Templates are JSON objects defining a Satori node tree. Edit them live in the admin UI — no redeploy needed.

{
  "name": "hero",
  "defaultSize": { "w": 1200, "h": 630 },
  "required": ["title"],
  "sampleParams": { "title": "Hello", "subtitle": "World" },
  "defaults": { "eyebrow": "My Site" },
  "tree": {
    "type": "div",
    "tw": "flex h-full w-full flex-col justify-center bg-slate-950 text-white px-20",
    "children": [
      { "type": "div", "tw": "text-2xl text-slate-400", "text": "{{eyebrow}}", "when": "eyebrow" },
      { "type": "div", "tw": "text-7xl font-bold", "text": "{{title}}" },
      { "type": "div", "tw": "text-4xl text-slate-300 mt-6", "text": "{{subtitle}}", "when": "subtitle" }
    ]
  }
}
typeElement tag: div, span, img, p, h1h6.
twTailwind classes (Satori subset). Containers with more than one child must include flex. Supports {{name}} interpolation.
textLeaf text content. {{name}} interpolates from query params.
srcImage source URL for img elements. Supports {{name}} interpolation.
whenConditional render — node is dropped when the named param is missing or empty string.
styleInline CSS object (fallback for things the Tailwind subset doesn't cover). String values support {{name}} interpolation — useful for dynamic colors.
childrenNested node array.
defaultsPer-param fallback values applied when the request omits them or passes an empty string.

API keys

API keys control access to the image endpoints. Keys are ogsk_-prefixed 64-character hex strings managed in the admin UI.

# query param — works in <img src> tags
/render?url=https://example.com&api_key=ogsk_abc123…

# Authorization header
curl -H "Authorization: Bearer ogsk_abc123…" /chart?c='{"type":"bar","data":{"labels":["A"],"datasets":[{"data":[1]}]}}'
Enforcement is in transition. Requests without a valid key currently receive a placeholder SVG image rather than an HTTP error. Hard enforcement (401/403) will be enabled once all callers have migrated.

Cache behaviour

Cached image endpoints share the same KV-backed cache with a 30-day TTL.

ScenarioResult
Default request (no digest) Cache key includes the ISO week (YYYY-Www). Images roll over weekly automatically.
Pinned digest=v2 Cache key is locked to that string. The same image is served until you change the digest.
Template edited in admin UI The default digest for /template/:name includes updatedAt, so saved edits automatically bust cached renders.
force=1 Skips the cache read. A new image is rendered and written back to KV.
HTTP responses carry Cache-Control: public, max-age=31536000, immutable. Serve them through a CDN or directly — the KV cache is the source of truth.