Sliick / Articles / Product
Product 15 June 2026

Sliick Files API: Apex and Flow Reference

Reference for the public sliick.FilesApi surface. Bulk-first, provider-agnostic file operations from Apex, a System.Callable dynamic surface for soft dependencies, and six Flow actions. Bytes land in the org's configured storage provider.

Jerry Huang

Jerry Huang

Author

Sliick Files API: Apex and Flow Reference

Sliick Files API: Apex and Flow reference

sliick.FilesApi is the public, provider-agnostic file API. Operations are written once and the bytes land in the org’s configured storage provider - Salesforce Files, Sliick Cloud, S3-compatible, GCP, Azure, or SharePoint. The provider is selected in org setup and is never named in caller code.

This is the operational reference. For the design rationale, see the architecture article.


What this unlocks

One API. Every storage backend. The bytes land wherever the org points storage - Salesforce Files, Sliick Cloud, the customer’s own S3 bucket, GCP, Azure, or SharePoint - and your code never changes. That single fact opens up patterns that used to mean a rewrite per backend:

  • Write generated documents straight to the customer’s bucket. A nightly batch renders thousands of invoices, calls uploadFiles, and they land in the org’s S3/GCP/Azure/SharePoint - no provider-specific code, no Salesforce storage bill, exactly where compliance wants them.
  • Push huge files server-side, no browser, no ceiling. Generate a 200MB export, drop it in Salesforce Files, and relocateFiles streams it to external storage asynchronously via multipart. The 4MB Apex wall is gone.
  • Turn a portal page into branded file intake. A screen flow places the Sliick File Upload component on an Experience Cloud page; customer photos and documents fly browser-direct into the org’s bucket - thumbnails generated automatically, zero Salesforce storage consumed.
  • Switch storage providers without touching a line of code. An org moves from Salesforce Files to its own bucket; the same API quietly routes new uploads to the new provider and relocates the existing files in bulk.
  • Let admins build file workflows in clicks. Record-triggered flows rename and route inbound email attachments to the right parent record - point and click, no Apex, callout-free.
  • Hand file operations to an AI agent. The invocable actions are exactly the shape agent platforms consume: “attach this signed contract to the opportunity” becomes configuration, not an engineering project.
  • Build on it from another package - safely. A sibling package detects Sliick Files via Type.forName + getCapabilities and drives every operation through Callable, degrading gracefully when it is not installed. No hard dependency, no version lockstep.

Everything below is how to wire it up.


Quick reference: Apex

Upload a small file (≤ ~4MB, base64)

Sliick.FilesApi.UploadFileRequest req = new Sliick.FilesApi.UploadFileRequest();
req.recordId    = accountId;
req.fileName    = 'invoice-2026-06.pdf';
req.contentType = 'application/pdf';
req.bodyBase64  = EncodingUtil.base64Encode(pdfBlob);   // raw base64, no data: prefix

List<Sliick.FilesApi.UploadFileResult> results =
  Sliick.FilesApi.uploadFiles(new List<Sliick.FilesApi.UploadFileRequest>{ req });

if (results[0].success) {
  String fileId = results[0].file.fileId;
} else if (results[0].errorCode == Sliick.FilesApi.ERROR_FILE_TOO_LARGE) {
  // use the presigned pair or relocateFiles instead
}

Upload a large file (presigned, external storage)

// 1) Mint a presigned PUT URL
Sliick.FilesApi.UploadUrlRequest u = new Sliick.FilesApi.UploadUrlRequest();
u.recordId    = accountId;
u.fileName    = 'site-survey-4k.mp4';
u.contentType = 'video/mp4';

Sliick.FilesApi.UploadUrlResult url =
  Sliick.FilesApi.requestUploadUrls(new List<Sliick.FilesApi.UploadUrlRequest>{ u })[0];
// url.uploadUrl + url.requiredHeaders -> client PUTs the bytes directly to storage
// url.uploadKey -> pass to confirm

// 2) Confirm after the client PUT completes (creates the file record)
Sliick.FilesApi.ConfirmUploadRequest c = new Sliick.FilesApi.ConfirmUploadRequest();
c.uploadKey = url.uploadKey;

Sliick.FilesApi.UploadFileResult confirmed =
  Sliick.FilesApi.confirmUploads(new List<Sliick.FilesApi.ConfirmUploadRequest>{ c })[0];
// confirmed.file.processingStatus == 'Pending' for images until thumbnails finish

Relocate an existing Salesforce file to external storage (async, no size cap)

