Created
May 28, 2026 17:55
-
-
Save aheld/3dc4d459db8275ace9375a5dca5cc2df to your computer and use it in GitHub Desktop.
Unsplash Skill creator prompt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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