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