File Upload Integration
Written by Rohman Beny Riyanto
How to upload and display files (images, videos, documents) through the API. The actual file bytes never pass through this API's own servers - your upload/download goes directly to storage, using a temporary signed URL the API hands you first.
The basic idea
Two separate paths:
- Getting permission / a link - your app asks the API for a URL, the API replies with one.
- Sending/receiving the actual file - your app talks directly to storage using that URL. The API is not in this path at all.
Base path: /v2/files/*. Currently public - no login required yet. This will require authentication once it's wired into a real feature instead of the internal test app it's used with today.
Uploading a small file
The API decides whether your file is "small" (one direct upload) or "large" (split into parts) - you don't choose this yourself, just follow what it tells you.
The last step (bind) matters: an uploaded-but-never-bound file is automatically deleted after a while. Always bind a file to something once it's confirmed, or it disappears on its own.
Uploading a large file (split into parts)
If your file is above the size threshold for its policy, create-upload-slot returns multipart: true instead of a single upload_url.
Keep the ETag from every part's upload response - complete needs {part_number, etag} for every single part, not just how many there were.
Displaying/downloading a file
Always ask for a fresh link right before you need to show/download a file - don't store the link long-term, private-file links expire.
Endpoint reference
All under /v2/files. For POST/PUT/PATCH/DELETE, wrap the body as {"command": "<name>", "data": {...}}.
| Method | Path | Command/Query | Purpose |
|---|---|---|---|
| POST | /uploads | create-upload-slot | Start an upload, get upload_url or multipart flag |
| POST | /uploads/:file_id/confirm | confirm-upload | Finalize a small/single upload |
| DELETE | /uploads/:file_id | abort-upload | Cancel an upload that was never confirmed |
| POST | /uploads/:file_id/multipart | init-multipart-upload | Start a large-file (multipart) upload |
| POST | /uploads/:file_id/multipart/parts | get-part-url | Get a signed URL for one part |
| GET | /uploads/:file_id/multipart | get-multipart-progress | Which parts already finished (for resuming) |
| POST | /uploads/:file_id/multipart/complete | complete-multipart-upload | Finish a large-file upload |
| DELETE | /uploads/:file_id/multipart | abort-multipart-upload | Cancel an unfinished large-file upload |
| POST | /:file_id/bind | bind-file | Attach a file to something (required - see above) |
| DELETE | /:file_id/bind | unbind-file | Detach a file |
| GET | /:file_id | get-file-info | File metadata + status |
| GET | /:file_id/url | get-file-url | Get a link to view/download the file |
| GET | ?owner_service=&owner_type=&owner_ref= | list-files-by-owner | All files attached to one thing |
| GET | /policies/:code | get-policy | Rules for one upload type (allowed formats, size limit) |
| GET | /policies | list-policies | Every available upload type and its rules |
get-part-url and get-multipart-progress are exempt from the app-wide rate limit, so uploading a large file with many parts won't get throttled.
Attaching files to your own records (coming later)
Right now this API only handles files by themselves. When features like "attach a video to an order" or "set a device's thumbnail" are built, attaching a file will work the same way as bind above: you'll pass the file_id you got from uploading when you create/update that record, and the record will remember which file(s) belong to it. No change expected on the upload flow itself - the extra step is just telling that other feature which file to use.