Sliick.FilesApi.RelocateFileRequest req = new Sliick.FilesApi.RelocateFileRequest();
req.fileId   = contentDocumentId;   // 069... existing Salesforce file
req.recordId = accountId;
// req.deleteSourceAfterRelocate = true;  // optional; default keeps the SF source

Sliick.FilesApi.RelocateFileResult res =
  Sliick.FilesApi.relocateFiles(new List<Sliick.FilesApi.RelocateFileRequest>{ req })[0];

if (res.success) {
  String handle = res.fileId;   // durable, permanent fileId; status == 'Uploading'
} else if (res.errorCode == Sliick.FilesApi.ERROR_STREAMING_NOT_ENABLED) {
  // org not entitled for streaming
}

// Poll for completion (callout-free)
Sliick.FilesApi.RelocationStatusRequest s = new Sliick.FilesApi.RelocationStatusRequest();
s.handle = handle;
Sliick.FilesApi.RelocationStatusResult st =
  Sliick.FilesApi.getRelocationStatus(new List<Sliick.FilesApi.RelocationStatusRequest>{ s })[0];
// st.status: 'Uploading' | 'Complete' | 'Failed'

List, download URL, delete, rename, move

// LIST - Salesforce + external merged, createdDate DESC, max 200 per record
Sliick.FilesApi.ListFilesRequest lr = new Sliick.FilesApi.ListFilesRequest();
lr.recordId = accountId;
List<Sliick.FilesApi.FileInfo> files =
  Sliick.FilesApi.listFiles(new List<Sliick.FilesApi.ListFilesRequest>{ lr })[0].files;

// DOWNLOAD URL - short-lived; fetch just before use, do not store
Sliick.FilesApi.DownloadUrlRequest dr = new Sliick.FilesApi.DownloadUrlRequest();
dr.fileId = fileId;
String downloadUrl =
  Sliick.FilesApi.getDownloadUrls(new List<Sliick.FilesApi.DownloadUrlRequest>{ dr })[0].downloadUrl;

// DELETE - detach from this record; file is destroyed only when its last link is removed
Sliick.FilesApi.DeleteFileRequest del = new Sliick.FilesApi.DeleteFileRequest();
del.fileId   = fileId;
del.recordId = accountId;      // both required
Sliick.FilesApi.deleteFiles(new List<Sliick.FilesApi.DeleteFileRequest>{ del });

// RENAME - display name only; extension stripped; storage paths unchanged
Sliick.FilesApi.RenameFileRequest rn = new Sliick.FilesApi.RenameFileRequest();
rn.fileId  = fileId;
rn.newName = 'Signed Contract - Acme';
Sliick.FilesApi.renameFiles(new List<Sliick.FilesApi.RenameFileRequest>{ rn });

// MOVE - repoint attachment to another record; no bytes move
Sliick.FilesApi.MoveFileRequest mv = new Sliick.FilesApi.MoveFileRequest();
mv.fileId         = fileId;
mv.sourceRecordId = caseId;
mv.targetRecordId = accountId;
Sliick.FilesApi.moveFiles(new List<Sliick.FilesApi.MoveFileRequest>{ mv });

Soft dependency (System.Callable - no compile-time dependency)

Type apiType = Type.forName('Sliick', 'FilesApi');
if (apiType == null) { return; }   // Sliick Files not installed
Callable filesApi = (Callable) apiType.newInstance();

// Feature-detect (no custom permission required for this call)
Map<String, Object> caps = (Map<String, Object>) filesApi.call('getCapabilities', null);
// caps => { 'actions' => List<String>, 'revision' => Integer }

List<Object> results = (List<Object>) filesApi.call('uploadFiles',
  new Map<String, Object>{
    'requests' => new List<Object>{
      new Map<String, Object>{
        'recordId'    => accountId,
        'fileName'    => 'contract.pdf',
        'contentType' => 'application/pdf',
        'bodyBase64'  => body
      }
    }
  });
Map<String, Object> first = (Map<String, Object>) results[0];
if ((Boolean) first.get('success')) {
  String fileId = (String) ((Map<String, Object>) first.get('file')).get('fileId');
}

call() returns loosely-typed Map/List only - no package DTO types. Datetimes render as ISO-8601 strings.


Quick reference: Flow

Six invocable actions, category Sliick Files in Flow Builder.

ActionInputsOutputs (plus Success / Error Code / Error Message)
Sliick Files: Upload FileRecord ID, File Name, Content Type, File Body (Base64)File ID, Download URL
Sliick Files: List FilesRecord IDFiles (FileInfoOutput collection)
Sliick Files: Get Download URLFile IDDownload URL, Expires At
Sliick Files: Delete FileFile ID, Record ID-
Sliick Files: Rename FileFile ID, New Name-
Sliick Files: Move FileFile ID, Source Record ID, Target Record ID-

