Using Salesforce (Apex)
To use the CubeMaster API, you need an API key (TokenID) for authentication. Here's how to get started:
TokenID (e.g., abc123xyz789). Copy this key and store it securely, as it will be used in the HTTP headers of your API requests.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:
https://api.cubemaster.net/loads.POST method.TokenID) and the data format (Content-Type: application/json).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.
CubeMaster APICubeMaster_APIhttps://api.cubemaster.netNamed PrincipalCustomTokenIDContent-Typeapplication/jsonTo easily build the request and parse the response, we create Apex classes that mirror the JSON structure.
CubeMasterRequestWrapper.cls)// CubeMasterRequestWrapper.cls
public class CubeMasterRequestWrapper {
public String Title;
public String Description;
public List<Cargo> Cargoes;
public List<Container> 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;
}
}
CubeMasterResponseWrapper.cls)// CubeMasterResponseWrapper.cls
public class CubeMasterResponseWrapper {
public String status;
public String message;
public String calculationError;
public Document document;
public LoadSummary loadSummary;
public List<FilledContainer> 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.
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<CubeMasterRequestWrapper.Cargo>();
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>();
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.
Shipment__c ObjectIn 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)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:
Shipment__c record and adds its related Shipment_Item__c records.ShipmentTrigger fires and calls the CubeMasterService future method.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!