S-Drive 3.6 Documentation

REST API Detail

S-Drive REST API

The S-Drive REST API lets external applications work with files stored in your S-Drive-enabled Salesforce org: search file records, create and update them, upload new files to your S3 bucket, and generate download links.


Getting started

Base URL

All S-Drive endpoints live under your Salesforce My Domain, with the cg namespace segment:

https://<your-domain>.my.salesforce.com/services/apexrest/cg/

The namespace segment cg is required. Requests that omit it will not resolve.

Authentication

The API uses standard Salesforce OAuth 2.0. Obtain an access token through any supported Salesforce OAuth flow, then send it as a bearer token:

Authorization: Bearer 00D5f000000abcd!AQEAQ...

Requests run as the authenticated user. Sharing rules, object permissions, and field-level security all apply — a user who cannot see a file record through the Salesforce UI cannot see it through the API either.

Required access

The calling user needs:

  • Read access to the S-Drive file objects being addressed (for example cg__AccountFile__c)

  • Access to the S-Drive File Objects and S-Drive Bucket List custom settings

  • Create, edit, or delete permission on the file object for the corresponding operations

You can verify a user's permission set assignment before starting work using Check user permission.

Content type

Send Content-Type: application/json on all requests with a body. Responses are JSON unless noted.


Conventions

Status codes

Code

Meaning

200

Success

201

Record created

204

Request valid, but no records matched

400

Missing, malformed, or invalid parameters

403

The user lacks permission for the requested object or field

404

The referenced record or resource was not found

406

Input could not be parsed

500

Unexpected error; see message for details

501

Requested feature is not supported on this endpoint

Error responses

Errors return a message field describing what went wrong:

JSON
{
  "files": [],
  "message": "Wrong input or missing input fields"
}

Partial success

Bulk operations can partially succeed. When they do, the response is still 200 and the outcome is described in message. Always check message in addition to the status code on bulk delete and bulk upload-completion calls — the Delete files and Complete an upload sections describe exactly what to look for.


Uploading a file

Uploading is a three-step process. Your application sends the file bytes directly to Amazon S3 — they never pass through Salesforce, which is what allows S-Drive to handle files of any size.

1. POST /SDrive/v2/upload/     Ask S-Drive to prepare the upload.
                               Returns a signed policy and an S3 endpoint.

2. PUT  <s3Endpoint>           Send the file bytes straight to S3 using
                               the credentials from step 1.

3. PUT  /SDrive/v2/upload/     Tell S-Drive the upload finished.
                               The file record becomes visible in S-Drive.

Until step 3 completes, the file record exists but is flagged as work-in-progress and is excluded from searches and the S-Drive UI.

For large files, replace step 2 with the multipart upload sequence.

Worked example

Step 1 — initialize

Bash
curl -X POST \
  "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v2/upload/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "objectId": "0015f00000XyZaBAAV",
    "fileType": "cg__AccountFile__c",
    "fileProperties": [{
      "fileName": "invoice.pdf",
      "fileLocation": "",
      "description": "October invoice",
      "fileSizeInBytes": "184320",
      "relationshipFieldName": "cg__Account__c"
    }],
    "policyMap": {}
  }'

Response:

JSON
{
  "accessKey": "AKIAIOSFODNN7EXAMPLE",
  "bucketName": "acme-sdrive",
  "s3EndPoint": "s3.us-east-1.amazonaws.com",
  "uploadRequestInfo": [{
    "wipFileId": "a0X5f000000abcdEAA",
    "fileName": "invoice.pdf",
    "fileLocation": "0015f00000XyZaBAAV/a0X5f000000abcdEAA/invoice.pdf",
    "signature": "0dc2...",
    "policy": "eyJleHBpcmF0aW9u...",
    "timeStamp": "20260819T143022Z",
    "awsRegion": "us-east-1",
    "awsCredential": "AKIAIOSFODNN7EXAMPLE/20260819/us-east-1/s3/aws4_request",
    "isSuccess": true
  }],
  "message": "success"
}

Step 2 — send bytes to S3

Use signature, policy, awsCredential, and timeStamp to construct a standard AWS Signature Version 4 upload against s3EndPoint and bucketName, targeting the key in fileLocation.

