# Cost Book

Maintain cost schedules, corrections, mistaken-entry removal, approved batches, and Current Source.

Canonical HTML: https://developer.shelfcycle.com/guides/cost-book

Maintain complete supplier cost schedules, preserve the history behind corrections, remove mistaken entries, apply approved batches, and choose the precise Current Source a product should follow.

Cost Book amounts use decimal-string cents per product base unit in the organization's base currency, preserving precision from source through readback. Each direct entry carries a complete rate or quantity schedule, terms, and an Arrangement that identifies its commercial lane. Several current costs for the same product and supplier can coexist when they belong to different origins or destination terms. Current Source identifies the exact supplier and ship-from origin a variable-supplier product should follow, while fixed-supplier products continue to follow their assigned supplier.

## Endpoints

| Endpoint | Use it for |
| --- | --- |
| `GET /cost-book-entries` | Read current Cost Book entries or history for an exact product, supplier, and optional ship-from origin. |
| `GET /products/{id}/cost-context` | Compare current and prior Cost Book entries with purchasing evidence for an explicit or resolved supplier. |
| `GET /products/{id}/current-cost-source` | Read fixed, selected, unconfigured, or unavailable source state and eligible supplier-location choices. |
| `PUT /products/{id}/current-cost-source` | Preview, set, replace, or clear the exact supplier-location Current Source for a variable-supplier product. |
| `POST /cost-book-entries` | Check readiness, then create or supersede one verified direct or constructed Cost Book entry. |
| `PATCH /cost-book-entries/{id}` | Correct one selected Cost Book event while preserving the original event and its source history. |
| `DELETE /cost-book-entries/{id}` | Preview or remove one mistaken direct, Landed, or Built from Cost Book entry and restore its preceding version when ready. |
| `POST /cost-book-entries/batch` | Check and apply as many as 50 independent updates or corrections with ordered item results. |

Cost Book reads require `cost-book-entries:read`, and Cost Book writes require `cost-book-entries:write`. Current Source reads require `current-cost-sources:read`, and Current Source changes require `current-cost-sources:write`. The acting user's current ShelfCycle Cost Book access is checked on every request. `GET /me` reports `effectiveCapabilities.costBookEntries`, `effectiveCapabilities.currentCostSources`, and matching write-readiness fields.

## Read current Cost Book entries

`GET /cost-book-entries` defaults to the current view. Narrow by `productId`, `supplierId`, or both when the workflow knows the product and supplier it needs. Add `shipFromLocationId` to choose one origin. Send `shipFromLocationId=null` for the supplier's general cost with no ship-from location, or omit the parameter to include every origin.

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries?productId=product-id&supplierId=supplier-id&shipFromLocationId=location-id&asOf=2026-08-18" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Supplier quote review"
```

Each complete entry identifies its product, supplier, `arrangement`, `shipFromLocation`, `incoterm`, `supplierTerms`, ordered `rates`, effective dates, direct or constructed shape, starting cost, additional costs, total cost, provenance, lineage, and completeness. A `null` ship-from location is the supplier's general cost; a matching location-specific cost takes precedence when the origin is known. Use `meta.matchStatus`, `meta.completeness`, and `meta.warnings` together. A missing or unresolved amount remains `null`, so callers can distinguish unavailable cost from a real zero.

For the full history of one product and supplier pair, send `view=history` with both `productId` and `supplierId`. Add a ship-from location when the workflow needs one origin's history:

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries?view=history&productId=product-id&supplierId=supplier-id&shipFromLocationId=location-id&limit=25" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

History can include current and superseded entries, their effective windows, complete rate schedules, and lineage. `lineage.authoredByMode` distinguishes a commercial update from a correction, while `supersedesEntryId` and `correctsEntryId` connect each newer event to the history it follows. When `meta.hasMore` is true, continue with `meta.nextCursor` as the only query parameter.

## Read Current Source

Read Current Source before making a supplier-and-origin decision:

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/current-cost-source" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Product cost workflow"
```

The response reports one of four `authorityState` values:

