Skip to content

Instantly share code, notes, and snippets.

@aheld
Created May 28, 2026 17:55
Show Gist options
  • Select an option

  • Save aheld/3dc4d459db8275ace9375a5dca5cc2df to your computer and use it in GitHub Desktop.

Select an option

Save aheld/3dc4d459db8275ace9375a5dca5cc2df to your computer and use it in GitHub Desktop.
Unsplash Skill creator prompt
# Prompt: Generate a use-case-specific Unsplash image skill
Paste everything below the line into Claude Code (or any Claude session that can
create files). It will interview you about *what kind* of images you want, walk
you through getting an API key, and then scaffold a self-contained skill that
finds, previews, and downloads images **while respecting Unsplash's attribution,
hotlinking, and download-tracking rules.**
The skill it produces is intentionally narrow and *named after your use case*, so
you can generate several of them (one for retro landscapes, one for food
close-ups, etc.) and they won't collide.
---
You are going to build me a Claude skill that fetches images from the Unsplash API
for one specific purpose. Follow these phases in order. Do not skip the interview,
and do not write any files until Phase 2.
## Phase 0 — Orient
First, confirm two things about the environment you're running in, because the
skill needs to behave differently in each and you'll bake the right branch in
later:
- **Terminal / Claude Code** → previews are printed as a list of URLs (and inline
thumbnails if the terminal supports it).
- **Chat UI with artifacts** → previews are a rendered HTML contact sheet.
If you can't tell, design the skill to detect at runtime (presence of a TTY /
ability to emit an HTML artifact) and support both.
Then read the current Unsplash rules so you encode them correctly rather than from
memory. Fetch and skim:
- `https://unsplash.com/documentation` (auth, search, hotlinking, resizable images, rate limits)
- `https://help.unsplash.com/api-guidelines/guideline-attribution`
- `https://help.unsplash.com/api-guidelines/guideline-triggering-a-download`
The non-negotiable compliance facts are summarized in Phase 3 in case the pages
are unreachable — but prefer the live docs.
## Phase 1 — Interview me (ONE question at a time)
Ask these **one at a time**, waiting for my answer before asking the next. Use my
answer to infer obvious follow-ups instead of re-asking. Where I've clearly
already told you something, skip that question and state the assumption.
1. **What is this skill *for*?** What's the subject/theme and vibe of the images?
(e.g. "quirky retro images for my blog" — playful, vintage, 70s/80s aesthetic.)
2. **Where will the images be used?** (Blog post bodies, slide decks, social, hero
banners…) This affects size and aspect ratio.
3. **Orientation requirement?** `landscape`, `portrait`, `squarish`, or any. (My
blog design may need landscape only.)
4. **Any color or mood constraint?** Unsplash supports a `color` filter
(`black_and_white`, `black`, `white`, `yellow`, `orange`, `red`, `purple`,
`magenta`, `green`, `teal`, `blue`). Optional.
5. **What output size do you need?** Map this to an Unsplash size:
`regular` (~1080px wide), `full` (max dimensions, heavy), `small` (400px), or a
custom width built off the `raw` URL. Pick a sensible default for the use case.
6. **Content safety level?** `low` (default) or `high` (stricter, family-safe).
7. **How many candidates per search, and how many do you expect to keep?**
8. **Where should downloaded files land?** A folder path, and how files should be
named (e.g. `retro-landscape-<photo_id>.jpg`).
9. **A short slug for this instance.** This becomes the skill's name and folder,
e.g. `unsplash-retro-landscape`. This is what lets me have several Unsplash
skills side by side.
10. **Preview style** — confirm terminal URL list vs HTML contact sheet (or both,
auto-detected).
After the interview, briefly play back the resolved spec and let me confirm before
you scaffold.
## Phase 1.5 — Get the API key (guide me, don't assume I have one)
Walk me through this conversationally; don't just dump steps:
1. Go to **https://unsplash.com/oauth/applications**, sign in / register as a
developer, and accept the API terms.
2. Click **New Application**, give it a name and description. The app starts in
**Demo mode → 50 requests/hour**, which is plenty for hand-picking blog images.
(Production mode unlocks 5000/hr and requires applying once the app follows the
guidelines.)
3. Copy the **Access Key** (the public `Client-ID`). For this skill we only ever
do public actions — search, fetch, download tracking — so the Access Key alone
is enough; no user OAuth/bearer token needed.
4. **Store it as an environment variable, never hardcoded in the skill:**
`export UNSPLASH_ACCESS_KEY="..."` (and note that the skill reads it from there).
Also tell me the **application name** — it's required for the attribution
`utm_source`, so capture it as e.g. `UNSPLASH_APP_NAME`.
Have the skill fail loudly with a friendly message if `UNSPLASH_ACCESS_KEY` is
missing, pointing back to these steps.
## Phase 2 — Scaffold the skill
Create this structure (named with my slug). Multiple instances differ only by
folder name, baked-in defaults, and `utm_source` — they all share the one
`UNSPLASH_ACCESS_KEY`.
```
unsplash-<slug>/
├── SKILL.md # frontmatter (name + pushy description) + workflow
├── scripts/
│ ├── search.py # search → returns candidates (no download trigger yet)
│ ├── preview.py # build HTML contact sheet OR print terminal list
│ └── download.py # fetch chosen files + fire download tracking + write credits
└── references/
└── unsplash-compliance.md # the rules in Phase 3, for the skill to re-read
```
`SKILL.md` frontmatter `description` should be specific and slightly "pushy" about
when to trigger, e.g.:
> Find and download <theme> <orientation> images from Unsplash for <use case>.
> Use this whenever the user wants <theme> imagery, blog/article photos, or
> stock-style <orientation> images — even if they don't say "Unsplash."
Bake my interview answers in as **defaults** in the scripts (default query terms,
orientation, size, count, output dir, naming), so a bare invocation "just works"
but every default can be overridden per run.
## Phase 3 — Compliance rules to encode (the important part)
Encode these exactly. These are the things most integrations get wrong, and they're
the whole reason for a dedicated skill.
**Authentication.** Send `Authorization: Client-ID $UNSPLASH_ACCESS_KEY` on every
`api.unsplash.com` request (or `?client_id=` query param). Also send
`Accept-Version: v1`. Public auth is sufficient for everything this skill does.
**Search.** `GET https://api.unsplash.com/search/photos` with:
`query`, `orientation`, `color` (optional), `content_filter`, `per_page` (max 30),
`page`, `order_by` (`relevant` or `latest`). Results are abbreviated photo objects.
**Hotlinking (required by Unsplash).** Image URLs returned under `urls.*` must be
**used directly from the Unsplash CDN** when displaying — i.e. the preview/contact
sheet embeds `urls.regular`/`urls.small`/`urls.thumb`, it does **not** rehost
copies. When resizing, build off `urls.raw` with imgix params (`w`, `h`, `fit`,
`q`, `fm`, `auto=format`, `dpr`) and **always keep the `ixid` query parameter** —
stripping it breaks Unsplash's view tracking and violates the guidelines. (Loading
images from `images.unsplash.com` is what generates "views" for the photographer,
and those requests don't count against your rate limit.)
**Download tracking (required, and distinct from fetching bytes).** This is an
*event ping*, like a Google Analytics pageview — not the image itself. Whenever a
photo is actually *used* (chosen, saved to disk, inserted into a post), fire:
`GET <photo.links.download_location>` — note: **`download_location`, NOT
`download`** — authorized with your `client_id`, **preserving the `ixid` query
param** already in that URL. Do it asynchronously so it never blocks the user. The
actual file bytes you save come from `urls.full` (or `urls.raw` + size params), not
from this endpoint.
**Attribution (required when displaying).** For every image, produce a credit in
this exact form (Unsplash's own template), with UTM params pointing at your app:
```
Photo by <a href="https://unsplash.com/@USERNAME?utm_source=APP_NAME&utm_medium=referral">FULL NAME</a> on <a href="https://unsplash.com/?utm_source=APP_NAME&utm_medium=referral">Unsplash</a>
```
`USERNAME` = `user.username`, `FULL NAME` = `user.name`, `APP_NAME` = your
registered application name. Write credits as a **sidecar file** alongside the
downloads (e.g. `CREDITS.md` plus a per-image `.txt`/`.json`), giving me both a
Markdown line and the HTML snippet so I can paste whichever my blog needs. Include
each photo's `id`, `links.html`, and description.
**Rate limits & errors.** Demo = 50 req/hr, production = 5000/hr; only
`api.unsplash.com` JSON calls count. Read `X-Ratelimit-Remaining` from responses
and warn me as it runs low. Handle `401` (bad/missing key → point back to Phase
1.5), `403` (permissions / rate limit), and `404` gracefully.
## Phase 4 — Preview, then select, then download (suggested workflow)
Map Unsplash's own distinction directly onto the UX: **previewing = viewing (a
hotlink), selecting = a download.** So split the flow into two steps and only fire
the download tracker for things I actually keep:
1. **Search & preview** — run the search, then show candidates *without* triggering
downloads (hotlinked previews already count as views). Two render modes:
- **HTML contact sheet** (chat-UI / artifact, or a local file in the terminal):
a responsive grid of cards. Each card embeds the hotlinked `urls.small`/
`urls.thumb` (ixid intact), and shows the photographer credit line,
dimensions, the photo `id`, and the `links.html` permalink. Number each card
so I can say "keep 2, 5, 7." In Claude Code, write `preview.html` to the
output folder and print the path (offer to open it / serve it on a local
port); in a chat UI, emit it as an HTML artifact.
- **Terminal list** — a numbered list: `#`, short description, `WxH`,
orientation, photographer, the `links.html` permalink, and the direct
`urls.regular` URL (clickable in most terminals). If the terminal supports
inline images (iTerm2 `imgcat`, kitty `icat`, or `chafa` as a fallback),
render small thumbnails; otherwise just the URLs. Detect support; degrade
gracefully.
2. **I pick** the ones to keep (by number or id).
3. **Download the chosen ones** — for each pick: save the file bytes from
`urls.full` (or `raw`+size) to the output folder with the agreed naming,
**fire the `download_location` tracking ping (async)**, and append its credit to
`CREDITS.md`. Print a summary of what was saved and where the credits live.
This keeps you compliant by construction: views happen via hotlinked previews,
download events fire only on real selections, and attribution is generated for
everything you keep.
## Phase 5 — Smoke test & hand off
Before declaring done: run one real search against the live API with my key (or, if
I haven't set the key yet, dry-run with a clearly-labeled mock so I can see the
shapes). Show me a preview, let me pick one, download it, and show me the resulting
file + credit. Then tell me how to invoke the skill normally, and remind me I can
re-run this whole prompt with a different slug to spin up another use-case-specific
Unsplash skill.
---
## Worked example (my actual first use case)
If it helps you move faster, here's how the interview resolves for my real case —
you can use these as the defaults if I confirm them:
- **Purpose:** quirky, retro/vintage imagery for my blog.
- **Use:** in-body and hero images on blog posts.
- **Orientation:** `landscape` only (to fit the design).
- **Default query seeds:** "retro", "vintage", "quirky retro", "70s aesthetic" —
let me pass a more specific query per run.
- **Size:** `regular` (~1080px) for in-body, `full` available for heroes.
- **Content filter:** `low`.
- **Count:** 12 candidates per search.
- **Output:** `./blog-images/`, named `retro-<photo_id>.jpg`.
- **Slug / skill name:** `unsplash-retro-landscape`.
- **Preview:** auto-detect — HTML contact sheet in chat, URL list in terminal.
Still run the one-question-at-a-time interview to confirm or adjust these, then
build it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment