Upload a file with the API

Turn any file into a public link in three HTTP calls: create the upload, PUT the bytes, complete it. With curl, Node and Python.

An upload is three calls. The file itself never passes through our API servers: you send it straight to storage with a one-time signed URL, which is why the API handles files as large as your plan allows.

1POST /uploads

Name, type and size → signed URL

2PUT signed URL

Raw file bytes → storage

3POST /uploads/complete

Checked, scanned → public link

Full example#

Set LIS_API_KEY to your key (see Authentication), then:

FILE=report.pdf; TYPE=application/pdf
SIZE=$(wc -c < "$FILE" | tr -d ' ')

# 1. Start the upload
START=$(curl -s -X POST https://linkinseconds.com/api/v1/uploads \
  -H "Authorization: Bearer $LIS_API_KEY" -H "Content-Type: application/json" \
  -d "{\"name\":\"$FILE\",\"type\":\"$TYPE\",\"size\":$SIZE}")
URL=$(echo "$START" | jq -r .signedUrl)
UPLOAD_PATH=$(echo "$START" | jq -r .path)

# 2. Send the bytes straight to storage
curl -s -X PUT -H "Content-Type: $TYPE" --data-binary @"$FILE" "$URL"

# 3. Publish
curl -s -X POST https://linkinseconds.com/api/v1/uploads/complete \
  -H "Authorization: Bearer $LIS_API_KEY" -H "Content-Type: application/json" \
  -d "{\"path\":\"$UPLOAD_PATH\",\"name\":\"$FILE\",\"type\":\"$TYPE\"}"

1. Start the upload#

POST/api/v1/uploadsAPI key required

Checks your key, plan, the hourly limit and the file name and size, then returns a signed upload URL.

JSON body

namestringrequired
File name with its extension, e.g. report.pdf. The extension must be an allowed type.
typestringrequired
MIME type, e.g. application/pdf. Send the same value as Content-Type on the PUT.
sizeintegerrequired
Exact size in bytes. The signed URL only accepts this many bytes.
slugstring
Custom link name (Pro). Send it again on complete. See Link settings.
idempotencyKeyuuid
Makes retries safe. Reuse the same value on complete. If a lost response is retried, you get the existing link back instead of a duplicate.
200 OK
{
  "path": "6f1c…/a1b2c3d4.pdf",
  "signedUrl": "https://…"
}

If the idempotencyKey already produced a link, the response is { "existing": { "slug", "title", "url" } } and you can skip the next two steps.

2. Send the file#

PUT{signedUrl}No auth

PUT the raw bytes (not form data) with the same Content-Type you declared. The URL is valid for 10 minutes and can not be reused for another file. No API key is needed on this call.

3. Publish it#

POST/api/v1/uploads/completeAPI key required

We read the real size and first bytes from storage, check the file really is what its name says, run the safety scan and create the link. If anything fails, the uploaded bytes are deleted.

JSON body

pathstringrequired
The path returned by step 1.
namestringrequired
Same file name as step 1.
typestringrequired
Same MIME type as step 1.
linkNamestring
Title shown on the link page. Defaults to the file name.
slugstring
Custom link name (Pro), same as step 1.
allowDownloadboolean
Set false for a view-only link. Default true.
idempotencyKeyuuid
Same value as step 1.
200 OK
{
  "slug": "report-x7",
  "title": "report",
  "url": "https://linkinseconds.com/p/report-x7"
}

When a file is held for review

If the safety scan flags a file, the response includes "review": true. The link exists but is not public until a person clears it. Videos can return "processing": true while they are being scanned. Tell your users the link will start working shortly.

Retries

Network blips happen, especially on big files. Always send an idempotencyKey: retrying step 1 or step 3 with the same key never creates a second link or uses a second free slot.

What you can upload#

  • Any type listed in Supported file types: PDFs, images, video, audio, documents, HTML, ZIP and more. Executables and scripts are always refused.
  • Up to your plan's per-file size and total storage. See Plans and limits.
  • Albums and static websites are created from the website, not the API, for now.

Last updated 4 October 2026. Something unclear or missing? Tell us.