Multipart uploads
Use this guide when you need explicit control over upload chunks. For ordinary files, let your SDK select the upload mode.
Sequential uploads
Use multipart upload when the full file is inconvenient to send in one request. Send parts in order, one at a time.
The JavaScript SDK streams multipart bytes through POST / for the first part and POST / for later parts. The JSON/base64 equivalents shown below use POST / and the same flat continuation endpoint.
const uploading = await client.assets.create({
name: "large-image.png",
part: {
uint8Array: firstPartBytes,
type: "image/png",
partNumber: 1,
isFinal: false,
},
});
const completed = await client.assets.uploadPart({
assetId: uploading.id,
part: {
uint8Array: finalPartBytes,
partNumber: 2,
isFinal: true,
},
});
- Start the upload with POST
/.projects/ :projectId/ assets - Send the first chunk in
part.base64withpart.partNumber: 1. - Continue with POST
/.assets/ :assetId/ part - Mark the last chunk with
part.isFinal: true.
Multipart uploads currently expect:
- Chunks provided as either
base64strings orint8Arraynumber arrays. - Sequential
partNumbervalues starting at1. - The first request to include
part.typefor the full asset MIME type. - Optional
totalSizeInByteson the first request if you already know the size of the whole file. See Declaring the file size. - Non-final parts to be at least
5MiB; only the final part may be smaller. - A concrete file type. Ambiguous/container request MIME types such as
application/octet-streamandmultipart/*are rejected because CalmLens cannot infer how to process or store them as assets.
Raw ReadableStream parts must include their exact sizeInBytes. Buffer, Blob, base64, and byte-array payload sizes are calculated by the SDK.
If you use base64, split the original bytes first and then base64-encode each part. Do not split one full base64 string into substrings.
Multipart image uploads must be between 10MB and 20MB. If needed, an image may be re-encoded to a more efficient format during finalization.
Step 1: create the upload
import { CalmLensClient } from "@calmlens/sdk";
const client = new CalmLensClient({
apiKey: "your-api-key",
projectId: "your-project-id"
});
const response = await client.assets.create({
"name": "large-image.png",
"part": {
"base64": "your-part-1-base64",
"type": "image/png",
"partNumber": 1,
"isFinal": false
},
"sizeInBytes": 7340032
});
The response returns an asset with status: "uploading" and an id you will use for the remaining parts.
Step 2: send more parts
import { CalmLensClient } from "@calmlens/sdk";
const client = new CalmLensClient({
apiKey: "your-api-key",
projectId: "your-project-id"
});
const response = await client.assets.uploadPart({
"part": {
"base64": "your-part-2-base64",
"partNumber": 2,
"isFinal": false
},
"assetId": "your-asset-id"
});
Step 3: finalize the upload
import { CalmLensClient } from "@calmlens/sdk";
const client = new CalmLensClient({
apiKey: "your-api-key",
projectId: "your-project-id"
});
const response = await client.assets.uploadPart({
"part": {
"base64": "your-final-part-base64",
"partNumber": 3,
"isFinal": true
},
"assetId": "your-asset-id"
});
The final part completes the upload and returns the asset. If a part number is skipped or repeated, the API returns 409. If the temporary upload is no longer open, the part endpoint also returns 409.
Queue mode
Choose queue when you start the upload. CalmLens saves the choice with the upload and applies it when the final part arrives, so the final part can come from another process or a restarted client that only knows the asset ID. Leave queue off later parts to use the saved choice, or pass it on the final part to override it for that upload.
Uploads started without queue are classified synchronously when they finish, like other uploads with queue=false. Audio and video are always processed asynchronously.
Declaring the file size
Pass the size of the whole file as totalSizeInBytes on the first request when you know it. CalmLens then:
- Rejects an upload that could never finish before storing any of it, for example an image declared below
10MB(400 upload/multipart-image-too-small) or above20MB(400 upload/multipart-image-too-large). - Refuses a part that would take the upload past the declared size, and a final part that leaves it short, with 400 upload/size-mismatch. The refused part is not stored, so you can send the correct part with the same part number.
Without totalSizeInBytes, CalmLens checks the accumulated size as parts arrive and the final total when the upload completes.
For raw uploads, sizeInBytes is the size of the request body: the first part on POST / and each later part on POST /.
Retrying parts
Parts are sequential, so a part sent twice is refused with 409 upload/invalid-part-number, and its details.expectedPartNumber tells you which part CalmLens needs next. If a request times out, retry it with the same part number: a part that already arrived is refused and you can move on, and a part that didn't arrive is accepted.
The final part closes the upload, so a retried final part normally returns 409 upload/not-open even though the upload succeeded. Send an Idempotency-Key header with the first request and with each part to get the original response back instead. See Retrying requests safely.