Logofile.kiwi
Cadastrar

api.file.kiwi

Public API for file.kiwi — create WebFolders and upload E2E encrypted files programmatically.
Upload a file list to get download links instantly — uploads are processed asynchronously.

Base URL: https://api.file.kiwi

Overview

1. Generate encryption keys (client-side)
   secretKey  ← 16 random bytes (base64url)
   ske        ← secretKey encrypted via RFC 8188

2. POST /v1/delivery-webfolder
   { title, ske, files: [{ filename (encrypted), filesize }] }
   → webfolderId, apiAuth, files[].fid, files[].uploadUrls

3. Share
   https://file.kiwi/{webfolderId}#{secretKey}
   (secretKey is never sent to the server — URL fragment is client-only)

4. Upload chunks (client-side, encrypted via RFC 8188)
   PUT {uploadUrls.head}{path}/{chunkNumber}?{tail}&X-Amz-Signature={sig}
   Body: <encrypted chunk>

5. Verify upload
   GET /v1/upload/check/{fid}?webfolderId=...&apiAuth=...
   → { complete: true/false, missing: [...] }

No authentication required. (and some limits)

SDK

Endpoints

POST /v1/delivery-webfolder

Create a WebFolder and get presigned upload URLs for all files in a single call. All files are E2E encrypted — the server never sees plaintext data.

Request

POST https://api.file.kiwi/v1/delivery-webfolder
Content-Type: application/json
{
  "title": "Project assets",
  "ske": "<encrypted-secret-key>",
  "files": [
    { "filename": "<encrypted-filename>", "filesize": 1048576 },
    { "filename": "<encrypted-filename>", "filesize": 5242880, "mimetype": "image/jpeg" }
  ]
}
Field Type Required Description
title string Yes WebFolder title (max 200 characters)
ske string Yes Encrypted secret key for E2E encryption (see E2E Encryption)
files array Yes Files to upload (1–10 files)
files[].filename string Yes Encrypted filename (base64)
files[].filesize number Yes File size in bytes
files[].mimetype string No MIME type

Response 200 OK

