Screenshot
Screenshot
Capture a web page as an image: the visible window, the whole page, a single element by CSS selector, or, in impressum mode, a tight crop of the company details on an Impressum page.
[!NOTE]
See the API Guide for the shared request lifecycle (sync, async polling, async webhook), error envelope, custom_vars, and deduplication. This page documents only the fields and behaviour unique to this endpoint.
Try it
Pricing
Credits are reserved when the request arrives and booked only when an image is delivered. Every failed capture gives the reservation back, so a blocked page, an internal address or a missing Impressum block costs nothing.
Endpoint
The generic endpoint answers with JSON and a signed bild_url. To get the image itself in the response, use the dedicated endpoint below.
Image endpoint: `POST /api/v1/screenshot`
Same input fields as below, plus:
| Field |
Type |
Default |
Description |
antwort |
string |
bild |
bild returns the image bytes (image/png or image/webp). json returns JSON with bild_base64. |
async |
boolean |
false |
true answers 202 at once with an ergebnis_url; fetch the image there. |
With antwort: bild the response carries these headers:
| Header |
Meaning |
X-Screenshot-Id |
Id of the screenshot (the enrichment id) |
X-Screenshot-Final-Url |
URL after redirects |
X-Screenshot-Breite |
Image width in pixels |
X-Screenshot-Hoehe |
Image height in pixels |
X-Credits-Charged |
Credits booked for this image |
The request waits up to 90 seconds. If the screenshot is not done by then, it answers 202 with ergebnis_url. GET /api/v1/screenshot/{id} (optional ?antwort=json) returns the image once it is done, 202 while it is still being taken, 422 when it failed and 410 after the 7 day retention.
curl -X POST https://api.skael.de/api/v1/screenshot \
-H "Authorization: Bearer eak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.example.de/impressum", "modus": "impressum"}' \
--output impressum.png
{
"id": 123,
"status": "completed",
"finale_url": "https://www.example.de/impressum/",
"http_status": 200,
"modus": "impressum",
"format": "png",
"breite": 1134,
"hoehe": 992,
"bytes": 87341,
"bild_url": "https://files.skael.de/screenshots/42/123.png?X-Amz-Expires=86400",
"bild_url_gueltig_bis": "2026-10-27T09:15:00+00:00",
"ausschnitt": { "x": 146, "y": 186, "breite": 567, "hoehe": 496 },
"angaben": ["firma", "anschrift", "vertretung", "register", "steuer", "kontakt"],
"bild_base64": "iVBORw0KGgoAAAANSUhEUgAA...",
"credits": 5
}
Impressum mode
modus: impressum looks for the block with the company details: company name with legal form, address, managing directors or owner, commercial register (HRB, HRA, FN, register court), VAT id (USt-IdNr., UID) or tax number, phone and e-mail. Lines that sit close together and carry these details form a block; the block with the most kinds of details wins, legal prose such as liability disclaimers does not count. The crop is about 24 px around the block, narrower where another line would otherwise be cut.
Before the capture, cookie banners are accepted or removed, the page is scrolled once to the bottom and back so lazy images load, and fixed elements over the crop (sticky headers, chat buttons) are hidden. angaben lists which details were found. When no block with at least three kinds of details exists, the screenshot fails with No company details block (Impressum) found on the page and costs nothing.
Security and limits
- Only
http and https on ports 80 and 443. URLs with credentials are refused.
- Private, loopback, link-local and cluster-internal addresses are refused when the request arrives (IP addresses,
localhost, names without a dot, .local, .internal, .svc) and again when the page is loaded: the browser blocks every request to such an address, and a page that redirects to one or loads anything from one fails without an image.
- Load timeout 5 to 60 seconds (
timeout). Full pages are capped at 16000 image pixels in height, images at 10 MB.
- Images are kept for 7 days.
bild_url is valid for 24 hours.
- At most 30 screenshot requests per minute and organization on
POST /api/v1/screenshot (429 with code: rate_limited beyond). Screenshots run on their own low-priority queue with a few parallel captures per organization; at peak times they wait instead of failing.
Examples
Output Fields
Error Responses
Validation Error (422)
{
"message": "The url must be a public http or https address.",
"errors": {
"url": ["The url must be a public http or https address."]
}
}
Failed Screenshot (422)
{
"id": 123,
"status": "failed",
"error_message": "Target website redirected to or loaded content from an internal address",
"credits": 0
}
Common error messages:
No company details block (Impressum) found on the page
No visible element matches the selector
Target website redirected to or loaded content from an internal address
Target website blocked the browser (cloudflare)
Target website did not respond in time
Screenshot exceeds the size limit of 10.0 MB
OpenAPI
openapi: 3.1.0
info:
title: Skael Screenshot API
version: '1'
servers:
- url: https://api.skael.de/api
security:
- bearerAuth: []
paths:
/v1/screenshot:
post:
summary: Take a screenshot
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url]
properties:
url: { type: string, maxLength: 2048 }
viewport_breite: { type: integer, minimum: 320, maximum: 2560, default: 1440 }
skala: { type: integer, enum: [1, 2], default: 2 }
voll_seite: { type: boolean, default: false }
selektor: { type: string, maxLength: 500 }
modus: { type: string, enum: [seite, impressum], default: seite }
format: { type: string, enum: [png, webp], default: png }
timeout: { type: integer, minimum: 5, maximum: 60, default: 30 }
antwort: { type: string, enum: [bild, json], default: bild }
async: { type: boolean, default: false }
responses:
'200':
description: The image, or JSON with bild_base64 when antwort is json
content:
image/png: { schema: { type: string, format: binary } }
image/webp: { schema: { type: string, format: binary } }
application/json: { schema: { $ref: '#/components/schemas/Screenshot' } }
'202': { description: Still being taken, fetch ergebnis_url }
'402': { description: Not enough credits }
'422': { description: Invalid input or the screenshot failed (no charge) }
'429': { description: Rate limit of the organization reached }
/v1/screenshot/{id}:
get:
summary: Fetch a screenshot
parameters:
- { name: id, in: path, required: true, schema: { type: integer } }
- { name: antwort, in: query, schema: { type: string, enum: [bild, json], default: bild } }
responses:
'200': { description: The image or JSON }
'202': { description: Still being taken }
'404': { description: Not found for this organization }
'410': { description: Image no longer stored }
'422': { description: The screenshot failed }
components:
securitySchemes:
bearerAuth: { type: http, scheme: bearer }
schemas:
Screenshot:
type: object
properties:
id: { type: integer }
status: { type: string }
finale_url: { type: string }
http_status: { type: [integer, 'null'] }
modus: { type: string }
format: { type: string }
breite: { type: integer }
hoehe: { type: integer }
bytes: { type: integer }
bild_url: { type: string }
bild_url_gueltig_bis: { type: string, format: date-time }
ausschnitt:
type: [object, 'null']
properties: { x: { type: integer }, y: { type: integer }, breite: { type: integer }, hoehe: { type: integer } }
angaben: { type: array, items: { type: string } }
bild_base64: { type: string }
credits: { type: integer }
Integration Examples
JavaScript/Node.js
import { writeFile } from 'node:fs/promises';
const response = await fetch('https://api.skael.de/api/v1/screenshot', {
method: 'POST',
headers: {
Authorization: 'Bearer eak_your_api_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: 'https://www.example.de/impressum', modus: 'impressum' }),
});
if (response.status === 200) {
await writeFile('impressum.png', Buffer.from(await response.arrayBuffer()));
}
Python
import requests
response = requests.post(
'https://api.skael.de/api/v1/screenshot',
headers={'Authorization': 'Bearer eak_your_api_key_here'},
json={'url': 'https://www.example.de/impressum', 'modus': 'impressum', 'antwort': 'json'},
)
screenshot = response.json()
print(screenshot['breite'], screenshot['hoehe'], screenshot['angaben'])