| State | Meaning |
| --- | --- |
| `fixed_supplier` | The product follows its fixed supplier. |
| `current_source` | An exact supplier and ship-from origin was selected for the variable-supplier product. |
| `unconfigured` | No Current Source is selected. Review `locationCandidates` before choosing a supplier and origin. |
| `unavailable` | The selected supplier-origin combination can no longer provide eligible Cost Book context. Review the reason, then replace or clear it. |

`authoritativeLane` identifies the fixed or selected supplier and its ship-from location. `selectionProvenance` reports who selected a Current Source and when. `locationCandidates` contains each eligible supplier-location choice, while `candidates` remains a supplier-level view for workflows that only need to choose the supplier. `inferredDisplay` may describe an available choice for display, but it is distinct from a selected Current Source.

## Preview and manage Current Source

Current Source is available for variable-supplier products. Send the desired `supplierId` and `shipFromLocationId`, together with the supplier and location observed in the preceding GET as `expectedCurrentSupplierId` and `expectedCurrentShipFromLocationId`. Use `null` for an expected value when no Current Source or no ship-from location is selected.

Preview the change with `dryRun=true`:

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/current-cost-source?dryRun=true" \
  -X PUT \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "supplierId": "22222222-2222-4222-8222-222222222222",
    "shipFromLocationId": "33333333-3333-4333-8333-333333333333",
    "expectedCurrentSupplierId": null,
    "expectedCurrentShipFromLocationId": null
  }'
```

The preview returns `consequence` as `set`, `replace`, `clear`, or `unchanged`, the desired and previous supplier-location values, eligibility, and a verification path. When the result matches the intended source choice, send the same PUT without `dryRun=true`.

To clear a selection, send `supplierId: null` and `shipFromLocationId: null`, plus the currently selected supplier and location as the expected values. A successful change returns the exact current state and selection provenance. Repeating an already completed desired state returns `unchanged`.

If another selection happened after the read, `stale_current_cost_source` returns the current expected supplier and ship-from location. Read Current Source again and review the intended change before retrying.

## Understand direct, Landed, and Built from costs

A direct entry begins with one entered material or landed cost and has no additional components.

A Landed entry begins with an entered material or landed cost. A Built from entry follows the Cost Book entry for one exact bulk product and Arrangement. Either can add ordered freight, packaging, duty, storage, processing, handling, tax, or other costs.

Direct, Landed, and Built from entries can use any Arrangement kind. A Built from entry keeps the exact bulk Arrangement selected when it is saved. When more than one current bulk Arrangement is available, send the chosen bulk `arrangementId`.

Added costs can use a percentage of starting cost, cents per base unit, or cents per package. Read `resolvedContribution`, `additionalCostTotal`, and `totalCost` as decimal strings. If a Built from entry is waiting for a current starting price, `totalCost` remains `null` and `waitingReason` explains what is needed. Never replace that missing total with zero.

## Keep the Arrangement and rate schedule together

An Arrangement names the commercial lane for a supplier cost. It can represent a general supplier relationship, a named pickup origin, or a named destination context. Read the current Cost Book entry before updating it, then reuse its `arrangement.id` when the new cost belongs to that same lane.

When the first complete cost needs a new lane, send `newArrangement` with its name, kind, and the exact ship-from or destination details that apply. ShelfCycle creates the Arrangement and cost together, then returns the authoritative `arrangement`, `supplierTerms`, and ordered `rates` on the saved entry.

If the submitted Arrangement name already exists for that product and supplier, the API returns `409 cost_book_conflict` with the existing `arrangementId`. Continue with that ID when it represents the intended lane rather than creating a second Arrangement with the same name.

Every direct rate carries decimal-string cents per product base UOM. An unconditional rate omits a minimum. A quantity rate adds `minimumQuantity` with `minimumBasis: "product_uom"`, or identifies a package with `minimumBasis: "package"` and `minimumProductPackagingId`.

## Compare product and supplier cost context

Use the cost-context endpoint with an exact active product. Include `supplierId` when the workflow has already chosen a specific supplier:

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/cost-context?supplierId=supplier-id&asOf=2026-08-18" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Supplier quote review"
```