{
  "webfolderId": "a1b2c3d4",
  "webfolderUrl": "https://file.kiwi/a1b2c3d4",
  "retentionHours": 90,
  "apiAuth": "a1b2c3d4e5f6g7h8",
  "files": [
    {
      "filename": "<encrypted-filename>",
      "fid": "e5f6g7h8",
      "chunkSize": 10485760,
      "chunks": 1,
      "freeDownloadHours": 24,
      "uploadUrls": {
        "head": "https://filekiwi.xxx.r2.cloudflarestorage.com/",
        "tail": "X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
        "path": "stbox/0328/e5f6g7h8",
        "signatures": ["<signature_for_chunk_00001>"],
        "headers": {
          "x-amz-meta-folder_id": "a1b2c3d4",
          "x-amz-meta-chunks": "1"
        }
      }
    }
  ]
}
Response Field Description
webfolderId Unique WebFolder identifier
webfolderUrl Shareable URL (append #secretKey for decryption)
retentionHours How long files are stored on the server (90 hours)
apiAuth Authentication token for upload verification (keep secret)
files[].fid Unique file identifier
files[].chunkSize Size of each upload chunk in bytes
files[].chunks Total number of chunks
files[].freeDownloadHours Hours of free download availability after upload completes
files[].uploadUrls Presigned URL components for chunk uploads

Error Responses

Status Body Cause
400 {"error": "title is required"} Missing or empty title
400 {"error": "title must be 200 characters or less"} Title too long
400 {"error": "files array is required and must not be empty"} Missing files array
400 {"error": "Maximum 10 files per request"} Too many files
400 {"error": "files[N].filename is required"} Missing filename at index N
400 {"error": "files[N].filesize must be a positive number"} Invalid filesize at index N
500 {"error": "Failed to create webfolder"} Internal error creating WebFolder
500 {"error": "Failed to register files"} Internal error registering files

GET /v1/upload/check/:fid

Verify which chunks have been uploaded for a file.

When apiAuth is provided and matches the token returned from WebFolder creation, the server extends the file's expiration times if they have less than 2 hours remaining. This prevents files from expiring during long uploads.

Request

GET https://api.file.kiwi/v1/upload/check/e5f6g7h8?webfolderId=a1b2c3d4&apiAuth=a1b2c3d4e5f6g7h8
Parameter Type Required Description
:fid string Yes File ID (URL path parameter)
webfolderId string Yes WebFolder ID
apiAuth string No Auth token from WebFolder creation. Extends expiration on completion if < 2h remaining.

Response 200 OK

Upload complete:

{
  "complete": true,
  "missing": []
}

Upload incomplete:

{
  "complete": false,
  "missing": [2, 5]
}

Error Responses

Status Body Cause
400 {"error": "fid is required"} Missing file ID
400 {"error": "webfolderId is required"} Missing WebFolder ID
404 {"error": "File not found"} File metadata not found in database
500 {"error": "Failed to check upload status"} R2 listing failed

Upload Flow

1. Create WebFolder

POST /v1/delivery-webfolder
  -> webfolderId, files[].uploadUrls

2. Share

Share the WebFolder URL with recipients:

https://file.kiwi/{webfolderId}#{secretKey}

The #secretKey is the decryption key. It is never sent to the server (URL hash fragments are client-only).

3. Upload Chunks

For each file, upload each chunk via presigned PUT URL:

PUT {head}{path}/{chunkNumber}?{tail}&X-Amz-Signature={signatures[i]}
Headers: (all headers from uploadUrls.headers)
Body: <encrypted chunk data>
  • chunkNumber is zero-padded to 5 digits (e.g., 00001, 00002)
  • All headers from uploadUrls.headers must be included in the PUT request
  • Presigned URLs expire after 36 hours
  • Recommended upload order: first chunk → last chunk → remaining chunks sequentially. This enables video preview on the receiver's side, as the first chunk contains the file header and the last chunk contains the moov atom (for MP4).

4. Verify Upload

GET /v1/upload/check/{fid}?webfolderId=WEBFOLDER_ID&apiAuth=TOKEN
  -> { complete: true/false, missing: [...] }

When complete: true, the file is marked as ready for download and progress tracking data is cleaned up.


Limits

Default limits for unauthenticated requests. To increase limits with an API key, contact [email protected] or chat.

Limit Value
Files per WebFolder 10
Max file size Unlimited
Max title length 200 characters
File retention 90 hours
Presigned URL validity 36 hours
Concurrent chunk uploads 1 (upload one chunk at a time)
Rate limit Undisclosed

Free Download Hours by File Size

File Size Free Download Hours
< 10 GB 24 hours
10 GB – 20 GB 12 hours
≥ 20 GB 6 hours

These hours differ from the web version.

E2E Encryption

All files are encrypted client-side before upload using RFC 8188 — Encrypted Content-Encoding for HTTP (AES-128-GCM). The server never sees plaintext data — it stores only ciphertext.

ske (Encrypted Secret Key)

ske is the secret key encrypted via RFC 8188, sent to the server for ownership verification. It is generated as follows:

  1. Generate a Keychainnew Keychain(null, null) creates a random 16-byte key and 16-byte salt using crypto.getRandomValues().
  2. Extract secretKey — the 16-byte key is encoded as base64url (keychain.keyB64). This becomes the URL fragment #secretKey.
  3. Encrypt secretKey into ske — the base64url string is UTF-8 encoded, then encrypted using keychain.encryptStream():
    • The key is derived via HKDF-SHA-256 (using the Keychain's salt) into an AES-128-GCM content encryption key and nonce.
    • The data is encrypted per RFC 8188 (21-byte header + AES-128-GCM ciphertext with authentication tag).
    • The resulting bytes are base64 encoded to produce the ske string.
  4. Share via URL fragment — the original secretKey is placed in the URL hash (#{secretKey}), which is never sent to the server.

The #secretKey in the URL is the only way to decrypt the files. If it is lost, the files cannot be recovered.