Pikzor Documentation

Generate branded OG images for every page on your site — no design tools, no image hosting, no per-post manual work.

Quick Start

You can be up and running in under 5 minutes:

  1. Sign up at pikzor.com/dashboard
  2. Choose a template and enter your brand color
  3. Copy your token URL — it looks like /og/t_xK9mP2
  4. Add it to your site as the og:image meta tag

Your OG image URL looks like this:

https://pikzor.com/og/t_xK9mP2?title=My+Post+Title&author=Jane

That's it. Pikzor generates the image on first request and caches it for 30 days.

No token yet? Use /og/test?template=og-gradient&title=Hello to try the renderer without signing up.

URL-Based Usage

The simplest way to use Pikzor is by constructing a URL with query parameters. No API key needed for your own token.

GET /og/:token?title=...&author=...&date=...

Example

GET /og/t_xK9mP2?title=How+I+Deployed+in+5+Minutes&author=Jane+Doe&date=April+5%2C+2026

Returns a 1200×630 PNG image. The response includes:

HTML / Static Sites

Add these tags inside your <head>. Works with any static site generator (Astro, Hugo, Eleventy, plain HTML):

<meta property="og:title" content="My Post Title" />
<meta property="og:description" content="A short description." />
<meta property="og:image"
  content="https://pikzor.com/og/t_xK9mP2?title=My+Post+Title&author=Jane" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image"
  content="https://pikzor.com/og/t_xK9mP2?title=My+Post+Title&author=Jane" />

Template variable syntax

In Liquid (Jekyll/Eleventy):

<meta property="og:image"
  content="https://pikzor.com/og/t_xK9mP2?title={{ page.title | url_encode }}&author={{ page.author | url_encode }}" />

In Go templates (Hugo):

<meta property="og:image"
  content="https://pikzor.com/og/t_xK9mP2?title={{ .Title | urlize }}&author={{ .Params.author | urlize }}" />

Next.js

In app/blog/[slug]/page.tsx:

import type { Metadata } from 'next'

interface Props {
  params: { slug: string }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const post = await getPost(params.slug)

  const ogImageUrl = new URL('https://pikzor.com/og/t_xK9mP2')
  ogImageUrl.searchParams.set('title', post.title)
  ogImageUrl.searchParams.set('author', post.author)

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: ogImageUrl.toString(), width: 1200, height: 630 }],
    },
    twitter: {
      card: 'summary_large_image',
      images: [ogImageUrl.toString()],
    },
  }
}

In your blog post component:

import Head from 'next/head'

export default function BlogPost({ post }) {
  const ogImageUrl =
    `https://pikzor.com/og/t_xK9mP2` +
    `?title=${encodeURIComponent(post.title)}` +
    `&author=${encodeURIComponent(post.author)}`

  return (
    <>
      <Head>
        <title>{post.title}</title>
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:image" content={ogImageUrl} />
        <meta property="og:image:width" content="1200" />
        <meta property="og:image:height" content="630" />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:image" content={ogImageUrl} />
      </Head>
      {/* post content */}
    </>
  )
}

JavaScript

Build the URL in any JavaScript environment:

function pikzorUrl(token, params) {
  const url = new URL(`https://pikzor.com/og/${token}`)
  for (const [key, value] of Object.entries(params)) {
    if (value) url.searchParams.set(key, value)
  }
  return url.toString()
}

// Usage
const imageUrl = pikzorUrl('t_xK9mP2', {
  title: post.title,
  author: post.author,
  date: post.date,
})

POST /api/v1/render

Programmatic rendering via API key. Returns JSON with a hosted image URL instead of the binary PNG.

POST /api/v1/render

Request headers

Authorization: Bearer sk_live_<your-api-key>
Content-Type: application/json

Request body

{
  "templateId": "og-gradient",
  "brandColor": "#3B82F6",
  "title": "My Post Title",
  "author": "Jane Doe",
  "date": "April 5, 2026"
}
FieldTypeRequiredDescription
templateIdstringyesOne of the 5 template IDs
brandColorstringno6-digit hex, e.g. #3B82F6
titlestringnoMain heading text
authorstringnoShown in card footer
datestringnoShown in card footer
descriptionstringnoSubtitle (og-light template only)

