Skip to content
THE YOINK API

Your files.
One API away.

Upload a file. Get a direct link. Build it into whatever you’re making.

Start uploading No account needed.
01
Get an upload URLAsk Yoink where to send it.
02
Send your fileUpload directly to storage.
03
Share the linkHotlink it. Embed it. Yoink it.
01 /

Your first yoink

Quickstart

Two requests, one shareable URL. This example uploads hello.txt as a guest, then prints its public link.

Files are public to anyone with the link. There’s no list or delete API, and no guaranteed retention period.

set -eu
printf 'Hello, Yoink!\n' > hello.txt

response=$(curl --fail-with-body --silent --show-error --retry 2 \
  https://yoink.zip/api/v1/presign \
  -H 'Content-Type: application/json' \
  -d '{"filename":"hello.txt","contentType":"text/plain"}')

curl --fail-with-body --silent --show-error --retry 2 \
  --request PUT "$(printf '%s' "$response" | jq -er '.uploadUrl')" \
  -H "Content-Type: $(printf '%s' "$response" | jq -er '.contentType')" \
  --data-binary @hello.txt

printf '%s' "$response" | jq -er '.publicUrl'
Requires cURL 7.76+ and jq. Run in a POSIX shell.Something go wrong?

Using this from another website? Send uploads from your backend. Direct browser PUT uploads aren’t enabled for other origins.

02 /

Account optional.

Guest uploads work without credentials. To attribute uploads to a signed-in Yoink account, send its current Firebase ID token with the presign request.

Authorization header
Authorization: Bearer <FIREBASE_ID_TOKEN>

Already signed in?

Use a fresh ID token from the Firebase SDK’s getIdToken() for the yoink-zip project. Tokens from another Firebase project won’t work. There are no API keys to create.

Same link, same visibility.

Authentication attributes the upload; it doesn’t make the file private. Invalid or expired credentials return 401, not a guest upload. Send the token to Yoink only, never to the storage PUT URL.

03 /

Create an upload URL

POSThttps://yoink.zip/api/v1/presign

Request a short-lived URL for one file. Send JSON with Content-Type: application/json. This step doesn’t upload any bytes.

Request body

filenamestringrequired
The file’s name. Yoink strips directory components, cleans unsupported filename characters, and limits the name to 200 characters.
contentTypestring
The MIME type to store, such as image/png. Defaults to application/octet-stream.
sizenumber
File size in bytes, when known. Accepted range: 0–5,368,709,120 (5 GiB). This is a declaration, not a verification of the uploaded bytes.
Request body / JSON
{
  "filename": "hello.txt",
  "contentType": "text/plain",
  "size": 14
}

Response 200 OK

Example response / JSON
{
  "uploadUrl": "https://<account>.r2.cloudflarestorage.com/<signed-path>",
  "path": "anon/<timestamp>/<id>/hello.txt",
  "publicUrl": "https://files.yoink.zip/anon/<timestamp>/<id>/hello.txt",
  "contentType": "text/plain",
  "expiresIn": 900
}
uploadUrlstring
Temporary, signed PUT URL. Use the full returned value unchanged, including its query string. Keep it private until you’ve finished uploading.
publicUrlstring
Ready-to-use, encoded public link. Share it only after the PUT succeeds.
pathstring
Generated storage key, with an account or guest prefix, timestamp, random ID and cleaned filename. Save the returned value rather than constructing it.
contentTypestring
The exact Content-Type header to send with your PUT.
expiresInnumber
Always 900 seconds (15 minutes). This is the upload URL’s lifetime, not the file’s lifetime.
04 /

Send the bytes

PUT{uploadUrl}

Send the raw file as the request body, using the returned contentType. The signed URL already authorizes this request: leave out the Firebase bearer token.

Upload file / cURL
curl --fail-with-body --request PUT "$UPLOAD_URL" \
  -H "Content-Type: $CONTENT_TYPE" \
  --data-binary @hello.txt

Set UPLOAD_URL and CONTENT_TYPE to the values returned by presign, or use the complete quickstart.

Raw bytes. Nothing extra.

Don’t wrap the file in JSON or FormData. The content type must match exactly. Check for a successful HTTP status; the PUT doesn’t return Yoink JSON.

One file, one PUT.

The single-PUT limit is 5 GiB. Request the signed URL just before uploading. If it expires, get a new one and restart. Multipart and resumable uploads aren’t exposed by this API.

06 /

Direct upload

Legacy
POSThttps://yoink.zip/api/v1/upload

A single-request alternative for small files. It buffers the full body in the Worker and is subject to lower platform limits. Use presign for larger files.

Request, response & headers

Send raw bytes, not multipart form data. Set x-filename to the file name; it defaults to file. The same optional bearer authentication applies.

Direct upload / cURL
curl --fail-with-body --silent --show-error \
  https://yoink.zip/api/v1/upload \
  -H 'Content-Type: text/plain' \
  -H 'x-filename: hello.txt' \
  --data-binary @hello.txt
Direct upload response / JSON
{
  "success": true,
  "path": "anon/<timestamp>/<id>/hello.txt",
  "url": "https://files.yoink.zip/anon/<timestamp>/<id>/hello.txt"
}

This route returns url, not publicUrl. Your Content-Type is preserved as file metadata; absent or empty values default to application/octet-stream. Set it explicitly with cURL, which otherwise sends application/x-www-form-urlencoded.

07 /

When a request goes sideways

Handled upload API errors use a JSON error field. Storage PUTs, platform errors and unmatched routes may return XML, HTML or plain text instead. Check the status before parsing.

Example error / 401
{ "error": "Unauthorized" }
400

Invalid request

Presign expects a JSON object with a non-empty filename. Check your JSON syntax and Content-Type header.

401

Unauthorized

A supplied credential is invalid, expired or malformed. Refresh the Firebase ID token. For an intended guest upload, omit credentials entirely.

413

File too large

Presign rejects a declared size below zero or above 5 GiB. Platform request limits may also reject direct uploads.

500

Server error

Wait briefly and retry. Presign can return this when upload storage isn’t configured. Keep the response body if the problem persists.

Storage returned 403?

Check that the signed URL hasn’t expired, the Content-Type is unchanged, and no bearer header was attached. Use the complete URL, including its signature.

Retry without surprises.

Retry transient network failures and 5xx responses with a short, increasing delay and a limit on attempts. Retry a PUT at the same URL while it’s valid; a new presign creates a new path. Don’t retry a validation error unchanged.