> ## Documentation Index
> Fetch the complete documentation index at: https://docs.amber.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Compare quotes and price breaks

> Follow a Quote through its visible revisions, items, and quantity bands without mixing up their IDs

Build a comparison table that keeps supplier offers, brand targets, quantity
bands, and currencies separate. `/quotes` returns the **Quotes** shown in Amber.
Each Quote has revisions, each revision has items, and each item can have
multiple price bands.

Before you start, set your [API key and brand](/authentication) and save
[`amber-client.mjs`](/pagination#reusable-nodejs-client). The example requires
Node.js 22 or later. Set `PRODUCT_ID` from a [product lookup](/tutorials/find-records).

<Steps>
  <Step title="Choose a Quote">
    ```bash theme={null}
    curl --fail-with-body \
      -H "Authorization: Bearer $AMBER_API_KEY" \
      "https://app.amber.ai/api/public/v1/brands/$AMBER_BRAND/quotes?productId=$PRODUCT_ID&limit=25"
    ```

    Pick a returned `id`, then read `/quotes/{id}` for the supplier, Parent
    Product, and source header under `details`. Save the ID as `QUOTE_ID`.
    `quoteNumber` is a display number, not the ID used in URLs.

    The header's `targetPrice` and `quantity` summarize one revision and one
    band. They are not the complete supplier price history.
  </Step>

  <Step title="Follow the three levels">
    Each URL names its parent explicitly:

    | Request | Parent |
    | - | - |
    | `/quotes/{quoteId}/revisions` | Quote |
    | `/quote-revisions/{revisionId}/items` | Quote Revision |
    | `/quote-items/{itemId}/price-bands` | Quote Item |
    | `/quotes/{quoteId}/notes` | Quote discussion |

    Use each returned record's `id` in the next URL. A visible parent with no
    children returns an empty page. An unavailable parent returns `404`.
  </Step>

  <Step title="Export every visible revision and band">
    ```javascript compare-quotes.mjs theme={null}
    import { apiUrl, readJson, records } from "./amber-client.mjs";

    const quoteId = process.env.QUOTE_ID;
    if (!quoteId) throw new Error("Set QUOTE_ID from /quotes");
    const quote = await readJson(apiUrl(`quotes/${encodeURIComponent(quoteId)}`));

    for await (const revision of records(apiUrl(`quotes/${encodeURIComponent(quote.id)}/revisions`))) {
      for await (const item of records(apiUrl(`quote-revisions/${encodeURIComponent(revision.id)}/items`))) {
        for await (const band of records(apiUrl(`quote-items/${encodeURIComponent(item.id)}/price-bands`))) {
          console.log(JSON.stringify({
            quoteId: quote.id,
            quoteNumber: quote.quoteNumber,
            supplierId: quote.supplier.id,
            revisionId: revision.id,
            round: revision.round,
            status: revision.status,
            isBrandQuote: revision.isBrandQuote,
            submittedAt: revision.submittedAt,
            currency: revision.currency,
            incoterm: revision.incoterm,
            leadTimeDays: revision.leadTimeDays,
            paymentTermId: revision.paymentTermId,
            itemId: item.id,
            skuId: item.skuId,
            productVersionId: item.productVersionId,
            bandId: band.id,
            bandNumber: band.bandNumber,
            quantity: band.quantity,
            unitPrice: band.unitPrice,
            targetUnitCost: band.targetUnitCost,
          }));
        }
      }
    }
    ```

    ```bash theme={null}
    node compare-quotes.mjs > quote-bands.ndjson.tmp && mv quote-bands.ndjson.tmp quote-bands.ndjson
    ```

    The output has one row per band. No output means no visible bands, which
    can also mean a revision has no items or an item has no bands. Read the
    corresponding collection's `totalItems` when diagnosing an empty result.
  </Step>
</Steps>

## Compare like-for-like offers

Keep `isBrandQuote`, status, and submission time so you do not treat a brand
target as a submitted supplier offer. Supplier-private drafts and canceled
revisions are excluded from the public read surface; brand drafts can appear.

Compare matching SKU or product-version requirements at the same quantity and
currency, with compatible incoterms. `unitPrice` and `targetUnitCost` are decimal
strings or `null`; preserve precision and treat `null` as unknown, not zero.
The collection is not sorted newest-first. Use revision metadata to select a
round rather than taking the first row.

For supplier comparisons, filter `/quotes?productId={productId}` and optionally
add `supplierId`, `status`, or `quoteNumber`. Filters combine with AND. List rows
include `supplierId`, `productId`, and `productVersionId`, so you can group results
without an extra detail request per Quote. Keep different product versions and
sourcing groups separate when comparing offers.

For brand-wide analytics, page `/quote-revisions`, `/quote-items`, and
`/quote-price-bands` once each and join locally. This avoids one request per
parent in a large export. See [Entity relationships](/entity-relationships)
for discussions and attachments.
