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_key

Requests 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.

CredentialGrantsDoes not grant
ogim_ API keyCreate 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 tokenThe 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

FieldTypeNotes
titlestringRequired. 1–1000 chars. The creative brief.
descriptionstringOptional. Up to 2000 chars of extra direction.
brandColorstringOptional. Hex color, e.g. #6366f1.
styleenumOptional. One of minimal, editorial, bold, retro, photographic, illustrated, gradient, dark.
displayTextstringOptional headline overlaid on the card (≤120 chars). Omit for a textless image.
displaySubtitlestringOptional subtitle under the headline (≤180 chars).
qualityenumflash (default) or max.
variantsinteger1 (default), 2, 3, or 4.
brandKitIduuidOptional. 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.
referenceImageUrlstringOptional. 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).
idempotencyKeystringOptional, ≤200 chars. Dedupes retries, see Idempotency. The standard Idempotency-Key header works too.
webhookUrlstringOptional. HTTPS endpoint that receives a generation.completed event when the job finishes, see Webhooks.
webhookSecretstringOptional, 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.

QualityPer variantExamples
flash1 creditflash×1 = 1, flash×2 = 2, flash×4 = 4
max3 creditsmax×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.

StatuscodeMeaning
400invalid_jsonThe body was not valid JSON.
400invalid_parametersA field failed validation. Read issues, fix the named fields, do not retry unchanged.
400moderation_blockedThe prompt failed content moderation. Rewrite it, do not retry the same text.
401unauthorizedMissing or unknown API key.
402insufficient_creditsNot enough credits. Body includes creditsRemaining.
403plan_requiredValid key, but the plan is free, a paid plan is required.
404not_foundThe id does not exist, or belongs to another account.
413payload_too_largeThe body exceeded the size limit. Send a URL, not base64.
429rate_limitedRate limit exceeded (60 req/min per key). Wait for Retry-After, then retry.
500generation_failedThe image job failed. Credits are refunded automatically.
500internal_errorUnexpected 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 paramNotes
templateOne of minimal, gradient, split, card, dots, quote, banner, terminal, blog, changelog, stat, profile, event, photo, product, showcase.
titleMain headline.
subtitleSecondary line.
brandColorHex color (URL-encode the # as %23).
themelight or dark.
siteNameSmall 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" }
EndpointDescription
POST /api/v1/templatesRender + 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/templatesList your hosted template images.
DELETE /api/v1/templates/:idDelete 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).