ThumbTo.it

thumbto.it documentation

thumbto.it makes thumbnail images of documents: the page of a PDF you choose, ready to put in a web page or app. Thumbnails of web pages and Office documents are coming; asking for them today returns input_not_available.

Each account gets one or more keys. A key has two parts:

  • a key id like tk_9cXk2…, which is public and appears in image URLs;
  • a secret like ts_Hq4…, which signs your image URLs and authenticates API calls. Keep it on your server: never put it in a web page or an app people download.

There are two ways to get thumbnails. Image URLs go straight into an <img> tag and are made on first view. The API lets your server ask ahead of time, check progress, and be told by webhook when a thumbnail is ready.

Image URLs

https://thumbto.it/i/{key_id}/{signature}?url=…&kind=document[&page=1&w=400&h=300&fit=cover&fmt=webp]
ParameterMeaning
urlRequired. The document's address: http or https, standard ports, on the public internet.
kindRequired. document (today a PDF). web is coming.
pageWhich page of a document, from 1 (the default).
wWidth in pixels, 16–1600. Default 400.
hHeight in pixels, 16–3200. Leave it out to keep the source's proportions.
fitWith h: cover (the default) fills w × h, cropping from the top; contain fits inside w × h without cropping.
fmtwebp (the default), png or jpeg.

What a request returns

  • Ready: the image, with Cache-Control, an ETag, and X-Thumbto-Status: ready. Every size and format comes from one render: changing w, h, fit or fmt never renders again.
  • First request: the thumbnail is queued and the request waits up to a few seconds for it. If it isn't ready by then, you get a Preparing preview placeholder (Cache-Control: no-store, X-Thumbto-Status: queued or rendering) and the real image on a later load.
  • Failed: a No preview placeholder with the error's HTTP status and X-Thumbto-Error set to an error code.
  • A bad key, signature or parameter: a JSON error, with no image.

A document's thumbnail is kept until it hasn't been viewed for 30 days. To make a new one after the document changes, use the API's refresh.

Signing a URL

The signature is the base64url encoding (no = padding) of HMAC-SHA256 of the query string, keyed with your secret. Sign the exact text that follows the ?, in the order you send it: any URL encoding and parameter order works, as long as the URL isn't changed after signing.

PHP

function thumbto_url(string $keyId, string $secret, array $params): string
{
    $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    $sig = rtrim(strtr(base64_encode(hash_hmac('sha256', $query, $secret, true)), '+/', '-_'), '=');

    return "https://thumbto.it/i/{$keyId}/{$sig}?{$query}";
}

echo thumbto_url('tk_…', getenv('THUMBTO_SECRET'), ['url' => 'https://example.com/spec.pdf', 'kind' => 'document', 'w' => 400]);

JavaScript (Node.js)

import { createHmac } from 'node:crypto';

function thumbtoUrl(keyId, secret, params) {
  const query = new URLSearchParams(params).toString();
  const sig = createHmac('sha256', secret).update(query).digest('base64url');
  return `https://thumbto.it/i/${keyId}/${sig}?${query}`;
}

Python

import base64, hashlib, hmac
from urllib.parse import urlencode

def thumbto_url(key_id, secret, params):
    query = urlencode(params)
    sig = base64.urlsafe_b64encode(hmac.new(secret.encode(), query.encode(), hashlib.sha256).digest()).rstrip(b"=").decode()
    return f"https://thumbto.it/i/{key_id}/{sig}?{query}"

Go

func thumbtoURL(keyID, secret string, params url.Values) string {
	query := params.Encode()
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(query))
	sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return "https://thumbto.it/i/" + keyID + "/" + sig + "?" + query
}

Signed URLs can be copied and reused, which is fine: they only ever show that thumbnail. Only your secret can make new ones.

API

Base https://thumbto.it/api/v1. Send Authorization: Bearer ts_… (a key with the API scope). Answers are JSON.

