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

  1. Verify the photo id still exists via the Unsplash API before downloading
  2. Handle this as a non-retryable error: remove the stale item and prompt the user to re-pick it
  3. Check Unsplash API status/rate-limit headers if it affects many photos
  4. 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

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


AI-assisted analysis of transloadit/uppy@5d4dedd02a (2026-08-28). Data as JSON: /api/errors/9e962a70830a304d. Report an issue: GitHub.