Uploading files
Upload a file to Lightfield using the file upload lifecycle.
This guide shows how to upload a file to Lightfield: create an upload session, upload the raw bytes to storage, and complete the file in Lightfield.
Before you begin
Section titled “Before you begin”You will need:
- A valid API key
- The
files:createscope to create, complete, or cancel uploads - The
files:readscope if you want to verify the uploaded file or fetch a signed download URL
All Lightfield API calls in this flow use these headers:
Authorization: Bearer YOUR_API_KEYLightfield-Version: 2026-03-01Content-Type: application/jsonStep 1: Create an upload session
Section titled “Step 1: Create an upload session”Create a pending file upload by calling POST /v1/files.
The purpose field determines what the uploaded file will be used for. Each purpose has its own MIME type and size limits, and routes the file to the appropriate storage location.
| Purpose | Use for |
|---|---|
meeting_transcript | A transcript file to attach to a meeting. See Uploading meeting transcripts. |
knowledge_user | A file staged in the authenticated user’s personal uploads area, accessible to the chat agent. See Uploading knowledge files. |
knowledge_workspace | A file staged in the organization’s shared uploads area, accessible to the chat agent. Requires the caller to be an org admin. See Uploading knowledge files. |
email_attachment | A file to attach to a sent email. See Emails and attachments. |
The example below uses purpose: "meeting_transcript" because the meeting transcript flow in the next guide builds directly on this upload lifecycle.
curl https://api.lightfield.app/v1/files \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" \ -H "Content-Type: application/json" \ -d '{ "purpose": "meeting_transcript", "filename": "manual-upload-check.txt", "mimeType": "text/plain", "sizeBytes": 29 }'A successful response looks like:
{ "id": "fil_abc123", "filename": "manual-upload-check.txt", "status": "PENDING", "uploadUrl": "https://...", "uploadHeaders": { "content-type": "text/plain" }, "expiresAt": "2026-04-08T12:00:00.000Z"}Save the returned id, filename, uploadUrl, and uploadHeaders. You will use them in the next two steps.
Step 2: Upload the raw bytes
Section titled “Step 2: Upload the raw bytes”Upload the file contents directly to the returned uploadUrl.
printf 'hello from Lightfield upload\n' > /tmp/manual-upload-check.txt
curl -i -X PUT "$UPLOAD_URL" \ -H "content-type: text/plain" \ --data-binary @/tmp/manual-upload-check.txtUse the exact headers returned in uploadHeaders. The example only shows content-type, but your upload session may include additional required headers as well — for knowledge uploads, uploadHeaders will also contain if-none-match: *, and you must pass it on the PUT. Depending on the storage backend, the upload typically returns 200, 201, or 204.
Collision handling
Section titled “Collision handling”Knowledge uploads never overwrite an existing object. Two safeguards enforce this:
- Timestamped filenames.
POST /v1/filesstamps each filename with an epoch-millisecond suffix before the extension (playbook.md→playbook_1713045600000.md,archive.tar.gz→archive.tar_1713045600000.gz). The response’sfilenamefield returns the stored name. - Conditional PUT. The presigned URL for knowledge uploads is signed with
IfNoneMatch: *, and the requiredif-none-match: *header is included inuploadHeaders. If an object already exists at the target key, S3 rejects the PUT with412 Precondition Failed. The signed-headers policy means clients cannot strip the header without invalidating the signature.
There is no overwrite field on the create request. To replace an uploaded file, upload it again — the new timestamped name will land alongside the previous one.
Step 3: Complete the upload
Section titled “Step 3: Complete the upload”After the raw bytes have been uploaded successfully, finalize the file in Lightfield by calling POST /v1/files/{id}/complete.
curl https://api.lightfield.app/v1/files/$FILE_ID/complete \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" \ -H "Content-Type: application/json" \ -d '{}'If you computed an MD5 checksum while uploading, you can include it here:
{ "md5": "5d41402abc4b2a76b9719d911017c592"}After this call, the file status should transition from PENDING to COMPLETED.
Optional verification
Section titled “Optional verification”You can verify the upload in two common ways.
Read the file metadata
Section titled “Read the file metadata”curl https://api.lightfield.app/v1/files/$FILE_ID \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01"This is useful for checking file status and stored metadata.
Get a temporary download URL
Section titled “Get a temporary download URL”curl https://api.lightfield.app/v1/files/$FILE_ID/url \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01"This returns a signed URL you can use to download the uploaded file.
Cancel a pending upload
Section titled “Cancel a pending upload”If you created an upload session but do not want to complete it, you can cancel it while the file is still PENDING.
curl https://api.lightfield.app/v1/files/$FILE_ID/cancel \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01"Only pending uploads can be cancelled.
End-to-end flow
Section titled “End-to-end flow”The minimal file upload lifecycle is:
POST /v1/filesPUT <uploadUrl>POST /v1/files/{id}/complete
Common follow-up calls:
GET /v1/files/{id}GET /v1/files/{id}/url
Common errors
Section titled “Common errors”Returned by any /v1/files endpoint. 400 responses include a code field; 403, 404, and 409 use the standard envelope without one.
| Status | Code | Meaning |
|---|---|---|
400 | upload_verification_failed | The presigned URL was never uploaded to, or the MD5 checksum did not match. Re-upload the bytes and retry /complete. |
400 | upload_url_expired | The presigned URL has expired. Create a new upload session. |
400 | file_empty | The uploaded object is 0 bytes. Re-upload a non-empty file before calling /complete. The file stays PENDING so the same session can be reused. |
400 | file_too_large | The uploaded file exceeds the per-purpose cap (for example, 3 MB for email_attachment) or the global limit. |
400 | invalid_mime_type | The file’s MIME type is not allowed for the chosen purpose. The error’s param is mimeType. |
400 | purpose_requires_user | The chosen purpose requires a user-scoped API key. |
400 | limit_too_large | GET /v1/files limit exceeds the maximum allowed page size. |
403 | n/a | The chosen purpose requires an organization admin and the current API key is not. |
404 | n/a | File does not exist or is not accessible to the current API key. |
409 | n/a | The file is in a state that does not allow the requested action — for example, calling /complete on a file that is already COMPLETED, calling /cancel on a non-PENDING file, or downloading a file whose upload never completed. |
See Errors for the standard error envelope.
Next steps
Section titled “Next steps”- Uploading meeting transcripts — Upload a transcript file and attach it to a meeting.
- Uploading knowledge files — Stage files for the chat agent to promote into your knowledge base.
- API Reference — Full endpoint reference for files, meetings, and other resources.
- Errors — Learn how to handle 4xx and 5xx responses.