---
title: "Uploads"
description: "Upload files of any size with signed URLs, in one request or in parts, with checksums and versions."
---

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

# Uploads

Bytes go straight to storage through signed URLs, so large files never pass through the API.

| Method and path | Scope | What it does |
| --- | --- | --- |
| `POST /uploads` | `files:write` | Start an upload and get signed URLs |
| `GET /uploads/{id}` | `files:write` | See where an upload stands |
| `POST /uploads/{id}/parts` | `files:write` | Get fresh signed URLs for parts |
| `POST /uploads/{id}/complete` | `files:write` | Finish the upload and get the file |
| `DELETE /uploads/{id}` | `files:write` | Abort an unfinished upload |

## Flow

1. **Start.** `POST /uploads` with `{ "name", "size", "folder_id"?, "content_type"?, "sha256"?, "on_conflict"?, "modified_at"? }`. The answer is `201` with signed URLs.
2. **Send the bytes.** In mode `single`, `PUT` the whole file to `uploads[0].url`. In mode `multipart`, `PUT` part N to its URL. Each part is `part_size` bytes. Ask for more URLs with `POST /uploads/{id}/parts` and `{ "from_part": 1, "count": 100 }` (up to 100 at a time).
3. **Complete.** `POST /uploads/{id}/complete`. It answers `409 incomplete` with `details.missing_parts` until every part is stored. It is safe to repeat.

## Same name already there

The same rules as the app apply. Free renames the new file to `name (2)`. Pro saves a new version of the existing file: the answer has `kind: "version"` and `file_id`, and `complete` returns the file with `version_created`. A clash the app would ask about is `409 conflict` with the choices in `details`. Identical content answers `200` with `skip`. Send `on_conflict` to choose.

## Checksums and times

`complete` takes an optional body `{ "sha256", "modified_at", "multipart_id" }`. Up to 100 MiB the server hashes the stored object. A match is stored as `sha256_source: server`. A mismatch is `422 integrity_mismatch` and the file moves to the bin, so upload it again. Above 100 MiB your claim is stored as `client`.

## Empty files

`size: 0` creates the empty file at once and answers `201` with `{ "action": "created", "kind": "empty", "file": { ... } }`. There is no upload step. An empty file cannot become a new version (`409 empty_version_not_supported`), so send `on_conflict` as `separate` or `skip`.

## Limits

Storage and plan limits apply as in the app: `413 storage_full`, `413 file_too_large`. A transfer-held item follows the same transfer rules. Uploads count against the files rate limit (600 a minute per token).

Source: https://tarkle.com/docs/api/uploads/index.mdx
