transloadit/uppy · error · Error
Unexpected Unsplash response: missing download links
Error message
Unexpected Unsplash response: missing download links
What it means
Unsplash's download() fetches photo metadata and destructures links.download and links.download_location. If either is falsy in the API response, Companion throws 'Unexpected Unsplash Unsplash response: missing download links' because it can neither stream the file nor record attribution (required by the Unsplash API guidelines).
Source
Thrown at packages/@uppy/companion/src/server/provider/unsplash/index.ts:77
override async download({
id,
providerUserSession: { accessToken: token },
}: {
id: string
providerUserSession: UnsplashUserSession
}): Promise<{ stream: Readable; size: number | undefined }> {
return this.#withErrorHandling(
'provider.unsplash.download.error',
async () => {
const client = getClient({ token })
const {
links: { download: url, download_location: attributionUrl },
} = await getPhotoMeta(client, id)
if (!url || !attributionUrl) {
throw new Error(
'Unexpected Unsplash response: missing download links',
)
}
const stream = got.stream.get(url, { responseType: 'json' })
const { size } = await prepareStream(stream)
// To attribute the author of the image, we call the `download_location`
// endpoint to increment the download count on Unsplash.
// https://help.unsplash.com/en/articles/2511258-guideline-triggering-a-download
await client.get(attributionUrl, {
prefixUrl: '',
responseType: 'json',
})
// finally, stream on!
return { stream, size }
},View on GitHub (pinned to 5d4dedd02a)
Solutions
- Verify the photo id still exists via the Unsplash API before downloading
- Handle this as a non-retryable error: remove the stale item and prompt the user to re-pick it
- Check Unsplash API status/rate-limit headers if it affects many photos
- Keep the Unsplash application credentials healthy so metadata requests succeed
Example fix
// before
const { stream } = await provider.download({ id, providerUserSession })
// after
let stream
try {
({ stream } = await provider.download({ id, providerUserSession }))
} catch (err) {
if (err.message.includes('missing download links')) {
uppy.removeFile(fileId) // stale/removed photo
return
}
throw err
} Defensive patterns
Strategy: fallback
Validate before calling
// verify the photo still exists before download
const meta = await fetch(`https://api.unsplash.com/photos/${id}`, { headers: { Authorization: `Client-ID ${key}` } })
if (!meta.ok) { /* stale id: remove item, skip download */ } Try / catch
try { await provider.download(args) } catch (e) { if (e instanceof Error && e.message.includes('missing download links')) { uppy.removeFile(fileId); return null } throw e } Prevention
- Treat cached Unsplash ids as potentially stale; re-validate before download
- Handle per-file failures gracefully instead of failing the whole batch
When it happens
Trigger: Calling download() for a photo id whose Unsplash metadata lacks links.download or links.download_location — removed/deleted photos, an id from a different endpoint shape, or an Unsplash API change; also triggered by rate limiting responses that surface as malformed payloads.
Common situations: Stale photo ids cached client-side after the photo was deleted from Unsplash; Unsplash API contract changes; heavy usage hitting Unsplash rate limits and getting error bodies.
Related errors
- Companion hostname is required, please consult https://uppy.
- Failed to download file
- File data is missing for file ${options.file.id}
- Missing S3 object key for aborting upload
- Missing S3 object key for resuming upload
AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28).
Data as JSON: /api/errors/9e962a70830a304d.
Report an issue: GitHub.