STAGING — non-production environment. Billing is disabled; data may be wiped at any time.

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

FieldDescription
nameDisplay name for the file
filenameOriginal filename
sizeFile size in bytes
mimeTypeMIME type (auto-detected)
sha256Lowercase hexadecimal SHA-256 checksum of the file bytes
integrityStatusLatest persisted result: verified, mismatch type, or missing
integrityCheckedAtTime of the latest upload verification, explicit check, or missing-file download observation
versionIdOptional: link to a specific app version
planIdOptional reserved metadata; it does not restrict downloads
publicWhether the file is publicly downloadable
downloadsDownload 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