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).

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.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: 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)