Device upload in a screen flow uses the Sliick File Upload screen component (not the Upload File action). Inputs: Record ID, Require Upload. Outputs: File ID, File Name. It uploads browser-direct to the active provider (presigned PUT for external storage - no 4MB cap, thumbnails included; native Salesforce Files upload otherwise).


Setup

A caller - Apex running user, or the running user of a flow - requires both:

  1. Files API Access custom permission (sliick__SliickFilesAPI). Ships enabled in the SliickFilesAdmin permission set. For other users, add the custom permission to a permission set.
  2. Data access - the SliickFilesUser permission set, or equivalent CRUD/FLS on sliick__Cloud_File__c and sliick__Cloud_File_Link__c. All queries and DML run in USER_MODE; record-level sharing applies per item.

The custom permission is the entitlement gate. The sharing model governs which records and files each call can act on.


Contracts

Bulk-first. Every method takes List<...Request> and returns a parallel-index List<...Result>. Single-item callers pass a one-element list and read index [0].

Per-item outcomes. Each result carries success, errorCode, errorMessage. A failing item does not abort the rest of the list. Branch on errorCode, not on errorMessage text.

Call-level exception. FilesApiException is thrown only for whole-call failures: missing custom permission, null/empty request list, unknown Callable action, malformed Callable args. In Flow, this maps to the fault path.

Opaque fileId. A ContentDocumentId for Salesforce-stored files, the storage object key otherwise. Pass it back verbatim; do not parse it. One id works across all providers and all methods.

Callouts. uploadFiles, requestUploadUrls, getDownloadUrls (external), and getFileBodies (external) perform HTTP callouts: max 100 per transaction, cannot run after DML in the same transaction, cannot run directly in triggers (use a Queueable). listFiles, deleteFiles, renameFiles, moveFiles, and relocateFiles (accept) are callout-free.

Error codes

ConstantMeaning
ERROR_ACCESS_DENIEDMissing custom permission, or no access to the record/file
ERROR_NOT_FOUNDFile or record does not exist / is not visible
ERROR_INVALID_INPUTRequired field missing or malformed
ERROR_UNSUPPORTED_PROVIDEROperation unavailable on the active provider
ERROR_FILE_TOO_LARGEExceeds the ~4MB Apex byte-relay cap
ERROR_STREAMING_NOT_ENABLEDrelocateFiles: large-file streaming not entitled for the org
ERROR_RELOCATION_PENDINGA relocate handle was used as a fileId before its async copy finished
ERROR_PROVIDER_FAILEDDownstream storage/database failure (cause in the message)

Limits

LimitValue
Base64 upload / file-body read~4MB binary per file (Apex heap + provider cap)
listFilesmax 200 files per record, createdDate DESC
Download URLs (external)presigned, expire within minutes - fetch just before use
confirmUploadsidempotent per uploadKey (replay returns the existing file)
relocateFilesasync, no size ceiling (multipart); accept is callout-free; idempotent per (source file, record)

Method notes

uploadFiles

Server-side base64 upload for small files. Request: recordId, fileName (with extension), contentType, bodyBase64 (raw base64, no data: prefix). Result: file (FileInfo), downloadUrl. Images uploaded this way skip the thumbnail pipeline - use the presigned pair for images.

requestUploadUrls / confirmUploads

Large-file path for external providers. requestUploadUrls returns uploadUrl, uploadKey, expiresAt, requiredHeaders (include on the PUT), requiresContentRange (SharePoint). The client PUTs bytes directly to storage, then confirmUploads creates the file record and enqueues image pipeline processing. Fails with ERROR_UNSUPPORTED_PROVIDER on Salesforce storage.

relocateFiles / getRelocationStatus

Moves an existing Salesforce file (069… ContentDocumentId) into the org’s active external provider, server-side and asynchronously via multipart upload. No practical size ceiling; the accept call is callout-free, so it runs after DML and inside Batchable/Queueable.

Request: fileId (source 069…, required), recordId (required), deleteSourceAfterRelocate (optional, default false - keep the Salesforce source; true deletes it after the copy lands).

The synchronous result is an acceptance, not an upload-complete signal: on accept, success = true, status = 'Uploading', and fileId is the relocated file’s durable opaque handle. Store it once; it is the file’s permanent id and resolves to the external file once complete (and back to the retained source if the relocation fails).

