# 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 - Persistent snapshot links (48-hour TTL, immutable, shareable) ## API Endpoints ### POST /generate Generate a quote image using JSON request body. **Request:** ```bash 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:** ```bash curl "https://quotes.imbenji.net/generate?displayName=John%20Doe&username=@johndoe&text=Hello%20World×tamp=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 ```javascript 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 ```javascript 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 ```javascript 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 ```python 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 ```bash 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 ```javascript 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 ```javascript 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:** ```bash 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):** ```json { "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. ```bash 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. ```bash 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:** ```bash # 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:** ```bash # 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. ```bash 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: ` - `X-Cache: HIT` or `MISS` ### DELETE /v2/quote/:id Delete a session. ```bash curl -X DELETE https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` Returns `204 No Content` on success. ### POST /v2/quote/:id/snapshot-link Create a persistent snapshot link. This captures the current state of your session and generates a shareable URL that persists for 48 hours (refreshing on each access). Unlike the regular `/image` endpoint, snapshots are immutable - they always show the image as it was when the snapshot was created, even if you update the session afterwards. **Request:** ```bash curl -X POST https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890/snapshot-link ``` **Response (201):** ```json { "token": "xY9pQmN3kL8vFw2jRtZ7", "url": "https://quotes.imbenji.net/v2/snapshot/xY9pQmN3kL8vFw2jRtZ7", "sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "createdAt": 1735600000, "expiresAt": 1735772800 } ``` ### GET /v2/snapshot/:token Retrieve a snapshot image using its token. ```bash curl https://quotes.imbenji.net/v2/snapshot/xY9pQmN3kL8vFw2jRtZ7 --output snapshot.png ``` **Response headers:** - `Content-Type: image/png` - `X-Snapshot-Token: ` - `X-Cache: HIT` or `MISS` **Snapshot behavior:** - **Immutable:** Shows the session state when the snapshot was created - **TTL:** 48 hours from last access (resets on each view) - **Cascade delete:** Deleted automatically when parent session is deleted - **Shareable:** Token can be shared with anyone ### v2 Example Workflow ```javascript // 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(); // 5. (Optional) Create a shareable snapshot link const snapshotRes = await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}/snapshot-link`, { method: 'POST' }); const snapshot = await snapshotRes.json(); console.log('Shareable URL:', snapshot.url); // Anyone can access: https://quotes.imbenji.net/v2/snapshot/ ``` ### 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:** ```json { "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:** ```javascript const timestamp = Math.floor(Date.now() / 1000); ``` **Python:** ```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 ### Docker (Recommended) ```bash git clone cd quotegen docker-compose up -d ``` The API will be available at `http://localhost:3000` ### Manual Setup ```bash npm install npm start ``` ## Health Check ```bash curl https://quotes.imbenji.net/health ``` Response: ```json { "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) - **Snapshot TTL:** 48 hours from last access (v2 API) - **Cleanup interval:** Every hour - **Database:** SQLite (for v2 sessions and snapshots) ## 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