Pricing
The catalog deliberately publishes no prices. Two endpoints are where a real number comes from before an order exists:
POST /v1/products/{productId}/quoteprices the goods - the same pricing the storefront checkout computes, for one product at one quantity.POST /v1/shipping/quoteprices getting them to an address - one price per shipping service the site offers for that destination.
Both take partner-api/products:read. A quote is a read that happens to be a POST, so there is
no separate scope for either, and no shipping:read.
This page covers both requests, what each response field means, and the limits every integrator eventually runs into - the discount a product quote cannot see, the precision it does not carry, and the one number the shipping quote will not compute for you.
Quoting a product
Section titled “Quoting a product”The productId comes from the catalog - either a GET /v1/products
entry or the productId on an existing order line. The endpoint needs
partner-api/products:read, the same scope as the rest of the product endpoints; a
403 insufficient_scope here means the grant is missing, not that the product is off-limits.
The body carries exactly one field:
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "quantity": 250 }' \ https://api.partners.collaterate.com/v1/products/SLS_918273/quotequantity is required and must be a positive integer. There is nothing else to send - no
siteId, in particular. Every SLO_/SLS_ id belongs to exactly one site, so the site is
resolved from the productId itself.
The product quote response
Section titled “The product quote response”{ "productId": "SLS_918273", "quantity": 250, "totalPrice": 89.99, "createdOn": "2026-07-30T00:00:00.000Z" }Four fields, all always present:
| Field | What it is |
|---|---|
productId |
The product you asked about, echoed back. |
quantity |
The quantity you asked about, echoed back. |
totalPrice |
The price for this quantity - the whole-quantity total, not a per-unit price. Post site-markup, post any product-level discount the site has configured. A JSON number; see the caution below. |
createdOn |
When this quote was computed. |
createdOn is worth reading for what it is not: a quote is a point-in-time answer, not a
reservation and not a locked price. Nothing about a later submission is bound to it - there is
no quote id to send with an order, and Collaterate prices every order fresh from its own
catalog and site rules at submission time (which is also why
a submission carries no price field at all).
A quote that has been sitting in your cache is a record of what the price was, and the only
way to learn the current one is to quote again.
This is the site’s price, not a specific customer’s price
Section titled “This is the site’s price, not a specific customer’s price”Collaterate applies two independent things when it prices a product: the site’s own pricing (a markup and, per product, an optional discount) and, separately, a specific customer’s own negotiated account discount. This endpoint has no logged-in customer to attach to a quote request, so it only ever reflects the first - it does not, and cannot, reflect what a specific already-discounted customer would actually pay. If you’re quoting on behalf of a customer who has their own negotiated pricing, this number will read higher than what they’d really be charged at checkout.
Quoting shipping
Section titled “Quoting shipping”The product quote tells you what the goods cost. POST /v1/shipping/quote tells you what it
costs to get them somewhere:
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "siteId": 42, "shipTo": { "country": "US", "state": "MN", "postalCode": "55416" }, "weightOunces": 37.5 }' \ https://api.partners.collaterate.com/v1/shipping/quote| Field | Required | What it is |
|---|---|---|
siteId |
yes | Which of your granted sites to price against. |
shipTo.country |
yes | Two-letter country code - the same vocabulary you send on POST /v1/orders and read back on shipments. |
shipTo.state |
yes | State or province abbreviation as spelled within that country. |
shipTo.postalCode |
yes | Destination postal code, passed to the carrier as given. |
weightOunces |
yes | Total shipment weight in ounces. See below - this one is on you. |
productId |
no | Selects the fulfilment centre the shipment would ship from, making the quote origin-correct. Must be a product on siteId. |
siteId is required here even though the product quote needs none, and the difference is real:
an SLO_/SLS_ id names exactly one site, but shipping is priced from site configuration -
which carriers are enabled, whether negotiated rates apply, the site’s own custom shipping
options - and productId is optional. With two granted sites and nothing to resolve from, the
alternative would be quoting against whichever one we picked.
There is no shipTo.name, address1 or city. Carriers rate on destination and weight; a quote
needs no recipient, so this endpoint does not ask for one. Unknown keys are refused by name rather
than ignored, at both levels, so sending a full order-shaped shipTo tells you which parts do not
apply instead of quietly discarding them.
state is looked up inside the country you sent, because state abbreviations are not unique
across countries: BC is British Columbia under CA and Baja California under MX. Get the pair
wrong and you get 422 unknown_state_code, never a quote for the wrong continent.
Why you have to supply weightOunces
Section titled “Why you have to supply weightOunces”This API will not compute the weight for you, and that is deliberate.
GET /v1/products publishes a per-unit weight for every variant, so the obvious thing is for us
to multiply it by a quantity and convert. We will not, for one reason: the platform records no
unit. There is no unit column, no per-site setting, and no documented convention - the API
reference says as much in as many words, describing weight as being “in the catalog’s own unit”.
Measured across 142,967 active variants the values run from 0 to 8,645 with a mean of 4.15, which
is consistent with ounces, with pounds, and with kilograms.
Converting an unknown unit to ounces means inventing a conversion factor. The failure mode is not an error you would see - it is a plausible-looking shipping price that is wrong by a constant multiple on every quote, which neither you nor we could detect from the response. So the endpoint asks, because we genuinely do not know.
You are in a better position than we are. If you know what unit your own products are kept in,
weight x quantity, converted once by you, is exactly the number to send - and it is right.
weightOunces is a decimal, not an integer (half an ounce is a real parcel weight), must be
greater than zero, and is capped at 5,000,000 as a guard against a transposed decimal point. That
cap is not a carrier limit; carriers refuse what they refuse and tell you so.
The shipping quote response
Section titled “The shipping quote response”{ "quotes": [ { "serviceName": "UPS Ground", "price": "12.45" }, { "serviceName": "UPS 2nd Day Air", "price": "31.80" } ], "warnings": [], "messages": []}| Field | What it is |
|---|---|
quotes[].serviceName |
The carrier service as Collaterate names it - what to show a customer. |
quotes[].price |
The price for that service, as a decimal string. Unlike the product quote’s totalPrice, this follows the API-wide money rule. |
warnings |
Collaterate’s reasons that some option could not be priced. Non-empty here means a partial result - typically one carrier failing while others answered. |
messages |
Informational notes intended for display. Never errors. |
Both message lists are display text, not codes. Show them; do not branch on them.
quotes is never empty on a 200. Upstream, a destination nothing can be shipped to comes
back as a perfectly ordinary success with an empty list and the reason buried in a side channel -
which reads exactly like “everything is fine and there is nothing to ship with”. This API does not
relay that shape: no priced service at all is a 422 shipping_quote_unavailable with Collaterate’s
own reasons in detail. You never have to tell an empty array apart from a real answer.
Collaterate’s own service ids and its “carrier service or site custom option” flag are not
published. The id is an internal key that nothing in this API accepts back - POST /v1/orders
carries no service selection - and the flag describes how Collaterate is configured rather than
what you are buying.
Errors
Section titled “Errors”Product quote
Section titled “Product quote”| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_product_id |
The productId path segment doesn’t match SLO_/SLS_ plus digits. |
| 400 | invalid_quantity |
quantity is missing or not a positive integer. |
| 403 | insufficient_scope |
Your token lacks partner-api/products:read - ask Partner Integrations to add it. |
| 404 | product_not_found |
No such product, or it exists outside your granted sites - these read identically on purpose, see Tenancy and access. |
| 404 | quote_product_not_found |
Rare: the product resolved in our catalog but Collaterate could not price it. |
| 422 | quote_invalid |
Collaterate rejected the quote on business-validation grounds (e.g. quantity over the product’s configured maximum). detail carries Collaterate’s own message. |
The 422 deserves a sentence beyond its table row, because it is the one you will actually
see: products carry quantity rules configured in Collaterate - a maximum is the common case -
and the catalog does not publish them, so this API cannot check your quantity against them
up front. Asking for more than the product allows comes back as quote_invalid with
Collaterate’s own validation message in detail; the fix is to change the request (reduce the
quantity), not to retry it.
Shipping quote
Section titled “Shipping quote”| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_shipping_quote_request |
The body failed validation - malformed JSON, a missing or mistyped field, an unknown key, or a weightOunces outside 0 < w <= 5000000. detail names the field. |
| 400 | invalid_site_id |
siteId is not one of your granted sites. Check GET /v1/me. |
| 403 | insufficient_scope |
Your token lacks partner-api/products:read. |
| 404 | product_not_found |
The productId does not exist, is outside your granted sites, or belongs to a different site than the siteId you sent. All three read identically. |
| 422 | unknown_country_code |
shipTo.country is not a country code Collaterate recognises. |
| 422 | unknown_state_code |
shipTo.state is not a state or province of the country you sent. |
| 422 | shipping_quote_unavailable |
Nothing could be priced for that destination and weight. detail carries Collaterate’s own reasons - an invalid postal code for the country is the common one. |
Both endpoints also return the API-wide 401, 429, 500 and 503 responses, which behave
the same here as everywhere else - see Errors for the full catalog.
- Catalog and products - where the
productIdyou quote comes from, and the per-unitweightyou convert intoweightOunces. - Submitting orders - turning the quoted product into an order, and the receipt that comes back.
- Errors - the full code catalog, including the API-wide responses above.