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#
/api/v1/uploadsAPI key requiredChecks 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 asContent-Typeon 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.
{
"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#
{signedUrl}No authPUT 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#
/api/v1/uploads/completeAPI key requiredWe 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
falsefor a view-only link. Defaulttrue. idempotencyKeyuuid- Same value as step 1.
{
"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
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.