Client
Thumbrella provides client libraries for TypeScript/JavaScript, Python, Rust. Support for Go, Ruby, PHP, and more will be coming soon. These libraries handle caching, streaming, error recovery, and other conveniences on top of the core HTTP API.
There are also higher level clients that provide simple web components like
<Thumbnail src="/media/welcome.pdf">. These are the preferred way to integrate
Thumbrella into web based applications. At launch, these components are available
for React and Astro.
A client library is not required to use Thumbrella. The HTTP API is intentionally simple and works with any tool that can make an HTTP request — curl, fetch, or the standard library of any language.
Connect
Section titled “Connect”Every client is configured through a connect string. The simplest form is a Thumbrella Cloud token or a server URL:
# Cloud token — routes to Thumbrella Cloud automaticallyexport TBR_CONNECT=tbr_a_3QnzBcWx7KpRmYT2vLfJdE9sMhXuoG6i
# Self-hosted serverexport TBR_CONNECT=http://localhost:3114
# Self-hosted server with a handshake secretexport TBR_CONNECT=http://localhost:3114,wafflecones
# Demo server — free, no account neededexport TBR_CONNECT=https://demo.thumbrella.devAll client libraries read $TBR_CONNECT automatically when no explicit
connect string is provided. This makes it easy to point the same application
at a local server for development and Thumbrella Cloud for production without
touching application code.
The connection string can also apply arbitrary http headers to any server. These are provided with comma separated values that define the header. These will only be needed for connect strings that already provide a custom server url.
export TBR_CONNECT=https://tbr.mycompany.net,x-credentials=secret,x-tool-name=frontpageCustom servers can also define a special handshake token. This is simply passed
like the other http headers, but no = assignment is used, just the value.
The handshake and regular http headers can be combined.
export TBR_CONNECT=https://tbr.mycompany.net,secret-handshake-1Libraries
Section titled “Libraries”Install the library for your language and make a first request. Each example
below uses $TBR_CONNECT from the environment.
TypeScript / JavaScript
Section titled “TypeScript / JavaScript”npm install @thumbrella/clientimport { writeFileSync } from "node:fs";import { Client } from "@thumbrella/client";
const tbr = await new Client().verify();const result = await tbr.thumb("https://demo.thumbrella.dev/media/harbor-trucks.mp4");
const m = result.media;if (!m) { console.error("Thumbnail failed:", result.status); process.exit(1);}
console.log(`${m.kind} ${m.fileSize.toLocaleString()} bytes -> ${m.thumbnail.length} bytes`);writeFileSync("thumb.jpg", m.thumbnail.bytes);The Client constructor accepts an optional connect string. Without it the
client reads $TBR_CONNECT. verify() confirms the server is reachable before
making any thumbnail requests — useful at startup to catch misconfiguration
early.
Python
Section titled “Python”uv add thumbrella# or: pip install thumbrellaimport thumbrellafrom pathlib import Path
tbr = thumbrella.Client().verify()result = tbr.thumb("https://demo.thumbrella.dev/media/harbor-trucks.mp4")
m = result.mediaif m is None: print("Thumbnail failed:", result.status) raise SystemExit(1)
print(f"{m.kind} {m.file_size:,} bytes -> {len(m.thumbnail):,} bytes")Path("thumb.jpg").write_bytes(m.thumbnail.bytes)
# Or open directly in Pillow without a temporary filefrom PIL import Imageimg = Image.open(m.thumbnail.io)print(img.mode, img.width, img.height)The Python client provides both a synchronous API (using requests) and an
async streaming interface (using aiohttp). The thumbnail.io property
returns a file-like BytesIO object, so image libraries like Pillow and
OpenCV can load thumbnails without writing to disk.
[dependencies]thumbrella = "0.1"tokio = { version = "1", features = ["full"] }use std::fs;use thumbrella::Client;
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let tbr = Client::new(None); tbr.verify().await?;
let result = tbr.thumb("https://demo.thumbrella.dev/media/harbor-trucks.mp4").await?; if let Some(media) = &result.media { fs::write("thumb.jpg", media.thumbnail.bytes())?; println!( "{} {:>8} bytes -> {} bytes ({})", media.kind, media.file_size, media.thumbnail.len(), result.source.as_deref().unwrap_or("render"), ); } Ok(())}Client::new(None) reads $TBR_CONNECT from the environment. Pass a string
directly to use a specific connect string.
Result
Section titled “Result”Every thumbnail request returns a Result. Results are returned for every URL,
including failures. A placeholder image will always be returned for every request,
even if the server is unreachable or the file format is completely unknown.
The Result structure is split into two levels. The outer top level describes the
operation itself. An inner media field describes data that comes from the
remote media itself and the thumbnail. The media information is what a client
side cache will want to preserve.
| Field | Type | Description |
|---|---|---|
url | string | The original URL that was requested. |
status | string | Outcome of this request. (success failed overloaded intermediate) |
source | string | How the thumbnail was produced. (render shortcut cache not_modified fallback placeholder) |
message | string | Human-readable detail, usually only set on failure. |
duration | number | Wall-clock seconds to produce this result (fractional, e.g. .0015). |
downloadSize | number | Bytes fetched from the upstream source. |
httpStatus | number | HTTP status returned by the upstream source, if fetched. |
media | object | The thumbnail and its metadata. null on total failure. |
The media object carries the stable, cacheable payload. Two results for the
same file share the same media — clients can compare fields to deduplicate.
| Field | Type | Description |
|---|---|---|
url | string | Source URL that produced this thumbnail. |
thumbnail | base64 | JPEG thumbnail, base64-encoded. |
kind | string | Media category. (image video audio document vector geometry archive text binary unknown) |
extension | string | Canonical file extension, no dot (e.g. jpeg, png, pdf). |
mime | string | Sniffed MIME type (e.g. image/jpeg). |
fileSize | number | Content-Length from the upstream server, or 0. |
placeholder | string | Non-empty when the thumbnail is a fallback icon. Clients can compare this to deduplicate placeholder images. |
cache | string | Cache token for round-tripping. Format: <hex_epoch>:<base64>. Empty means do not cache. |
properties | object | Format-specific metadata. See below. |
source describes how the thumbnail was produced. render means a fresh
thumbnail was generated. shortcut means an embedded thumbnail inside the
source file was used (common for video and audio). cache and not_modified
mean the response came from a cache without re-rendering.
Client libraries will convert the thumbnail base64 information into a binary
or bytes representation appropriate for that language. The contents are usually
around 5kb to 10kb in size.
Properties
Section titled “Properties”The media properties object holds format-specific metadata. Many of the fields
are common across kinds where they have the same meaning. If a value cannot be
determined reliably it is omitted.
The values are always numbers. Some properties like “lossless” represent a
boolean value with a numeric 0 or 1.
Image
| Field | Unit | Description |
|---|---|---|
width | px | Source pixel width (not the thumbnail). |
height | px | Source pixel height. |
bpp | bits | Colour bits per pixel, excluding alpha. Omitted when ambiguous. |
alpha | bool | Has a transparency / alpha channel. |
lossless | bool | Codec uses lossless compression. |
Video
| Field | Unit | Description |
|---|---|---|
width | px | Frame width. |
height | px | Frame height. |
bpp | bits | Colour bits per pixel, if known from the codec. |
duration | sec | Playback duration. |
channels | num | Audio track count. 0 if silent. |
Audio
| Field | Unit | Description |
|---|---|---|
channels | num | Audio channel count. 0 if silent. |
duration | sec | Playback duration. |
lossless | bool | Inferred from the file extension (flac, wav, aiff). |
Geometry, Vector, Document, Archive, Text, Binary, Unknown
These kinds currently have no properties.
Batch Streaming
Section titled “Batch Streaming”The server provides two ways to fetch multiple thumbnails in its batch operation.
In both cases the server will process all thumbnails concurrently, the only difference
is when and how the results are returned.
The client libraries will ensure every requested URL receives a Result
object. Even invalid requests or inaccessible servers will provide a
failure placeholder.
The behavior is controlled with the Accepts: HTTP header. In the client
libraries it is exposed as two separate methods, batch() and stream().
The default Accepts is application/json, where the server collects all
results and returns them after every thumbnail has completed.
This can be convenient because each result is returned in the same order
as the requested urls.
The streaming mode is enabled with Accepts: application/ndjson or Accepts: xss-....
In this mode, the server does two additional things.
- Individual Results are provided the moment they are ready, in arbitrary order.
- Intermediate results can be provided after a file has been identified but before using a complicated renderer for the item. These will use the
status: "intermediate"value in the Result.
// TypeScript — stream multiple thumbnails with live progressconst tbr = await new Client().verify();
const urls = [ "https://demo.thumbrella.dev/media/harbor-trucks.mp4", "https://demo.thumbrella.dev/media/painting.jpg", "https://demo.thumbrella.dev/media/document.pdf",];
for await (const result of tbr.stream(urls)) { if (result.status === "intermediate") continue; // placeholder in progress const kind = result.media ? `${result.media.kind}(${result.media.extension})` : "<nomedia>"; console.log(`${result.status.padEnd(12)} ${kind.padEnd(16)} ${result.url}`);}# Python — async streamingimport asyncio, thumbrella
async def main(): tbr = thumbrella.Client().verify() urls = [ "https://demo.thumbrella.dev/media/harbor-trucks.mp4", "https://demo.thumbrella.dev/media/painting.jpg", ] async with tbr: async for result in tbr.stream(urls): if result.status == "intermediate": continue kind = f"{result.media.kind}({result.media.extension})" if result.media else "<nomedia>" print(f"{result.status:<12} {kind:<16} {result.url}")
asyncio.run(main())Caching
Section titled “Caching”Client libraries maintain a local cache of results keyed by URL. On the next request for the same URL the client first checks whether its cached entry is still fresh. If it is, the client returns the cached result immediately without any network call.
By default clients use an in-memory cache that holds a few hundred icons. Ideally a client will configure a persistent cache using one of the optional cache backends provided by each language and environment.
The client caching is layered on top of any server side caching. The two work together quite well using packaged “cache strings”. See the server caching section on ways to interact with these caches directly. Client libraries will handle all this automatically.
# Python — persist cache strings across runs with a JSON fileimport json, asyncio, thumbrellafrom pathlib import Path
CACHE_FILE = Path("thumbrella_cache.json")
async def main(): cache: dict[str, str] = {} try: cache = json.loads(CACHE_FILE.read_text()) except (FileNotFoundError, json.JSONDecodeError): pass
tbr = thumbrella.Client().verify() urls = ["https://demo.thumbrella.dev/media/harbor-trucks.mp4"]
async with tbr: async for result in tbr.stream(urls): if result.media and result.media.cache: cache[result.url] = result.media.cache
CACHE_FILE.write_text(json.dumps(cache))
asyncio.run(main())See the Server docs for how server-side caching interacts with the client layer.
HTTP Thumbnail API
Section titled “HTTP Thumbnail API”There are two URLs used to look up thumbnails.
GET /thumb.jpeg?url=<url>
Section titled “GET /thumb.jpeg?url=<url>”The Thumbrella provides a light weight simple url for basic HTTP requests.
This is a subset of the functionality in the other calls. It is a useful
tool for simple one-off requests or testing. Interactive applications should
prefer to use the other /batch call.
Returns a JPEG thumbnail as the response body. The simplest possible request. There is no way to use client side caching with this call, but the Thumbrella server can still handle caching the results.
curl "http://localhost:3114/thumb.jpeg?url=https://example.com/photo.jpg" \ --output thumb.jpgPass a handshake header for servers that require one:
curl -H "x-tbr-handshake: wafflecones" \ "http://localhost:3114/thumb.jpeg?url=https://example.com/photo.jpg" \ --output thumb.jpgPOST /batch
Section titled “POST /batch”This is the primary url for interacting with Thumbrella. It is a POST request that requires a simple json uploaded body to define the requests.
The Accepts: HTTP header controls if the results are returned in a
single json batch, or incrementally streamed through various streaming
formats. See the stream section section for more
streaming details.
curl -s http://localhost:3114/batch \ -H "Content-Type: application/json" \ -H "Accept: application/x-ndjson" \ -d '{"items": [{"url": "https://example.com/photo.jpg"}]}' \ | jq -c 'del(.media.thumbnail)'The uploaded JSON body must be an object with an items field that is a
list of objects. Each object must have an url field, and can optionally
contain a cache string.
The server will always generate a response for each url. Be aware that if passing
a cache value it is possible the server will respond with status: "not_modified"
for that URL.
{ "items": [ {"url": "https://demo.thumbrella.dev/media/golden-gate.exr"}, {"url": "https://demo.thumbrella.dev/media/game-level.png", "cache": "beef:AAA"}, {"url": "https://demo.thumbrella.dev/media/miss-library.avif"} ]}The batch mode returns a single JSON object with a result for each provided URL, in the same order as requested.
{ "items": [ {"url": "https://demo.thumbrella.dev/media/golden-gate.exr", "status": "success", ...}, {"url": "https://demo.thumbrella.dev/media/game-level.png", "status": "not-modified"}, {"url": "https://demo.thumbrella.dev/media/miss-library.avif", "status": "failed", ...}, ]}HTTP Utility API
Section titled “HTTP Utility API”GET /health
Section titled “GET /health”Returns a small JSON object confirming the server is running:
{"status": "ok", "thumbrella": 1}Client libraries use this endpoint during verify() to confirm the connection
is valid.
The "thumbrella" field contains the major version of the Thumbrella server.
(Currently it returns 0 while the project is in prerelease.)
The Thumbrella Cloud server provides an additional "token": boolean field to
this object. This will confirm if the provided authentication token is valid.
When running a custom server with a TBR_HANDSHAKE defined, this url
will respond with status 4XX when the handshake is invalid.
Components
Section titled “Components”Higher-level components are available for browser environments that wrap the client library and integrate with framework conventions.
- Astro — an
<Image>component that requests thumbnails at build time or on the edge and renders them inline. - React — a
<Thumbnail>component that lazily fetches thumbnails from a Thumbrella server and handles loading and error states.
These components are in the clients repository alongside the core libraries. They handle the client connection, result rendering, and placeholder display so no direct client API usage is needed for standard UI cases.
thumbrella.dev