Files
Files are a shallow resource under projects: you list and create files in the context of a project (/projects/{project}/files), but get, update, and delete a single file by its ID (/files/{id}). Files are stored externally; the API uses a create → upload to pre-signed URL → confirm flow.
List Files
List files that belong to a project. Only files that have been fully uploaded (is_uploaded = true) are returned.
GET https://v1.freeqr.io/api/projects/{project}/files
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string (ULID) | Yes | Project ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (default: 1) |
limit | integer | No | Items per page (default: 10, max: 100) |
sort | string | No | Sort field (default: -id; prefix with - for descending) |
ids | array | No | Filter by file IDs |
Response
{
"data": [
{
"id": "01arz3ndektsv4rrffq69g5fav",
"key": "project-id/2024/01/01/abc123/image.png",
"type": "image",
"mimetype": "image/png",
"size": 102400,
"thumbnail_url": "https://v1.freeqr.io/api/files/01arz3ndektsv4rrffq69g5fav/download?signature=...&image_size=960",
"download_url": "https://v1.freeqr.io/api/files/01arz3ndektsv4rrffq69g5fav/download?signature=...",
"user_id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"is_uploaded": true,
"is_public": false,
"extension": "png",
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}
],
"meta": {
"total": 1
}
}
Note: Only files with is_uploaded: true appear in this list. Files created but not yet confirmed (see upload flow below) are excluded.
Get File
Get a single file by ID.
GET https://v1.freeqr.io/api/files/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | File ID |
Response
{
"data": {
"id": "01arz3ndektsv4rrffq69g5fav",
"key": "project-id/2024/01/01/abc123/image.png",
"type": "image",
"mimetype": "image/png",
"size": 102400,
"thumbnail_url": "https://v1.freeqr.io/api/files/01arz3ndektsv4rrffq69g5fav/download?signature=...&image_size=960",
"download_url": "https://v1.freeqr.io/api/files/01arz3ndektsv4rrffq69g5fav/download?signature=...",
"user_id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"is_uploaded": true,
"is_public": false,
"extension": "png",
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z"
}
}
Upload flow (create file, then upload, then confirm)
Uploading a file is a three-step process:
- Create file (Step 1) —
POSTto create a file record. The response includes anuploadfield: a string that is the pre-signed S3 URL. - Upload file content (Step 2) — Send a PUT request to that URL with the raw file body and
Content-Typematching the file’smimetype. No API auth needed. See that section for examples. - Confirm upload (Step 3) — Call
PATCHon the file withis_uploaded: true. The API verifies the file in storage and updates the record. After this, the file appears in List Files and can be used.
Until step 3 is done, the file has is_uploaded: false and is not returned by List Files or usable for download.
Create File (step 1)
Create a file record and obtain a pre-signed URL for uploading the file content.
POST https://v1.freeqr.io/api/projects/{project}/files
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string (ULID) | Yes | Project ID |
Request Body
Content-Type: application/json
| Parameter | Type | Required | Description |
|---|---|---|---|
extension | string | Yes | File extension (e.g. png, jpg, pdf). Must be a supported value. |
name | string | No | Custom file name (max 256 characters) |
size | number | No | Expected file size in bytes (min: 1, max: configured limit, e.g. 100MB). Optional but can help with validation. |
Response
The response is the file resource plus upload: a string that is the pre-signed AWS S3 URL. Use this URL in Step 2 to upload the file with a PUT request.
{
"data": {
"id": "01arz3ndektsv4rrffq69g5fav",
"key": "project-id/2024/01/01/abc123/image.png",
"type": "image",
"mimetype": "image/png",
"size": 0,
"thumbnail_url": null,
"download_url": "https://v1.freeqr.io/api/files/01arz3ndektsv4rrffq69g5fav/download?signature=...",
"user_id": "01arz3ndektsv4rrffq69g5fav",
"project_id": "01arz3ndektsv4rrffq69g5fav",
"is_uploaded": false,
"is_public": false,
"extension": "png",
"created_at": "2024-01-01T00:00:00.000000Z",
"updated_at": "2024-01-01T00:00:00.000000Z",
"upload": "https://bucket.s3.region.amazonaws.com/key?X-Amz-Algorithm=...&X-Amz-Credential=...&X-Amz-Date=...&X-Amz-Expires=21600&X-Amz-SignedHeaders=content-type&X-Amz-Signature=..."
}
}
upload— String. Pre-signed AWS S3 URL. Send a PUT request to this URL with the raw file body andContent-Typeset to the file’smimetype(e.g.image/pngfrom the response).is_uploaded— Staysfalseuntil you complete Step 3.
Next: Upload the file content (Step 2), then Confirm upload (Step 3).
Upload file content (step 2)
Take the upload string from the Create File (Step 1) response. It is a pre-signed AWS S3 URL. Upload the file by sending a PUT request to that URL.
Requirements:
- Method:
PUT - Body: Raw file content (binary). Do not send JSON or form data.
- Headers: Set
Content-Typeto the file’smimetypefrom the Step 1 response (e.g.image/png).
No API authentication is needed for this request; the URL is already authorized. The URL is valid for a limited time (e.g. 6 hours).
Example: cURL
# UPLOAD_URL = data.upload from Step 1 (the string URL)
# Use the mimetype from data.mimetype (e.g. image/png)
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
--data-binary @./image.png
Example: JavaScript (fetch)
// uploadUrl = data.upload from Step 1 (the string)
// contentType = data.mimetype from Step 1 (e.g. 'image/png')
// file = File from <input type="file"> or Blob
const res = await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': contentType },
body: file,
});
if (!res.ok) {
throw new Error(`Upload failed: ${res.status}`);
}
After a successful PUT
Call the Update File (Step 3) endpoint with is_uploaded: true so the API can verify the file and mark it as uploaded. Until then, the file will not appear in List Files and cannot be downloaded.
Update File (step 3: confirm upload)
Update a file. You can only update a file while is_uploaded is false. The main use is to set is_uploaded: true after you have successfully uploaded the binary to the pre-signed URL (step 2). The API then checks that the file exists in storage, sets the stored size, and marks the file as uploaded. After that, the file is returned by List Files and can be downloaded.
Once a file has is_uploaded: true, further update requests return 409 Conflict (cannot update an uploaded file).
PATCH https://v1.freeqr.io/api/files/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | File ID |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
is_uploaded | boolean | No | Set to true after you have uploaded the file content to the pre-signed URL. The API verifies the file in storage and updates size. Required to complete the upload flow. |
Response
Same structure as Get File. After a successful update with is_uploaded: true, the file's size is set from storage and is_uploaded is true.
Errors
- 409 Conflict — The file is already uploaded (
is_uploaded: true). No further updates are allowed. - 400 Bad Request — File not found in storage, or file too large (e.g. image over 25MB or file over configured max). Ensure the binary was uploaded correctly to the pre-signed URL before confirming.
Delete File
Delete a file. Removes the file record and typically triggers deletion from storage.
DELETE https://v1.freeqr.io/api/files/{id}
Requires Authentication: Yes
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (ULID) | Yes | File ID |
Response
Returns the deleted file resource (same structure as Get File).
Download File
Download a file. Access is allowed if either:
- The request includes a valid
signaturequery parameter (e.g. from thedownload_urlorthumbnail_urlon the file resource), or - The request is authenticated and the user has access to the file's project.
The file must have is_uploaded: true. If not, the endpoint returns 404.
GET https://v1.freeqr.io/api/files/{file}:download
Requires Authentication: No when signature is provided; otherwise Yes (Bearer token with access to the file's project).
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
file | string (ULID) | Yes | File ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
signature | string | No | Signed token (included in download_url and thumbnail_url from the file resource). When present, auth is not required. |
image_size | integer | No | For images: requested size in pixels. Must be an ImageSize enum value: 100 (TINY), 320 (SMALL), 640 (MEDIUM), 960 (LARGE), 1280 (XL), 1920 (XXL). Use the value from the file's thumbnail_url when present. |
Response
Redirects to the actual file URL or streams the file with appropriate Content-Type (and optionally Content-Disposition). Without a valid signature or auth, returns 403 Forbidden. If the file is not yet uploaded, returns 404 Not Found.