A self-hosted image API. Ask for a keyword and a size, get a real Unsplash photo served from local storage โ cached, resized, instant. Falls back to a clean generated placeholder when no match exists.
Two endpoints. No keys. No SDK. Drop a URL into any <img> tag.
Pulls a matching photo, resizes & center-crops it, caches the result.
GET /api/photo/nature/800/600
Random gradient or solid color, optional text and rounded corners.
GET /api/400/300?color=6366f1&text=Hello
All images below are live calls to this server. Keywords come from a curated set of ~24,000 tags. Add ?seed=anything to lock the same photo across requests.
Returns JPEG. Falls back to a generated placeholder when no photo is found for the keyword.
| Param | Type | Default | Description |
|---|---|---|---|
keyword | path | โ | Subject of the photo. Examples: nature, mountain, water, animal, sunset, forest. |
width | path ยท int | โ | 1โ2000 pixels. |
height | path ยท int | โ | 1โ2000 pixels. |
seed | query ยท string | random | Stable photo selection. Same seed โ same photo across requests. |
grayscale | query ยท bool | false |
Convert to black & white. |
Pure programmatic placeholders โ no photo lookup. Useful for layouts, design mocks, fallbacks.
| Param | Type | Default | Description |
|---|---|---|---|
text | string | placeholder | Centered text label. |
color | string | gradient | 6-char hex (ff0000) or named color (red, skyblue, orange...). |
radius | int | 0 | Rounded corner radius in pixels (SSAA-smoothed). |
grayscale | bool | false | Black & white. |
hide_size | bool | false | Hide the WxH suffix from text. |
no_text | bool | false | Disable all text rendering. |
When you don't need a photo โ or when one isn't available.
Because every project needs placeholders, and most public services rate-limit or disappear.
Single static Go binary plus a SQLite file. Air-gap friendly.
First request resizes from the master. Every request after is a static file read.
Same seed always returns the same photo. Same URL โ same bytes.
No photo for the keyword? You still get a clean gradient with the keyword as label.