API documentation

Base URL: https://api.devshot.dev · Machine-readable spec: /openapi.json

Authentication

Two kinds of keys:

KeyUseHow
sk_live_…server-side, POST requestsAuthorization: Bearer sk_live_…
pk_live_…public, GET requests (meta tags)?key=pk_live_…

Never put sk_ keys in client-side code or meta tags — that's what pk_ is for.

POST /v1/code — code screenshot

curl -X POST https://api.devshot.dev/v1/code \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "code": "const answer = 42;",
    "language": "typescript",
    "theme": "vitesse-dark",
    "background": "gradient-purple",
    "window": "mac",
    "lineNumbers": false,
    "highlightLines": [2, 5],
    "scale": 2,
    "format": "png"
  }' > snippet.png

Returns the binary image. Headers: X-Cache: HIT|MISS, X-Render-Time-Ms. Themes and backgrounds: GET /v1/themes. Omit language (or pass "auto") and we'll detect it from the code.

Pro: pass "font" to change the code typeface — jetbrains (default), fira-code, ibm-plex-mono, or source-code-pro. Non-default fonts require the Pro plan (otherwise 403).

Node.js

const res = await fetch('https://api.devshot.dev/v1/code', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.DEVSHOT_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ code, language: 'typescript', theme: 'nord' }),
});
await fs.promises.writeFile('snippet.png', Buffer.from(await res.arrayBuffer()));

POST /v1/og — Open Graph image

curl -X POST https://api.devshot.dev/v1/og \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template": "article",
    "title": "How I built an API in 4 weekends",
    "subtitle": "and what came out of it",
    "author": "John Doe",
    "avatar": "https://example.com/me.png",
    "accent": "#3b82f6"
  }' > og.png

Always 1200×630. Templates: article, minimal, code, stats.

template: "code" takes code (+ optional language) and renders a highlighted code window under the title — great for tutorial posts. template: "stats" takes stats: "Stars:12.4k,Downloads:1M" (up to 4 pairs) for release/changelog cards.

GET mode — og:image straight in your meta tags

No code at all: use the public key and query params. First request renders, every next one is a cache hit.

<meta property="og:image"
  content="https://api.devshot.dev/v1/og?title=My+Post&theme=dark&key=pk_live_..." />

Next.js (app router)

export const metadata = {
  openGraph: {
    images: [`https://api.devshot.dev/v1/og?${new URLSearchParams({
      title: post.title, author: post.author, key: process.env.NEXT_PUBLIC_DEVSHOT_PK,
    })}`],
  },
};

Astro

---
const og = `https://api.devshot.dev/v1/og?title=${encodeURIComponent(title)}&key=${import.meta.env.PUBLIC_DEVSHOT_PK}`;
---
<meta property="og:image" content={og} />

GET /v1/code

Same params as POST, but code is base64-encoded and highlightLines is comma-separated: ?code=Y29uc3Q...&highlightLines=2,5&key=pk_live_...

Signed URLs (recommended for public keys)

Your pk_ key is public by design — anyone who sees your meta tag can read it. Two protections:

1. Quota counts renders, not requests. Repeats of the same URL are cache hits and cost you nothing — replaying your URLs is pointless.

2. URL signing stops strangers from generating new images on your quota. Enable «Require signature» in the dashboard, then sign each URL with your signing secret — HMAC-SHA256 over the path+query, appended as the last parameter:

import crypto from 'node:crypto';

const url = `/v1/og?${new URLSearchParams({ title, key: process.env.DEVSHOT_PK })}`;
const sig = crypto.createHmac('sha256', process.env.DEVSHOT_SIGNING_SECRET)
  .update(url).digest('hex');

const ogImage = `https://api.devshot.dev${url}&sig=${sig}`;

Unsigned mode stays available (default) — fine while your traffic is small; flip the toggle when it matters.

GET /v1/me — account status

curl "https://api.devshot.dev/v1/me?key=pk_live_..." # or Authorization: Bearer sk_live_...
{ "email": "you@dev.io", "plan": "hobby", "month": "2026-07",
  "used": 123, "limit": 5000, "remaining": 4877, "watermark": false }

Human version: devshot.dev/account. Every image response also carries an X-Usage: used/limit header, so your client always knows the remaining quota.

Limits & errors

StatusMeaning
401Missing/invalid key, or pk_ used for POST
429Monthly plan limit reached (body contains an upgrade link) or rate limit (10 req/s per key). Only rendered images count — cache hits are free
400Validation error — the body says which field