2025-12-31 07:07:52 +00:00
2025-12-31 07:07:52 +00:00

Quote Generator API

Generate Twitter-style quote images programmatically. Perfect for creating social media content, screenshots, or fake tweets.

Base URL: https://quotes.imbenji.net

Features

  • Twitter-authentic styling
  • Support for images and text-only tweets
  • Verified badge (blue checkmark)
  • Engagement metrics (likes, retweets, replies, views)
  • High-resolution output (3240x3240)
  • Built-in caching (24-hour TTL)
  • Base64 image support
  • Docker-ready
  • v2 API with stateful sessions for incremental updates

API Endpoints

POST /generate

Generate a quote image using JSON request body.

Request:

curl -X POST https://quotes.imbenji.net/generate \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "Geoff Marshall",
    "username": "@geofftech",
    "avatarUrl": "https://example.com/avatar.jpg",
    "text": "Does anyone else find it immensely satisfying when you turn a pocket inside out and get rid of the crumbs and fluff stuck in the bottom.",
    "timestamp": 1499766270
  }' --output quote.png

GET /generate

Generate a quote image using query parameters.

Request:

curl "https://quotes.imbenji.net/generate?displayName=John%20Doe&username=@johndoe&text=Hello%20World&timestamp=1735574400" --output quote.png

Parameters

Parameter Type Required Description
displayName string No Display name (defaults to "Anonymous")
username string No Twitter handle with @ (defaults to "@anonymous")
avatarUrl string No Avatar image URL or base64 data URI
text string No Tweet text content
imageUrl string No Tweet image URL, base64 data URI, or null
timestamp integer No Unix epoch timestamp in seconds
verified boolean No Show verified badge (blue checkmark) next to name
engagement object No Engagement metrics (likes, retweets, replies, views)

Response

  • Content-Type: image/png
  • Headers:
    • X-Cache: HIT (served from cache) or MISS (newly generated)

Examples

Text-only tweet

fetch('https://quotes.imbenji.net/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'John Doe',
    username: '@johndoe',
    text: 'Just deployed my new API!',
    timestamp: Math.floor(Date.now() / 1000)
  })
})
.then(res => res.blob())
.then(blob => {
  const url = URL.createObjectURL(blob);
  document.getElementById('img').src = url;
});

Tweet with image

const response = await fetch('https://quotes.imbenji.net/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'Jane Smith',
    username: '@janesmith',
    avatarUrl: 'https://example.com/avatar.jpg',
    text: 'Check out this amazing view! 🌄',
    imageUrl: 'https://example.com/photo.jpg',
    timestamp: 1735574400
  })
});

const blob = await response.blob();
// Save or display the image

Using base64 images

fetch('https://quotes.imbenji.net/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'Alice',
    username: '@alice',
    avatarUrl: 'data:image/png;base64,iVBORw0KGgo...',
    text: 'Using base64 images works too!',
    imageUrl: 'data:image/jpeg;base64,/9j/4AAQSkZJ...',
    timestamp: 1735574400
  })
});

Python example

import requests
import time

response = requests.post('https://quotes.imbenji.net/generate', json={
    'displayName': 'Python User',
    'username': '@pythonista',
    'text': 'Making API calls with Python!',
    'timestamp': int(time.time())
})

with open('quote.png', 'wb') as f:
    f.write(response.content)

cURL with GET

curl -G "https://quotes.imbenji.net/generate" \
  --data-urlencode "displayName=Test User" \
  --data-urlencode "username=@testuser" \
  --data-urlencode "text=This is a test tweet" \
  --data-urlencode "timestamp=1735574400" \
  --output quote.png

Verified badge

fetch('https://quotes.imbenji.net/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'Elon Musk',
    username: '@elonmusk',
    text: 'Just bought Twitter!',
    verified: true,
    timestamp: 1735574400
  })
});

Engagement metrics

fetch('https://quotes.imbenji.net/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'Popular User',
    username: '@viral',
    text: 'This tweet went viral!',
    verified: true,
    engagement: {
      replies: 1234,
      retweets: 5678,
      likes: 98765,
      views: 1500000
    },
    timestamp: 1735574400
  })
});

Note: All four engagement fields (replies, retweets, likes, views) must be provided for the engagement bar to appear. Numbers are automatically formatted (e.g., 1234 → 1.2K, 1500000 → 1.5M).


v2 API (Stateful Sessions)

The v2 API lets you build quote images incrementally. Instead of sending everything in one request, you create a session and update fields as needed. This is more efficient when making multiple edits since you dont need to resend large images every time.

How it works

  1. Create a session with POST /v2/quote
  2. Update fields with PATCH /v2/quote/:id (only send whats changed)
  3. Render the image with GET /v2/quote/:id/image

Sessions expire after 24 hours of inactivity.

POST /v2/quote

