REST API for AI OG image generation
Generate Open Graph images from your own backend, CI pipeline, or serverless function. Two endpoints: one to kick off an AI generation, one to poll it. Prefer to work inside your editor? The MCP integration uses the same key.
Building against this with an agent or a code generator? The full contract is published as OpenAPI 3.1 at /openapi.json (YAML), the MCP server describes itself at /.well-known/mcp.json, and /llms.txt indexes everything on the site.
1. Get your API key
You need any active paid plan (Starter, Growth, or Pro). Your key appears at /dashboard/mcp and /dashboard/billing. Keys start with ogim_. Treat them like passwords, they can spend your credits.
2. Authentication
Every request sends the key as a bearer token in the Authorization header:
Authorization: Bearer ogim_your_keyRequests without a valid key return 401. Keys on the free plan (or after a downgrade) return 403.
What a credential can do
There are two ways to authenticate, and they grant different levels of access. Pick the narrower one that still does the job.
| Credential | Grants | Does not grant |
|---|---|---|
ogim_ API key | Create and read generations, create, list and delete hosted template images, read brand kits, spend credits. | Billing changes, plan changes, account settings, deleting the account, reading or rotating the key itself. |
| MCP OAuth token | The same product surface as an API key, limited to the scopes the user approved in the consent screen: openid, profile, email, offline_access. | Everything an API key cannot do, plus anything outside the approved scopes. The user can revoke the connection at any time from /dashboard/mcp. |
Both credentials are scoped to a single account and can only see resources that account owns. There is no organization-wide or cross-account credential. A key is not required to render templates through GET /api/template/preview, which is public and rate limited by IP.
Revocation. Rotating your key at /dashboard/mcp invalidates the previous one immediately. Revoking an MCP connection invalidates its access and refresh tokens on the next request.
3. Create a generation
POST /api/v1/generations, enqueues an AI generation and returns immediately with 202. It does not wait for the image to render.
Request body
| Field | Type | Notes |
|---|---|---|
title | string | Required. 1–1000 chars. The creative brief. |
description | string | Optional. Up to 2000 chars of extra direction. |
brandColor | string | Optional. Hex color, e.g. #6366f1. |
style | enum | Optional. One of minimal, editorial, bold, retro, photographic, illustrated, gradient, dark. |
displayText | string | Optional headline overlaid on the card (≤120 chars). Omit for a textless image. |
displaySubtitle | string | Optional subtitle under the headline (≤180 chars). |
quality | enum | flash (default) or max. |
variants | integer | 1 (default), 2, 3, or 4. |
brandKitId | uuid | Optional. Apply a specific saved brand kit. When omitted, your default kit auto-fills any of brandColor, style, displayText/displaySubtitle and the reference image (kit logo) that you did not set explicitly. |
referenceImageUrl | string | Optional. CDN URL of a logo or product shot to seed the image. Must be on the OGImagen CDN (upload via dashboard or brand kit first). |
idempotencyKey | string | Optional, ≤200 chars. Dedupes retries, see Idempotency. The standard Idempotency-Key header works too. |
webhookUrl | string | Optional. HTTPS endpoint that receives a generation.completed event when the job finishes, see Webhooks. |
webhookSecret | string | Optional, 8–200 chars. Your HMAC secret for signing the webhook delivery. |
Example
curl -X POST https://ogimagen.com/api/v1/generations \
-H "Authorization: Bearer ogim_your_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Shipping fast with feature flags",
"description": "Blog post hero, developer audience",
"brandColor": "#6366f1",
"style": "bold",
"displayText": "Ship faster",
"quality": "flash",
"variants": 2
}'Response, 202 Accepted
{
"id": "b1f0c8e2-...-...",
"status": "pending",
"creditsCharged": 2,
"creditsRemaining": 178
}4. Poll for the result
GET /api/v1/generations/:id, returns the current status and, once done, the image URLs. Poll every ~2 seconds until status is done or failed.
curl https://ogimagen.com/api/v1/generations/b1f0c8e2-...-... \
-H "Authorization: Bearer ogim_your_key"Response, 200 OK
{
"id": "b1f0c8e2-...-...",
"status": "done",
"urls": {
"og": "https://cdn.ogimagen.com/usr_.../og.jpg",
"variantB": "https://cdn.ogimagen.com/usr_.../variant_b.jpg",
"extras": []
},
"error": null
}While rendering, status is pending and the URLs are null. On failure, status is failed and error carries the reason.
5. Webhooks (skip the polling)
Pass webhookUrl on create and OGImagen POSTs a generation.completed event to that endpoint when the job finishes, on success and on failure, so your pipeline never hangs. The URL must be HTTPS and publicly reachable.
POST <your webhookUrl>
Content-Type: application/json
X-OGImagen-Event: generation.completed
X-OGImagen-Timestamp: 1786900000
X-OGImagen-Signature: sha256=3f1d... (only when webhookSecret was set)
{
"event": "generation.completed",
"generation": {
"id": "b1f0c8e2-...",
"status": "done",
"urls": {
"og": "https://cdn.ogimagen.com/usr_.../og.jpg",
"variantB": "https://cdn.ogimagen.com/usr_.../variant_b.jpg",
"extras": []
},
"error": null
}
}With a webhookSecret, the signature is HMAC-SHA256 over `${timestamp}.${rawBody}`. Verify it before trusting the payload:
import { createHmac, timingSafeEqual } from 'crypto';
function verify(req: { headers: Record<string, string>; rawBody: string }, secret: string) {
const ts = req.headers['x-ogimagen-timestamp'];
const sig = req.headers['x-ogimagen-signature'] ?? '';
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 min replay window
const expected = 'sha256=' + createHmac('sha256', secret)
.update(`${ts}.${req.rawBody}`)
.digest('hex');
return sig.length === expected.length &&
timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}Delivery is attempted twice (10s timeout each, one retry after 5s). Respond with a 2xx quickly and do heavy work asynchronously. If both attempts fail, the generation itself is unaffected, you can always fall back to polling.
6. Idempotency
CMS publish hooks fire more than once. Send an idempotencyKey (body field, or the standard Idempotency-Key header) and a retry with the same key returns the original generation, 200 with deduplicated: true, current status and URLs, instead of charging you again. Keys are scoped to your account.
# a good key: hash of what the image depends on
idempotencyKey = sha256(post.slug + post.title + brandKitId)That way an unchanged republish is free, and an edited title naturally produces a new key and a fresh image.
7. Credit costs
Credits = per-variant cost × number of variants.
| Quality | Per variant | Examples |
|---|---|---|
flash | 1 credit | flash×1 = 1, flash×2 = 2, flash×4 = 4 |
max | 3 credits | max×1 = 3, max×2 = 6, max×4 = 12 |
Credits are deducted atomically when the job is accepted. Moderation rejections and insufficient-credit errors never charge you.
8. Errors
Every non-2xx response is JSON with the same shape. Branch on code, which is stable, never on message, which is written for humans and can change.
{
"error": "Not enough credits",
"code": "insufficient_credits",
"message": "Not enough credits",
"resolution": "Buy more credits, or lower cost by using quality \"flash\" and fewer variants. Template renders never consume credits.",
"docs": "https://ogimagen.com/#pricing"
}error repeats message and exists only so integrations written before this shape keep working. resolution tells you what to do next, and is written so an agent can act on it without a human. invalid_parameters also carries an issues object keyed by field name.
| Status | code | Meaning |
|---|---|---|
400 | invalid_json | The body was not valid JSON. |
400 | invalid_parameters | A field failed validation. Read issues, fix the named fields, do not retry unchanged. |
400 | moderation_blocked | The prompt failed content moderation. Rewrite it, do not retry the same text. |
401 | unauthorized | Missing or unknown API key. |
402 | insufficient_credits | Not enough credits. Body includes creditsRemaining. |
403 | plan_required | Valid key, but the plan is free, a paid plan is required. |
404 | not_found | The id does not exist, or belongs to another account. |
413 | payload_too_large | The body exceeded the size limit. Send a URL, not base64. |
429 | rate_limited | Rate limit exceeded (60 req/min per key). Wait for Retry-After, then retry. |
500 | generation_failed | The image job failed. Credits are refunded automatically. |
500 | internal_error | Unexpected server error. Retry with backoff. |
Rate limits
60 requests per minute per account, counted across every /api/v1 endpoint. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 adds Retry-After in seconds. Generation throughput is governed by credits, not by this limit.
Moderation
Prompts are checked before a credit is spent. OGImagen makes link preview cards, so briefs describing something else, or asking for real people, explicit content, or another brand's marks, come back as moderation_blocked with no charge. Rewriting the brief to describe the page the card is for is the fix.
9. Free template endpoint (no key)
Need a lightweight, non-AI card with zero setup? GET /api/template/preview is public, no key, no credits. It renders a template server-side and returns a PNG directly, so you can point an <meta> tag straight at it.
https://ogimagen.com/api/template/preview?template=gradient&title=Hello&subtitle=World&brandColor=%236366f1&theme=dark&siteName=ogimagen.com| Query param | Notes |
|---|---|
template | One of minimal, gradient, split, card, dots, quote, banner, terminal, blog, changelog, stat, profile, event, photo, product, showcase. |
title | Main headline. |
subtitle | Secondary line. |
brandColor | Hex color (URL-encode the # as %23). |
theme | light or dark. |
siteName | Small footer label, e.g. your domain. |
| Optional (per template) | Each template consumes a subset of font (inter/serif/mono), label, pattern (none/dots/grid/lines), bgFrom, bgTo, authorName, authorHandle, avatarUrl, imageUrl, price, version, date, location, statValue, statTrend. avatarUrl/imageUrl/logoUrl must be OGImagen CDN URLs. |
10. Hosted templates (paid key)
The preview endpoint renders on demand; if you want a permanent CDN URL instead, host the render in one call. Same Bearer auth as generations, paid plans only, unlimited hosting.
curl -X POST https://ogimagen.com/api/v1/templates \
-H "Authorization: Bearer $OGIMAGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"stat","title":"Signups are up","label":"Growth","statValue":"+38%","statTrend":"+12.4%","theme":"dark"}'
# -> 201 { "id": "…", "url": "https://cdn.ogimagen.com/templates/…png", "template": "stat" }| Endpoint | Description |
|---|---|
POST /api/v1/templates | Render + host. Body: the same JSON fields as the preview query params, plus optional brandKitId (fills brandColor and logoUrl from the kit). Returns 201 with the permanent URL. |
GET /api/v1/templates | List your hosted template images. |
DELETE /api/v1/templates/:id | Delete a hosted image (frees the R2 object). |
FAQ
Which plan includes the REST API?
Every paid plan, Starter ($5), Growth ($15), and Pro ($29/month). The same ogim_ key works for both the REST API and MCP. The free plan has no key.
How does authentication work?
Send your API key as a bearer token: Authorization: Bearer ogim_your_key. Get the key from /dashboard/mcp or /dashboard/billing. Keys start with ogim_ and should be treated like passwords.
How much does a generation cost?
Credits = per-variant cost × number of variants. flash is 1 credit per variant, max is 3 credits per variant. So flash×1 = 1, flash×2 = 2, max×2 = 6, max×4 = 12.
Is generation synchronous?
No. POST /api/v1/generations returns 202 immediately with a generation id and status "pending". Poll GET /api/v1/generations/:id until status is "done" (or "failed"), then read the image URLs.
Is there a rate limit?
Yes, 60 requests per minute per key. Exceeding it returns 429 with a Retry-After header.
Is there a free endpoint?
Yes. GET /api/template/preview renders a template-based OG image with no key and no credits. It returns a PNG directly and is ideal for lightweight, non-AI cards.
Can I get a webhook instead of polling?
Yes. Pass webhookUrl (and optionally webhookSecret) on create and OGImagen POSTs a generation.completed event to your endpoint when the job finishes, done or failed. With a secret, the request is signed with HMAC-SHA256 so you can verify it came from us.
What happens if my CMS fires the same publish hook twice?
Send an idempotencyKey (body field or Idempotency-Key header). A retry with the same key returns the original generation with a 200 and deduplicated: true, no second charge, no second image.
Can the API use my brand kit?
Yes. Pass brandKitId to apply a specific saved kit, or nothing, your default kit auto-fills brandColor, style, headline defaults, and uses your logo as the reference image for any field you did not set explicitly.
Ready to start? Pick a plan, grab your key, read the MCP docs, or plug the API into your CMS with the integration recipes (Sanity, Strapi, headless WordPress, n8n/Make).