URL rendering and OG images
Signed image URLs for CMS embeds and Open Graph tags, without sending an API key on fetch.
<img> and og:image cannot send Authorization. Mint a signed URL with your API key, then let crawlers fetch it anonymously.
Mint
POST /api/v1/img/sign
Authorization: Bearer sk_...
Content-Type: application/json
{
"template": "<design-id>",
"format": "png",
"layers": { "headline": { "text": "Hello" } }
}{
"url": "https://app.jsontoimg.com/api/v1/img/<design-id>?headline=Hello&version=1&sig=…",
"expiresAt": null
}Put url in <img src> or <meta property="og:image">. Optional expiresIn (seconds) sets exp on the URL.
TypeScript:
const { url } = await client.signImageUrl({
template: "<design-id>",
format: "png",
layers: { headline: { text: post.title } },
});Minting does not consume a credit. The first unique GET of each URL does.
Batch mint
Pass jobs (1–50) on the same endpoint. This mints CMS / OG links; it does not queue renders (POST /api/v1/renders/batch still does that).
POST /api/v1/img/sign
Authorization: Bearer sk_...
Content-Type: application/json
{
"jobs": [
{ "template": "<design-id>", "layers": { "headline": { "text": "Hello" } } },
{ "template": "<design-id>", "layers": { "headline": { "text": "World" } } }
]
}{
"urls": [
{ "url": "https://app.jsontoimg.com/api/v1/img/<design-id>?headline=Hello&version=1&sig=…", "expiresAt": null },
{ "url": "https://app.jsontoimg.com/api/v1/img/<design-id>?headline=World&version=1&sig=…", "expiresAt": null }
]
}const { urls } = await client.signImageUrls({
jobs: [
{ template: "<design-id>", layers: { headline: { text: "Hello" } } },
{ template: "<design-id>", layers: { headline: { text: "World" } } },
],
});Fetch
GET that URL with no API key. The first unique payload renders (one credit, billed to the template owner). The same title again streams the stored file.
Tampering with query params (for example changing the headline) invalidates sig → 401, no render. Duplicate query keys (for example headline=Hello&…&headline=Injected) are rejected with 400 before verification.
If the first render is still running, the response is 503 with Retry-After (not JSON). Crawlers retry.
Completed jobs
GET /api/v1/renders/{id} (and wait / webhooks / batch) returns assetUrl with ?sig=. That URL also works in <img> until the file is archived (410).
Authenticated GET /asset without sig still streams bytes for API clients.
Dry-run
POST /api/v1/render with "dryRun": true (or ?dryRun=1) validates layers and variables and returns { valid: true, … } with no job and no credit.
JPEG
format: "jpeg" is accepted and stored as jpg.