Skip to content

Start a resumable upload

POST/api/v1/uploads/resumable

Access
API key
Permission
content:write

Admit an account-bound upload; at most ten pending, 24 hours

Example request

curl -X POST "https://lac.pics/api/v1/uploads/resumable" \
  -H "Authorization: Bearer lac_api_YOUR_KEY" \
  -H "Idempotency-Key: 6c1e0f7a-3b2d-4e8f-9a01-5d4c3b2a1f0e" \
  -H "Content-Type: application/json" \
  -d '{
  "accountId": "cmf8a1x2k0000q7lh3v9w2e4d",
  "filename": "recording.mp4",
  "size": 4194304,
  "chunkSize": 2097152,
  "chunkHashes": [
    "9d1e6b0c7a52f3e84b1c0d9a7e6f5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a",
    "0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9"
  ],
  "fingerprint": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
}'

Parameters

Headers

  • X-Lacuna-Accountstring

    Optional expected account ID; mismatch fails409 before reading state or buffering bytes.

  • Idempotency-Keystring · uuidrequired

Request body

application/jsonrequired

  • accountIdstringrequired

    The account id (user.id from GET /account) the upload belongs to.

    length 1–128

  • filenamestringrequired

    Stored without control characters or path separators and shortened to 180 characters, extension kept. A repeated start with the same Idempotency-Key and bytes returns the same upload and its first name.

    length 1–1,024

  • sizeinteger · int64required

    At most limits.maxUploadBytes of /v1/account (413 UPLOAD_TOO_LARGE): the plan’s largest limit, or the server’s ceiling of 4 GB when the plan has none (params.ceiling = server). The first part reveals the type: another format (400 UPLOAD_FORMAT_UNSUPPORTED) or a file above its type limit (413 IMAGE_TOO_LARGE / VIDEO_TOO_LARGE) cancels the upload at once.

    1–4,294,967,296

  • chunkSizeintegerrequired

    The part size: 2,097,152 bytes (2 MB).

    values: 2097152

  • chunkHashesarray of stringrequired

    The SHA-256 of every part in order, in hex.

    1–2,048 items

  • fingerprintstringrequired

    SHA256 UTF8 lacuna-resumable-v1\n{size}\n2097152\n{chunk SHA256 hashes joined by LF}\n. File name and lastModified are not identity.

    pattern ^[a-f0-9]{64}$

Responses

  • 200Confirmed persisted state. No unconfirmed chunk bytes contribute to progress.ResumableUpload
    • idstring · uuidrequired

      The upload id.

    • statestringrequired

      The state: UPLOADING, COMPLETING, COMPLETED, CANCELLED or EXPIRED.

      values: "UPLOADING" "COMPLETING" "COMPLETED" "CANCELLED" "EXPIRED"

    • filenamestringrequired
    • sizeinteger · int64required

      Size in bytes.

      1–4,294,967,296

    • chunkSizeintegerrequired

      The part size: 2,097,152 bytes (2 MB).

      values: 2097152

    • chunkCountintegerrequired

      1–50

    • fingerprintstringrequired

      pattern ^[a-f0-9]{64}$

    • receivedarray of integerrequired

      Numbers of the confirmed parts.

      unique

    • receivedBytesinteger · int64required

      Confirmed bytes.

      0–4,294,967,296

    • expiresAtstring · date-timerequired
    • contentContentrequiredor null
      Content 40 fields
      • idstringrequired

        The file id.

      • titlestringrequired

        Title, up to 180 characters.

      • tagsarray of stringrequired

        NFKC, 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

      • originalNamestring

        The original file name.

      • typestringrequired

        IMAGE, VIDEO, PASTE or FOLDER (a folder of code).

        values: "IMAGE" "VIDEO" "PASTE" "FOLDER"

      • mimestringrequired

        The MIME type of the stored file.

      • pasteFormatstringor null

        values: "plain" "markdown" "code"

      • pasteLanguagestringor null
      • folderFilesintegeror null

        A 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

      • revisionintegerrequired

        Optimistic revision for editing saved paste text; distinct from share access version.

        at least 1

      • accessVersionintegerrequired

        Share access version: 1 for a new file, raised by every change of visibility, password or one-time. From 2 on, url carries it as ?v= so messengers build a fresh link card instead of the one they kept for the previous access.

        at least 1

      • sizeinteger · int64required

        Size in bytes.

      • visibilitystringrequired

        private 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"

      • allowDownloadbooleanrequired
      • passwordProtectedbooleanrequired
      • oneTimebooleanrequired
      • consumedAtstring · date-timeor null
      • readExpiresAtstring · date-timeor null
      • readMaxExpiresAtstring · date-timeor null
      • hasVideoTextboolean
      • videoTextRevisioninteger

        0–2,147,483,647

      • viewsintegerrequired

        Explicit guest opens, deduplicated during the viewer grant (normally 24 hours). Owner previews are excluded.

        at least 0

      • viewLimitintegeror null

        Link 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 null

        When the view limit was used up; null while views remain or without a limit.

      • mediaStatusstring

        New 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 null

        The reason when processing failed.

      • widthintegeror null

        Width in pixels.

      • heightintegeror null

        Height in pixels.

      • durationMsintegeror null

        Video duration in milliseconds.

      • videoCodecstringor null
      • hasAudioboolean
      • previewUrlstringor null

        Encrypted derived preview served with the same access checks as the source.

      • expiresAtstring · date-timeor null
      • createdAtstring · date-timerequired

        When it was created.

      • urlstring · urirequired

        The file page to share: {WEB_ORIGIN}/s/{id}, with ?v={accessVersion} once access changed (the page ignores the parameter; older links keep working).

      • mediaUrlstringrequired

        The address of the file itself.

      • moderationHoldstringor null

        Automatic 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"

      • favoriteboolean

        Present in list responses

      • ownedboolean

        Present in list responses

      • authorobject
        3 fields
        • idstring

          The author’s id.

        • usernamestring
        • namestring
  • 400Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.UPLOAD_FORMAT_UNSUPPORTED
  • 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.
  • 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.IMAGE_TOO_LARGEUPLOAD_TOO_LARGEVIDEO_TOO_LARGE
  • 429Typed error. Reconcile uncertain results through status; retry completion with the same key and metadata.
  • 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

200 · application/json
{
  "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

CodeStatusMessage
UPLOAD_FORMAT_UNSUPPORTED400Supported formats are still PNG, JPEG, WebP and AVIF images, plus MP4 and WebM video. Images can contain up to 40 megapixels.
IMAGE_TOO_LARGE413The image exceeds the size limit. Compress it or reduce its resolution.
UPLOAD_TOO_LARGE413The file exceeds the size limit.
VIDEO_TOO_LARGE413The video exceeds the size limit. Make it smaller with Compress in your lacuna console or the Windows app.

Answers of any method with a key (an invalid or expired key, a missing permission, too many requests) are in Errors.

Start a resumable upload: POST /api/v1/uploads/resumable · lacuna API