Write readiness

Use optional non-mutating dryRun checks before supported POST, PATCH, and PUT writes.

The API supports dryRun=true on notes, contacts, customer, supplier, parent-scoped child location/address, attached document, product, product-family, Product SDS, Cost Book, Current Source, and Price Book write endpoints. A dry run checks the exact intended request without creating or updating records.

dryRun=true is optional. Authorized callers may execute directly when their integration policy allows it. ShelfCycle does not require a human approval step before supported public API writes.

Supported endpoints

EndpointScopeExecution header
POST /notesnotes:writeIdempotency-Key
PATCH /notes/{id}notes:writeIf-Match
POST /contactscontacts:createIdempotency-Key
PATCH /contacts/{id}contacts:updateIf-Match
POST /customers, POST /supplierscustomers:write or suppliers:writeIdempotency-Key
PATCH /customers/{id}, PATCH /suppliers/{id}customers:write or suppliers:writeIf-Match
POST /customers/{id}/locations, POST /customers/{id}/locations/{locationId}/addressescustomers:writeIdempotency-Key
PATCH /customers/{id}/locations/{locationId}, PATCH /customers/{id}/locations/{locationId}/addresses/{addressId}customers:writeIf-Match
POST /customers/{id}/documentscustomers:writeIdempotency-Key
POST /suppliers/{id}/ship-from-locationssuppliers:writeIdempotency-Key
PATCH /suppliers/{id}/ship-from-locations/{locationId}suppliers:writeIf-Match
POST /suppliers/{id}/documentssuppliers:writeIdempotency-Key
POST /productsproducts:writeIdempotency-Key
PATCH /products/{id}products:writeIf-Match
POST /products/{id}/documentsproducts:writeIdempotency-Key
PUT /products/{id}/sdsproducts:writeIf-Match
POST /product-familiesproducts:writeIdempotency-Key
PATCH /product-families/{id}products:writeIf-Match
POST /cost-book-entriescost-book-entries:writeIdempotency-Key
PUT /products/{id}/current-cost-sourcecurrent-cost-sources:writeexpectedCurrentSupplierId in the body
POST /price-book-entriesprice-book-entries:writeIdempotency-Key

Dry runs use the same write scope, live user permission, and rate-limit family as execution. They do not introduce a separate permission model.

Request

Send the same method, endpoint, headers, and body you plan to execute, with dryRun=true in the query string.

curl "$SHELFCYCLE_API_BASE_URL/contacts?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gmail-thread-123:customer-id:create-contact:jane@example.com" \
  -d '{
    "parent": { "type": "customer", "id": "customer-id" },
    "name": "Jane Buyer",
    "title": "Purchasing",
    "email": "jane@example.com",
    "phone": "555-0100"
  }'

dryRun must be exactly true. Invalid values such as dryRun=false, dryRun=1, or repeated dryRun parameters fail validation and must not be retried as execution.

Price Book uses a review-and-apply sequence. Its dry run returns expectedAffectedEntryIds and expectedPlanToken; include both in the later execution while keeping the same stable Idempotency-Key. See the Price Book guide for the complete request shape.

Current Source uses a read-preview-apply sequence. Read GET /products/{id}/current-cost-source, preview the desired supplierId and shipFromLocationId with the observed expectedCurrentSupplierId and expectedCurrentShipFromLocationId, then send the same PUT without dryRun=true. The preview returns the desired and previous source, consequence, eligibility, and a verification path. See the Cost Book guide for set, replace, and clear examples.

Response

Most dry runs return HTTP 200 with data.type: "write_readiness" when the request can be evaluated. Current Source returns its focused status, consequence, eligibility, and verification fields. Use response data, not HTTP status alone, to decide what happened.

{
  "data": {
    "type": "write_readiness",
    "operation": "contacts.create",
    "status": "ready",
    "wouldWrite": false,
    "checks": [
      { "code": "scope_authorized", "status": "passed", "message": "The key can create contacts." },
      { "code": "parent_available", "status": "passed", "message": "The parent is visible and active." },
      { "code": "duplicate_check_clear", "status": "passed", "message": "No duplicate contact candidate was found." },
      { "code": "idempotency_available", "status": "passed", "message": "The idempotency key is available." }
    ],
    "duplicateCandidates": [],
    "idempotency": { "required": true, "keyProvided": true, "wouldReplay": false },
    "submit": { "method": "POST", "path": "/api/v1/contacts", "query": {} },
    "verification": {
      "available": true,
      "method": "GET",
      "path": null,
      "pathTemplate": "/api/v1/contacts/{id}",
      "requiresScopes": ["contacts:read"],
      "fallback": "detail_get"
    },
    "requestId": "request-id"
  }
}

wouldWrite is always false for dry runs. A dry run must not create business rows, update updatedAt, create idempotency identities, write embeddings, acquire mutation locks, or trigger downstream side effects.

Ready and blocked

status: "ready" means the request passed the available checks at that moment. Execution confirms the request again against current ShelfCycle data.

status: "blocked" means the API found a condition your workflow must handle before execution. Common blocked checks include:

duplicate_candidate_found
idempotency_key_required
idempotency_key_reused
stale_record
target_not_found_or_not_visible
unsupported_profile_shape
unsupported_linked_record
not_api_created
primary_change_unsupported
address_role_required
product_family_reference_active
product_packaging_reference_active
supplier_reference_active
reference_visibility_blocked
cost_ship_from_location_not_visible
cost_book_base_ambiguous
invalid_variable_qty
order_sensitive_update_blocked
validation_failed
archived_record
duplicate_document

Duplicate candidates are advisory and bounded. If the key cannot read the matching detail route, the candidate can be redacted to readable: false with only type and matchReasons.

Execute and verify

To execute, remove dryRun=true and send the same request when your integration policy allows it.

POST execution requires Idempotency-Key. PATCH and Product SDS execution require If-Match. Current Source PUT requires the supplier and ship-from location observed before the change as expectedCurrentSupplierId and expectedCurrentShipFromLocationId. Execution can still return 409 if another write changes state after the dry run.

After execution, use verification.path when the response provides one and the key has the required scope. If verification is not available, use the execute response as readback. Price Book execution returns the complete saved entry and ready links.self and links.history paths.

Guardrail

A readiness response never grants extra access. Use the exact record-specific scope for an attachment or SDS assignment, and verify the result through the selected record's read route.