Product Files
Product Files let you upload files (installers, updates, assets) to a product and distribute them to licensed users via authenticated downloads.
File Properties
| Field | Description |
|---|---|
name | Display name for the file |
filename | Original filename |
size | File size in bytes |
mimeType | MIME type (auto-detected) |
sha256 | Lowercase hexadecimal SHA-256 checksum of the file bytes |
integrityStatus | Latest persisted result: verified, mismatch type, or missing |
integrityCheckedAt | Time of the latest upload verification, explicit check, or missing-file download observation |
versionId | Optional: link to a specific app version |
planId | Optional reserved metadata; it does not restrict downloads |
public | Whether the file is publicly downloadable |
downloads | Download count |
Managing Files (Dashboard API)
List Files
GET /v1/dashboard/product-files/products/:productId
Upload a File
Files are uploaded via multipart form data:
const formData = new FormData();
formData.append('file', fileBlob, 'setup.exe');
formData.append('name', 'Installer v2.1');
// formData.append('versionId', 'version-uuid'); // Optional
// formData.append('planId', 'plan-uuid'); // Optional
// formData.append('public', 'false'); // Optional
await fetch('/v1/dashboard/product-files/products/PRODUCT_ID', {
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
body: formData
});
Maximum file size: 500 MB.
New uploads are hashed before their database record is created. Existing files are hashed and updated automatically before their first eligible download is streamed after this feature is enabled.
If supplied, versionId must reference an app version belonging to the same product. planId is currently metadata only; do not use it as an access-control boundary.
The dashboard upload dialog lists the product's existing app versions and marks the current release.
Update a File
Rename a file, change download access, or link a different app version without re-uploading it:
PATCH /v1/dashboard/product-files/products/:productId/:fileId
Content-Type: application/json
{ "name": "Installer v2.2", "public": true, "versionId": "VERSION_UUID" }
All properties are optional, but at least one is required. Names are trimmed and must contain 1–255 characters. Set versionId to null to remove a version link. A non-null version must belong to the same product. Access changes take effect for subsequent download requests.
The dashboard exposes both controls directly in the product-files table.
Replace File Contents
Replace the stored bytes without changing the file ID or its existing display name, access setting, version and plan links, download count, or public URL:
PUT /v1/dashboard/product-files/products/:productId/:fileId/content
Content-Type: multipart/form-data
file=<replacement file>
The replacement is limited to 500 MB, receives a new filename, MIME type, size, and SHA-256 checksum, and begins with a verified integrity status. The new upload is removed if hashing or persistence fails. The previous stored file is removed only after the database points to the replacement. Product administrators can run this operation from the replace action in the files table; existing download links then serve the new content.
Delete a File
DELETE /v1/dashboard/product-files/products/:productId/:fileId
Deletes the database record before removing the file from disk, so a database failure leaves the still-live download intact. Disk removal is best effort after the record is deleted.
Verify File Integrity
Product administrators can re-hash a stored file without downloading it:
POST /v1/dashboard/product-files/products/:productId/:fileId/integrity
The response compares the stored SHA-256 checksum and size with the bytes on
disk. integrityOk is false when either value differs. Legacy files without
a checksum establish their baseline on the first check. Checks are audited and
limited to six requests per minute per IP because hashing large files is
resource intensive. The product-files table exposes this operation through the
shield action.
To verify every file in a product with one bounded scan, use:
POST /v1/dashboard/product-files/products/:productId/integrity
The product-wide response includes aggregate total, passed, failed,
missing, and baselinesCreated counts plus per-file results. Missing files
are reported without aborting the remaining checks. This operation is limited
to two requests per minute per IP and emits one aggregate audit event. The
dashboard exposes it through Verify all.
Individual and batch results are persisted and shown per row as Verified,
Mismatch, or Missing with the latest check time; legacy unchecked rows
remain explicitly labeled Not checked until scanned. New uploads begin as
verified because their checksum and byte length are established before the
database record is created.
The dashboard keeps a visible summary of checked,
verified, missing, and unchecked files, with a shortcut to show detected issues.
The integrity filter can isolate verified files, unchecked files, all detected
issues, or files missing from storage.
Sort by Oldest integrity check to place unchecked files first, followed by
the least recently verified files.
CSV exports include the persisted integrity status, diagnostic detail, and
latest check time for each visible row.
Download from the Dashboard
Authenticated product viewers can download public or private files without creating an API key:
GET /v1/dashboard/product-files/products/:productId/:fileId/download
The product-files table exposes this route through the download action. Product membership is checked before the file is looked up, and the file must belong to the requested product.
Downloading Files (Client API)
Public Files
Files uploaded with public=true can be downloaded without credentials:
GET https://api.geckoguard.net/v1/product-files/public/FILE_ID
Private and unknown file IDs both return 404, so the endpoint does not reveal whether a private file exists.
For public files, the dashboard also provides a copy action that builds this URL from NEXT_PUBLIC_API_BASE_URL.
API-key Downloads
Authenticated downloads use an API key with file:download permission:
const response = await fetch(
'https://api.geckoguard.net/v1/product-files/download/FILE_ID',
{
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
}
);
// Response is the file stream with appropriate Content-Type and Content-Disposition headers
// X-Checksum-SHA256 contains the lowercase hexadecimal SHA-256 checksum
const blob = await response.blob();
The API key must belong to the same product as the file. Download counts are
incremented only after the response stream completes, on a best-effort basis;
a transient counter-write failure never blocks an otherwise valid transfer.
All public, API-key, and dashboard download routes support one standard
Range: bytes=… header for resuming large transfers. A valid range returns
206 Partial Content with Content-Range and Accept-Ranges: bytes; an
invalid or multi-range request returns 416. X-Checksum-SHA256 always
describes the complete file, not only the returned range.
Send HEAD to any of the three download routes to inspect the same filename,
size, MIME type, checksum, and range headers without transferring bytes or
incrementing the download count. HEAD uses the same authorization boundary
as GET.
Every successful metadata or download response also includes a strong ETag
derived from the complete-file SHA-256 checksum. Send it in If-None-Match to
receive 304 Not Modified without transferring bytes or incrementing the
download count. For safe resumes, send the same value in If-Range alongside
Range; a stale value causes a complete 200 response instead of combining
bytes from different file versions.
Public downloads use Cache-Control: public, max-age=0, must-revalidate, so
caches may retain bytes but must validate the checksum-backed ETag before reuse.
Dashboard and API-key downloads use Cache-Control: private, no-store and are
never stored by shared or browser caches. Browser clients may read the sanitized
Content-Disposition filename because it is included in the CORS exposure list.
If any authorized download resolves a database record whose file is missing from
storage, the API persists a Missing integrity result before returning 404.
Use Cases
- Software distribution — host installers and updates behind license validation
- Asset delivery — distribute premium content to licensed users
- Version-specific files — link files to app versions for organized releases