fyuhls Public API
The current /api/v1/ API is designed for account-bound integrations. It supports personal API tokens, direct-to-storage multipart uploads, a managed upload shortcut for desktop tools, owner-scoped file metadata (including public share fields), and application-controlled download links that keep delivery policy inside fyuhls.
Overview
The API follows the same quota, package, visibility, storage, and delivery rules used by the main application. A token belongs to a specific user, so uploads created with that token appear in that user's account and count against that user's quota.
- Uploads are account-bound.
- Quota is reserved before upload completion.
- Large files should stay on the multipart direct-to-storage path.
- Download links stay application-controlled so fyuhls can decide CDN, signed-origin, or tracked delivery.
- Public file metadata includes the same share fields shown on the download page.
Authentication
Headers
Authorization: Bearer fyu_your_token_here
X-API-Token: fyu_your_token_here
Notes
- Token requests do not require CSRF.
- Browser-session calls still work for the web UI.
- Revoking a token blocks future API use immediately.
Tokens and Scopes
Users create personal API tokens from the account settings page. Tokens are displayed once, stored hashed, and can be revoked without changing the user's password.
| Scope | Purpose |
|---|---|
files.upload |
Create upload sessions, sign parts, report parts, complete, and abort multipart uploads. |
files.read |
Read file metadata (including public share fields) and request application-controlled download links. |
Idempotency
Upload creation and completion support Idempotency-Key or X-Idempotency-Key so clients can safely retry after a timeout or dropped response.
Idempotency-Key: desktop-client-42e0f2f4-1
- Completed responses are replayed when the same key and payload are reused.
- In-flight duplicates return
409. - Reusing the same key with a different payload is rejected.
Endpoint Map
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /api/v1/uploads/sessions | Create a multipart upload session. |
| POST | /api/v1/uploads/managed | Create a session and return signed part URLs in one response. |
| GET | /api/v1/uploads/sessions/{id} | Inspect an upload session for resume or retry. |
| POST | /api/v1/uploads/sessions/{id}/parts/sign | Request signed URLs for specific part numbers. |
| POST | /api/v1/uploads/sessions/{id}/parts/report | Report a successful part upload and its ETag. |
| POST | /api/v1/uploads/sessions/{id}/complete | Finalize the upload and create the file record. |
| POST | /api/v1/uploads/sessions/{id}/abort | Abort a multipart session and release reservation state. |
| GET | /api/v1/files/{id} | Return owner-scoped file metadata, including public share fields. |
| GET | /api/v1/downloads/{id}/link | Return an application-signed download link. |
Code Samples
These examples start with the managed-upload shortcut because it is the easiest public integration path. Replace the domain, token, file IDs, and folder IDs with your own values.
curl: create a managed upload
curl -X POST "https://your-site.example/api/v1/uploads/managed" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: desktop-upload-001" \
-d '{
"filename": "archive.iso",
"size": 10737418240,
"mime_type": "application/octet-stream",
"folder_id": 123,
"part_numbers": [1, 2, 3],
"expires_in": 3600
}'
curl: get file metadata
curl "https://your-site.example/api/v1/files/123" \
-H "Authorization: Bearer fyu_your_token_here"
curl: get a download link
curl "https://your-site.example/api/v1/downloads/123/link" \
-H "Authorization: Bearer fyu_your_token_here"
PHP: create a managed upload
<?php
$payload = [
'filename' => 'archive.iso',
'size' => 10737418240,
'mime_type' => 'application/octet-stream',
'folder_id' => 123,
'part_numbers' => [1, 2, 3],
'expires_in' => 3600,
];
$ch = curl_init('https://your-site.example/api/v1/uploads/managed');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer fyu_your_token_here',
'Content-Type: application/json',
'Idempotency-Key: desktop-upload-001',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
var_dump($status, json_decode($response, true));
Node.js: create a managed upload
const response = await fetch('https://your-site.example/api/v1/uploads/managed', {
method: 'POST',
headers: {
'Authorization': 'Bearer fyu_your_token_here',
'Content-Type': 'application/json',
'Idempotency-Key': 'desktop-upload-001'
},
body: JSON.stringify({
filename: 'archive.iso',
size: 10737418240,
mime_type: 'application/octet-stream',
folder_id: 123,
part_numbers: [1, 2, 3],
expires_in: 3600
})
});
const data = await response.json();
console.log(response.status, data);
End-to-end multipart example
1. Create the upload session
curl -X POST "https://your-site.example/api/v1/uploads/sessions" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: upload-session-001" \
-d '{
"filename": "archive.iso",
"size": 10737418240,
"mime_type": "application/octet-stream",
"folder_id": 123
}'
2. Request a signed URL for part 1
curl -X POST "https://your-site.example/api/v1/uploads/sessions/ups_ab12cd34ef56/parts/sign" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"part_numbers": [1],
"expires_in": 3600
}'
3. Upload that part directly to object storage
curl -X PUT "https://signed-storage-url-from-step-2" \
-H "Content-Type: application/octet-stream" \
--data-binary "@archive.part1"
4. Report the completed part back to fyuhls
curl -X POST "https://your-site.example/api/v1/uploads/sessions/ups_ab12cd34ef56/parts/report" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"part_number": 1,
"etag": "\"etag-returned-by-storage\"",
"part_size": 67108864
}'
5. Complete the upload after all parts are reported
curl -X POST "https://your-site.example/api/v1/uploads/sessions/ups_ab12cd34ef56/complete" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: upload-complete-001" \
-d '{
"checksum_sha256": "optional-final-sha256"
}'
Managed Upload
POST /api/v1/uploads/managed is the easiest entry point for desktop tools. It creates the upload session and returns signed part URLs in one call.
Example request
{
"filename": "archive.iso",
"size": 10737418240,
"mime_type": "application/octet-stream",
"folder_id": 123,
"part_numbers": [1, 2, 3],
"expires_in": 3600
}
Example response
{
"status": "ok",
"session": {
"public_id": "ups_ab12cd34ef56",
"status": "pending"
},
"part_size_bytes": 67108864,
"parts": [
{
"part_number": 1,
"method": "PUT",
"url": "https://..."
}
],
"complete_url": "/api/v1/uploads/sessions/ups_ab12cd34ef56/complete",
"report_part_url": "/api/v1/uploads/sessions/ups_ab12cd34ef56/parts/report"
}
Multipart Upload Flow
- Create a session or use the managed-upload shortcut.
- Request signed part URLs for the next batch of parts.
- Upload the parts directly to your configured object storage bucket.
- Report each uploaded part with its part number, ETag, and size.
- Complete the session once all parts are uploaded and reported.
Report-part example
{
"part_number": 1,
"etag": "\"8b1a9953c4611296a827abf8c47804d7\"",
"part_size": 67108864
}
Complete example
{
"checksum_sha256": "optional-client-calculated-sha256"
}
For large-file integrations, keep the client on the direct multipart path. That is the scalable design for multi-terabyte daily upload volume.
Resume Interrupted Uploads
The expected resume flow is: keep the upload session ID locally, fetch the current session state after interruption, identify the parts that still need to be uploaded, and then request new signed URLs for only those missing parts.
- Persist the upload session
public_idwhen the upload starts. - After a restart or dropped network, call
GET /api/v1/uploads/sessions/{id}. - Use the returned state to determine what is already complete and what is still missing.
- Request fresh signed URLs for the missing parts only.
- Upload those parts, report them, and then complete the session.
Resume check
curl "https://your-site.example/api/v1/uploads/sessions/ups_ab12cd34ef56" \
-H "Authorization: Bearer fyu_your_token_here"
Typical resumed session response
{
"status": "ok",
"session": {
"public_id": "ups_ab12cd34ef56",
"status": "uploading",
"expected_size": 10737418240,
"uploaded_bytes": 201326592,
"completed_parts": 3
}
}
Request fresh URLs only for the missing parts
curl -X POST "https://your-site.example/api/v1/uploads/sessions/ups_ab12cd34ef56/parts/sign" \
-H "Authorization: Bearer fyu_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"part_numbers": [4, 5, 6],
"expires_in": 3600
}'
Do not reuse stale signed storage URLs after a long pause. Request fresh part URLs before resuming when the original URLs may have expired.
Files and Downloads
File metadata
GET /api/v1/files/{id} returns owner-scoped metadata including filename, file size, mime type, short ID, visibility, folder ID, status, download count, and public share fields (when the file is public).
Download links
GET /api/v1/downloads/{id}/link returns an application-signed link with a time-limited expires value. fyuhls still decides whether delivery uses CDN, signed origin, or an app-controlled path.
{
"status": "ok",
"file": {
"id": 123,
"short_id": "1a2dd2a8",
"filename": "example.zip",
"file_size": 1048576,
"mime_type": "application/zip",
"is_public": 1,
"downloads": 42,
"share_fields": [
{"label": "Page Link", "value": "https://your-site.example/file/1a2dd2a8"},
{"label": "HTML Code", "value": "<a href="...">example.zip</a>"}
]
}
}
{
"status": "ok",
"url": "https://your-site.example/download/123?token=...",
"expires_in": 3600,
"delivery": "cdn",
"delivery_reason": "public_object_storage_cdn"
}
Errors and Limits
401: authentication failed or the token is invalid.403: scope is missing or CSRF failed for a browser-session write.404: the file or upload session is not accessible to the caller.409: the same idempotency key is already being processed.422: request validation failed or upload/provider state is inconsistent.
API calls are rate-limited per token, per user, and per IP. Exact thresholds are site-configurable.
Production Checklist
- Use personal API tokens instead of browser cookies.
- Send idempotency keys on create and complete.
- Expose
ETagin bucket CORS for multipart uploads. - Do not hardcode bucket URLs in clients. Use the download-link endpoint.
- Persist upload session IDs locally so tools can resume.
- Validate the full path against your real B2, R2, Wasabi, or S3-compatible bucket before going live.