Step 3 — complete

Bash
curl -X PUT \
  "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v2/upload/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files": [{"id": "a0X5f000000abcdEAA"}]}'
JSON
{ "files": [], "message": "success" }

The file is now visible in S-Drive.


Discovery

List parent objects

Returns every Salesforce object configured for S-Drive, together with its file objects and the relationship field name you need when uploading.

GET /SDrive/v2/parentObjects/

Query parameters

Name

Required

Description

parentObjectName

No

API name of a single parent object. Omit to return all.

Only one query parameter is accepted.

Example

Bash
curl "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v2/parentObjects/?parentObjectName=Account" \
  -H "Authorization: Bearer $TOKEN"
JSON
{
  "objects": [{
    "name": "Account",
    "label": "Account",
    "children": [{
      "objectApiName": "cg__AccountFile__c",
      "objectRelationshipName": "cg__Account__c"
    }]
  }],
  "message": "success"
}

Use objectApiName as fileType and objectRelationshipName as relationshipFieldName when initializing an upload.


Check whether folders are empty

Returns the fill status of one or more folders. Useful for rendering folder icons without querying their contents.

GET /SDrive/v2/fileObjects/

Query parameters — all three are required.

Name

Description

fileObjectName

File object API name, for example cg__AccountFile__c

parentObjectId

ID of the parent record

folderIds

Comma-separated folder record IDs

Example

Bash
curl "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v2/fileObjects/?fileObjectName=cg__AccountFile__c&parentObjectId=0015f00000XyZaBAAV&folderIds=a0X5f000000abcdEAA,a0X5f000000efghEAA" \
  -H "Authorization: Bearer $TOKEN"
JSON
{
  "Empty": ["a0X5f000000abcdEAA"],
  "NonEmpty": ["a0X5f000000efghEAA"],
  "Unknown": [],
  "message": "success"
}

Folders appear under Unknown when their status could not be determined.


Files

Search files

Returns a paginated list of file records matching your criteria.

GET /SDrive/v2/files/?searchQuery=<url-encoded JSON>

Query parameters

Name

Required

Description

searchQuery

Yes

A URL-encoded Search query JSON object. This is the only accepted parameter.

Example search query (shown unencoded)

JSON
{
  "fileObjectApiName": "cg__AccountFile__c",
  "searchFields": [
    { "sequence": 1, "fieldApiName": "cg__File_Name__c", "value": "invoice", "operator": "contains" },
    { "sequence": 2, "fieldApiName": "cg__Account__c", "value": "0015f00000XyZaBAAV", "operator": "equals" }
  ],
  "conditionalExpression": "{1} AND {2}",
  "orderByFieldApiName": "cg__File_Name__c",
  "limitValue": 25,
  "offset": 0
}

Response

JSON
{
  "files": [ { /* File object */ } ],
  "totalPages": 4,
  "page": 1,
  "message": "success"
}

When no records match, the response is 204 with "message": "no records were found".

Pagination

limitValue is required and must be greater than zero. offset must be an exact multiple of limitValue — request page 3 of a 25-per-page result set with offset: 50, not offset: 51. Violating either rule returns 400.

totalPages reflects the full result set, not the current page.

Default filters

Searches automatically exclude records that are in progress, superseded, or deleted. Four conditions are applied unless you override them:

Field

Default applied

<prefix>WIP__c

false — excludes uploads that have not been completed

<prefix>Is_Latest_Version__c

true — returns only the current version of each file

<prefix>Is_Deleted__c

false — excludes files in the recycle bin

<prefix>Is_Parent_Deleted__c

false — excludes files whose folder was deleted

To retrieve records the defaults would hide, include that field explicitly in searchFields. For example, to list every version of a file rather than just the latest:

JSON
{
  "fileObjectApiName": "cg__AccountFile__c",
  "searchFields": [
    { "sequence": 1, "fieldApiName": "cg__File_Name__c", "value": "invoice.pdf", "operator": "equals" },
    { "sequence": 2, "fieldApiName": "cg__Is_Latest_Version__c", "value": "true", "operator": "notequals" }
  ],
  "conditionalExpression": "{1}",
  "limitValue": 50
}

Naming a field in searchFields replaces its default condition entirely.


Create file records

