Upload a part
PUT/api/v1/uploads/resumable/{id}/parts/{index}
- Access
- API key
- Permission
content:write
Upload one immutable part; repeats verify the same source hash. Parts have their own rate budget (1200 a minute), apart from the account’s 60; an account sends three at a time (429 UPLOAD_USER_BUSY otherwise). A body idle for 20 seconds is dropped (408 UPLOAD_STALLED).
Example request
curl -X PUT "https://lac.pics/api/v1/uploads/resumable/2f1b9c4e-8d3a-4b7e-9c61-0a5d2e8f7b13/parts/0" \
-H "Authorization: Bearer lac_api_YOUR_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @part-0.binParameters
Path
idstring · uuidrequiredThe file or note id.
indexintegerrequiredThe part number, from zero.
0–49
Headers
X-Lacuna-AccountstringOptional expected account ID; mismatch fails409 before reading state or buffering bytes.
Content-Lengthintegerrequired1–2,097,152
Request body
application/octet-streamrequired
Exactly the declared part size; last part can be shorter. SHA256 must match the immutable start manifest.
Responses
200Confirmed persisted state. No unconfirmed chunk bytes contribute to progress.
ResumableUploadidstring · uuidrequiredThe upload id.
statestringrequiredThe state:
UPLOADING,COMPLETING,COMPLETED,CANCELLEDorEXPIRED.values:
"UPLOADING""COMPLETING""COMPLETED""CANCELLED""EXPIRED"filenamestringrequiredsizeinteger · int64requiredSize in bytes.
1–4,294,967,296
chunkSizeintegerrequiredThe part size: 2,097,152 bytes (2 MB).
values:
2097152chunkCountintegerrequired1–50
fingerprintstringrequiredpattern
^[a-f0-9]{64}$receivedarray of integerrequiredNumbers of the confirmed parts.
unique
receivedBytesinteger · int64requiredConfirmed bytes.
0–4,294,967,296
expiresAtstring · date-timerequiredcontentContentrequiredor nullContent40 fieldsidstringrequiredThe file id.
titlestringrequiredTitle, up to 180 characters.
tagsarray of stringrequiredNFKC, trim, collapse spaces and lowercase; deduplicated after normalization. Raw controls are invalid. Omission preserves tags on update; an empty array clears them.
up to 10 items · unique
originalNamestringThe original file name.
typestringrequiredIMAGE,VIDEO,PASTEorFOLDER(a folder of code).values:
"IMAGE""VIDEO""PASTE""FOLDER"mimestringrequiredThe MIME type of the stored file.
pasteFormatstringor nullvalues:
"plain""markdown""code"pasteLanguagestringor nullfolderFilesintegeror nullA code folder (type FOLDER): files in its archive. Null for other kinds. The archive itself is the media: GET /api/media/{id} serves it (Range requests read one file by the ZIP central directory), ?download=1 as an attachment.
at least 1
revisionintegerrequiredOptimistic revision for editing saved paste text; distinct from share access version.
at least 1
accessVersionintegerrequiredShare access version: 1 for a new file, raised by every change of visibility, password or one-time. From 2 on,
urlcarries it as ?v= so messengers build a fresh link card instead of the one they kept for the previous access.at least 1
sizeinteger · int64requiredSize in bytes.
visibilitystringrequiredprivate is owner-only. unlisted is accessible by link. public also appears in the authenticated feed and profile. public cannot be combined with oneTime (400 ONE_TIME_NOT_PUBLIC). Omitted on creation: use the account default (private or unlisted). Omitted on update: retain current access.
values:
"private""unlisted""public"allowDownloadbooleanrequiredpasswordProtectedbooleanrequiredoneTimebooleanrequiredconsumedAtstring · date-timeor nullreadExpiresAtstring · date-timeor nullreadMaxExpiresAtstring · date-timeor nullhasVideoTextbooleanvideoTextRevisioninteger0–2,147,483,647
viewsintegerrequiredExplicit guest opens, deduplicated during the viewer grant (normally 24 hours). Owner previews are excluded.
at least 0
viewLimitintegeror nullLink control (Pro, plan feature view_limit): after this many counted views the link answers like an expired one (404 SHARE_UNAVAILABLE) to everyone but the owner and viewers still holding the grant of their counted open. Media of a limited file is served only after an explicit open (POST /api/share/{id}/open), so direct media links and link cards cannot bypass the count. Null: no limit.
1–1,000,000
viewLimitReachedAtstring · date-timeor nullWhen the view limit was used up; null while views remain or without a limit.
mediaStatusstringNew videos are processed asynchronously. Playback and previews require READY. The owner may explicitly download the original with ?download=1 while PROCESSING or FAILED; it is returned as an application/octet-stream attachment.
values:
"PROCESSING""READY""FAILED"mediaErrorstringor nullThe reason when processing failed.
widthintegeror nullWidth in pixels.
heightintegeror nullHeight in pixels.
durationMsintegeror nullVideo duration in milliseconds.
videoCodecstringor nullhasAudiobooleanpreviewUrlstringor nullEncrypted derived preview served with the same access checks as the source.
expiresAtstring · date-timeor nullcreatedAtstring · date-timerequiredWhen it was created.
urlstring · urirequiredThe file page to share: {WEB_ORIGIN}/s/{id}, with ?v={accessVersion} once access changed (the page ignores the parameter; older links keep working).
mediaUrlstringrequiredThe address of the file itself.
moderationHoldstringor nullAutomatic check:
review— hidden from everyone but the owner until a moderator decides;blocked— kept hidden by a moderator. Others get such a file exactly like a private one, so only the owner ever sees a non-null value.values:
"review""blocked"favoritebooleanPresent in list responses
ownedbooleanPresent in list responses
authorobject3 fields
idstringThe author’s id.
usernamestringnamestring
- 400Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 401Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 403Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 404Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 408Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.UPLOAD_STALLED
- 409Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 410Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 413Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 429Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.UPLOAD_USER_BUSY
- 500Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
- 503Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
Example response
{
"id": "Xq3u9RkT0bLmA7cV2pWz1eFy",
"state": "COMPLETED",
"filename": "recording.mp4",
"size": 4194304,
"chunkSize": 2097152,
"chunkCount": 2,
"fingerprint": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
"received": [
0,
1
],
"receivedBytes": 4194304,
"expiresAt": "2026-09-28T09:30:00.000Z",
"content": {
"id": "Xq3u9RkT0bLmA7cV2pWz1eFy",
"title": "capture",
"tags": [
"release"
],
"originalName": "capture.png",
"type": "IMAGE",
"mime": "image/png",
"pasteFormat": null,
"pasteLanguage": null,
"folderFiles": 1,
"revision": 1,
"accessVersion": 1,
"size": 482133,
"visibility": "unlisted",
"allowDownload": true,
"passwordProtected": false,
"oneTime": false,
"consumedAt": null,
"readExpiresAt": null,
"readMaxExpiresAt": null,
"hasVideoText": false,
"videoTextRevision": 0,
"views": 3,
"viewLimit": null,
"viewLimitReachedAt": null,
"mediaStatus": "READY",
"mediaError": null,
"width": 1920,
"height": 1080,
"durationMs": null,
"videoCodec": null,
"hasAudio": false,
"previewUrl": null,
"expiresAt": null,
"createdAt": "2026-09-27T09:30:00.000Z",
"url": "https://lac.pics/s/Xq3u9RkT0bLmA7cV2pWz1eFy",
"mediaUrl": "/api/media/Xq3u9RkT0bLmA7cV2pWz1eFy",
"moderationHold": null,
"favorite": false,
"owned": true,
"author": {
"id": "cmf8a1x2k0000q7lh3v9w2e4d",
"username": "alex",
"name": "Alex"
}
}
}Error codes
| Code | Status | Message |
|---|---|---|
UPLOAD_STALLED | 408 | The file stopped arriving. Check your connection and upload it again. |
UPLOAD_USER_BUSY | 429 | Your account already has uploads in progress. The next one starts when one of them finishes. |
Answers of any method with a key (an invalid or expired key, a missing permission, too many requests) are in Errors.