Upload presets
An upload preset is a saved set of upload settings with its own ID. Browsers and apps upload through it without an API key, and your server can upload through it too, so every upload of that kind follows the same rules. Manage presets in Portal › Upload presets. For a step-by-step setup, see the quick start.
Who can upload
Each preset accepts browser uploads in one of two ways:
| Access | How a browser uploads | Use it for |
|---|---|---|
Users your server approves (token) | With an upload token your server signed for that upload | Apps with sign-in |
Anyone with the preset ID (anyone) | With the preset ID alone, up to the preset's daily limit | Static sites, public forms and prototypes |
New presets require a token. A preset open to anyone must have a daily upload limit.
Settings
| Setting | Effect |
|---|---|
| File types | MIME types the preset accepts, such as image/* or video/mp4. Empty accepts every supported type. |
| Maximum file size | Largest file the preset accepts. Multipart uploads are checked as their parts arrive. |
| Folder | Folder that uploads go into. Browser uploads can't choose another one. |
| Visibility | Visibility of uploaded assets. |
| Keep content after moderation | Keeps each uploaded file so you can serve it from the CDN. When off, the file is deleted once it's moderated and only its record and report remain. |
| Read text in images | Runs OCR on uploaded images and moderates the text it finds as well. |
| Show the decision to the uploader | When on, browser uploads see status for their own upload. Reports, metadata and URLs are never returned to the browser. |
| Daily upload limit | Most browser uploads per UTC day. Uploads from your server don't count against it. |
| Allowed sites | Origins allowed to upload from the browser, such as https://example.com. Requests from other sites, or without an Origin header, are refused. |
| Accept uploads | Turn off to pause the preset without deleting it. |
A browser can only name and describe its upload. The folder, visibility, metadata, processing options and externalId come from the preset or the upload token; sending them from the browser fails with upload-preset/field-not-allowed.
Upload tokens
An upload token lets one browser upload through a preset that requires tokens. Sign tokens on your server with the JavaScript, Python, Go, Java, Ruby or Rust SDK. Signing happens locally with your API key and makes no request to CalmLens.
| SDK | Method | Options |
|---|---|---|
| JavaScript | client.uploadTokens.sign | uploadPresetId, externalId, expiresInSeconds |
| Python | client.upload_tokens.sign | upload_preset_id, external_id, expires_in_seconds |
| Go | client.UploadTokens.Sign | UploadPresetID, ExternalID, ExpiresInSeconds |
| Java | client.uploadTokens.sign | uploadPresetId, externalId, expiresInSeconds |
| Ruby | client.upload_tokens.sign | upload_preset_id:, external_id:, expires_in_seconds: |
| Rust | client.upload_tokens().sign | upload_preset_id, external_id, expires_in_seconds |
Each returns the token and when it expires, in milliseconds since the Unix epoch.
const { token, expiresAt } = await client.uploadTokens.sign({
uploadPresetId: "your-upload-preset-id",
externalId: "post-42",
expiresInSeconds: 600,
});
Each token:
- uploads one asset. A second upload with it fails with
upload-token/used; an upload that fails frees the token for a retry. - starts an upload until it expires, and stays valid for that upload's later parts and status checks for up to a day.
- acts for the API key that signed it. The key must be able to create assets in the preset's folder. Revoking the key stops its tokens working.
Uploading from a browser
Only the JavaScript SDK runs in browsers and apps. Its CalmLensAppClient, from @calmlens/sdk/app, uploads through an upload preset instead of an API key:
<script type="module">
import { CalmLensAppClient } from "@calmlens/sdk/app";
const client = new CalmLensAppClient({
uploadPresetId: "your-upload-preset-id",
token: () => fetchTokenFromYourServer(), // only for presets that require a token
});
</script>This client has only assets.create, assets.moderate, assets.uploadPart and assets.retrieve, for its own uploads. They return the asset's id, name, uploadPresetId and createdAt, plus status when the preset shows decisions. It refuses an API key with sdk/invalid-config.
Over HTTP, browser uploads use the same bodies and query parameters as the project upload endpoints, under the preset's path, with the token as Authorization: Bearer <token>:
| Request | Path |
|---|---|
| Upload a file | POST /v1/upload-presets/{uploadPresetId}/assets/upload |
| Start a multipart upload | POST /v1/upload-presets/{uploadPresetId}/assets/multipart-upload |
| Upload the next part | POST /v1/upload-presets/{uploadPresetId}/assets/{assetId}/part |
| Check an upload | GET /v1/upload-presets/{uploadPresetId}/assets/{assetId} |
Uploading from your server through a preset
Pass uploadPresetId (upload_preset_id in Python and Ruby, UploadPresetID in Go, upload_preset_id in Rust) to client.assets.create() with your API key, in any SDK, to apply a preset's folder, visibility, file types and size limit. You can still set externalId, metadata and processing options. A folder or visibility that differs from the preset's fails with upload-preset/field-not-allowed.
Errors
| Code | Meaning |
|---|---|
upload-preset/not-found | No preset with this ID in the project. |
upload-preset/disabled | The preset is paused. |
upload-preset/token-required | The preset requires an upload token and none was sent. |
upload-preset/content-type-not-allowed | The preset doesn't accept this file type. |
upload-preset/too-large | The file is larger than the preset allows. |
upload-preset/daily-limit-reached | The preset reached its daily limit. Retry-After says when the next UTC day starts. |
upload-preset/origin-not-allowed | The site isn't in the preset's allowed sites. |
upload-preset/field-not-allowed | The upload set a field the preset or token controls. |
upload-token/invalid | The token is malformed, for another preset, or signed by a key that can't upload here. |
upload-token/expired | The token expired before the upload started. |
upload-token/used | The token already uploaded an asset. |