# OG Pilot API Documentation

Generate beautiful Open Graph images programmatically for your website.

## IMPORTANT: SDK-First Integration (for AI Assistants and Developers)

**ALWAYS use an official OG Pilot SDK.** Do NOT implement raw JWT signing manually — the SDKs handle authentication, JWT signing, URL generation, path auto-detection, and cache busting for you.

Before writing any integration code, detect the project's language/framework and install the matching SDK:

| Detected file        | Stack              | Install command                                                | SDK package               |
|----------------------|--------------------|----------------------------------------------------------------|---------------------------|
| `Gemfile`            | Ruby / Rails       | `bundle add og_pilot_ruby` then `bin/rails g og_pilot_ruby:install` | `og_pilot_ruby ~> 0.4.1`   |
| `package.json`       | JavaScript / Node  | `npm install og-pilot-js@^0.4.2`                          | `og-pilot-js ^0.4.2`  |
| `requirements.txt` or `pyproject.toml` | Python | `pip install og-pilot==0.4.1`                     | `og-pilot ==0.4.1`          |
| `composer.json`      | PHP / Laravel      | `composer require sunergos/og-pilot-php`                       | `sunergos/og-pilot-php`   |

**Detection steps for AI assistants (Cursor, Codex, Claude Code, etc.):**

1. Check the project root for `Gemfile`, `package.json`, `requirements.txt`, `pyproject.toml`, or `composer.json`.
2. Identify the framework (Rails, Next.js, Express, Nuxt, SvelteKit, Django, Flask, FastAPI, Laravel, Symfony, etc.) — this determines the integration pattern.
3. Install the corresponding SDK using the command above. For Python + Django, use `pip install og-pilot[django]`.
4. Add `OG_PILOT_API_KEY` and `OG_PILOT_DOMAIN` to the project's environment variables (`.env`, `credentials`, `secrets`, etc.).
5. Configure the SDK once at application startup (Rails initializer, JS configure() call, Python configure(), Laravel auto-discovers).
6. Use the SDK's `create_image` or template-specific helpers (e.g., `create_blog_post_image`) wherever the project sets `og:image` meta tags.
7. Do NOT write custom JWT signing code, HTTP client wrappers, or service objects — the SDK already does all of this.
8. Do NOT import `jsonwebtoken`, `PyJWT`, `firebase/php-jwt`, or any JWT library for OG Pilot — use the SDK.

If none of the above dependency files exist, use the raw API with JWT signing as described in the Authentication section below.