RequestDoes
POST /thumbnailsAsk for a thumbnail: {"kind": "document", "url": "…", "page": 1, "refresh": false}. 200 if it's already made, 202 if queued. Asking again for the same thing returns the same thumbnail. refresh: true renders it again.
GET /thumbnails/{id}Its status: queued, rendering, ready or failed. Poll this, or use a webhook.
GET /thumbnails/{id}/imageThe image, taking w, h, fit and fmt as above. For servers that would rather fetch it than embed a URL.
GET /usageThis month's renders, cache hits, failures and bytes served, with your limits.
curl -s https://thumbto.it/api/v1/thumbnails \
  -H "Authorization: Bearer $THUMBTO_SECRET" -H 'Content-Type: application/json' \
  -d '{"kind": "document", "url": "https://example.com/spec.pdf"}'

{
  "id": "Aj3o3A5u",
  "kind": "document",
  "url": "https://example.com/spec.pdf",
  "page": 1,
  "status": "ready",
  "error": null,
  "width": 1275, "height": 1650,
  "source": {"content_type": "application/pdf", "bytes": 837376, "pages": 4, "sha256": "…"},
  "requested_at": "2026-10-31T14:02:11+00:00",
  "rendered_at": "2026-10-31T14:02:12+00:00",
  "image_url": "https://thumbto.it/i/tk_…/…?url=…&kind=document&page=1&w=400",
  "links": {"self": "…/api/v1/thumbnails/Aj3o3A5u", "image": "…/api/v1/thumbnails/Aj3o3A5u/image"}
}

image_url is a ready-signed image URL, present when the key can also sign image URLs. Fields may be added; existing ones won't change.

Webhooks

Optional: pulling always works. If your account has a webhook, thumbnails asked for through the API send thumbnail.ready or thumbnail.failed when they finish:

POST https://your-app.example/hooks/thumbto
webhook-id: msg_Aj3o3A5u_1761919332
webhook-timestamp: 1761919333
webhook-signature: v1,K5o…=

{"type": "thumbnail.ready", "timestamp": "…", "data": { …the thumbnail, as GET /thumbnails/{id}… }}

They follow the Standard Webhooks scheme, so its libraries verify them. By hand: base64-decode your secret after whsec_, compute HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, base64 it, and compare with the value after v1,. Reject timestamps more than 5 minutes old.

Answer with any 2xx. Otherwise we retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then stop. Webhook URLs must be HTTPS on a public address; redirects aren't followed. Use webhook-id to ignore repeats.

Errors

Errors are {"error": {"code": "…", "message": "…"}}. Program against the code; messages may be reworded. Codes are only ever added.

CodeHTTPMeaning
invalid_key401The key is unknown, revoked, expired, or lacks the scope for this.
invalid_signature403The signature does not match the URL.
rate_limited429Too many new thumbnails requested in the last minute for this account's plan.
input_not_available422This kind of thumbnail is not available yet.
not_found404No such thumbnail on this account.
account_suspended403This account is suspended.
input_not_allowed403This account's plan does not include this kind of thumbnail.
quota_exceeded429This account has used this month's renders.
invalid_url422The URL is not a valid http or https address.
invalid_options422The options are not valid.
unresolvable_host422The URL's host name does not resolve.
blocked_address422The URL points at a private or reserved network address.
source_unreachable502The source could not be reached.
source_http_error502The source answered with an error.
source_too_large422The source is larger than this plan allows.
source_type_unsupported422The source is not a supported type.
source_blocked_address422The source redirected to a private or reserved network address.
document_encrypted422The document is password protected.
page_out_of_range422The document does not have that page.
render_failed500The renderer could not make an image from the source.
render_timeout504Rendering took too long.

Plans and limits

PlanRenders a monthNew renders a minuteLargest document
Trial100 1010 MB
Starter5,000 3025 MB
Business50,000 12050 MB

A render is one new thumbnail. Serving one already made — in any size or format — is not a render. Thumbnails waiting in the queue count toward the monthly limit, so it can't be overrun by asking quickly.

Our renderer

Documents are fetched from the internet by our renderers, which identify themselves as thumbto.it-renderer/1.0 (+https://thumbto.it). They only connect to public internet addresses — never a private network — keep no cookies, and delete every file they fetch once the thumbnail is made. Only the thumbnail image is kept.

Acceptable use

Use thumbto.it for documents you have the right to show. We remove thumbnails and block sources on a valid request from the owner of the material: write to contact@scaledapps.com with the image URL. See also the terms and privacy policy.