Creates file or folder records in bulk. This creates Salesforce records only — to upload file content, use the upload flow.

The most common use is creating folders.

POST /SDrive/v2/files/

Request body

JSON
{
  "files": [{
    "fileObjectName": "cg__AccountFile__c",
    "parentObjectName": "cg__Account__c",
    "parentObjectId": "0015f00000XyZaBAAV",
    "parentFolderId": "",
    "fileName": "Q3 Reports",
    "type": "Folder",
    "fields": [
      { "fieldApiName": "cg__Description__c", "fieldValue": "Quarterly reports" }
    ]
  }]
}

Field

Required

Description

fileObjectName

Yes

File object API name

parentObjectName

Yes

Relationship field API name on the file object

parentObjectId

Yes

ID of the parent record

parentFolderId

Yes

ID of the containing folder; empty string for the root

fileName

Yes

Name of the file or folder

type

Yes

Content type. Use the exact value Folder to create a folder.

fields

No

Additional field values to set on the new record

Response 201

JSON
{ "files": ["a0X5f000000abcdEAA"], "message": "success" }

Returns 403 if the user cannot create records on the specified object.


Update file records

Updates fields on existing file records in bulk.

PUT /SDrive/v2/files/

Request body

JSON
{
  "files": [{
    "fileObjectName": "cg__AccountFile__c",
    "fileObjectId": "a0X5f000000abcdEAA",
    "fields": [
      { "fieldApiName": "cg__Description__c", "fieldValue": "Updated description" }
    ]
  }]
}

Supply fieldValue using the JSON type that matches the Salesforce field: a string for text and picklist fields, true/false for checkboxes, a number for numeric fields.

Response 200

JSON
{ "files": ["a0X5f000000abcdEAA"], "message": "success" }

If no records changed, message is "no updates were made" and files is empty.


Delete files

Deletes file records and their S3 objects.

DELETE /SDrive/v2/files/?fileObjectName=<name>&fileObjectIds=<ids>

Query parameters — both required.

Name

Description

fileObjectName

File object API name

fileObjectIds

Comma-separated record IDs. Maximum 10,000 per request.

Example

Bash
curl -X DELETE \
  "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v2/files/?fileObjectName=cg__AccountFile__c&fileObjectIds=a0X5f000000abcdEAA,a0X5f000000efghEAA" \
  -H "Authorization: Bearer $TOKEN"

Response 200

files lists the IDs that were successfully deleted. Check message to determine the outcome:

message

Meaning

success

Every file was deleted

delete operation failed for some. deleted only the file(s) with given ids.

Some files were deleted; files names them

nothing has been deleted

No files were deleted

Requests exceeding 10,000 IDs return 400. Split larger deletions into batches.


Uploads

Initialize an upload

Creates a work-in-progress file record and returns the signed credentials your application needs to send the file to S3.

POST /SDrive/v2/upload/

Request body

JSON
{
  "objectId": "0015f00000XyZaBAAV",
  "fileType": "cg__AccountFile__c",
  "fileProperties": [{
    "fileName": "invoice.pdf",
    "fileLocation": "a0X5f000000abcdEAA",
    "description": "October invoice",
    "fileSizeInBytes": "184320",
    "relationshipFieldName": "cg__Account__c",
    "versionId": ""
  }],
  "policyMap": {}
}

Field

Required

Description

objectId

Yes

ID of the parent record

fileType

Yes

File object API name

policyMap

Yes

Upload policy overrides. Send {} for defaults. All values must be strings.

fileProperties[].fileName

Yes

Name of the file being uploaded

fileProperties[].fileLocation

Yes

ID of the destination folder; empty string for the root

fileProperties[].fileSizeInBytes

Yes

File size in bytes, as a string

fileProperties[].relationshipFieldName

Yes

From List parent objects

fileProperties[].description

No

Description to store on the record

fileProperties[].versionId

No

Supply to upload a new version of an existing file

All entries in a single request must share the same relationshipFieldName.

Response 201 — see the worked example. Each entry in uploadRequestInfo corresponds to one entry in fileProperties; see Upload request info for the full field list.


Complete an upload

Clears the work-in-progress flag after the file has reached S3, making it visible in S-Drive.

PUT /SDrive/v2/upload/

