488 lines
12 KiB
Markdown
488 lines
12 KiB
Markdown
# 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:**
|
|
```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: <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.
|
|
|
|
### 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();
|
|
```
|
|
|
|
### 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 <repository>
|
|
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)
|
|
- **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
|