Self-hosted screenshot API with full-site capture, S3 storage, and smart animation handling.
- Single or batch screenshots - Capture one URL or multiple in a single request
- Full-site capture - Automatically discover and screenshot every page via sitemap
- Animation handling - Forces visibility of scroll-triggered content (motion.dev, GSAP, AOS, etc.)
- S3-compatible storage - MinIO included, works with any S3 provider
- Async processing - Site captures run in background with progress polling
- Auto-cleanup - Screenshots expire after 24 hours (configurable)
# Clone and configure
git clone https://github.com/YOUR_USERNAME/webshot.git
cd webshot
cp .env.example .env
cp config/api-keys.example.json config/api-keys.json
# Edit .env with secure passwords
# Edit config/api-keys.json with your API keys
# Start
docker compose up -dVerify: curl http://localhost/health
All endpoints require X-API-Key header (except health checks).
Capture one or more URLs.
curl -X POST http://localhost/screenshot \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{
"urls": ["https://example.com"],
"viewport": "desktop",
"fullPage": true
}'Request:
| Field | Type | Default | Description |
|---|---|---|---|
| urls | string[] | required | URLs to capture (max 10) |
| viewport | "desktop" | "mobile" | "desktop" | Viewport size |
| fullPage | boolean | true | Full page or viewport only |
| waitTime | number | 2000 | Extra wait time (ms, max 30000) |
| clientName | string | - | Filter tag |
| projectName | string | - | Filter tag |
Response:
{
"success": true,
"screenshots": [{
"id": "uuid",
"url": "https://example.com",
"downloadUrl": "/screenshot/uuid/download",
"fileSize": 245678
}]
}Capture an entire website via sitemap (async).
curl -X POST http://localhost/site \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_KEY" \
-d '{
"url": "https://example.com",
"maxPages": 50
}'Request:
| Field | Type | Default | Description |
|---|---|---|---|
| url | string | required | Site URL (will auto-discover sitemap) |
| sitemapUrl | string | - | Override sitemap location |
| maxPages | number | 100 | Max pages to capture |
| viewport | "desktop" | "mobile" | "desktop" | Viewport size |
| fullPage | boolean | true | Full page capture |
Response (202 Accepted):
{
"siteId": "uuid",
"status": "processing",
"totalPages": 28,
"message": "Poll GET /site/uuid for progress."
}Get site capture progress and results.
{
"id": "uuid",
"status": "completed",
"totalPages": 28,
"capturedPages": 28,
"failedPages": 0,
"pages": {
"/": { "id": "uuid", "downloadUrl": "/screenshot/uuid/download" },
"/about": { "id": "uuid", "downloadUrl": "/screenshot/uuid/download" }
}
}Download a screenshot (redirects to presigned S3 URL).
List screenshots with filtering.
| Param | Type | Description |
|---|---|---|
| clientName | string | Filter by client |
| projectName | string | Filter by project |
| limit | number | Max results (default 50) |
| offset | number | Pagination offset |
Delete a screenshot.
Service health check.
| Variable | Default | Description |
|---|---|---|
| POSTGRES_PASSWORD | - | PostgreSQL password |
| MINIO_ACCESS_KEY | - | MinIO access key |
| MINIO_SECRET_KEY | - | MinIO secret key |
| MINIO_PUBLIC_URL | - | Public URL for presigned URLs |
| API_KEYS | - | JSON array of API keys (see below) |
| MAX_CONCURRENT_PAGES | 3 | Parallel browser pages |
| SCREENSHOT_TIMEOUT | 30000 | Page load timeout (ms) |
| PRESIGNED_URL_EXPIRY | 3600 | Download URL validity (seconds) |
Option 1: Environment Variables (recommended for Coolify)
API_KEY=sk_live_your_secret_key
API_KEY_NAME=Production
API_KEY_RATE_LIMIT=10/second
Option 2: Config File (fallback)
Create config/api-keys.json:
{
"keys": [
{
"key": "sk_live_your_secure_key",
"name": "Production",
"rateLimit": "10/second",
"enabled": true
}
]
}Rate limit formats: "10/second", "100/minute"
- Create new service from Git repository
- Set build pack to Docker Compose
- Add environment variables in Coolify dashboard (including
API_KEYS) - Deploy
┌─────────────────────────────────────┐
│ API (port 3000) │
│ Express.js │
└───────────┬───────────┬─────────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ PostgreSQL│ │ MinIO │
│ metadata │ │ storage │
└───────────┘ └───────────┘
- All endpoints served directly by the API
- File downloads streamed through
/screenshot/:id/download - MinIO accessed internally only
Webshot handles scroll-triggered animations (motion.dev, GSAP, AOS, Framer Motion) by:
- Emulating
prefers-reduced-motion: reduce - Injecting CSS to force visibility (
opacity: 1,visibility: visible,transform: none) - Scrolling through the page to trigger lazy loading
- Waiting for content to settle
This ensures full-page screenshots capture all content, not just above-the-fold elements.
- Configurable per API key (e.g.,
"10/second","100/minute") - In-memory sliding window (no database overhead)
- Returns
429 Too Many RequestswithretryAfterheader - Default: 10 requests/second if not specified
MIT