SDK versions verified on February 23, 2026.
SDK source code: [Ruby](https://github.com/sunergos-ro/og_pilot_ruby) | [JavaScript](https://github.com/sunergos-ro/og-pilot-js) | [Python](https://github.com/sunergos-ro/og-pilot-python) | [PHP](https://github.com/sunergos-ro/og-pilot-php)

## Quick Start

1. Sign up at https://ogpilot.com
2. Add your domain and verify ownership via DNS TXT record
3. Generate an API key in Settings -> API Keys
4. Install the SDK for your stack (see table above)
5. Set `OG_PILOT_API_KEY` and `OG_PILOT_DOMAIN` environment variables
6. Call the SDK's `create_image` function wherever you set `og:image` meta tags

### Ruby on Rails

```bash
bundle add og_pilot_ruby
bin/rails g og_pilot_ruby:install
```

The generator creates `config/initializers/og_pilot_ruby.rb` which reads from environment variables automatically.
Use `og_pilot_ruby` directly in controllers/views. Do **not** create a custom service wrapper for JWT signing.

```ruby
# In a controller or view — use OgPilotRuby directly
@og_image = OgPilotRuby.create_image(
  title: "My Page Title",
  description: "Page description",
  iat: Time.current.to_i
)

# Template-specific helpers set the template automatically:
@og_image = OgPilotRuby.create_blog_post_image(
  title: @post.title,
  author_name: @post.author.name,
  publish_date: @post.published_at.to_date.iso8601,
  path: request.fullpath,
  iat: Time.current.to_i
)
```

Available template helpers: `create_blog_post_image`, `create_podcast_image`, `create_product_image`, `create_event_image`, `create_book_image`, `create_company_image`, `create_portfolio_image`.

Path auto-detection: in Rails, the SDK automatically reads `request.fullpath`. Pass `default: true` to force path to `/`, or `path: "/custom"` to override.

### JavaScript / Node.js

```bash
npm install og-pilot-js@^0.4.2
```

```javascript
import { configure, createImage } from "og-pilot-js";

configure((config) => {
  config.apiKey = process.env.OG_PILOT_API_KEY;
  config.domain = process.env.OG_PILOT_DOMAIN;
});

const imageUrl = await createImage(
  { template: "page", title: "My Page" },
  { iat: Date.now() }
);
```

Available template helpers: `createBlogPostImage`, `createPodcastImage`, `createProductImage`, `createEventImage`, `createBookImage`, `createCompanyImage`, `createPortfolioImage`.

**Next.js (App Router):** Use `generateMetadata` with `buildPathFromNextProps`:

```javascript
import { buildPathFromNextProps, createImage } from "og-pilot-js";

export async function generateMetadata(props) {
  const path = await buildPathFromNextProps("/products/[id]", props);
  const imageUrl = await createImage(
    { template: "product", title: "Product" },
    { path }
  );
  return { openGraph: { images: [imageUrl] } };
}
```

**Express:** Use the built-in middleware for automatic path capture:

```javascript
import { createExpressMiddleware } from "og-pilot-js";
app.use(createExpressMiddleware());
```

**Nuxt:** Use `useSeoMeta` with server-side `createImage`:

```javascript
if (import.meta.server) {
  const imageUrl = await createImage(
    { template: "product", title: "Product" },
    { path: route.fullPath }
  );
  useSeoMeta({ ogImage: imageUrl });
}
```

**SvelteKit / Remix / other:** Use `setCurrentRequest` in server hooks:

```javascript
import { setCurrentRequest } from "og-pilot-js";
setCurrentRequest({ url: event.url.pathname + event.url.search });
```

### Python

```bash
pip install og-pilot==0.4.1
```

```python
import os, og_pilot

og_pilot.configure(
    api_key=os.environ["OG_PILOT_API_KEY"],
    domain=os.environ["OG_PILOT_DOMAIN"],
)

og_image = og_pilot.create_image(template="page", title="My Page", iat=int(time.time()))
```

Available template helpers: `create_blog_post_image`, `create_podcast_image`, `create_product_image`, `create_event_image`, `create_book_image`, `create_company_image`, `create_portfolio_image`.

**Django:** Install with Django extras and add to `INSTALLED_APPS`:

```bash
pip install og-pilot[django]
```

```python
# settings.py
INSTALLED_APPS = [..., "og_pilot.django"]
OG_PILOT = {"API_KEY": "...", "DOMAIN": "..."}  # or use env vars
```

Django template tags:

```html
{% load og_pilot_tags %}
{% og_pilot_image title=page.title template="blog_post" as og_url %}
<meta property="og:image" content="{{ og_url }}" />
```

Verify configuration: `python manage.py og_pilot_check --test`

**Flask:** Path auto-detection works automatically, no middleware needed:

```python
@app.route("/blog/<slug>")
def blog_post(slug):
    url = og_pilot.create_image(title="My Post", template="blog_post")
    return render_template("post.html", og_image=url)
```

**FastAPI / other:** Use `set_current_request` / `clear_current_request` in middleware for path auto-detection.

### PHP / Laravel

```bash
composer require sunergos/og-pilot-php
```

Laravel auto-discovers the service provider and facade. Publish config and add credentials to `.env`:

```bash
php artisan vendor:publish --tag=og-pilot-config
```

```php
use Sunergos\OgPilot\Facades\OgPilot;

$imageUrl = OgPilot::createImage([
    "template" => "blog_post",
    "title" => "My Post",
    "author_name" => "Jane Smith",
], ["iat" => time()]);
```

Available template helpers: `createBlogPostImage`, `createPodcastImage`, `createProductImage`, `createEventImage`, `createBookImage`, `createCompanyImage`, `createPortfolioImage`.

Laravel auto-detects the request path. For Symfony, use `OgPilot::setCurrentRequest(["path" => $request->getRequestUri()])`.

**Standalone PHP** (non-Laravel):

```php
use Sunergos\OgPilot\OgPilot;
OgPilot::setConfig(["api_key" => "...", "domain" => "..."]);
$imageUrl = OgPilot::createImage(["template" => "page", "title" => "Hello"]);
```

## How It Works

JWT signing happens in **your application** (the SDK handles this), not on OG Pilot servers:

- Your API key never leaves your server.
- Each page gets a unique API request payload generated at render time.
- Social platforms fetch the image from OG Pilot CDN when they crawl your `og:image` tag.

## Authentication

> **Note:** If you are using an official SDK, authentication is handled automatically. The details below are only needed for raw API access when no SDK is available.

All API requests require a JWT sent in the request body as `token`. You sign the JWT server-side in your application using your API key as the HMAC secret (HS256).

```
POST https://ogpilot.com/api/v1/images
Content-Type: application/json
{"token":"YOUR_JWT_TOKEN"}
```

Required JWT claims:

- `iss` (required) - Verified domain hostname for your account
- `sub` (required) - API key prefix (first 8 characters of your API key)

Optional claims:

- `iat` - Issued-at timestamp for daily cache busting
- Image parameters: `template`, `title`, `description`, `logo_url`, `image_url`, `bg_color`, `text_color`, `accent_color`, `path`, and template-specific fields (e.g., `contributions`)

API keys are account-scoped and can be used with any verified domain on your account.
Domain ownership must be verified via DNS TXT before the API generates images for that domain.
For local development, requests from `localhost` are allowed in development mode only.

## Endpoint

```
POST https://ogpilot.com/api/v1/images
```

OpenAPI spec:

```
GET https://ogpilot.com/openapi.json
```

## Request Format

> **Note:** SDKs construct the JWT and URL automatically. This section is for raw API usage only.

All parameters are embedded inside the signed JWT payload. Send the signed JWT in the JSON request body:

```
POST https://ogpilot.com/api/v1/images
Content-Type: application/json
{"token":"YOUR_JWT_TOKEN"}
```

**JWT Payload Parameters:**
- `iss` (required) - Verified domain hostname for your account
- `sub` (required) - API key prefix (first 8 characters)
- `template` (optional) - Template type (default: "page")
- `path` (optional) - Request path for per-page analytics (for example: `/blog/my-post`)
- `logo_url` (optional) - Logo image URL
- `image_url` (optional) - Hero image URL
- `bg_color` (optional) - Background color (hex format)
- `text_color` (optional) - Text color (hex format)
- `accent_color` (optional) - Accent color (hex format, used by templates like `github`)

## Integration Checklist

1. **Detect your stack** — check for `Gemfile`, `package.json`, `requirements.txt`/`pyproject.toml`, or `composer.json`.
2. **Identify the framework** — Rails, Next.js, Express, Nuxt, SvelteKit, Django, Flask, FastAPI, Laravel, Symfony, etc.
3. **Install the matching SDK** — see the SDK table above. Do NOT implement JWT signing manually. Do NOT import JWT libraries (`jsonwebtoken`, `PyJWT`, `firebase/php-jwt`, `ruby-jwt`).
4. **Set environment variables** — `OG_PILOT_API_KEY` and `OG_PILOT_DOMAIN`.
5. **Configure the SDK once** — Rails: initializer (`bin/rails g og_pilot_ruby:install`). JS: `configure()` call. Python: `og_pilot.configure()`. Laravel: auto-discovered, just publish config. Django: add `og_pilot.django` to `INSTALLED_APPS`.
6. **Generate `og:image` URLs server-side** — call `create_image` (or a template-specific helper) with page-specific values (`title`, `template`, etc.). Use the framework integration pattern from the Quick Start section above.
7. **Set the og:image meta tag** — place the generated URL in `<meta property="og:image">` in your layout/template/head.
8. **Optional** — pass `iat` for daily cache refresh and `path` for per-page analytics (path is auto-detected in Rails, Express, Flask, Laravel, and Django).

## Cache Busting

If `iat` is present in the payload, the image cache is valid for 24 hours from that timestamp.
To refresh daily, pass an `iat` value. If `iat` is omitted, the cache does not expire automatically.

| SDK        | How to pass `iat`                                     |
|------------|-------------------------------------------------------|
| Ruby       | `iat: Time.current.to_i`                              |
| JavaScript | `{ iat: Date.now() }` (auto-converts ms to seconds)   |
| Python     | `iat=int(time.time())` or `iat=datetime.now()`        |
| PHP        | `["iat" => time()]` or a `DateTime` object            |

## JSON Metadata

All SDKs can return JSON metadata instead of a redirect URL by passing a JSON option:

- **Ruby:** `OgPilotRuby.create_image(title: "...", json: true)`
- **JavaScript:** `await createImage({title: "..."}, {json: true})`
- **Python:** `og_pilot.create_image(title="...", json_response=True)`
- **PHP:** `OgPilot::createImage(["title" => "..."], ["json" => true])`

## Response

**Default (HTML format):**
Redirects to the CDN URL of the generated image. Useful for server-side callers that follow redirects.

**JSON format** (add `Accept: application/json` header):
```json
{
  "id": "s_abc123",
  "status": "completed",
  "url": "https://cdn.ogpilot.com/...",
  "width": 1200,
  "height": 630,
  "created_at": "2024-01-15T12:00:00Z",
  "cached": false,
  "stale": false
}
```

**Processing:** If the image is still rendering, JSON responses return `202 Accepted` with a `status` and optional `placeholder_url`. HTML/PNG responses redirect to a processing placeholder PNG. Failed renders return an error placeholder while OG Pilot retries in the background.

**Stale fallback:** When a new render is queued (e.g., after a new `iat`), OG Pilot will serve the last completed image while regeneration happens in the background. JSON responses include `stale: true` when this happens.

## Path Handling

All SDKs auto-detect the current request path for per-page analytics. The `path` parameter tracks which OG images perform better across different pages.

| Option            | Behavior |
|-------------------|----------|
| (default)         | Auto-detects from the current request (Rails `request.fullpath`, Express/Flask/Laravel request, etc.). Falls back to `/`. |
| `default: true`   | Forces path to `/` (useful for site-wide default images). |
| `path: "/custom"` | Uses the provided path verbatim, overriding auto-resolution. |

All SDKs support `strip_extensions` (enabled by default) which normalizes `/docs.html`, `/docs.md`, `/docs.php` to `/docs` for consolidated analytics.

## SDK Configuration Options

All SDKs share these configuration options:

| Option            | Ruby                    | JavaScript            | Python              | PHP                   |
|-------------------|-------------------------|-----------------------|---------------------|-----------------------|
| API Key           | `config.api_key`        | `config.apiKey`       | `api_key=`          | `api_key`             |
| Domain            | `config.domain`         | `config.domain`       | `domain=`           | `domain`              |
| Base URL          | `config.base_url`       | `config.baseUrl`      | `base_url=`         | `base_url`            |
| Connect timeout   | `config.open_timeout`   | `config.openTimeoutMs`| `open_timeout=`     | `connect_timeout`     |
| Read timeout      | `config.read_timeout`   | `config.readTimeoutMs`| `read_timeout=`     | `timeout`             |
| Strip extensions  | `config.strip_extensions` | `config.stripExtensions` | `strip_extensions=` | `strip_extensions` |

All SDKs default to reading `OG_PILOT_API_KEY` and `OG_PILOT_DOMAIN` from environment variables when not explicitly configured.

## Templates

All SDKs support every template via the `template` parameter in `create_image`. All SDKs also provide template-specific helpers:

| Template    | Ruby helper                          | JS helper                  | Python helper                    | PHP helper                     |
|-------------|--------------------------------------|----------------------------|----------------------------------|--------------------------------|
| page        | `create_image` (default)             | `createImage` (default)    | `create_image` (default)         | `createImage` (default)        |
| blog_post   | `create_blog_post_image`             | `createBlogPostImage`      | `create_blog_post_image`         | `createBlogPostImage`          |
| podcast     | `create_podcast_image`               | `createPodcastImage`       | `create_podcast_image`           | `createPodcastImage`           |
| product     | `create_product_image`               | `createProductImage`       | `create_product_image`           | `createProductImage`           |
| event       | `create_event_image`                 | `createEventImage`         | `create_event_image`             | `createEventImage`             |
| book        | `create_book_image`                  | `createBookImage`          | `create_book_image`              | `createBookImage`              |
| company     | `create_company_image`               | `createCompanyImage`       | `create_company_image`           | `createCompanyImage`           |
| portfolio   | `create_portfolio_image`             | `createPortfolioImage`     | `create_portfolio_image`         | `createPortfolioImage`         |

### page (default)
General purpose template for landing pages and homepages.
Additional parameters:
- `title` - Headline text
- `description` - Subtitle text

### blog_post
Blog articles with author and date.
Additional parameters:
- `title` - Blog post title
- `author_name` - Author name
- `author_avatar_url` - Author avatar
- `publish_date` - Publication date (ISO 8601)

### podcast
Podcast episodes.
Additional parameters:
- `title` - Episode title
- `episode_date` - Episode date (ISO 8601)

### product
E-commerce product pages.
Additional parameters:
- `title` - Product name
- `unique_selling_point` - USP badge text

### event
Event pages.
Additional parameters:
- `title` - Event name
- `event_date` - Date or range
- `event_location` - Location

### book
Book pages.
Additional parameters:
- `title` - Book title
- `description` - Book description
- `book_author` - Author
- `book_series_number` - Series number
- `book_description` - Description
- `book_genre` - Genre

### portfolio
Personal portfolio pages.
Additional parameters:
- `title` - Headline text

### company
Company profile and careers pages.
Additional parameters:
- `title` - Company name
- `company_logo_url` - Centered company logo URL
- `description` - Supporting text (e.g., open roles count, metrics, etc.)
Note: `image_url` is ignored for this template.

### github
GitHub contribution graph (7 rows, 26 columns).
Additional parameters:
- `title` - Header title (organization/repository name)
- `description` - Summary line under the title
- `contributions` - Array of day cells, each item: `{ date, count }`
  - `date` should be ISO 8601 (`YYYY-MM-DD`)
  - `count` should be an integer
  - one item maps to one day cell
  - the last 175 days are shown; missing days are auto-filled with `count: 0`
  - accepted forms: array of `{date,count}` (preferred), a single `{date,count}` object, or a date=>count map
  - rows are always 7 and columns are currently fixed at 26 (empty placeholders align first/last visible days)
- `accent_color` - Hex color used for contribution intensity (default `#e6a91a`)

## Rate Limits

- 600 requests per minute per API key
- 60,000 requests per hour per API key

## Error Codes

| Code | Description |
|------|-------------|
| 400 | Bad request - missing or invalid parameters |
| 401 | Unauthorized - invalid JWT token |
| 403 | Forbidden - domain mismatch, not verified, or subscription required |
| 404 | Not found |
| 429 | Rate limited |

## Full Examples

### cURL (raw API — prefer an SDK instead)

```bash
curl -X POST "https://ogpilot.com/api/v1/images"   -H "Content-Type: application/json"   -H "Accept: application/json"   -d '{"token":"YOUR_JWT_TOKEN"}'

```

### JavaScript (using og-pilot-js SDK)

```javascript
// npm install og-pilot-js@^0.4.2

import { configure, createImage } from "og-pilot-js";

configure((config) => {
  config.apiKey = process.env.OG_PILOT_API_KEY;
  config.domain = process.env.OG_PILOT_DOMAIN ?? "yourdomain.com";
});

const imageUrl = await createImage(
  {
    template: "blog_post",
    title: "How to Build Amazing OG Images",
    description: "A complete guide to social media previews",
    author_name: "Jane Smith",
    publish_date: "2024-01-15",
    path: "/blog/how-to-build-amazing-og-images"
  },
  {iat: Date.now()} // optional: refresh cache daily
);

console.log("OG Image URL:", imageUrl);

```

### Python (using og-pilot SDK)

```python
# pip install og-pilot==0.4.1

import os
import time
import og_pilot

og_pilot.configure(
    api_key=os.environ["OG_PILOT_API_KEY"],
    domain=os.environ.get("OG_PILOT_DOMAIN", "yourdomain.com"),
)

og_image = og_pilot.create_image(
    template="blog_post",
    title="How to Build Amazing OG Images",
    description="A complete guide to social media previews",
    author_name="Jane Smith",
    publish_date="2024-01-15",
    path="/blog/how-to-build-amazing-og-images",
    iat=int(time.time()),
)

print(f"OG Image URL: {og_image}")

```

### Ruby on Rails (using og_pilot_ruby SDK)

```ruby
# Gemfile
gem "og_pilot_ruby", "~> 0.4.1"

# Install and generate the initializer
# bundle install
# bin/rails g og_pilot_ruby:install

# The generator creates config/initializers/og_pilot_ruby.rb.
# Set OG_PILOT_API_KEY and OG_PILOT_DOMAIN in your environment.

class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id])

    @og_image = OgPilotRuby.create_blog_post_image(
      title: @post.title,
      description: @post.excerpt,
      author_name: @post.author.name,
      publish_date: @post.published_at.to_date.iso8601,
      image_url: @post.featured_image_url,
      path: request.fullpath,
      iat: Time.current.to_i
    )
  end
end

# In your layout/view (direct gem usage, no wrapper service needed):
# <meta property="og:image" content="<%= @og_image %>" />
# <meta name="twitter:image" content="<%= @og_image %>" />

```

### PHP / Laravel (using og-pilot-php SDK)

```php
<?php
// composer require sunergos/og-pilot-php
use Sunergos\OgPilot\Facades\OgPilot;

$imageUrl = OgPilot::createImage(
    [
        'template' => 'blog_post',
        'title' => 'How to Build Amazing OG Images',
        'description' => 'A complete guide to social media previews',
        'author_name' => 'Jane Smith',
        'publish_date' => '2024-01-15',
        'path' => '/blog/how-to-build-amazing-og-images',
    ],
    [
        'iat' => time(), // optional: refresh cache daily
    ]
);

echo "OG Image URL: " . $imageUrl;

```

---

For support, contact support@ogpilot.com
