File uploads for deployed apps use presigned S3 URLs. The client uploads directly to S3 (no proxy through the server), supporting files up to 30GB with progress tracking.
How It Works
- Init - App calls
POST /api/<APP_GUID>/uploads/initwith file metadata. Server returns a presigned S3 URL. - Upload - Client PUTs the file directly to the presigned URL (S3). No auth header needed - the URL is pre-authorized.
- Complete - App calls
POST /api/<APP_GUID>/uploads/complete. Server copies the file to permanent storage, creates the VFS record, generates variants (thumbnails for images, text for PDFs).
Client Helper Library
Include the helper script for automatic progress tracking, multipart handling, and retries:
<script src="https://media.gipity.ai/scripts/gipity-upload.js"></script>
Usage:
const result = await Gipity.upload(fileInput.files[0], {
appGuid: '<APP_GUID>',
appToken: token,
onProgress: (pct) => progressBar.style.width = pct + '%',
public: false,
table: 'attachments',
recordId: 'rec_123',
});
console.log(result.guid, result.url);
The helper handles single-part uploads (< 5GB) and multipart uploads (5GB+) transparently. It retries failed parts up to 3 times with exponential backoff.
Endpoints
POST /api//uploads/init
Get a presigned upload URL.
Auth: App token (X-App-Token), API key (X-Api-Key), or session cookie.
Request:
{
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size": 2048576,
"public": false,
"table": "incidents",
"record_id": "42",
"path": "/uploads"
}
filename(required) - original filenamecontent_type(required) - MIME typesize(required) - file size in bytes (max 30GB)public(optional, default false) - public CDN URL or signed URLtable(optional) - associate with a record tablerecord_id(optional) - associate with a specific recordpath(optional, default "/uploads") - virtual directory path
Response (single-part, < 5GB):
{
"data": {
"upload_guid": "fl_Xk7mNp2R",
"method": "PUT",
"url": "https://muda.gipity.ai.s3...",
"headers": { "Content-Type": "image/jpeg" },
"expires_in": 600
}
}
Response (multipart, >= 5GB):
{
"data": {
"upload_guid": "fl_Xk7mNp2R",
"method": "multipart",
"upload_id": "abc123...",
"part_size": 104857600,
"parts": [
{ "partNumber": 1, "url": "https://..." },
{ "partNumber": 2, "url": "https://..." }
],
"expires_in": 600
}
}
POST /api//uploads/complete
Finalize the upload after the client has PUT the file to S3.
Request:
{
"upload_guid": "fl_Xk7mNp2R",
"parts": [
{ "part_number": 1, "etag": "\"abc123\"" },
{ "part_number": 2, "etag": "\"def456\"" }
]
}
upload_guid(required) - from the init responseparts(required for multipart only) - part numbers and ETags from each S3 PUT response
Response:
{
"data": {
"guid": "fl_Xk7mNp2R",
"name": "photo.jpg",
"size": 2048576,
"content_type": "image/jpeg",
"width": 1920,
"height": 1080,
"url": "https://media.gipity.ai/app-files/...",
"is_public": false,
"table": "incidents",
"record_id": "42",
"variants": [
{ "type": "thumbnail", "url": "...", "content_type": "image/jpeg", "status": "complete" }
]
}
}
GET /api//files
List uploaded files. Supports filtering.
Query params: table, record_id, path, limit (default 50, max 200), offset
GET /api//files/:guid
Get file metadata + variant list.
GET /api//files/:guid/content
Download file. Redirects to S3 URL (public) or signed URL (private).
GET /api//files/:guid/variants/:type
Get a specific variant (e.g., thumbnail). Redirects to URL.
Variants
Variants are auto-generated on upload for small files (< 10MB):
- thumbnail - Images resized to 200x200px (JPEG)
- text - PDFs have text extracted to plain text
Limits
- Max file size: 30GB
- Presigned URL expiry: 10 minutes
- Rate limit: 300 uploads per minute
- Multipart part size: 100MB
Manual Upload (without helper library)
If you can't use the helper library, implement the three-step flow directly:
// 1. Init
const init = await fetch(`https://a.gipity.ai/api/${appGuid}/uploads/init`, {
method: 'POST',
headers: { 'X-App-Token': token, 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
content_type: file.type,
size: file.size,
}),
}).then(r => r.json());
// 2. Upload to S3
await fetch(init.data.url, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file,
});
// 3. Complete
const result = await fetch(`https://a.gipity.ai/api/${appGuid}/uploads/complete`, {
method: 'POST',
headers: { 'X-App-Token': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ upload_guid: init.data.upload_guid }),
}).then(r => r.json());
console.log(result.data.url); // permanent file URL
Integration with Records
Files can be associated with records via table and record_id fields (soft reference). Example flow:
- User uploads a screenshot with
table: 'incidents', record_id: '42' - Frontend lists attachments via
GET /api/:app/files?table=incidents&record_id=42 - Each attachment shows the thumbnail variant as a preview