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
relocateFilesstreams 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+getCapabilitiesand drives every operation throughCallable, 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.
| Action | Inputs | Outputs (plus Success / Error Code / Error Message) |
|---|---|---|
| Sliick Files: Upload File | Record ID, File Name, Content Type, File Body (Base64) | File ID, Download URL |
| Sliick Files: List Files | Record ID | Files (FileInfoOutput collection) |
| Sliick Files: Get Download URL | File ID | Download URL, Expires At |
| Sliick Files: Delete File | File ID, Record ID | - |
| Sliick Files: Rename File | File ID, New Name | - |
| Sliick Files: Move File | File 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:
- 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. - Data access - the SliickFilesUser permission set, or equivalent
CRUD/FLS on
sliick__Cloud_File__candsliick__Cloud_File_Link__c. All queries and DML run inUSER_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
| Constant | Meaning |
|---|---|
ERROR_ACCESS_DENIED | Missing custom permission, or no access to the record/file |
ERROR_NOT_FOUND | File or record does not exist / is not visible |
ERROR_INVALID_INPUT | Required field missing or malformed |
ERROR_UNSUPPORTED_PROVIDER | Operation unavailable on the active provider |
ERROR_FILE_TOO_LARGE | Exceeds the ~4MB Apex byte-relay cap |
ERROR_STREAMING_NOT_ENABLED | relocateFiles: large-file streaming not entitled for the org |
ERROR_RELOCATION_PENDING | A relocate handle was used as a fileId before its async copy finished |
ERROR_PROVIDER_FAILED | Downstream storage/database failure (cause in the message) |
Limits
| Limit | Value |
|---|---|
| Base64 upload / file-body read | ~4MB binary per file (Apex heap + provider cap) |
listFiles | max 200 files per record, createdDate DESC |
| Download URLs (external) | presigned, expire within minutes - fetch just before use |
confirmUploads | idempotent per uploadKey (replay returns the existing file) |
relocateFiles | async, 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 = falsewith anError 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
FileInfoOutputrecords (an Apex-defined type) you can loop over; fields mirrorFileInfo. - The presigned pair and Get File Bodies are not exposed to Flow.
Pattern: screen-flow device upload
- Screen: collect record fields.
- Create Records: insert the record; store its id (e.g.
recordId). - Screen: add the Sliick File Upload component; set Record ID =
{!recordId}, Require Upload =true. - Files upload browser-direct to the active provider;
File ID/File Nameare available to later elements.
Pattern: record-triggered attachment routing
- Record-Triggered Flow, after-save.
- Sliick Files: List Files (Record ID = source record) → loop the
FileInfoOutputcollection. - Per file: Rename File, then Move File (Source = source record, Target = related record). Both are callout-free.
- Decision on
Successafter 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.
We'll audit your architecture, security, and integration posture.