Integrating Salesforce with the CubeMaster API
A step-by-step guide to calculating load plans using the https://api.cubemaster.net/loads
endpoint.
To use the CubeMaster API, you need an API key (TokenID) for authentication. Here's how to get started:
- Visit the CubeMaster website: https://cubemaster.net.
- Locate the "Sign In" option (typically found in the top-right corner).
- Fill out the registration form with your details (e.g., name, email, password, company information).
- After signing up, log in to your account dashboard.
- Navigate to the "Settings" - "Integration" section to generate your API key (TokenID).
- Generate an API key. Once generated, you’ll receive a unique
TokenID
(e.g.,abc123xyz789
). Copy this key and store it securely, as it will be used in the HTTP headers of your API requests. - Copy the TokenID and store it securely.
Note: The TokenID will be used in the HTTP headers of your POST request for authentication.
Before diving into Salesforce, let's clarify the concepts we'll be using:
- REST API: A set of rules for how applications talk to each other. We make requests to specific URLs (endpoints) to get or send data.
- Endpoint: The specific URL we will communicate with. In our case, it's
https://api.cubemaster.net/loads
. - HTTP Method: The type of request. To create a new load plan, we will use the
POST
method. - Headers: Extra information sent with our request, including our API key for authentication (
TokenID
) and the data format (Content-Type: application/json
). - Request Body: The data we send to the endpoint, structured in JSON format.
- Response Body: The data the API sends back after processing our request.
- JSON (JavaScript Object Notation): A lightweight, human-readable format for structuring data as key-value pairs, which is the standard for most modern APIs.
We will use Named Credentials to securely store the CubeMaster API endpoint and authentication details. This is a Salesforce best practice as it separates credentials from code.
- In Salesforce Setup, search for and navigate to "Named Credentials".
- Click "New Named Credential".
-
Fill in the details:
- Label:
CubeMaster API
- Name:
CubeMaster_API
- URL:
https://api.cubemaster.net
- Identity Type:
Named Principal
- Authentication Protocol:
Custom
- Generate Authorization Header: Uncheck this box.
- Label:
- Click Save.
-
On the detail page, scroll to "Custom Headers" and click "New".
- Header Field Name:
TokenID
- Value: Paste your CubeMaster API Key here.
- Header Field Name:
-
Click "New" again to add another header.
- Header Field Name:
Content-Type
- Value:
application/json
- Header Field Name:
- Click Save.
To easily build the request and parse the response, we create Apex classes that mirror the JSON structure.
1. Request Wrapper Class (CubeMasterRequestWrapper.cls
)
// CubeMasterRequestWrapper.cls
public class CubeMasterRequestWrapper {
public String Title;
public String Description;
public List Cargoes;
public List Containers;
public Rules Rules;
public class Cargo {
public String Name;
public Decimal Length;
public Decimal Width;
public Decimal Height;
public Decimal Weight;
public String OrientationsAllowed;
public Boolean TurnAllowedOnFloor;
public Integer Qty;
public String ColorKnownName;
}
public class Container {
public String VehicleType;
public String Name;
public Decimal Length;
public Decimal Width;
public Decimal Height;
public String ColorKnownName;
}
public class Rules {
public Boolean IsWeightLimited;
public Boolean IsSequenceUsed;
public String FillDirection;
public String CalculationType;
}
}
2. Response Wrapper Class (CubeMasterResponseWrapper.cls
)
// CubeMasterResponseWrapper.cls
public class CubeMasterResponseWrapper {
public String status;
public String message;
public String calculationError;
public Document document;
public LoadSummary loadSummary;
public List filledContainers;
public class Document {
public String title;
public String createdBy;
public Datetime createdAt;
}
public class LoadSummary {
public Integer cargoesLoaded;
public Integer piecesLeft;
public Decimal volumeLoaded;
public Decimal weightLoaded;
}
public class FilledContainer {
public String name;
public ContainerLoadSummary loadSummary;
public Graphics graphics;
}
public class ContainerLoadSummary {
public Decimal volumeUtilization;
public Decimal floorUtilization;
public Decimal weightLoaded;
}
public class Graphics {
public Images images;
}
public class Images {
public String path3DDiagram;
public String pathComposite;
}
}
This class contains the logic to build the request, make the callout, and process the response. This example assumes you have custom objects Shipment__c
and a related child Shipment_Item__c
.
Apex Service Class (CubeMasterService.cls
)
// CubeMasterService.cls
public class CubeMasterService {
// Use @future for callouts from Triggers or other synchronous processes
@future(callout=true)
public static void calculateLoadPlan(Id shipmentId) {
// 1. Query Salesforce data
Shipment__c shipment = [SELECT Id, Name, Description__c,
(SELECT Name, Product_Name__c, Quantity__c, Length__c, Width__c, Height__c, Weight__c
FROM Shipment_Items__r)
FROM Shipment__c WHERE Id = :shipmentId];
// 2. Build the Request Body using our wrapper class
CubeMasterRequestWrapper requestBody = new CubeMasterRequestWrapper();
requestBody.Title = shipment.Name;
requestBody.Description = shipment.Description__c;
// --- Cargoes ---
requestBody.Cargoes = new List();
for (Shipment_Item__c item : shipment.Shipment_Items__r) {
CubeMasterRequestWrapper.Cargo c = new CubeMasterRequestWrapper.Cargo();
c.Name = item.Product_Name__c;
c.Qty = Integer.valueOf(item.Quantity__c);
c.Length = item.Length__c;
c.Width = item.Width__c;
c.Height = item.Height__c;
c.Weight = item.Weight__c;
c.OrientationsAllowed = 'OrientationsAll';
c.TurnAllowedOnFloor = false;
c.ColorKnownName = 'Brown';
requestBody.Cargoes.add(c);
}
// --- Containers ---
requestBody.Containers = new List();
CubeMasterRequestWrapper.Container container = new CubeMasterRequestWrapper.Container();
container.VehicleType = 'Dry';
container.Name = '53FT-Intermodal';
container.Length = 630;
container.Width = 98;
container.Height = 106;
container.ColorKnownName = 'Blue';
requestBody.Containers.add(container);
// --- Rules ---
requestBody.Rules = new CubeMasterRequestWrapper.Rules();
requestBody.Rules.IsWeightLimited = true;
requestBody.Rules.IsSequenceUsed = false;
requestBody.Rules.FillDirection = 'FrontToRear';
requestBody.Rules.CalculationType = 'MixLoad';
String jsonPayload = JSON.serialize(requestBody, true);
System.debug('Request Payload: ' + jsonPayload);
// 3. Perform the HTTP Callout
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:CubeMaster_API/v1/loads');
request.setMethod('POST');
request.setBody(jsonPayload);
try {
HttpResponse response = http.send(request);
// 4. Process the Response
if (response.getStatusCode() == 200 || response.getStatusCode() == 201) {
CubeMasterResponseWrapper responseWrapper = (CubeMasterResponseWrapper) JSON.deserialize(response.getBody(), CubeMasterResponseWrapper.class);
// 5. Update Salesforce record with results
if (responseWrapper.status == 'succeed' && !responseWrapper.filledContainers.isEmpty()) {
CubeMasterResponseWrapper.FilledContainer firstContainer = responseWrapper.filledContainers[0];
Shipment__c shipmentToUpdate = new Shipment__c(Id = shipmentId);
shipmentToUpdate.Volume_Utilization__c = firstContainer.loadSummary.volumeUtilization;
shipmentToUpdate.Weight_Loaded__c = firstContainer.loadSummary.weightLoaded;
shipmentToUpdate.Load_Plan_Status__c = 'Success';
shipmentToUpdate.Load_Plan_3D_Image_URL__c = firstContainer.graphics.images.path3DDiagram;
update shipmentToUpdate;
} else {
handleError(shipmentId, 'CubeMaster Error: ' + responseWrapper.message);
}
} else {
handleError(shipmentId, 'HTTP Error: ' + response.getStatusCode() + ' ' + response.getStatus());
}
} catch (Exception e) {
handleError(shipmentId, 'Apex Exception: ' + e.getMessage());
}
}
private static void handleError(Id shipmentId, String errorMessage) {
Shipment__c shipmentToUpdate = new Shipment__c(
Id = shipmentId,
Load_Plan_Status__c = 'Error',
Load_Plan_Error_Message__c = errorMessage.left(255)
);
update shipmentToUpdate;
}
}
You need a way to run the service class. A trigger that fires when a checkbox is checked is a common and effective method.
1. Add New Fields to your `Shipment__c` Object
In Object Manager, add the following fields to your `Shipment__c` custom object:
Calculate_Load_Plan__c
(Checkbox, default unchecked)Load_Plan_Status__c
(Text or Picklist)Volume_Utilization__c
(Percent)Weight_Loaded__c
(Number)Load_Plan_3D_Image_URL__c
(URL)Load_Plan_Error_Message__c
(Text)
2. Create an Apex Trigger (ShipmentTrigger.trigger
)
// ShipmentTrigger.trigger
trigger ShipmentTrigger on Shipment__c (after update) {
if (Trigger.isAfter && Trigger.isUpdate) {
for (Shipment__c newShipment : Trigger.new) {
Shipment__c oldShipment = Trigger.oldMap.get(newShipment.Id);
// Check if the "Calculate" checkbox was changed from false to true
if (newShipment.Calculate_Load_Plan__c == true && oldShipment.Calculate_Load_Plan__c == false) {
// Call the future method to perform the callout
CubeMasterService.calculateLoadPlan(newShipment.Id);
// Immediately uncheck the box to prevent re-firing.
// This is safe because the DML for the callout is in a future context.
newShipment.Calculate_Load_Plan__c = false;
}
}
}
}
Once everything is set up, the end-to-end process will be seamless for the user:
- A user creates a
Shipment__c
record and adds its relatedShipment_Item__c
records. - When ready, they check the "Calculate Load Plan" box on the Shipment record and save.
- The
ShipmentTrigger
fires and calls theCubeMasterService
future method. - The service class builds the JSON request from the shipment data and sends it to the CubeMaster API.
- CubeMaster calculates the optimal load plan and sends back a JSON response.
- The service class parses the response and updates the original
Shipment__c
record with the results, including volume utilization, weight, status, and the 3D image URL.
Load_Plan_3D_Image_URL__c
field in a Rich Text formula field on the page layout to display the load plan image directly to your users!