---
title: Quickstart
description: Get an API key and render your first OG image in 5 minutes. Includes signing algorithm, curl, fetch, and Next.js examples.
slug: quickstart
---

# Quickstart

In this guide, you'll sign your first OG image URL and render it. Everything is free under 500 renders/month.

## Step 1: Get an API Key

Visit your [RenderOG dashboard](https://renderog.app/dashboard) and click **"Create API Key"**. A key like `sk_live_a1b2c3d4e5f6...` (40 hex chars) will appear exactly once.

Store it safely — you'll need it to view usage and rotate keys. Local dev? Create a `.env` file:

```bash
SIGNING_SECRET=your_signing_secret_here
```

## Step 2: Understand the Signing Algorithm

All `GET /og` requests are **HMAC-SHA256 signed** to prevent tampering.

**Steps:**
1. Collect all parameters except `sig`: `template=basic&title=Hello%20World&theme=dark&w=1200&h=630`
2. Sort alphabetically by key (already sorted in that example)
3. Compute `sig = HMAC-SHA256(SIGNING_SECRET, canonical)` as hex
4. Append to the URL: `...&sig=<hex>`

**Example (JavaScript):**
```javascript
const crypto = require('crypto');

const params = {
  template: 'basic',
  title: 'My First OG Image',
  theme: 'dark',
  w: '1200',
  h: '630',
};

// Sort and encode
const sorted = Object.keys(params)
  .sort()
  .map(k => `${k}=${encodeURIComponent(params[k])}`)
  .join('&');

const secret = process.env.SIGNING_SECRET;
const sig = crypto
  .createHmac('sha256', secret)
  .update(sorted)
  .digest('hex');

const url = `https://api.renderog.app/og?${sorted}&sig=${sig}`;
console.log(url);
```

## Step 3: Render with curl

Copy the signed URL from above and fetch it:

```bash
curl -s "https://api.renderog.app/og?template=basic&title=My%20First%20OG%20Image&theme=dark&w=1200&h=630&sig=abc123..." \
  -o image.png
```

Open `image.png` in your browser. You've rendered an OG image at the edge.

**Check performance headers:**
```bash
curl -i "https://api.renderog.app/og?template=basic&title=My%20First%20OG%20Image&theme=dark&w=1200&h=630&sig=abc123..." | head -20
```

Look for:
- `X-Render-Ms: 45` — milliseconds to render (first time) or near-zero (cache hit)
- `X-Cache: MISS` (first render) or `HIT` (cached)
- `Cache-Control: public, s-maxage=31536000, immutable` — cached for 1 year at the edge

## Step 4: Render with Fetch (JavaScript)

Modern Node or browser:

```javascript
async function renderOG() {
  const params = {
    template: 'basic',
    title: 'My First OG Image',
    theme: 'dark',
  };

  // Sign (use the crypto snippet from Step 2)
  const sorted = Object.keys(params)
    .sort()
    .map(k => `${k}=${encodeURIComponent(params[k])}`)
    .join('&');

  const sig = crypto
    .createHmac('sha256', process.env.SIGNING_SECRET)
    .update(sorted)
    .digest('hex');

  const url = `https://api.renderog.app/og?${sorted}&sig=${sig}`;

  const res = await fetch(url);
  const buffer = await res.arrayBuffer();
  const ms = res.headers.get('x-render-ms');
  const cached = res.headers.get('x-cache');

  console.log(`Rendered in ${ms}ms (${cached})`);
  return buffer; // PNG bytes
}

renderOG();
```

## Step 5: Use in Next.js Metadata

Generate a signed URL at build time (or on-the-fly in a server function) and use it in your metadata:

```javascript
// app/page.tsx
import crypto from 'crypto';

function signOG(params) {
  const sorted = Object.keys(params)
    .sort()
    .map(k => `${k}=${encodeURIComponent(params[k])}`)
    .join('&');

  const sig = crypto
    .createHmac('sha256', process.env.SIGNING_SECRET)
    .update(sorted)
    .digest('hex');

  return `https://api.renderog.app/og?${sorted}&sig=${sig}`;
}

export const metadata = {
  title: 'My Blog Post',
  openGraph: {
    images: [
      {
        url: signOG({
          template: 'blog',
          title: 'My Blog Post',
          subtitle: 'Published today',
          theme: 'dark',
        }),
        width: 1200,
        height: 630,
      },
    ],
  },
};

export default function Page() {
  return <h1>My Blog Post</h1>;
}
```

## What's Next?

- **[OG Reference](./og.md)** — All four templates, every parameter, CSS constraints
- **[Pricing & Usage](./pricing-api.md)** — Check your usage, understand billing
- **[Playground](https://renderog.app/playground)** — Live template editor with preview

## Debugging

**`403 invalid_signature`** — Double-check that `SIGNING_SECRET` matches your key, and that all parameter values are URL-encoded identically during signing and in the final URL.

**`400 invalid_params`** — Check that `title` is ≤200 chars, `w`/`h` are within limits (max 2400×1260), and `template` is one of: `basic`, `gradient`, `blog`, `product`.

**`422 render_failed`** — The rendering engine encountered an error (usually bad CSS or invalid template HTML). Check the browser console if using the playground; test with simple templates first.

**`429 rate_limited`** — You've exceeded the rate limit (60/min free, 600/min paid). Check the `Retry-After` header.