Response

{
  "url": "https://pikzor.com/images/api/a1b2c3d4e5f6.png",
  "cached": false,
  "width": 1200,
  "height": 630
}

curl example

curl -X POST https://pikzor.com/api/v1/render \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "og-gradient",
    "brandColor": "#6366f1",
    "title": "Hello from curl",
    "author": "Jane Doe"
  }'

Authentication

Signup

POST/auth/signup
{ "email": "you@example.com", "password": "mypassword" }
// → { "token": "eyJ...", "user": { "id": "...", "email": "...", "plan": "free" } }

Login

POST/auth/login
{ "email": "you@example.com", "password": "mypassword" }
// → { "token": "eyJ...", "user": { ... } }

Get current user

GET/auth/me
// Header: Authorization: Bearer <jwt>
// → { "id": "...", "email": "...", "plan": "free", "brand_color": "#3B82F6" }

Generate API key

POST/auth/api-key
{ "name": "My App" }
// → { "key": "sk_live_...", "prefix": "sk_live_12345", "warning": "Save this key..." }
Important: API keys are shown exactly once. Store them securely — you cannot retrieve them later.

List API keys

GET/auth/api-keys

Delete API key

DELETE/auth/api-key/:id

Update brand settings

PATCH/auth/brand
{ "brandColor": "#6366f1", "logoUrl": "https://example.com/logo.png" }

Setup Endpoints

Setup endpoints create the short token URLs (/og/t_xK9mP2) that power your OG images.

Create a token

POST/auth/setup
{
  "templateId": "og-gradient",
  "brandColor": "#3B82F6",
  "logoUrl": "https://example.com/logo.png",
  "domain": "myblog.com",
  "label": "Blog posts"
}
// → { "token": "t_xK9mP2", "url": "https://pikzor.com/og/t_xK9mP2?title=Your+Title", "id": "..." }

List your tokens

GET/auth/setup

Delete a token

DELETE/auth/setup/:id

Usage Endpoint

GET/auth/usage
// → {
//   "used": 12,
//   "cached": 88,
//   "limit": 50,
//   "plan": "free",
//   "month": "2026-04"
// }

limit is null for unlimited plans. used counts only fresh renders (cache hits are free and don't count toward the limit).

Templates

IDNameBest for
og-blog-minimalMinimalClean, text-heavy blogs
og-gradientGradientVisual, modern tech blogs
og-splitSplitCorporate or business blogs
og-boldBoldHigh-impact announcements
og-lightLightLightweight, readable design

Preview any template without a token:

GET /og/test?template=og-gradient&title=Preview+Title&author=Jane&brandColor=%236366f1

URL Parameters

ParamDescriptionExample
titleMain headingMy+Blog+Post
authorAuthor name in footerJane+Doe
dateDate in footerApril+5%2C+2026
descriptionSubtitle (og-light only)A+short+desc
brandColorOverride brand color (test endpoint only)%236366f1
Always use encodeURIComponent() in JavaScript or url_encode in template engines to avoid broken URLs for titles with special characters.

Plans & Limits

PlanFresh renders/monthRate limitWatermark
Free5010 req/minYes
Starter1,00030 req/minNo
Pro5,00060 req/minNo
BusinessUnlimited60 req/minNo

Cache hits are always free and don't count toward your monthly limit. The first render generates the image; all subsequent requests return the cached version instantly.

When you exceed your monthly limit, the API returns 402 Payment Required:

{
  "error": "Monthly render limit reached",
  "limit": 50,
  "used": 50,
  "upgrade_url": "/pricing"
}

Caching

Pikzor uses a two-layer cache:

  1. Redis — hot cache for frequently requested images
  2. Filesystem — persistent storage at /images/<namespace>/<hash>.png

Cache keys are derived from a SHA-256 hash of the template token + sorted parameters. This means:

The X-Cache response header tells you whether the image was served from cache (HIT) or freshly rendered (MISS).