Create a new session.

Request:

curl -X POST https://quotes.imbenji.net/v2/quote \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "John Doe",
    "username": "@johndoe",
    "text": "Hello world"
  }'

Response (201):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "displayName": "John Doe",
  "username": "@johndoe",
  "text": "Hello world",
  "avatarUrl": null,
  "imageUrl": null,
  "timestamp": null,
  "verified": false,
  "engagement": null,
  "createdAt": 1735600000,
  "updatedAt": 1735600000
}

GET /v2/quote/:id

Get current session state.

curl https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890

PATCH /v2/quote/:id

Update specific fields. Only send the fields you want to change.

curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Updated text!",
    "avatarUrl": "data:image/png;base64,..."
  }'

Engagement updates:

# Set all engagement metrics
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "engagement": {
      "replies": 100,
      "retweets": 250,
      "likes": 5000,
      "views": 50000
    }
  }'

# Partial update (update only likes, keep other fields)
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "engagement": {
      "likes": 10000
    }
  }'

# Clear engagement entirely (removes engagement bar)
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "engagement": null
  }'

Verified badge:

# Add verified badge
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Content-Type: application/json" \
  -d '{
    "verified": true
  }'

GET /v2/quote/:id/image

Render the current session state as a PNG image.

curl https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890/image --output quote.png

Response headers:

  • Content-Type: image/png
  • X-Session-Id: <session-id>
  • X-Cache: HIT or MISS

DELETE /v2/quote/:id

Delete a session.

curl -X DELETE https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890

Returns 204 No Content on success.

v2 Example Workflow

// 1. Create session with initial data
const res = await fetch('https://quotes.imbenji.net/v2/quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    displayName: 'Jane Doe',
    username: '@janedoe',
    text: 'Working on something cool'
  })
});
const session = await res.json();
const sessionId = session.id;

// 2. Later, add an avatar (only sends the avatar, not everything again)
await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}`, {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    avatarUrl: 'data:image/png;base64,iVBORw0KGgo...'
  })
});

// 3. Update the text
await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}`, {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    text: 'Finished building something cool!'
  })
});

// 4. Generate the final image
const imgRes = await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}/image`);
const blob = await imgRes.blob();

v2 Parameters

Parameter Type Description
displayName string Display name
username string Twitter handle with @
avatarUrl string Avatar image URL or base64
text string Tweet text content
imageUrl string Tweet image URL or base64
timestamp integer Unix epoch in seconds
verified boolean Show verified badge (blue checkmark)
engagement object/null Engagement metrics (see below)

Engagement object:

{
  "engagement": {
    "replies": 89,
    "retweets": 567,
    "likes": 1234,
    "views": 50000
  }
}

Engagement behavior:

  • All four fields must be provided for the engagement bar to appear
  • Partial updates: Only update specific fields, others remain unchanged
  • Set to null to clear all engagement and hide the engagement bar
  • Numbers are auto-formatted (1234 → 1.2K, 1000000 → 1M)

Caching

The API automatically caches generated images for 24 hours from the last request. Identical requests will be served from cache instantly.

  • Cache hits include X-Cache: HIT header
  • Cache misses include X-Cache: MISS header
  • Cache is cleaned up hourly
  • Images are stored based on SHA-256 hash of parameters

Timestamp Format

The timestamp parameter expects a Unix epoch timestamp in seconds (not milliseconds).

JavaScript:

const timestamp = Math.floor(Date.now() / 1000);

Python:

import time
timestamp = int(time.time())

The timestamp will be formatted as: 5:58 PM · Dec 29, 2025

Default Avatar

If no avatarUrl is provided, a default Twitter-style placeholder avatar will be used.

Rate Limiting

Currently, there are no rate limits. Please use responsibly.

Self-Hosting

git clone <repository>
cd quotegen
docker-compose up -d

The API will be available at http://localhost:3000

Manual Setup

npm install
npm start

Health Check

curl https://quotes.imbenji.net/health

Response:

{
  "status": "ok"
}

Technical Details

  • Resolution: 3240x3240 (1:1 aspect ratio)
  • Format: PNG
  • Max tweet width: 450px (auto-scaled to fit)
  • Cache TTL: 24 hours from last access
  • Session TTL: 24 hours from last update (v2 API)
  • Cleanup interval: Every hour
  • Database: SQLite (for v2 sessions)

Limitations

  • Tweet text is not wrapped intelligently (use \n for line breaks if needed)
  • Very long tweets may be cut off
  • External image URLs must be publicly accessible
  • Base64 images should be reasonably sized

Support

For issues or questions, please contact the maintainer.


Built with: Node.js, Express, Puppeteer, SQLite

Description
No description provided
Readme 255 KiB
Languages
JavaScript 75.9%
HTML 23.2%
Dockerfile 0.9%