---
title: "Create a transfer"
method: POST
path: "/transfers"
tags: ["Transfers"]
---

# Create a transfer

`POST /transfers`

## Request

- Base URL: `https://yungle.co/api/v1`
- URL: `https://yungle.co/api/v1/transfers`
- Auth: HTTP bearer

Always creates a **draft**: the link is not live and nobody is emailed until you call finalize. Returns a tus endpoint and one upload token per file. An unfinalized draft is swept after 24 hours.

## Request body

- object
  - `files` object[]: Files you will upload yourself.
    - `name` string, required: Filename as it should appear to the recipient.
    - `size` integer, required: Exact size in bytes. Must match what you upload.
    - `type` string: MIME type. Best effort; never trusted.
    - `path` string: Directory within an uploaded folder, e.g. "Ceremony/Raw". Omit for a loose file.
  - `imports` object[]: Files Yungle fetches from URLs itself, so the bytes never pass through you. Up to 20 per call. Each is created like an upload (quota, size ceilings, API metering) and imported in the background; follow it with `GET /imports/{fileId}`.
    - `url` string, uri, required: A public http(s) URL Yungle fetches the file from — a presigned S3 link, a CDN, a release asset. It must state the file size (Content-Length, or a Range answer). Private network addresses are refused.
    - `name` string: The file name. Defaults to the one the source gives.
    - `path` string: Folder to place it in, e.g. "Exports/Final".
  - `title` string: Shown in your dashboard; never to recipients.
  - `expiresInDays` integer: Defaults to 7. Clamped to your plan’s maximum (7 free, 365 paid) rather than rejected.
  - `e2ee` boolean: The files are encrypted by the client before upload and Yungle never holds the key, which travels in the link’s fragment. Such a transfer takes no title, no imports, and cannot be emailed or password-protected.

## Response `201`

Draft created, with upload targets.

- object
  - `tusEndpoint` string, uri, required: Stream each file here with tus.
  - `files` UploadTarget[], required: One per entry in `files`, in the same order.
    - `id` string, required: File id, for removing it later.
    - `name` string, required
    - `size` integer, required: Bytes, as registered. The upload must be exactly this long.
    - `uploadToken` string, required: Send as `x-yungle-upload-token` on every tus request for this file.
    - `uploadTokenExpiresAt` string, date-time, required: When `uploadToken` stops working (two hours). An upload that may run longer renews it before then: `POST /api/uploads/token` with the current token in `x-yungle-upload-token` returns `{ uploadToken, expiresAt }`. An expired token cannot be renewed.
  - `imports` ImportStarted[], required: One per entry in `imports`, in the same order. Empty when none were sent.
    - `fileId` string, required
    - `name` string, required
    - `size` integer, required: Bytes, as the source stated them.
    - `source` string, required: The host it is fetched from. Never the full URL, which may carry a signature.
    - `status` string, required: Currently one of: `queued`. Treat unknown values as unknown, not as an error.
  - `transfer` TransferDraft, required
    - `id` string, required
    - `slug` string, required
    - `expiresAt` string, date-time, required
    - `maxBytes` integer, required: The size ceiling this transfer was created under.

## Other responses

- `400`: The request was malformed or failed validation. Codes: `invalid_request`, `folder_error`.
- `401`: Missing or invalid credential. Codes: `unauthorized`.
- `402`: Needs a paid plan, or prepaid credit for metered usage. Codes: `upgrade_required`, `out_of_credit`.
- `403`: The credential lacks a required scope, or a shared link refused access. Codes: `insufficient_scope`, `password_required`, `wrong_password`, `guests_only`.
- `404`: No such resource in this workspace. Codes: `not_found`.
- `409`: Already exists, an Idempotency-Key conflict, or end-to-end encrypted content the server cannot serve. Codes: `conflict`, `e2ee_unsupported`.
- `413`: Over the storage quota or the transfer size ceiling. Codes: `quota_exceeded`, `transfer_too_large`.
- `429`: Rate limit or email budget exhausted. Wait for `Retry-After`. Codes: `rate_limited`, `email_budget_exhausted`.
- `500`: Our fault. `details.requestId` ties it to our logs. Codes: `internal_error`.

---

[API](https://skmtc.dev/yungle/apis/yungle-api.md) · [All operations](https://skmtc.dev/yungle/apis/yungle-api/llms.txt) · [OpenAPI document](https://skmtc.dev/yungle/apis/yungle-api/revisions/fee7cb2d7419?raw)
