Everything you need to integrate SnapForge into your application.
Authenticate by passing your API key as a Bearer token in the Authorization header, or as a key query parameter.
# Via header (recommended)
curl https://api.snapforgehq.com/v1/og?template=blog-post&title=Hello \
-H "Authorization: Bearer sf_live_your_key_here"
# Via query parameter (useful for meta tags)
<meta property="og:image"
content="https://api.snapforgehq.com/v1/og?template=blog-post&title=Hello&key=sf_live_your_key_here" />Base URL: https://api.snapforgehq.com
/v1/ogGenerate an OG image via query parameters. Ideal for use directly in HTML meta tags.
| Name | Type | Required | Description |
|---|---|---|---|
template | string | Yes | Template ID: blog-post, social, minimal, product-hunt, or github-card |
title | string | Yes | Main title text |
subtitle | string | No | Subtitle text (blog-post template) |
author | string | No | Author name (blog-post template) |
description | string | No | Description text (social template) |
brand | string | No | Brand name (social template) |
color | string | No | Background color hex (minimal template) |
text_color | string | No | Text color hex (minimal template) |
tagline | string | No | Product tagline (product-hunt template) |
category | string | No | Category label (product-hunt template) |
upvotes | string | No | Upvote count display (product-hunt template) |
name | string | No | Repo name (github-card template) |
stars | string | No | Star count (github-card template) |
language | string | No | Programming language (github-card template) |
language_color | string | No | Language dot color hex (github-card template) |
width | number | No | Image width in pixels (default: 1200) |
height | number | No | Image height in pixels (default: 630) |
format | string | No | Output format: png (default), jpeg, webp |
json | boolean | No | Set to true to get JSON with a link to the image instead of the image itself |
key | string | Yes | Your API key (alternative to Authorization header) |
/v1/ogGenerate an OG image via JSON body.
{
"template": "blog-post",
"params": {
"title": "My Blog Post",
"subtitle": "A deep dive into...",
"author": "Jane Doe"
},
"width": 1200,
"height": 630,
"format": "png"
}/v1/screenshotCapture a screenshot of a public URL or raw HTML as PNG, JPEG or WebP. Requires Starter plan or higher. Each screenshot counts as one generation.
{
"url": "https://example.com",
"width": 1280,
"height": 720,
"full_page": false,
"selector": "#main",
"format": "png",
"delay": 0
}/v1/usageGet your current usage stats for the billing period.
{
"used": 450,
"limit": 5000,
"month": "2026-03",
"planId": "starter"
}/v1/templatesList the built-in templates and your custom templates with their parameters. Custom templates come 100 at a time: pass ?offset= with the returned next_offset to get the next page (null on the last page).
const response = await fetch(
"https://api.snapforgehq.com/v1/og",
{
method: "POST",
headers: {
"Authorization": "Bearer sf_live_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
template: "blog-post",
params: {
title: "My Blog Post",
subtitle: "Written with love",
author: "Jane Doe",
},
}),
}
);
const imageBlob = await response.blob();
// Use the blob as needed (save, display, upload, etc.)import requests
response = requests.post(
"https://api.snapforgehq.com/v1/og",
headers={"Authorization": "Bearer sf_live_your_key_here"},
json={
"template": "blog-post",
"params": {
"title": "My Blog Post",
"subtitle": "Written with love",
"author": "Jane Doe",
},
},
)
with open("og-image.png", "wb") as f:
f.write(response.content)curl -X POST "https://api.snapforgehq.com/v1/og" \
-H "Authorization: Bearer sf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"template":"blog-post","params":{"title":"Hello World"}}' \
-o og-image.pngPro and Business plans can author their own OG templates from the dashboard. A saved template gets an id of the form tpl_<id>, which is passed as the template parameter to /v1/og exactly like a built-in template slug.
Write placeholders as {{name}} anywhere in your template HTML. A valid placeholder name matches ^[a-z][a-z0-9_]{0,49}$: lowercase letters, digits and underscores, starting with a letter, 1-50 characters. Anything else (uppercase, hyphens, leading digits, or internal whitespace like {{ title }}) is left as literal text instead of being treated as a placeholder.
At render time each placeholder is replaced in this order: the request's params, then the template's saved default_params, then an empty string if neither supplies a value. Substituted values are HTML-escaped, so a value can never inject markup or break out of an attribute.
Every image source — an <img src> or an inline-style url(...) — must be a data: URI or a public https:// URL. Anything else is rejected at save time and again at render time.
| Field | Limit |
|---|---|
| name | 1-60 characters |
| html | 20,000 bytes or less (UTF-8) |
| default params | up to 20, keys up to 50 characters, values up to 500 characters |
| templates per account | Pro: 10 · Business: unlimited |
curl "https://api.snapforgehq.com/v1/og?template=tpl_3f9a1c7e5b2d4a8f9e0c1b2a3d4e5f60&product=Acme&title=New+release&version=v2.4.0&date=Sep+27%2C+2026" \
-H "Authorization: Bearer sf_live_your_key_here" \
-o og-image.png| Status | Error | Description |
|---|---|---|
| 400 | bad_request | Invalid parameters. Check the error message for details. |
| 401 | unauthorized | Missing or invalid API key. |
| 400 | blocked_url | Screenshots of private or internal addresses are not allowed. |
| 403 | plan_required | The Screenshot API needs the Starter plan or higher. |
| 422 | capture_failed | The page could not be captured: it did not load, or the selector was not found. |
| 429 | too_many_requests | More than 10 screenshots in a minute. Retry after the Retry-After header. |
| 429 | rate_limit_exceeded | Monthly generation limit reached. Upgrade your plan. |
| 500 | generation_failed | Image generation failed. Contact support if this persists. |
| 503 | busy | Screenshot capacity is in use. Retry after the Retry-After header. |
| 404 | not_found | The custom template does not exist, isn't yours, or the id is malformed. |
| 403 | template_limit_reached | Your plan's custom template limit is reached. Delete one or upgrade to Business. |
| 400 | invalid_template | The template HTML could not be parsed or rendered. |
| 400 | blocked_image_source | An image source is not a data: URI or a public https:// URL. |
Rate limits are based on your plan's monthly generation quota. Cache hits do not count against your quota. Check the X-RateLimit-Remaining and X-RateLimit-Limit response headers.
| Plan | Monthly Limit | At the limit |
|---|---|---|
| Free | 50 | Stops (429) |
| Starter | 500 | Stops until reset (429), no overage charges |
| Pro | 5,000 | Stops until reset (429), no overage charges |
| Business | 25,000 | Stops until reset (429), no overage charges |
Limits reset on the first of each month (UTC). Cached images never count. If a payment fails, your paid limits stay in place for 7 days while the charge is retried. After that the account uses Free limits until the payment goes through.