Quickstart

From nothing to a sent file and a download receipt, in five steps. Transfers work on every plan, the free one included — you need an account and an API key, nothing else.

1. Create an API key

In the dashboard, open Settings → API keys and create a key with the transfers:read and transfers:write scopes. It is shown once. Put it in your environment rather than in your code:

export YUNGLE_API_KEY=yk_live_…

Then check it works:

curl https://yungle.co/api/v1/me \
  -H "Authorization: Bearer $YUNGLE_API_KEY"

2. Register the files

Creating a transfer registers the files and returns where to upload them. It is always a draft: nothing is live and nobody is emailed until step 4. size must be the exact byte count.

curl -X POST https://yungle.co/api/v1/transfers \
  -H "Authorization: Bearer $YUNGLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Final cut","files":[{"name":"final-cut.mov","size":8123456789}]}'

# → { "transfer": { "id": "01J…", "slug": "…", "expiresAt": "…" },
#     "tusEndpoint": "https://yungle.co/files",
#     "files": [ { "id": "01J…", "name": "final-cut.mov", "size": 8123456789,
#                  "uploadToken": "…" } ] }

3. Upload the bytes

File bytes never go through the JSON API. Each file streams to tusEndpoint over tus, a resumable protocol: a dropped connection continues from the last committed part instead of starting again. curl is not a tus client, so this step uses a library — or the CLI, which does steps 2 to 4 in one command.

import { createReadStream } from 'node:fs';
import * as tus from 'tus-js-client';

function upload(tusEndpoint, target, path) {
  return new Promise((resolve, reject) => {
    new tus.Upload(createReadStream(path), {
      endpoint: tusEndpoint,
      uploadSize: target.size,
      // Sent on EVERY request, not only the creation POST.
      headers: { 'x-yungle-upload-token': target.uploadToken },
      metadata: { fileId: target.id, token: target.uploadToken, filename: target.name },
      retryDelays: [0, 1000, 3000, 5000, 10000],
      onSuccess: resolve,
      onError: reject,
    }).start();
  });
}

await upload(created.tusEndpoint, created.files[0], path);
Send x-yungle-upload-token on every tus request, not only the first — HEAD and PATCH are authorized too. The details, and the rules that bite, are in Uploading files.

4. Send it

Finalizing makes the link live. Pass recipients to have Yungle email them, or leave it out and share the link yourself. It is safe to retry: nobody is emailed twice.

curl -X POST https://yungle.co/api/v1/transfers/01J…/finalize \
  -H "Authorization: Bearer $YUNGLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipients":["client@example.com"],"message":"Final cut attached."}'

# → { "transfer": { "url": "https://yungle.co/t/…", … }, "notified": ["client@example.com"] }

5. See who downloaded it

Download receipts arrive as your recipients collect the files. There are no webhooks yet, so poll — every few minutes is plenty.

curl https://yungle.co/api/v1/transfers/01J…/downloads \
  -H "Authorization: Bearer $YUNGLE_API_KEY"

# → { "downloads": [ { "fileName": null, "recipientEmail": "client@example.com",
#                      "sessionId": "…", "createdAt": "…" } ],
#     "recipients": [ { "email": "client@example.com", "opened": true, "downloaded": true, … } ],
#     "totalDownloads": 1 }

Where next

  • Transfers — adding files to a draft, expiry, download limits, revoking.
  • Collections — durable, folder-structured spaces for clients (paid plans).
  • Send from Python — this quickstart as one complete script.