JavaScript SDK
Build your own quoting interface on the buyer API with @quotometry/api-client, the same client the widget uses: upload, price, quote and order.
This guide shows how to drive the buyer API yourself with @quotometry/api-client, the same client the widget
uses, without its interface. Use it when the quoting flow belongs inside your own configurator, or when your
design system is not negotiable. By the end you can upload a part, price it, save a quote and place an order.
Before you begin
- A widget key, with your site’s origin in its Allowed origins. The SDK calls the buyer API from the browser, so the same origin check applies.
- A build that can install npm packages. The client is not published to the public npm registry yet; get it from your Quotometry contact or from the repository.
Install and create a client
npm install @quotometry/api-client
import { QuotometryClient, formatMoney } from '@quotometry/api-client';
const quotometry = new QuotometryClient('https://api.quotometry.example', 'wgt_live_YOURKEY');
Every response is checked against a schema before you see it, so a field that changed shape fails loudly at the
boundary instead of quietly three functions later. Every failure is an ApiError with status, code,
issues and retryable. The client retries transient failures itself, including a 429 after its
Retry-After.
1. Read what the shop offers
const boot = await quotometry.bootstrap();
// boot.configurations[] - each with its buyer option schema, currency and build volume
// boot.limits - max_files, max_file_bytes, formats
Call bootstrap() once per page. It tells you which materials, colours and layer heights exist, and what control
each option should be (cards, swatches, a slider, a stepper). Build your interface by rendering that schema,
not a list you hard-coded: the shop changes it without telling you.
2. Upload a part
const modelId = await quotometry.upload(file, 'mm', (sent, total) => {
progress.value = sent / total;
});
One call does the whole job: register, upload, complete.
- Small files go in a single
PUTto a presigned URL. Above the server’s threshold the client switches to a multipart upload, retries a failed part, and after a dropped connection asks the server what arrived and sends only the gap. A 100 MB part on mobile data does not start over because a tunnel interrupted it. - On a network that cannot reach the storage host at all (some providers and company firewalls block it), the
client notices within six seconds and sends the same bytes through the API’s upload relay
(
PUT /v1/uploads/{id}/contentor/parts/{number}).quotometry.model(id)does the same for the preview mesh and thumbnail: on such a network their URLs point at/v1/models/{id}/previewand/thumbnailon the API. - The file is hashed in the browser and the server checks the bytes that arrive, so a truncated upload is refused rather than quoted.
The unit argument matters for STL, which carries no units. Pass what the buyer told you (mm, cm, m or
inch), or omit it and let Quotometry infer the unit from the part’s size.
3. Wait for the analysis
const model = await quotometry.waitForModel(modelId, (status) => setStatus(status));
// model.parts[] - id, name, dimensions_mm, volume_cm3, area_cm2, verdict, triangles
waitForModel streams the status over server-sent events and falls back to polling where EventSource is not
available. Each part in the model can be priced on its own: an STL is usually one part, while a 3MF, AMF or
glTF file often holds several.
4. Price it
const price = await quotometry.price(
[{
line_ref: 'a',
model_part_id: model.parts[0].id,
configuration_id: boot.configurations[0].id,
selections: { material: 'petg', color: 'black', layer_height: 0.2, infill: 20 },
quantity: 10,
}],
'DE', // destination country, ISO 3166-1 alpha-2
sessionId,
{ coupon: 'WELCOME10' },
);
formatMoney(price.totals.total); // "€184.50"
Call price() whenever a selection changes, debounced. Nothing is stored and the buyer is committed to nothing.
5. Save a quote, then order
const quote = await quotometry.createQuote(lines, 'DE', { customer_token });
const order = await quotometry.createOrder(
quote.id,
{ email, name, company },
{ po_number, notes, metadata: { cart_id } },
crypto.randomUUID(), // idempotency key
);
The last argument of createOrder is an idempotency key. If you retry with the same key, you get the same
order back (with created: false) rather than a second order. Generate the key once, before the first attempt:
a key generated inside a retry loop is not an idempotency key.
To give the buyer a link back to a saved quote, call quotometry.shareQuote(quote.id, email). Omit email to
get the link without sending it.
When a part cannot be priced
const rfq = await quotometry.createRfq(lines, { email, name }, 'It has to fit a 6 mm shaft.', 'DE');
A request for quote is the path for an oversize part, a DFM problem the shop blocks on, or a quantity past the shop’s review threshold. It lands in the shop’s Inbox and a person answers it.
Methods
| Method | Returns | Description |
|---|---|---|
bootstrap() | Bootstrap | The widget’s configurations, options, limits, theme and checkout settings. |
upload(file, unit?, onProgress?) | string (model id) | Uploads a file and starts its analysis. |
model(id) | ModelView | The model’s status and parts, once. |
waitForModel(id, onStatus) | ModelView | Waits until the analysis finishes. |
price(lines, country?, sessionId?, extras?) | PriceView | Prices line items. extras takes coupon, customer_token, customer_email, region, postcode and custom_fields. |
createQuote(lines, country?, extras?) | QuoteView | Saves a quote. |
shareQuote(quoteId, email?) | ShareView | A link back to the quote, optionally e-mailed to the buyer. |
createOrder(quoteId, customer, extras, idempotencyKey) | OrderView | Places an order from a quote. |
createRfq(lines, customer, message, country?, customFields?) | { id, number, status, message } | Sends a request for quote. |
Types and helpers
Every type is exported and inferred from the schemas: Bootstrap, BootstrapConfig, OptionSpec,
Selections, ModelView, Part, PriceView, LineView, Totals, QuoteView, ShareView, OrderView,
LineItemRequest, Customer, PriceExtras, Money and ApiError.
formatMoney(value, locale)renders aMoneyvalue the way its currency and the locale expect.t(text, locale)picks a string from a translated label, falling back to English.
Next steps
- REST reference: every buyer API endpoint the client calls.
- Errors: problem documents, issue codes and line states.
- Rate limits: the budgets a browser client works within.
- Customer tokens and B2B pricing: show a signed-in buyer their own prices.