Introduction
Plenty of orgs want file storage to sit in Box while users keep working inside the CRM. Managed packages exist, but a custom Apex backend gives you finer control and the room for the custom logic and performance tuning that enterprise-scale work needs. Here is how I architect a custom integration on the Box REST API.
Establishing secure connectivity with OAuth 2.0
Authentication comes first, and Box uses OAuth 2.0. For server-to-server or app-user setups, JWT (JSON Web Token) authentication is the usual choice because it avoids manual user login prompts.
Start in the Box Developer Console: create a Custom App, configure the public/private key pair, and add the App Service Account to the Box folder you are targeting. On the Salesforce side, store the Private Key somewhere safe, either a Custom Metadata Type or an encrypted field.
To kick off the flow, have a service class build a JWT assertion, sign it with Crypto.sign(), and trade it for an access token:
public class BoxAuthService {
private static final String BOX_TOKEN_URL = 'https://api.box.com/oauth2/token';
public static String getAccessToken() {
// Generate JWT assertion here...
// Prepare request body for OAuth exchange
HttpRequest req = new HttpRequest();
req.setEndpoint(BOX_TOKEN_URL);
req.setMethod('POST');
req.setBody('grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=' + assertion + ...);
Http http = new Http();
HTTPResponse res = http.send(req);
// Parse JSON response to extract access_token
return token;
}
}
Implementing file uploads via Apex REST
Once you are authorized, the job is usually pushing Salesforce files or attachments into Box. Box wants a multipart/form-data request for uploads, and building the boundary string is where most developers get stuck.
Build a helper method for the request body. Point your Named Credential at api.box.com so the endpoints stay tidy.
public static void uploadFileToBox(String fileName, Blob fileContent, String folderId) {
String boundary = '--------------------------' + String.valueOf(Crypto.getRandomInteger());
String body = '--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="attributes"; filename="' + fileName + '"\r\n' +
'Content-Type: application/json\r\n\r\n' +
'{"name":"' + fileName + '", "parent":{"id":"' + folderId + '"}}\r\n' +
'--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="file"; filename="' + fileName + '"\r\n' +
'Content-Type: application/octet-stream\r\n\r\n';
// Append binary file content and closing boundary...
}
Handling asynchronous operations
Salesforce governor limits on heap size and callout counts make synchronous file processing risky once files get large. Push Box integrations into Queueable Apex. You get retries on failed requests, and the user interface stays responsive.
A pattern for an asynchronous file uploader:
public class BoxUploadQueueable implements Queueable, Database.AllowsCallouts {
private Id contentVersionId;
public BoxUploadQueueable(Id cvId) { this.contentVersionId = cvId; }
public void execute(QueueableContext qc) {
ContentVersion cv = [SELECT Title, VersionData FROM ContentVersion WHERE Id = :contentVersionId];
// Perform Box API Callout
// Handle 429 (Rate Limit) errors with a retry policy
}
}
Monitoring and error handling
Integrations fail. A logging framework you can actually debug against matters here, because Box API errors are hard to chase otherwise. Capture the X-Box-Request-Id header from every response. When an upload fails, write the response body and the request ID to a custom Integration_Log__c object.
On 401 Unauthorized, refresh your JWT token and retry. On 429 Too Many Requests, add a Retry-After header check. On 404 Not Found, confirm the parent folderId exists before you proceed.
Leave a Comment