> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yungle.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Send your first transfer through the Yungle API and read its download receipt in about five minutes, with curl, JavaScript or Python.

This page takes you from an API key to a sent transfer and its download receipt. It works on every plan, the free one included.

## Prerequisites

* A Yungle account. [Sign up](https://yungle.co) if you do not have one.
* One of: `curl` and `jq`; Node.js 22 or later; or Python 3.9 or later.
* A small file to send. The samples use `report.pdf`.

<Steps>
  <Step title="Create an API key">
    In the dashboard, open [Settings → API keys](https://yungle.co/dashboard/settings/api) and create a key with the `transfers:write` scope. That scope also lets the key read transfers.

    The key is shown once. Put it in your environment:

    ```bash theme={null}
    export YUNGLE_API_KEY=yk_live_...
    ```

    Install the SDK for your language, then check the key works:

    <CodeGroup>
      ```bash curl theme={null}
      curl https://yungle.co/api/v1/me \
        -H "Authorization: Bearer $YUNGLE_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      // npm install yungle-client
      import { YungleClient } from 'yungle-client';

      const yungle = new YungleClient({ apiKey: process.env.YUNGLE_API_KEY });
      console.log(await yungle.me());
      ```

      ```python Python theme={null}
      # pip install yungle
      from yungle import Yungle

      yungle = Yungle()  # reads YUNGLE_API_KEY
      print(yungle.me())
      ```
    </CodeGroup>

    The response describes the workspace, its plan and the key:

    ```json theme={null}
    {
      "workspace": { "id": "01J9Y4M2A7Q8R3S5T6V7W8X9YZ", "email": "you@example.com", "displayName": "Studio Noord", "customDomain": null },
      "plan": { "tier": null, "name": null, "status": "none", "quotaBytes": 0, "usedBytes": 0 },
      "key": { "id": "01JA1B2C3D4E5F6G7H8J9K0M1N", "name": "Quickstart", "scopes": ["transfers:write"], "via": "key", "canEmail": true }
    }
    ```
  </Step>

  <Step title="Create a draft transfer">
    Register the file with its exact size in bytes. The transfer starts as a draft: nothing is live and nobody is emailed until you send it in step 4.

    <CodeGroup>
      ```bash curl theme={null}
      SIZE=$(wc -c < report.pdf | tr -d ' ')

      curl -s -X POST https://yungle.co/api/v1/transfers \
        -H "Authorization: Bearer $YUNGLE_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"title\":\"Q3 report\",\"files\":[{\"name\":\"report.pdf\",\"size\":$SIZE}]}" \
        > draft.json
      ```

      ```javascript JavaScript theme={null}
      import { statSync } from 'node:fs';

      const draft = await yungle.createTransfer({
        title: 'Q3 report',
        files: [{ name: 'report.pdf', size: statSync('report.pdf').size }],
      });
      ```

      ```python Python theme={null}
      import os

      draft = yungle.create_transfer(
          [{"name": "report.pdf", "size": os.path.getsize("report.pdf")}],
          title="Q3 report",
      )
      ```
    </CodeGroup>

    You get back the draft, where to upload, and one upload target per file:

    ```json theme={null}
    {
      "transfer": { "id": "01JA8Z6Q2K3M4N5P6R7S8T9V0W", "slug": "k3v9xq7mtr2p", "expiresAt": "2026-10-18T09:12:44.000Z", "maxBytes": ... },
      "tusEndpoint": "https://yungle.co/files",
      "files": [
        {
          "id": "01JA8Z6Q3A4B5C6D7E8F9G0H1J",
          "name": "report.pdf",
          "size": 2481152,
          "uploadToken": "eyJhbGciOi...",
          "uploadTokenExpiresAt": "2026-10-11T11:12:44.000Z"
        }
      ],
      "imports": []
    }
    ```
  </Step>

  <Step title="Upload the file">
    File bytes do not go through the JSON API. You stream each file to `tusEndpoint` with [tus](https://tus.io), a resumable upload protocol, and send the file's `uploadToken` in the `x-yungle-upload-token` header on every request.

    <CodeGroup>
      ```bash curl theme={null}
      TUS=$(jq -r .tusEndpoint draft.json)
      FILE_ID=$(jq -r '.files[0].id' draft.json)
      TOKEN=$(jq -r '.files[0].uploadToken' draft.json)
      b64() { printf %s "$1" | base64 | tr -d '\n'; }

      # Create the upload. Yungle answers with its URL in the Location header.
      LOCATION=$(curl -si -X POST "$TUS" \
        -H "Tus-Resumable: 1.0.0" \
        -H "x-yungle-upload-token: $TOKEN" \
        -H "Upload-Length: $SIZE" \
        -H "Upload-Metadata: fileId $(b64 "$FILE_ID"),token $(b64 "$TOKEN"),filename $(b64 report.pdf)" \
        | grep -i '^location:' | awk '{print $2}' | tr -d '\r')

      # Send the bytes.
      curl -X PATCH "$LOCATION" \
        -H "Tus-Resumable: 1.0.0" \
        -H "x-yungle-upload-token: $TOKEN" \
        -H "Upload-Offset: 0" \
        -H "Content-Type: application/offset+octet-stream" \
        --data-binary @report.pdf
      ```

      ```javascript JavaScript theme={null}
      import { openAsBlob } from 'node:fs';

      // Resumable and retried; renews the two-hour upload token while it runs.
      await yungle.uploadFile(draft.tusEndpoint, draft.files[0], await openAsBlob('report.pdf'));
      ```

      ```python Python theme={null}
      from yungle import upload_file

      # Resumable; renews the two-hour upload token while it runs.
      upload_file(draft["tusEndpoint"], draft["files"][0], "report.pdf")
      ```
    </CodeGroup>

    The upload is done when the last request returns `204`.

    <Note>
      curl sends the whole file in one request and does not resume. That is fine for a small file. For large files, use an SDK or the CLI, which continue from the last committed part after a dropped connection. [Upload large files](/guides/upload-large-files) explains the rules.
    </Note>
  </Step>

  <Step title="Send it">
    Finalizing makes the link live. Pass `recipients` to have Yungle email them the link, or leave it out and share the link yourself. Use your own address while you try this.

    <CodeGroup>
      ```bash curl theme={null}
      TRANSFER_ID=$(jq -r .transfer.id draft.json)

      curl -X POST "https://yungle.co/api/v1/transfers/$TRANSFER_ID/finalize" \
        -H "Authorization: Bearer $YUNGLE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"recipients":["you@example.com"],"message":"Here is the Q3 report."}'
      ```

      ```javascript JavaScript theme={null}
      const sent = await yungle.finalizeTransfer(draft.transfer.id, {
        recipients: ['you@example.com'],
        message: 'Here is the Q3 report.',
      });
      console.log(sent.transfer.url);
      ```

      ```python Python theme={null}
      sent = yungle.finalize_transfer(
          draft["transfer"]["id"],
          recipients=["you@example.com"],
          message="Here is the Q3 report.",
      )
      print(sent["transfer"]["url"])
      ```
    </CodeGroup>

    The response carries the transfer, with its share `url`, and who was emailed:

    ```json theme={null}
    {
      "transfer": {
        "id": "01JA8Z6Q2K3M4N5P6R7S8T9V0W",
        "url": "https://yungle.co/t/k3v9xq7mtr2p",
        "status": "active",
        "recipients": ["you@example.com"],
        "finalizedAt": "2026-10-11T09:14:02.000Z",
        "expiresAt": "2026-10-18T09:12:44.000Z",
        ...
      },
      "notified": ["you@example.com"]
    }
    ```

    Finalizing is safe to retry: nobody is emailed twice.
  </Step>

  <Step title="Read the download receipt">
    Open the link from the email and download the file. Then ask who downloaded it:

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://yungle.co/api/v1/transfers/$TRANSFER_ID/downloads" \
        -H "Authorization: Bearer $YUNGLE_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const receipt = await yungle.transferDownloads(draft.transfer.id);
      for (const r of receipt.recipients) console.log(r.email, r.downloaded ? 'downloaded' : 'not yet');
      ```

      ```python Python theme={null}
      receipt = yungle.transfer_downloads(draft["transfer"]["id"])
      for r in receipt["recipients"]:
          print(r["email"], "downloaded" if r["downloaded"] else "not yet")
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "downloads": [
        {
          "id": "01JA8Z9K1M2N3P4Q5R6S7T8V9W",
          "fileName": "report.pdf",
          "recipientEmail": "you@example.com",
          "sessionId": "01JA8Z9K0A1B2C3D4E5F6G7H8J",
          "ipTruncated": null,
          "createdAt": "2026-10-11T09:20:31.000Z"
        }
      ],
      "recipients": [
        {
          "id": "01JA8Z7R1S2T3V4W5X6Y7Z8A9B",
          "email": "you@example.com",
          "notifiedAt": "2026-10-11T09:14:05.000Z",
          "downloaded": true,
          "opened": false,
          "bouncedAt": null,
          "bounceKind": null
        }
      ],
      "totalDownloads": 1
    }
    ```

    One visit to the link counts as one download, however many files it fetched. Files fetched in the same visit share a `sessionId`.
  </Step>
</Steps>

<Tip>
  Each SDK also does steps 2 to 4 in one call. In Python, `yungle.send(["report.pdf"], to=["you@example.com"])` creates, uploads and sends. From a terminal signed in with an API key (`yungle login --key`), `yungle send report.pdf --to you@example.com` does the same; see [CLI](/cli).
</Tip>

## Next steps

<Columns cols={2}>
  <Card title="Send a transfer" icon="paper-plane" href="/guides/send-a-transfer">
    Expiry, passwords, download limits, adding files and revoking.
  </Card>

  <Card title="Upload large files" icon="upload" href="/guides/upload-large-files">
    Resuming, chunk sizes and renewing upload tokens.
  </Card>

  <Card title="Webhooks" icon="bell" href="/guides/webhooks">
    Get told when a transfer is downloaded instead of polling.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    Scopes, plans and what each auth error means.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.