Custom Storefronts

Upload and price

Read the configuration options, upload a part, wait for the analysis, add the part to a cart, and get an instant price

Read the configuration options, upload a part and read its analysis, then add it to a cart and price it. The analysis result gives you the partRevisionId that all later calls use. Paths and shell variables are as in Before you start.

1. Read the configuration options

curl "$API/operator/processes"  -H "X-Store-Name: $STORE"
curl "$API/operator/lead-times" -H "X-Store-Name: $STORE"

/operator/processes returns a list of manufacturing processes. Each process contains its materials (with colours), infills, precisions, and post-processings. The id of a process is the processPricesId in later calls. Refer to GET /operator/processes.

2. Upload the file

Send one file for each request, as multipart/form-data.

  • The form field name is file.
  • Compress the file with gzip before you send it. The form field contains the gzip data.
  • The header X-Filename is mandatory. It contains the original file name, URL-encoded. The API reads the file type from the extension of this name.
  • Check the size against the store setting maximumFileSize (megabytes) before you upload. The API has a hard limit of 1024 MB for each request.
gzip -k bracket.step        # makes bracket.step.gz

curl -X PUT "$API/part-revision/upload" \
  -H "X-Store-Name: $STORE" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Filename: bracket.step" \
  -F "file=@bracket.step.gz"

Response 200: a JSON string, the analysis ID.

"0b6f5a0e-6f1c-4a2e-9a55-3c1f0e8d7b21"

A file extension that is not supported gives 400 with "error": "UNSUPPORTED_FILE_TYPE".

Common extensions: stl, step, stp, iges, igs, 3mf, x_t, sldprt.

3. Wait for the analysis

The analysis is asynchronous. Poll the status. You can ask for many IDs in one call.

curl "$API/part-revision/upload/status?analysisIds=$ANALYSIS_ID" \
  -H "X-Store-Name: $STORE" -H "Authorization: Bearer $TOKEN"
{ "0b6f5a0e-6f1c-4a2e-9a55-3c1f0e8d7b21": "ANALYSIS_STARTED" }
StatusMeaning
SENT_FOR_ANALYSIS, ANALYSIS_STARTED, REPAIR_STARTED, REPAIR_COMPLETED, ANALYSIS_COMPLETEDIn progress. Continue to poll.
RESULT_READYComplete. Read the result.
ANALYSIS_FAILED, DESIGN_DOES_NOT_EXISTFailed. An unknown or expired ID also gives ANALYSIS_FAILED.
  • Poll one time each second.
  • Do not read the result on ANALYSIS_COMPLETED. Wait for RESULT_READY.
  • The status is kept for 12 minutes after the upload. Stop after that time.

4. Read the analysis result

curl --compressed "$API/part-revision/upload/$ANALYSIS_ID/results" \
  -H "X-Store-Name: $STORE" -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/msgpack" -o result.msgpack

You can read the result one time only

A second call gives 404. Keep the data that you need.

  • The body is MessagePack, not JSON. The response has Content-Encoding: gzip, which HTTP clients remove automatically.
  • The result contains the partRevisionId. You need it for all later calls.

Decode example (Node.js):

import { decode } from '@msgpack/msgpack'

const response = await fetch(`${API}/part-revision/upload/${analysisId}/results`, {
  headers: { 'X-Store-Name': store, Authorization: `Bearer ${token}`, Accept: 'application/msgpack' }
})
const result = decode(new Uint8Array(await response.arrayBuffer())) as AnalysisResult
// result.partRevisionId, result.fileName, result.width, result.height, result.length, result.volume, …

The result also contains binary data: thumbnail (image bytes), stl (a gzip-compressed STL mesh for a 3D viewer), and wallThickness. Refer to Analysis result.

5. Create a cart and add the part

A cart keeps the parts and their configuration on the server. A cart does not contain prices.

curl -X POST "$API/cart" -H "X-Store-Name: $STORE" -H "Authorization: Bearer $TOKEN"
# -> { "cartId": "…", "items": [], "status": "OPEN", … }

curl -X POST "$API/carts/$CART_ID/items" \
  -H "X-Store-Name: $STORE" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "partRevisionId": "…",
    "quantity": 1,
    "units": "MILLIMETERS",
    "processPricesId": "…",
    "materialId": 12,
    "colorId": null,
    "infillId": null,
    "precisionPricesId": null,
    "leadTimeId": null,
    "postProcessingIds": [],
    "name": "bracket.step"
  }'

Select a sensible default configuration: the process, material, infill, and lead time that have isDefault (default for infills), and the first active precision.

Guests cannot read a cart back

A guest can create a cart and can add, change, and remove items. A guest cannot read a cart back (GET /cart needs a customer token). Keep the cart ID and the item data in your storefront until the visitor signs in.

6. Get a price

POST /pre-order calculates the price for a set of lines. The call is synchronous and stores no data. Call it again each time the configuration changes.

Each line has a key that you select (use a UUID). The response uses the same keys.

curl -X POST "$API/pre-order" \
  -H "X-Store-Name: $STORE" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "currency": "EUR",
    "requisitions": {
      "7b0c1a52-0d1e-4b0c-8f5e-0c6a1c0f3e11": {
        "sequence": 0,
        "partRevisionId": "…",
        "units": "MILLIMETERS",
        "quantity": 1,
        "processPricesId": "…",
        "materialId": 12,
        "infillId": null,
        "precisionId": null,
        "colorId": null,
        "leadTimeId": null,
        "postProcessingIds": [],
        "comments": [],
        "name": "bracket.step"
      }
    },
    "shipping": null,
    "shippingId": null,
    "billingAddressId": null,
    "discountId": null,
    "operatorNote": null,
    "affiliate": null,
    "cartId": null
  }'

The precision field is precisionPricesId in a cart item, and precisionId in a pre-order or order line.

The response has three parts:

{
  "quote": {
    "currency": "EUR",
    "price": 54.2,
    "subtotal": 45.0,
    "shipping": 0,
    "discount": 0,
    "topUp": null,
    "tax": { "totalPrice": 9.2, "totalPercentage": 20.44, "components": [], "isTaxExempted": false, "isTaxReverseCharged": false, "isTaxAppliedToShipping": true },
    "lineItems": [],
    "requisitions": {
      "7b0c1a52-0d1e-4b0c-8f5e-0c6a1c0f3e11": { "partRevisionId": "…", "quantity": 1, "price": 45.0, "unitPrice": 45.0, "manufacturingPrice": 45.0, "postProcessingPrice": 0, "postProcessingOptions": [] }
    },
    "expectedDispatchDate": "2026-11-02"
  },
  "purchasability": {
    "canBePurchased": true,
    "isPriceReviewRequired": false,
    "withinMaximumOrderValue": true,
    "withinBoundingBoxLimit": true,
    "withinWallThicknessLimit": true,
    "isMaterialPurchasable": true,
    "isPostProcessingPurchasable": true,
    "parts": {}
  },
  "constraints": {}
}

How to use the response

  • Prices are decimal numbers in the given currency (for example 45.0 is 45 euros).
  • If a line in quote.requisitions is null, there is no instant price for that part. The manufacturer must review it.
  • quote.topUp is an amount that is added to reach the minimum order amount of the store. Show it as a line.
  • If purchasability.canBePurchased is false, do not offer payment. Offer "request a quote" (create the order, then the manufacturer reviews it).

For all fields, refer to Prices.

Next

Continue to Checkout and payment.

Last updated on

On this page