Quick start
-
Get an API key
Sign up, pick a plan, and the key arrives by email.
-
Or try it with the demo credentials
These work without an account, against the live solver.
"apiKey": "test", "username": "test" -
Make the call
curl -X POST 'https://3dpack.ing/api/ai/calculate' \ -H 'Content-Type: application/json' \ -d '{ "prompt": "Pack 500 boxes of 100x100x100 mm into a 1m x 1m x 1m container", "apiKey": "test", "username": "test" }'Response
{ "containers": [{ "containerDims": { "length": 1000, "width": 1000, "height": 1000 }, "totalQuantity": 500, "itemQuantities": { "Boxes": 500 }, "volumeUsage": 50.0, "weightUsage": 0.0 }], "unpackedItems": { "total": 0, "breakdown": {} }, "linkToResult": "https://3dpack.ing/app?g=example-guid" }500 items packed, half the container used, and a link you can open.
The endpoint
POST/api/ai/calculate
Send a description of the shipment and your credentials as JSON. The description is parsed into items, constraints and container preferences, packed by the solver, and returned with a shareable link to the 3D view.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
prompt |
string | Yes | What you are shipping, in plain English. Truncated at 4,000 characters. |
apiKey |
string | Yes | Your key, or the string “test”. May also be sent as an X-API-Key header. |
username |
string | Yes | The account the key belongs to. Required whenever a key is sent. |
speed |
string | No | One of fast, normal or thorough. Defaults to the solver's own choice. |
stability |
integer | No | How much of a box must rest on what is underneath it, 75 to 100. Omit for 75, the standard rule. Raise it for cargo that must not overhang — 100 means every stacked box sits fully supported, which is steadier and fits fewer. |
Prompts it understands
Pack 50 boxes of 60x40x30 cm into a 20ft containerLoad 100 fragile items (80x60x40cm, max stack 3) into a 40ft high cubeShip mixed pallets: 10x euro pallets, 15x US pallets using the best container mixShip 24 pcs 200.3x120.2x100.2 cm (non-tiltable) using an optimal mix of 40ft and 20ft containers
Response
containers is one entry per container used. Each entry includes utilisation, the five largest overlapping free-space pockets and, when requested, every item placement. unpackedItems reports anything that did not fit. linkToResult opens the same plan in the 3D viewer.
{
"containers": [
{
"containerDims": { "length": 1203.2, "width": 235.0, "height": 269.24 },
"totalQuantity": 20,
"itemQuantities": { "Pallets": 20 },
"volumeUsage": 63.4,
"weightUsage": 41.8
},
{
"containerDims": { "length": 589.28, "width": 235.0, "height": 239.0 },
"totalQuantity": 4,
"itemQuantities": { "Pallets": 4 },
"volumeUsage": 29.2,
"weightUsage": 18.3
}
],
"unpackedItems": { "total": 0, "breakdown": {} },
"linkToResult": "https://3dpack.ing/app?g=56455ed9-5339-4f46-8c41-cb7462f7aaa1"
}
Item coordinates
Add the coordinates query parameter when your system needs the placement of every packed item. Each container then includes an items array with the item name, its packed dimensions and the x, y and z coordinates of its corner, in the request's unit.
curl -X POST 'https://3dpack.ing/api/ai/calculate?coordinates=true' \
-H 'Content-Type: application/json' \
-d '{
"prompt": "Pack 2 boxes of 60x40x30 cm into a 1m cube",
"apiKey": "test",
"username": "test"
}'
// Each packed container also includes:
"items": [{
"name": "Boxes",
"length": 60.0, "width": 40.0, "height": 30.0,
"x": 0.0, "y": 0.0, "z": 0.0
}]
The parameter is opt-in to keep summary responses small. The packed dimensions show the chosen orientation. Source-system item IDs and an explicit loading sequence are not part of the public contract today.
Errors
Failures come back as JSON with a single error field, and the status code says which kind it is.
| Status | Meaning |
|---|---|
400 |
A key was sent without a username. Send both, or neither. |
401 |
No API key in the body or the X-API-Key header. |
402 |
Not an error. The request is valid and asks for something the plan does not cover — multi-container optimisation, or a shipment above the free-tier size. Relay the offer rather than reporting a failure. |
403 |
The key was rejected, or the account it belongs to has no credit left. The message says which. |
500 |
The request reached the solver and something went wrong there. The message says what. |
{ "error": "When using API key, 'username' is also required in the request." }
Examples
The same call, in four languages.
const response = await fetch('https://3dpack.ing/api/ai/calculate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'Pack 50 boxes of 60x40x30 cm into a 20ft container',
apiKey: 'YOUR_API_KEY',
username: 'YOUR_USERNAME'
})
});
const result = await response.json();
console.log(`3D visualisation: ${result.linkToResult}`);
console.log(`Containers used: ${result.containers.length}`);
import requests
response = requests.post(
'https://3dpack.ing/api/ai/calculate',
json={
'prompt': 'Pack 50 boxes of 60x40x30 cm into a 20ft container',
'apiKey': 'YOUR_API_KEY',
'username': 'YOUR_USERNAME',
},
)
response.raise_for_status()
result = response.json()
print(f"3D visualisation: {result['linkToResult']}")
print(f"Containers used: {len(result['containers'])}")
using var client = new HttpClient();
var request = new
{
prompt = "Pack 50 boxes of 60x40x30 cm into a 20ft container",
apiKey = "YOUR_API_KEY",
username = "YOUR_USERNAME"
};
var response = await client.PostAsJsonAsync(
"https://3dpack.ing/api/ai/calculate", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine(result.GetProperty("linkToResult").GetString());
OkHttpClient client = new OkHttpClient();
String json = """
{"prompt": "Pack 50 boxes of 60x40x30 cm into a 20ft container",
"apiKey": "YOUR_API_KEY",
"username": "YOUR_USERNAME"}
""";
Request request = new Request.Builder()
.url("https://3dpack.ing/api/ai/calculate")
.post(RequestBody.create(json, MediaType.parse("application/json")))
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
MCP server
The same solver, as a tool an AI assistant can call for itself. Describe a shipment in plain English and it answers with the containers the cargo fits in, how full each one is, what did not fit, and a link to the interactive 3D plan. Asked whether 500 cartons fit in a 40-foot, an assistant without it does arithmetic on volumes — which ignores stacking, orientation and weight limits, and overstates what fits by a wide margin on real cargo.
Install
Claude Code, one line:
claude mcp add 3dpacking -- npx -y @3dpacking/mcp-server
Claude Desktop
{
"mcpServers": {
"3dpacking": {
"command": "npx",
"args": ["-y", "@3dpacking/mcp-server"]
}
}
}
It works immediately, with no account — the first calls run against a shared demo account on the free plan. To bill against your own limits, set THREEDPACKING_API_KEY and THREEDPACKING_USERNAME together; the API rejects a key without the username it belongs to. Published as @3dpacking/mcp-server on npm, and as ing.3dpack/container-loading in the MCP registry.
Pricing
Priced on calculations, not seats, because calculations are what your volume actually costs us and what your savings actually track. Every tier has the whole API — natural-language input, all six orientations, stacking and weight rules, centre of gravity. There is no feature held back for a higher tier.
| Plan | Per month | Calculations included | Beyond that | |
|---|---|---|---|---|
| Starter | $49 | 1,000 | $0.05 | Get a key |
| Growth | $199 | 10,000 | $0.03 | Get a key |
| Scale | $499 | 50,000 | $0.015 | Get a key |
| Enterprise | Talk to us | Agreed volume, on-premise if you need it | Custom constraints, SLA | Talk to us |
Prices in USD, billed monthly, cancel whenever. Overage is charged per calculation at the rate above rather than cutting you off mid-integration — a load plan failing in production because a counter rolled over is nobody's idea of a good outcome. If you are evaluating and need room to test, say so and we will open the limit while you do.
That is the whole API
One call in, a packed container out.