You may omit `supplierId` when the workflow should follow the product's source authority:

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/cost-context?asOf=2026-08-18" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

Without `supplierId`, a fixed-supplier product follows its fixed supplier, a configured variable-supplier product follows its exact Current Source, and a product with one eligible supplier can use that supplier's available Cost Book context. The response's `sourceSelection` explains how the source was resolved and includes candidate and selection details.

When several eligible lanes remain, `cost_source_ambiguous` returns the complete supplier choices. Select a supplier under the workflow's policy and retry with its `supplierId`. If a selected Current Source becomes unavailable, review it through the Current Source endpoint before replacing or clearing it.

The cost-context response brings together:

- the current and prior Cost Book entries;
- the latest completed-purchase material and landed evidence;
- the latest open-purchase-order material evidence;
- comparison amounts and percentages when the currency, base unit, cost basis, and timing align; and
- `expectedCurrentEntryId` for a later write.

Purchase evidence is presented as context alongside the Cost Book entry. Use each value's `comparisonBasis` and the comparison's `comparable`, `reason`, and `temporalRelation` fields before describing a change. Missing evidence remains `null`.

## Check and create a Cost Book update

Submit the intended `POST /cost-book-entries` request with `dryRun=true` to see the complete proposed entry, current-to-proposed comparisons, readiness checks, and warnings without saving it.

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: supplier-quote-2026-0901-line-4" \
  -d '{
    "productId": "11111111-1111-4111-8111-111111111111",
    "supplierId": "22222222-2222-4222-8222-222222222222",
    "expectedCurrentEntryId": "44444444-4444-4444-8444-444444444444",
    "effectiveFrom": "2026-09-01",
    "effectiveTo": null,
    "sourceRef": "supplier-quote-2026-0901-line-4",
    "terms": "Net 30. Full truckload pricing begins at 42,000 lb.",
    "arrangementId": "33333333-3333-4333-8333-333333333333",
    "construction": {
      "type": "direct",
      "basis": "material",
      "uom": "POUND",
      "rates": [
        { "costPerBaseUomCents": "49.5" },
        {
          "costPerBaseUomCents": "47.25",
          "minimumQuantity": "42000",
          "minimumBasis": "product_uom"
        }
      ]
    }
  }'
```

Use `arrangementId` for an existing commercial lane or `newArrangement` when the first cost should create the Arrangement. Direct costs require `basis`, the product base `uom`, and either `costPerBaseUomCents` for one unconditional rate or `rates[]` for a complete ordered schedule. Do not send both rate forms.

Use the current entry id returned by the exact Cost Book read or cost-context response. Send `null` only when that Arrangement lane reports no current entry. If the current entry changes, read it again before preparing a new request.

When the readiness response is `ready` and the workflow's policy allows the update, send the same body without `dryRun=true`. The saved entry returns its Arrangement, terms, rates, effective window, lineage, and verification details.

When freight is added to a delivered price, the preview and saved response include the `delivered_freight_caution` warning. The warning does not block the request; review it to confirm the delivered price and added freight belong together.

## Correct one saved event

Use `PATCH /cost-book-entries/{id}` when a selected Cost Book event needs a correction. PATCH keeps the same product, supplier, Arrangement lane, and effective window while creating a corrected replacement and preserving the selected event in history.

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries/44444444-4444-4444-8444-444444444444?dryRun=true" \
  -X PATCH \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: supplier-quote-2026-0901-line-4:correct-rate" \
  -d '{
    "expectedCurrentEntryId": "44444444-4444-4444-8444-444444444444",
    "terms": "Net 30. Full truckload pricing begins at 42,000 lb.",
    "construction": {
      "type": "direct",
      "basis": "material",
      "uom": "POUND",
      "rates": [
        { "costPerBaseUomCents": "49.5" },
        {
          "costPerBaseUomCents": "46.75",
          "minimumQuantity": "42000",
          "minimumBasis": "product_uom"
        }
      ]
    }
  }'
```

Review `wouldChange.fieldNames` and the predicted replacement in the dry run. Then send the identical PATCH without `dryRun=true`. If another entry became current after your read, refresh the lane and review the correction against the new `expectedCurrentEntryId`.

