API reference
Generate a thumbnail in three calls.
Poke a URL, poll until it's ready, then point an <img> at it. Same flow in any language — the examples below are the whole integration.
Base URL
Auth — send your key as the
Sizes —
https://webthumb.wisw.netAuth — send your key as the
X-API-Key header on POST /thumbs. Get one from your account. Serving needs no key.Sizes —
960x540, 640x360, 480x270, 1200x630.
01Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /thumbs | Queue a capture. Idempotent. Returns key, status, serveUrl, statusUrl. Needs X-API-Key. |
| GET | /thumbs/{key}/status | Poll the capture: pending → generating → ready (or failed). |
| GET | /t/{key}.png | The image. Placeholder until ready, then immutable + CDN-cacheable. No key. |
02Request body
| Field | Type | Notes |
|---|---|---|
url | string | The page to capture. Must be public http(s). |
size | string | One of the allowed sizes. Default 960x540. |
lastupdate | integer | Version token. Bump it (e.g. a unix time) to force a fresh capture; same value reuses the cached one. |
priority | integer | Optional. Lower = sooner in the queue. Default 100. |
03Full example — generate & get the URL
Each tab creates a thumbnail, waits for it, and prints the ready serveUrl.
# 1. Generate (queue a capture) curl -s -X POST https://webthumb.wisw.net/thumbs \ -H "X-API-Key: $WEBTHUMB_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","size":"960x540","lastupdate":1}' # → {"key":"cc35…","status":"pending","serveUrl":"…/t/cc35….png", …} # 2. Poll until ready curl -s https://webthumb.wisw.net/thumbs/cc35…/status # → {"status":"ready","serveUrl":"…"} # 3. Embed — no key needed # <img src="https://webthumb.wisw.net/t/cc35….png">
import time, requests BASE = "https://webthumb.wisw.net" KEY = "wt_your_key" def thumbnail(url, size="960x540", version=1): r = requests.post(f"{BASE}/thumbs", headers={"X-API-Key": KEY}, json={"url": url, "size": size, "lastupdate": version}) r.raise_for_status() d = r.json() for _ in range(30): s = requests.get(f"{BASE}/thumbs/{d['key']}/status").json() if s["status"] == "ready": return d["serveUrl"] if s["status"] == "failed": raise RuntimeError("capture failed") time.sleep(1) return d["serveUrl"] # still pending; placeholder resolves later print(thumbnail("https://example.com"))
// Node 18+ (built-in fetch) const BASE = "https://webthumb.wisw.net"; const KEY = process.env.WEBTHUMB_KEY; async function thumbnail(url, size = "960x540", version = 1) { const r = await fetch(`${BASE}/thumbs`, { method: "POST", headers: { "X-API-Key": KEY, "Content-Type": "application/json" }, body: JSON.stringify({ url, size, lastupdate: version }), }); if (!r.ok) throw new Error(`poke failed: ${r.status}`); const { key, serveUrl } = await r.json(); for (let i = 0; i < 30; i++) { const s = await (await fetch(`${BASE}/thumbs/${key}/status`)).json(); if (s.status === "ready") return serveUrl; if (s.status === "failed") throw new Error("capture failed"); await new Promise(res => setTimeout(res, 1000)); } return serveUrl; } thumbnail("https://example.com").then(console.log);
<?php $BASE = "https://webthumb.wisw.net"; $KEY = getenv("WEBTHUMB_KEY"); function thumbnail($url, $size = "960x540", $version = 1) { global $BASE, $KEY; $ch = curl_init("$BASE/thumbs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-API-Key: $KEY", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["url"=>$url, "size"=>$size, "lastupdate"=>$version]), ]); $d = json_decode(curl_exec($ch), true); curl_close($ch); for ($i = 0; $i < 30; $i++) { $s = json_decode(file_get_contents("$BASE/thumbs/{$d['key']}/status"), true); if ($s["status"] === "ready") return $d["serveUrl"]; if ($s["status"] === "failed") throw new Exception("capture failed"); sleep(1); } return $d["serveUrl"]; } echo thumbnail("https://example.com"), "\n";
use strict; use warnings; use LWP::UserAgent; use JSON::PP; use HTTP::Request; my $BASE = "https://webthumb.wisw.net"; my $KEY = $ENV{WEBTHUMB_KEY}; my $ua = LWP::UserAgent->new; sub thumbnail { my ($url, $size, $version) = @_; $size ||= "960x540"; $version ||= 1; my $req = HTTP::Request->new(POST => "$BASE/thumbs"); $req->header("X-API-Key" => $KEY, "Content-Type" => "application/json"); $req->content(encode_json({ url => $url, size => $size, lastupdate => $version })); my $d = decode_json($ua->request($req)->decoded_content); for (1..30) { my $s = decode_json($ua->get("$BASE/thumbs/$d->{key}/status")->decoded_content); return $d->{serveUrl} if $s->{status} eq "ready"; die "capture failed" if $s->{status} eq "failed"; sleep 1; } return $d->{serveUrl}; } print thumbnail("https://example.com"), "\n";
require "net/http"; require "json"; require "uri" BASE = "https://webthumb.wisw.net" KEY = ENV["WEBTHUMB_KEY"] def thumbnail(url, size: "960x540", version: 1) res = Net::HTTP.post(URI("#{BASE}/thumbs"), { url: url, size: size, lastupdate: version }.to_json, "X-API-Key" => KEY, "Content-Type" => "application/json") d = JSON.parse(res.body) 30.times do s = JSON.parse(Net::HTTP.get(URI("#{BASE}/thumbs/#{d['key']}/status"))) return d["serveUrl"] if s["status"] == "ready" raise "capture failed" if s["status"] == "failed" sleep 1 end d["serveUrl"] end puts thumbnail("https://example.com")
package main import ("bytes"; "encoding/json"; "fmt"; "net/http"; "time") const base = "https://webthumb.wisw.net" var key = "wt_your_key" func thumbnail(url, size string, version int) (string, error) { body, _ := json.Marshal(map[string]any{"url": url, "size": size, "lastupdate": version}) req, _ := http.NewRequest("POST", base+"/thumbs", bytes.NewReader(body)) req.Header.Set("X-API-Key", key) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return "", err } var d struct{ Key, ServeUrl string } json.NewDecoder(res.Body).Decode(&d); res.Body.Close() for i := 0; i < 30; i++ { s, _ := http.Get(base + "/thumbs/" + d.Key + "/status") var st struct{ Status string } json.NewDecoder(s.Body).Decode(&st); s.Body.Close() if st.Status == "ready" { return d.ServeUrl, nil } if st.Status == "failed" { return "", fmt.Errorf("capture failed") } time.Sleep(time.Second) } return d.ServeUrl, nil } func main() { u, _ := thumbnail("https://example.com", "960x540", 1); fmt.Println(u) }
using System.Net.Http.Json; using System.Text.Json; var BASE = "https://webthumb.wisw.net"; var KEY = Environment.GetEnvironmentVariable("WEBTHUMB_KEY"); var http = new HttpClient(); http.DefaultRequestHeaders.Add("X-API-Key", KEY); async Task<string> Thumbnail(string url, string size = "960x540", int version = 1) { var res = await http.PostAsJsonAsync($"{BASE}/thumbs", new { url, size, lastupdate = version }); var d = await res.Content.ReadFromJsonAsync<JsonElement>(); var key = d.GetProperty("key").GetString(); var serve = d.GetProperty("serveUrl").GetString(); for (int i = 0; i < 30; i++) { var s = await http.GetFromJsonAsync<JsonElement>($"{BASE}/thumbs/{key}/status"); var status = s.GetProperty("status").GetString(); if (status == "ready") return serve; if (status == "failed") throw new Exception("capture failed"); await Task.Delay(1000); } return serve; } Console.WriteLine(await Thumbnail("https://example.com"));
04Serving in your UI
Once you have a serveUrl, it's just an image. It shows a placeholder until the capture is ready, then the real PNG — cached and unlimited.
<img src="https://webthumb.wisw.net/t/cc35….png" alt="preview">
Tip — poke on save (fire-and-forget), store the returned
serveUrl on your record, and render the <img> immediately. To make a tile fill in live, poll /thumbs/{key}/status and swap img.src when it turns ready. Need Java, Rust, Elixir, or an OpenAPI spec? Ask us.