Request body

JSON
{
  "files": [
    { "id": "a0X5f000000abcdEAA" }
  ]
}

Field

Required

Description

files[].id

Yes

The wipFileId returned when the upload was initialized

files[].versionId

No

S3 version ID, for version-enabled buckets

If you include versionId on any entry, include it on every entry in the request.

Response 200

Check message to determine the outcome:

message

Meaning

success

Every upload was completed

put operation failed for some. could not update the file(s) with given ids.

Some failed; files names the failures

nothing has been updated

No records were updated

Note that on this endpoint files lists the records that failed, whereas on Delete files it lists the records that succeeded.


Get a signed payload

Returns a signed S3 payload for a given object key.

GET /SDrive/v2/upload/?fileKey=<key>

Query parameters

Name

Required

Description

fileKey

Yes

The S3 object key

type

No

Reserved for internal use

Response 200

JSON
{ "payload": "AKIAIOSFODNN7EXAMPLE/20260819/...", "message": "" }

Multipart uploads

For large files, replace the single PUT to S3 with a multipart sequence. All four operations use the same URL and are distinguished by HTTP method.

PUT    /SDrive/v2/upload/s3/     Start the multipart upload
GET    /SDrive/v2/upload/s3/     Get authorization headers for one part
POST   /SDrive/v2/upload/s3/     Finish the multipart upload
DELETE /SDrive/v2/upload/s3/     Cancel the multipart upload

The maxPartCount value returned when you initialize the upload tells you the maximum number of parts permitted.

Start a multipart upload

PUT /SDrive/v2/upload/s3/
JSON
{ "fileKey": "0015f00000XyZaBAAV/a0X5f000000abcdEAA/large-video.mp4" }
JSON
{ "uploadId": "2~pMEbxHW3iAG2SqBLxLM5r1vXNmVvC0v", "message": "" }

Use fileLocation from the initialize-upload response as fileKey.

Get authorization headers for a part

GET /SDrive/v2/upload/s3/?fileKey=<key>&uploadId=<id>&partNumber=<n>&contentLength=<bytes>

Name

Required

Description

fileKey

Yes

Same key used to start the upload

uploadId

Yes

From the start response

partNumber

Yes

Part number, starting at 1

contentLength

Yes

Size of this part in bytes

JSON
{
  "requestHeaders": {
    "Authorization": "AWS4-HMAC-SHA256 Credential=...",
    "x-amz-date": "20260819T143022Z",
    "x-amz-content-sha256": "UNSIGNED-PAYLOAD"
  },
  "message": ""
}

Apply these headers to your own PUT request to S3 for that part, and keep the ETag S3 returns.

Finish a multipart upload

POST /SDrive/v2/upload/s3/
JSON
{
  "fileKey": "0015f00000XyZaBAAV/a0X5f000000abcdEAA/large-video.mp4",
  "uploadId": "2~pMEbxHW3iAG2SqBLxLM5r1vXNmVvC0v",
  "eTagList": "\"9b2cf5...\",\"3d8a71...\",\"c04e29...\""
}

eTagList is a single comma-separated string, not a JSON array. List the ETags in part order.

JSON
{ "completeETag": "\"5f1a3c...-3\"", "message": "" }

After this succeeds, call Complete an upload to make the file visible in S-Drive.

Cancel a multipart upload

DELETE /SDrive/v2/upload/s3/?fileKey=<key>&uploadId=<id>
JSON
{ "message": "Abort multipart upload succeed." }

Cancelling releases the storage consumed by any parts already uploaded. Abandoned multipart uploads continue to incur S3 charges until aborted.


Downloads

Get a download URL

Returns a time-limited signed URL for downloading a file directly from S3.

GET /SDrive/files/?fileObjectId=<id>&parentId=<id>&timeValue=<seconds>

Query parameters

Name

Required

Description

fileObjectId

Yes

ID of the file record

parentId

Yes

ID of the parent record

timeValue

Yes

Number of seconds the link stays valid

Any additional query parameters are passed through to S3. Use this to control download behavior — for example, response-content-disposition=attachment forces a download rather than opening the file in the browser.

Example