The response identifies the corrected entry and its predecessor. A later history read returns both events with their original rates, source details, verification, and timestamps.

## Remove one mistaken entry

Use `DELETE /cost-book-entries/{id}` when a direct, Landed, or Built from Cost Book entry should not have been recorded. Start with the exact entry selected from a current or history read, then preview the removal:

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries/cost-entry-id?dryRun=true" \
  -X DELETE \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: supplier-quote-2026-0901:remove-wrong-entry" \
  -d '{
    "expectedCurrentEntryId": "cost-entry-id"
  }'
```

A ready preview returns `status: "ready"`, `operation: "deleteCost"`, the entry to remove, the preceding version that would return, its added-cost count, any surviving starting price, and a confirmation sentence without changing the Cost Book.

When the preview matches the intended removal, send the identical DELETE request without `dryRun=true`. A successful response returns `resultAction: "deleted"`. The selected version and its corrections are absent from both the current Cost Book and History, and the newest eligible preceding version returns with the removed version's end date. Saved Price Book decisions keep their original figures.

ShelfCycle preserves connected Cost Book relationships while evaluating the request. When the preview is blocked, the response explains what must be resolved before removal, such as a later version, a Built from cost that still depends on the selected price, an overlapping restored window, or the last eligible cost behind Current Source.

Reuse the same `Idempotency-Key` and body if the first response is interrupted. A completed retry returns the original receipt, including when the retry is sent with `dryRun=true` after the removal already completed.

## Apply an approved batch

Use `POST /cost-book-entries/batch` for 1 to 50 independent `updateCost` or `editCost` items. Give every source row a stable, unique `clientItemId` and give the whole submitted batch one stable `Idempotency-Key`.

```bash
curl "$SHELFCYCLE_API_BASE_URL/cost-book-entries/batch?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cost-review-2026-0901:approved-batch-4" \
  -d '{
    "items": [
      {
        "clientItemId": "quote-line-4",
        "mode": "updateCost",
        "payload": {
          "productId": "11111111-1111-4111-8111-111111111111",
          "supplierId": "22222222-2222-4222-8222-222222222222",
          "expectedCurrentEntryId": null,
          "effectiveFrom": "2026-09-01",
          "arrangementId": "33333333-3333-4333-8333-333333333333",
          "construction": {
            "type": "direct",
            "basis": "material",
            "uom": "POUND",
            "costPerBaseUomCents": "49.5"
          }
        }
      },
      {
        "clientItemId": "correction-line-9",
        "mode": "editCost",
        "payload": {
          "id": "55555555-5555-4555-8555-555555555555",
          "expectedCurrentEntryId": "55555555-5555-4555-8555-555555555555",
          "terms": "Net 30"
        }
      }
    ]
  }'
```

The dry run validates the full request and each item without saving results. Execute the identical body without `dryRun=true` after review. The response keeps input order, identifies each item by `clientItemId`, and summarizes successful, failed, and pending outcomes.

Known item failures are isolated to those items, so other valid entries can complete. A request-level error, duplicate Arrangement lane, more than 50 items, more than 500 total rate or component rows, or a body over 2 MB is rejected before any item is saved. Larger approved sets can be divided into stable requests of at most 50 items.

Batch rate limits are counted by submitted item and reported in `X-RateLimit-Cost`. If a response is interrupted, repeat the identical body, item order, `clientItemId` values, and `Idempotency-Key`. The retry returns completed outcomes and continues only the pending items.

## Keep the receipt

The first successful update or correction returns the full Cost Book entry with `resultAction` and `idempotencyStatus`. A successful removal returns its deletion receipt. A batch returns one ordered receipt for all submitted items. An exact retry with the same key and body returns the original completed result.

Preserve the receipt as write readback, then use the verification path when the key also has read access. A changed body represents a different intended change and needs a new stable key.

> **Guardrail**: Resolve exact product, supplier, Arrangement, package, and Cost Book entry ids; keep source values and units intact; and review every complete cost change before submitting it.
