API Documentation
Integrate Snapoena into your legal tech workflow. All endpoints accept and return JSON.
Base URL: https://snapoena.com
Machine-readable spec: OpenAPI 3.0 (JSON)
Authentication
API keys use the snap_ prefix and are passed as Bearer tokens. Generate keys from your dashboard.
Authorization: Bearer snap_your_api_key_hereThree authentication methods are supported:
- API key (recommended for scripts and integrations) - Pass via the
Authorizationheader as shown above. Keys are available on all tiers. - Session cookie (browser usage) - Automatically set when logged in via the web interface.
- Anonymous - No auth required for basic captures. Free tier rate limits apply.
API keys are hashed with SHA-256 before storage. The raw key is shown only once at creation time and cannot be retrieved again.
POST/api/capture
Capture a webpage using server-side rendering. Snapoena visits the URL with a headless browser, takes a full-page screenshot, embeds a metadata bar (URL, timestamp), computes a SHA-256 hash, and requests an RFC 3161 trusted timestamp.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | The URL to capture. Must be http or https. |
viewport | string | Optional | Device viewport. One of: desktop (1920x1080), tablet (768x1024), or mobile (375x812). Defaults to desktop. |
watermark | boolean | Optional | Include the Snapoena watermark. Defaults to true. Pro and Enterprise tiers can set to false to disable. |
note | string | Optional | Annotation to attach to the capture. Max 500 characters. Example: "Captured for Case #12345". |
Response (200)
{
"id": "abc123xyz",
"url": "https://example.com",
"timestamp": "2026-03-26T14:30:00.000Z",
"sha256": "a3f2b8c1d4e5f6...",
"imageUrl": "https://pub-xxx.r2.dev/captures/abc123xyz.png",
"htmlUrl": "https://pub-xxx.r2.dev/captures/abc123xyz.html",
"pageTitle": "Example Domain",
"truncated": false,
"durationMs": 3200,
"viewport": "desktop",
"tsrVerified": true,
"whoisAvailable": true,
"dnsAvailable": true,
"harAvailable": true,
"mhtmlAvailable": true,
"tlsAvailable": true,
"serverIp": "203.0.113.1",
"serverLocation": "US"
}Response fields
id- Unique capture identifier (nanoid)url- The captured URLtimestamp- ISO 8601 capture timestampsha256- SHA-256 hash of the final imageimageUrl- Direct URL to the PNG screenshothtmlUrl- Direct URL to the captured HTML sourcepageTitle- The page's title tagtruncated- Whether the screenshot was truncated (very long pages)durationMs- Capture duration in millisecondsviewport- The viewport usedtsrVerified- Whether an RFC 3161 timestamp was obtainedwhoisAvailable/dnsAvailable/harAvailable/mhtmlAvailable/tlsAvailable- Whether each evidence artifact is available for downloadserverIp- IP address of the capture serverserverLocation- Geographic location of the capture server
POST/api/capture/extension
Submit a capture from the browser extension. Unlike the server-side capture endpoint, the extension captures the visible tab locally and sends the data here for processing. This works for authenticated or logged-in pages that the server cannot access.
API key authentication is required. Anonymous extension captures are not supported.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | The URL of the captured page. Must be http or https. |
screenshot | string | Required | Base64 data URL of the screenshot. Must be a PNG or JPEG data URL (e.g. data:image/png;base64,...). |
html | string | Required | The full HTML source of the page. |
pageTitle | string | Optional | The page title. Max 500 characters. |
mhtml | string | Optional | Base64-encoded MHTML web archive. Captured via chrome.pageCapture. |
har | string | Optional | Base64-encoded HAR (HTTP Archive) data. Must be valid JSON with a log.version and log.entries array. |
Response (200)
{
"id": "ext_abc123",
"url": "https://app.example.com/dashboard",
"timestamp": "2026-03-26T14:30:00.000Z",
"sha256": "b4c3d2e1f0...",
"imageUrl": "https://pub-xxx.r2.dev/captures/ext_abc123.png",
"htmlUrl": "https://pub-xxx.r2.dev/captures/ext_abc123.html",
"pageTitle": "Dashboard",
"durationMs": 1200,
"captureMethod": "extension",
"tsrVerified": true
}Maximum payload size: 50 MB. Request timeout: 60 seconds.
POST/api/bulk
Capture multiple URLs in a single request. URLs are processed sequentially, and each counts against your monthly capture quota. Requires authentication (API key or session).
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
urls | string[] | Required | Array of URLs to capture. Maximum count depends on your tier: Free (3), Professional (25), Enterprise (100). |
viewport | string | Optional | Device viewport applied to all captures. One of: desktop, tablet, or mobile. Defaults to desktop. |
watermark | boolean | Optional | Include watermark. Pro/Enterprise can set to false. |
Response (200)
{
"results": [
{
"url": "https://example.com",
"id": "abc123",
"status": "success"
},
{
"url": "https://invalid.test",
"status": "error",
"error": "Could not resolve domain: invalid.test"
}
],
"summary": {
"total": 2,
"succeeded": 1,
"failed": 1
}
}Individual URL failures do not fail the entire batch. Each result includes its own status.
POST/api/capture/[id]/extend
Extend the retention period of a capture. Only available to Pro and Enterprise users. You must own the capture. Authentication is via session cookie (not API key).
Request
No request body is needed. The capture ID is in the URL path.
Response (200)
{
"success": true,
"expiresAt": "2027-03-26T14:30:00.000Z",
"retentionDays": 365
}Error responses
401-UNAUTHORIZED- Login required or session expired403-UPGRADE_REQUIRED- Free tier cannot extend retention403-FORBIDDEN- You do not own this capture404-NOT_FOUND- Capture not found or expired
Download endpoints
After a capture completes, use these endpoints to download individual artifacts. All are GET requests with the capture ID in the path.
| Endpoint | Description |
|---|---|
/api/capture/{id}/pdf | PDF evidence report |
/api/capture/{id}/webp | Screenshot as WebP (smaller than PNG) |
/api/capture/{id}/html | Captured HTML source |
/api/capture/{id}/tsr | RFC 3161 timestamp token (.tsr) |
/api/capture/{id}/bundle | Complete evidence bundle (ZIP) |
/api/capture/{id}/whois | WHOIS data as plain text (.com/.net only) |
/api/capture/{id}/dns | DNS records as plain text |
/api/capture/{id}/har | HTTP Archive (HAR) of network requests |
/api/capture/{id}/mhtml | MHTML web archive |
/api/capture/{id}/text | Extracted page text |
/api/capture/{id}/tls | TLS certificate info |
/api/capture/{id}/json | Machine-readable capture metadata |
Search and export
All search endpoints require authentication.
GET /api/captures/search?q=- Search captures by URLGET /api/captures/text-search?q=- Search captures by page contentGET /api/captures/export- Export captures as CSV
Monitors
Monitors automatically capture a URL on a schedule. Available on Pro (10 monitors) and Enterprise (unlimited) tiers.
GET /api/monitors- List your monitors (authenticated)POST /api/monitors- Create a monitor (Pro/Enterprise)
Actions
POST /api/capture/{id}/send- Send evidence via email (authenticated)
Rate limits
Every API response includes rate limit headers:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 17
X-RateLimit-Reset: 1711500000X-RateLimit-Limit- Your monthly capture quotaX-RateLimit-Remaining- Captures remaining this monthX-RateLimit-Reset- Unix timestamp when the limit resets
When the limit is exceeded, the response also includes a Retry-After header with the number of seconds until the limit resets. The HTTP status will be 429 Too Many Requests.
Tier limits
| Feature | Free | Professional | Enterprise |
|---|---|---|---|
| Monthly captures | 20 | Unlimited | Unlimited |
| Retention | 7 days | 365 days | Unlimited |
| Bulk capture (per batch) | 3 URLs | 25 URLs | 100 URLs |
| Monitors | - | 10 | Unlimited |
| Priority queue | No | Yes | Yes |
| Watermark removal | No | Yes | Yes |
| API access | Yes | Yes | Yes |
See pricing for full plan details.
Error codes
All errors return a consistent JSON structure:
{
"error": {
"code": "INVALID_URL",
"message": "Only http and https URLs are allowed."
}
}| Code | HTTP | Description |
|---|---|---|
MISSING_URL | 400 | No URL provided in the request body |
INVALID_URL | 400 | URL is not a valid http/https address |
DNS_RESOLUTION_FAILED | 400 | Domain could not be resolved |
PAGE_LOAD_ERROR | 400 | Page returned an HTTP error status |
PAGE_BLOCKED | 400 | Site blocked the capture tool |
CAPTURE_TIMEOUT | 408 | Page took too long to load (over 30 seconds) |
AUTH_REQUIRED | 401 | API key is required for this endpoint |
INVALID_API_KEY | 401 | API key is invalid or has been revoked |
UNAUTHORIZED | 401 | Login required or session expired |
FORBIDDEN | 403 | You do not own this resource |
UPGRADE_REQUIRED | 403 | Feature requires a paid tier |
NOT_FOUND | 404 | Capture not found or expired |
RATE_LIMIT_EXCEEDED | 429 | Monthly capture limit reached |
PAYLOAD_TOO_LARGE | 413 | Request body exceeds 50 MB limit |
MISSING_SCREENSHOT | 400 | Screenshot data URL is required (extension) |
MISSING_HTML | 400 | Page HTML is required (extension) |
INVALID_SCREENSHOT | 400 | Screenshot must be a PNG or JPEG data URL |
EMPTY_SCREENSHOT | 400 | Screenshot data is empty |
MISSING_URLS | 400 | URLs array is required (bulk) |
EMPTY_URLS | 400 | At least one URL is required (bulk) |
BULK_LIMIT_EXCEEDED | 400 | Too many URLs for your tier |
INVALID_BODY | 400 | Request body must be valid JSON |
BROWSER_CRASH | 500 | Capture failed due to browser crash |
IMAGE_PROCESSING_ERROR | 500 | Failed to process the screenshot |
STORAGE_ERROR | 500 | Storage service error |
PDF_GENERATION_ERROR | 500 | PDF report generation failed |
SERVICE_BUSY | 503 | Server is at capacity - try again shortly |
INTERNAL_ERROR | 500 | Unexpected server error |
Code examples
Capture a URL with curl
curl -X POST https://snapoena.com/api/capture \
-H "Content-Type: application/json" \
-H "Authorization: Bearer snap_your_api_key" \
-d '{"url": "https://example.com"}'Capture with options
curl -X POST https://snapoena.com/api/capture \
-H "Content-Type: application/json" \
-H "Authorization: Bearer snap_your_api_key" \
-d '{
"url": "https://example.com",
"viewport": "mobile",
"watermark": false,
"note": "Evidence for Case #12345"
}'JavaScript (fetch)
const response = await fetch("https://snapoena.com/api/capture", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer snap_your_api_key",
},
body: JSON.stringify({
url: "https://example.com",
viewport: "desktop",
}),
});
const data = await response.json();
if (!response.ok) {
console.error("Capture failed:", data.error.code, data.error.message);
} else {
console.log("Capture ID:", data.id);
console.log("Screenshot:", data.imageUrl);
console.log("SHA-256:", data.sha256);
console.log("Timestamp verified:", data.tsrVerified);
}Bulk capture with curl
curl -X POST https://snapoena.com/api/bulk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer snap_your_api_key" \
-d '{
"urls": [
"https://example.com",
"https://example.org",
"https://example.net"
]
}'Download the evidence bundle
# After capturing, download the full evidence ZIP
curl -O https://snapoena.com/api/capture/abc123xyz/bundleHandle rate limits
const response = await fetch("https://snapoena.com/api/capture", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer snap_your_api_key",
},
body: JSON.stringify({ url: "https://example.com" }),
});
// Check rate limit headers
const remaining = response.headers.get("X-RateLimit-Remaining");
const limit = response.headers.get("X-RateLimit-Limit");
console.log(`Captures remaining: ${remaining}/${limit}`);
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After");
console.log(`Rate limited. Retry after ${retryAfter} seconds.`);
}Notes
- All responses are JSON unless otherwise noted (e.g. download endpoints return binary data).
- Server-side capture (
/api/capture) cannot access authenticated or login-gated pages. Use the extension endpoint for those. - Free tier captures are stored for 7 days. Pro captures are kept for 1 year. Enterprise retention is unlimited.
- SSRF protection: private IPs and internal networks are blocked.
- RFC 3161 timestamps are provided by FreeTSA (freetsa.org).
- WHOIS lookups are available for .com and .net domains only.
- DNS, TLS, and WHOIS evidence supplements are added automatically for paid tier extension captures.