Bash
curl "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/files/?fileObjectId=a0X5f000000abcdEAA&parentId=0015f00000XyZaBAAV&timeValue=3600" \
  -H "Authorization: Bearer $TOKEN"
JSON
{ "FileUrls": ["https://acme-sdrive.s3.amazonaws.com/0015f00000.../invoice.pdf?X-Amz-Algorithm=..."] }

The returned URL requires no authentication and is valid for timeValue seconds. Treat it as a secret.

Requesting several URLs at once

To generate links for multiple files in one call, send a JSON body with no query parameters:

JSON
{
  "timeValue": 3600,
  "fileObjectIds": ["a0X5f000000abcdEAA", "a0X5f000000efghEAA"],
  "parentIds": ["0015f00000XyZaBAAV", "0015f00000XyZaBAAV"],
  "requestParameters": { "response-content-disposition": "attachment" }
}

fileObjectIds and parentIds must be the same length and in matching order. URLs are returned in the same order.

Because some HTTP clients and proxies strip bodies from GET requests, use the query-parameter form where possible.


Get a ZIP archive URL

Retrieves the download URL for an archive produced by S-Drive's asynchronous zip-and-download feature.

GET /SDrive/v2/AsyncZipAndDownload/?zipName=<name>

Name

Required

Description

zipName

Yes

Name of the generated archive

The response body is the URL itself as plain text, not JSON:

https://acme-sdrive.s3.amazonaws.com/sdrivejob/Download-Job-Account-20260819143022.zip?X-Amz-Algorithm=...

The URL is valid for 7 days.


Check user permission

Confirms whether the authenticated user is assigned a given permission set. Use this at sign-in to verify a user can proceed before making further calls.

GET /SDrive/v1/permissionCheck/?permissionSetName=<name>

Name

Required

Description

permissionSetName

Yes

Name of the permission set to check

Example

Bash
curl "https://acme.my.salesforce.com/services/apexrest/cg/SDrive/v1/permissionCheck/?permissionSetName=SDriveUserPSet" \
  -H "Authorization: Bearer $TOKEN"
JSON
{
  "message": "User authorization is confirmed for this application. User ID: 0055f00000ABCDEAA3",
  "hasPermission": true,
  "statusCode": 200
}

This endpoint always returns HTTP 200. Read the hasPermission and statusCode fields in the response body to determine the result:

statusCode

hasPermission

Meaning

200

true

User is assigned and authorized

400

false

permissionSetName was not supplied

401

false

User is not assigned the permission set

404

false

No permission set with that name exists in the org

500

false

An error occurred; see message

Checking ContentAuthoringPSet additionally requires that Desktop Experience be enabled in the org. If it is not, the result is 401 even when the user holds the permission set.


Object reference

Search query

Field

Type

Required

Description

fileObjectApiName

String

Yes

File object to search

limitValue

Integer

Yes

Records per page; must be greater than zero

offset

Integer

No

Records to skip; must be a multiple of limitValue

searchFields

Array

No

Conditions to apply; see Search field

conditionalExpression

String

No

How conditions combine. Defaults to all joined with AND.

orderByFieldApiName

String

No

Sort field. Defaults to Id.

conditionalExpression references search fields by their sequence using {n} placeholders, and supports grouping:

{1} AND {2}
{1} OR {2}
({1} OR {2}) AND {3}

Search field

Field

Type

Description

sequence

Integer

Number referenced by conditionalExpression

fieldApiName

String

Field to filter on; must exist on the file object

value

String

Value to compare against, always supplied as a string

operator

String

One of the operators below

Operators: equals, notequals, greaterthan, lessthan, contains, startswith, endswith

Supply value as a string even for checkbox, number, and date fields — for example "true" or "1024". S-Drive converts it to the correct type.

File

Returned by Search files.

Field

Type

Description

id

String

File record ID

fileName

String

File or folder name

fileObjectName

String

File object the record belongs to

contentType

String

MIME type, or Folder for folders

description

String

Description

tags

String

Tags

fileSize

String

Human-readable size, for example 180 KB

fileSizeInBytes

Number

Exact size in bytes

parentFolderId

String

Containing folder ID

parentFolderName

String

Containing folder name

createdById

String

ID of the creating user

createdByName

String

Name of the creating user

createdDate

Datetime

Creation timestamp

