Write readiness
Use optional non-mutating dryRun checks before supported POST and PATCH writes.
The API supports dryRun=true on existing notes, contacts, customer, supplier, parent-scoped child location/address, attached document, product, and product-family 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
| Endpoint | Scope | Execution header |
|---|---|---|
POST /notes | notes:write | Idempotency-Key |
PATCH /notes/{id} | notes:write | If-Match |
POST /contacts | contacts:create | Idempotency-Key |
PATCH /contacts/{id} | contacts:update | If-Match |
POST /customers, POST /suppliers | customers:write or suppliers:write | Idempotency-Key |
PATCH /customers/{id}, PATCH /suppliers/{id} | customers:write or suppliers:write | If-Match |
POST /customers/{id}/locations, POST /customers/{id}/locations/{locationId}/addresses | customers:write | Idempotency-Key |
PATCH /customers/{id}/locations/{locationId}, PATCH /customers/{id}/locations/{locationId}/addresses/{addressId} | customers:write | If-Match |
POST /customers/{id}/documents | customers:write | Idempotency-Key |
POST /suppliers/{id}/ship-from-locations | suppliers:write | Idempotency-Key |
PATCH /suppliers/{id}/ship-from-locations/{locationId} | suppliers:write | If-Match |
POST /suppliers/{id}/documents | suppliers:write | Idempotency-Key |
POST /products | products:write | Idempotency-Key |
PATCH /products/{id} | products:write | If-Match |
POST /products/{id}/documents | products:write | Idempotency-Key |
POST /product-families | products:write | Idempotency-Key |
PATCH /product-families/{id} | products:write | If-Match |
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.
Response
Dry runs return HTTP 200 with data.type: "write_readiness" when the request can be evaluated. Use data.status, 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 preflight checks available to the API at that moment. Execution is still authoritative and re-runs every invariant.
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
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 execution requires If-Match. 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.
Write readiness does not add permissions or broaden write surfaces. Document readiness applies only to link-based document attachment, and never to binary upload, document deletion, broad file export, order mutation, purchase-order mutation, inventory, receiving, payments, AP, AR, GL, or duplicate bypasses.