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

EndpointUse it for
GET /cost-book-entriesRead current Cost Book entries or history for an exact product, supplier, and optional ship-from origin.
GET /products/{id}/cost-contextCompare current and prior Cost Book entries with purchasing evidence for an explicit or resolved supplier.
GET /products/{id}/current-cost-sourceRead fixed, selected, unconfigured, or unavailable source state and eligible supplier-location choices.
PUT /products/{id}/current-cost-sourcePreview, set, replace, or clear the exact supplier-location Current Source for a variable-supplier product.
POST /cost-book-entriesCheck 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/batchCheck 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.

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:

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:

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:

StateMeaning
fixed_supplierThe product follows its fixed supplier.
current_sourceAn exact supplier and ship-from origin was selected for the variable-supplier product.
unconfiguredNo Current Source is selected. Review locationCandidates before choosing a supplier and origin.
unavailableThe 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:

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:

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:

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.

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.

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:

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.

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.