downloadUrl

String

Signed download URL

previewUrl

String

Signed preview URL

thumbnailUrl

String

Signed thumbnail URL

key

String

S3 object key

versionId

String

S3 version ID

bucketId

String

Bucket setting ID

bucketName

String

S3 bucket name

isVersionedBucket

Boolean

Whether the bucket has versioning enabled

wip

Boolean

Whether the upload is still in progress

isDeleted

Boolean

Whether the file is in the recycle bin

shortcut

String

Target of a shortcut, if the record is one

lockState

String

Lock status

checkedOutBy

String

ID of the user who has the file checked out

checkedOutByName

String

Name of that user

fileRestricted

Boolean

Whether access is restricted

file

Object

The complete underlying Salesforce record, including any custom fields

The file field gives you every field on the record, so custom fields can be read without a separate Salesforce query.

Upload request info

Returned by Initialize an upload, one entry per file.

Field

Type

Description

wipFileId

String

Record ID to send when completing the upload

fileName

String

File name

fileLocation

String

S3 object key to upload to

fileSize

String

File size

fileType

String

Content type

signature

String

Signature for the S3 request

policy

String

Base64-encoded upload policy

timeStamp

String

Timestamp used in signing

awsRegion

String

AWS region of the bucket

awsCredential

String

Credential scope string for signing

bucketName

String

Destination bucket

s3Endpoint

String

S3 endpoint hostname

s3EncryptionType

String

Server-side encryption to request

metadataHeaders

Object

Metadata headers to include on the upload

isS3TransferAccelerationEnabled

Boolean

Whether to use the S3 transfer acceleration endpoint

isVersioned

Boolean

Whether the bucket has versioning enabled

maxPartCount

Number

Maximum parts allowed for a multipart upload

storageService

String

Storage backend in use

isSuccess

Boolean

Whether initialization succeeded for this file

errorMessage

String

Reason for failure when isSuccess is false

Always check isSuccess before uploading. When it is false, errorMessage explains why.


Legacy endpoints

These endpoints predate the v2 API and remain available for existing integrations. New applications should use the v2 equivalents.

Legacy endpoint

Use instead

GET /SDrive/fileObjects/

GET /SDrive/v2/parentObjects/

POST /SDrive/files/

POST /SDrive/v2/upload/

PUT /SDrive/files/

PUT /SDrive/v2/upload/

DELETE /SDrive/files/

DELETE /SDrive/v2/files/

GET /SDrive/files/ is not deprecated — it remains the endpoint for download URLs.

List file objects

GET /SDrive/fileObjects/

Returns the API names of all configured file objects. Takes no parameters.

JSON
{ "fileObjects": ["cg__AccountFile__c", "cg__CaseFile__c"] }

The v2 equivalent additionally returns parent objects, labels, and relationship field names.

Initialize an upload (legacy)

POST /SDrive/files/

Accepts the same body as POST /SDrive/v2/upload/, minus description and versionId. Sending "multipartUpload": true returns 501 — use multipart uploads instead.

The response is not wrapped in a message envelope:

JSON
{
  "accessKey": "...",
  "bucketName": "...",
  "s3EndPoint": "...",
  "uploadRequestInfo": [ ... ]
}

Complete an upload (legacy)

PUT /SDrive/files/
JSON
{
  "wipIds": ["a0X5f000000abcdEAA"],
  "versionIds": ["3HL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY"]
}

versionIds is optional. The field names differ from v2, which uses files[].id and files[].versionId.

JSON
{
  "resultObjects": [
    { "status": "success", "errorMessage": null, "wipFileId": "a0X5f000000abcdEAA" }
  ]
}

Delete files (legacy)

DELETE /SDrive/files/?objectId=<id>&wipId=<id>

Deletes a single file. For bulk deletion, send a body instead:

JSON
{
  "objectId": "0015f00000XyZaBAAV",
  "wipIds": ["a0X5f000000abcdEAA", "a0X5f000000efghEAA"]
}
JSON
{
  "resultObjects": [
    { "status": "success", "errorMessage": null, "wipFileId": "a0X5f000000abcdEAA" }
  ]
}

Each entry reports the outcome for one file: status is success or fail, and errorMessage is populated only on failure.