getRelocationStatus is a callout-free poll by handle, returning status ('Uploading'/'Complete'/'Failed'), fileId (when Complete), and relocationErrorMessage (when Failed). It is the required completion channel for soft-dependent packages (which cannot subscribe to the Storage_Event__e platform event). Subscribers that can reference Sliick types may instead listen on Storage_Event__e.

Requires an external provider that supports server-side multipart upload and the large-file-streaming entitlement; otherwise items fail with ERROR_UNSUPPORTED_PROVIDER or ERROR_STREAMING_NOT_ENABLED.

One-time org prerequisite: an admin clicks Set up large-file streaming in the Storage Settings panel once, which writes the Remote Site Settings the async stream requires. A headless caller cannot provision these.

listFiles

Files attached to each record - Salesforce and external merged, createdDate DESC, max 200 per record. No download URLs (resolve per file with getDownloadUrls). FileInfo fields: fileId, name (no extension), extension, contentType, sizeBytes, provider ('Salesforce', 'Sliick_Cloud', 'S3_Compatible', 'GCP', 'Azure', 'SharePoint'), processingStatus ('Pending'/'Complete'/'Failed', null for Salesforce files), isCurrentVersion, tags (read-only in v1), createdDate.

getDownloadUrls / getFileBodies

getDownloadUrls returns a short-lived download URL per fileId, plus name, contentType, sizeBytes. External URLs are presigned and expire within minutes; expiresAt may be null when the provider does not report it.

getFileBodies returns raw base64 bytes (no data: prefix) plus contentType and fileName. Heap-bound (~4MB per file) - request large files individually, or use getDownloadUrls and fetch outside Apex.

There is no server-side ZIP. To bundle multiple files, fetch a getDownloadUrls manifest and assemble the ZIP in the browser (JSZip, or a streaming library like client-zip); bytes go browser ↔ provider directly. The bucket must allow cross-origin GET from the Lightning/Experience origin.

deleteFiles

Removes the file from the given record (fileId + recordId both required). Unified across providers: the file is detached from this record; when that was its last attachment the file itself is deleted (Salesforce files immediately, external files via the orphan-cleanup grace period). Deleting a file that is not attached is a successful no-op.

renameFiles / moveFiles

renameFiles updates the display name only (metadata; any extension in newName is stripped; storage paths unchanged). moveFiles repoints the attachment from sourceRecordId to targetRecordId (no bytes move); requires edit access on both records. If the file is already on the target, the move only removes it from the source.


Flow notes

The running user needs the same two grants as an Apex caller. Behaviour:

  • Branch on outputs, not faults. Per-item failures return Success = false with an Error Code - wire a Decision element. The fault path fires only for call-level failures (most commonly a missing custom permission).
  • Callout rules apply. Upload File (external) and Get Download URL (external) perform callouts: in record-triggered flows, run them from an asynchronous or scheduled path. List/Delete/Rename/Move are callout-free.
  • Device upload uses the screen component. The Upload File action is for files the flow already holds as base64 (generated documents, callout responses). For device uploads in a screen flow, use the Sliick File Upload screen component.
  • List Files returns FileInfoOutput records (an Apex-defined type) you can loop over; fields mirror FileInfo.
  • The presigned pair and Get File Bodies are not exposed to Flow.

Pattern: screen-flow device upload

  1. Screen: collect record fields.
  2. Create Records: insert the record; store its id (e.g. recordId).
  3. Screen: add the Sliick File Upload component; set Record ID = {!recordId}, Require Upload = true.
  4. Files upload browser-direct to the active provider; File ID / File Name are available to later elements.

Pattern: record-triggered attachment routing

  1. Record-Triggered Flow, after-save.
  2. Sliick Files: List Files (Record ID = source record) → loop the FileInfoOutput collection.
  3. Per file: Rename File, then Move File (Source = source record, Target = related record). Both are callout-free.
  4. Decision on Success after each action routes per-item failures.

Versioning

sliick.FilesApi is a managed-package global surface: once a version containing it is promoted, signatures and error codes are locked. The surface only grows - new methods, new optional request/result fields, new Callable and Flow actions. getCapabilities returns the current actions list and a revision that increments with each addition.

The full field-by-field reference ships in the package docs.

Need a hand?
Not sure your Salesforce setup is configured correctly?

We'll audit your architecture, security, and integration posture.

Book an audit →

Share this article

Jerry Huang
Written by
Jerry Huang

Jerry Huang is the Founder & CEO of Sliick. He is passionate about building apps, helping customers succeed, and starting and scaling great businesses with the Salesforce platform. Jerry has been in tech for over two decades. He has 30 Salesforce certifications, including the Salesforce Certified Technical Architect, and an approved U.S. patent.

Continue reading