---
title: "Edit inventory (quantity, exclude, add by item_key). Response carries the re-priced quote."
method: POST
path: "/jobs/{id}/items"
---

# Edit inventory (quantity, exclude, add by item_key). Response carries the re-priced quote.

`POST /jobs/{id}/items`

## Request

- Base URL: `https://widget.reelquoteai.com/api/v1`
- URL: `https://widget.reelquoteai.com/api/v1/jobs/{id}/items`
- Auth: HTTP bearer

## Request body

- object
  - `items` union[], required: Non-empty. Any edit clears the customer's approval and the e-signature.
    - union
      - object: Edit a row
        - `id` string, required: An existing row's id
        - `quantity` integer: 0 allowed
        - `excluded` boolean: true = staying. Rows are never deleted.
        - `notes` string: Up to 500 chars
      - object: Add a row (source becomes customer_added)
        - `item_key` string, required: From GET /catalog
        - `quantity` integer: Default 1
        - `room` string: Default 'Added by customer'
        - `notes` string

## Response `200`

The inventory block exactly as GET /jobs/{id} returns it, plus the re-priced quote

- object
  - `job_id` string
  - `inventory` Inventory
    - `item_count` integer, required: Rows
    - `total_quantity` integer: Units across all rows
    - `total_cuft` number, required: Priced volume: excluded rows contribute nothing
    - `total_weight_lbs` number
    - `estimated_boxes` integer, nullable: Medium boxes the analysis estimated from closets and cupboards; also present as a box row
    - `access_notes` string, nullable: Stairs, elevators, long carries seen or heard in the video
    - `video_quality_issues` string, nullable: Rooms skipped, closed closets, poor light — or 'None'
    - `approved_at` string, date-time, nullable: Customer sign-off; cleared whenever the inventory is edited
    - `customer_notes` string, nullable
    - `rooms` Room[], required
      - `room` string
      - `items` InventoryItem[]
        - `id` string, required: Row id. Pass it back to POST /jobs/{id}/items to edit this row.
        - `item_key` string, required: Catalog key (GET /catalog). `misc` when nothing on the catalog fit.
        - `name` string, required: As detected in the video, or as the catalog names a customer-added row
        - `quantity` integer, required
        - `cubic_feet` number, required: Per unit, copied from the catalog at analysis time
        - `weight_lbs` number: Per unit, copied from the catalog at analysis time
        - `fragile` boolean
        - `requires_disassembly` boolean
        - `two_person` boolean
        - `special_handling` boolean
        - `excluded` boolean, required: true = staying / not moving. Kept on the record with its volume unpriced; never deleted.
        - `confidence` number, nullable: 0–1 from the analysis; null on customer-added rows
        - `notes` string, nullable: From the narration or the customer
        - `source` 'ai' | 'customer_added', required: Who put the row here.
    - `packing` object, nullable: Display-only packing-materials estimate derived from this inventory (boxes by size, tape, paper). Quantities, not prices.
    - `gaps` Gaps
      - `prompts` GapPrompt[]: Recomputed from the CURRENT inventory on every read — fixing a gap removes its prompt. Empty is the common, good case.
        - `code` string: Stable code, e.g. bedrooms_missing
        - `severity` 'warn' | 'info'
        - `title` string
        - `message` string: Customer-voiced, e.g. 'only 1 of 3 bedrooms is in the video'
      - `acknowledged_at` string, date-time, nullable: When the customer said 'nothing is missing' via POST /jobs/{id}/gaps
  - `quote` Quote: A priced quote. bookable=false means a ballpark range; only bookable quotes can be booked. The key kind decides the view. A publishable key — it sits in the mover's page source — receives the customer view: each line's label, amount, hours, crew and miles, the overage rate, the assumptions and the inputs. A secret key also receives the rate behind each line, applied_rules, subtotal_cents, minimum_charge_cents, rate_card_id, rate_card_name and engine_version.
    - `kind` 'ballpark' | 'firm', required
    - `bookable` boolean, required: true only for a firm quote built from a filmed inventory. Gate any booking action on this flag, never on kind.
    - `currency` string, required: ISO 4217
    - `units` string: Measurement system for volumes and distances
    - `crew` integer, required: Movers on the job
    - `labour_hours` number, required: Billable hours at the home(s)
    - `overage` object: Disclosed on every quote: what an hour past the estimate costs.
      - `label` string
      - `hourly_cents` integer: Rate for time beyond the estimate, in integer cents. Currency travels beside it — never assume USD.
      - `same_as_hourly` boolean
    - `buckets` object, required: The price, line by line.
      - `labour` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `move_leg_time` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `deadhead_leg_time` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `truck_move_leg` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `truck_deadhead_leg` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `travel_flat` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `access_origin` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
      - `access_destination` Bucket: One line of the price: a label and an amount. Labour lines also carry hours and crew; truck legs carry miles.
        - `label` string: Plain-language line name
        - `cents` integer: This line, in integer cents. Currency travels beside it — never assume USD.
    - `applied_rules` object[]: Date and seasonal rules that fired, in order. Empty when none did.
      - `rule` string: Rule name from the mover's rate card, e.g. weekend, month_end
      - `mult` number, nullable: Multiplier, when the rule is one
      - `add_cents` integer, nullable: Flat addition, when the rule is one, in integer cents. Currency travels beside it — never assume USD.
      - `delta_cents` integer: What the rule changed the total by, in integer cents. Currency travels beside it — never assume USD.
    - `subtotal_cents` integer: Before date rules and the minimum charge, in integer cents. Currency travels beside it — never assume USD.
    - `minimum_charge_cents` integer: The mover's floor, in integer cents. Currency travels beside it — never assume USD.
    - `minimum_charge_applied` boolean: true when the floor lifted the total
    - `total_cents` integer, nullable, required: The price. null on a ballpark — use low_cents / high_cents, in integer cents. Currency travels beside it — never assume USD.
    - `low_cents` integer: Ballpark only: low end of the range, in integer cents. Currency travels beside it — never assume USD.
    - `high_cents` integer: Ballpark only: high end of the range, in integer cents. Currency travels beside it — never assume USD.
    - `inputs` object: What the price was computed from — pinned on the job, so a later rate-card change can never reprice history. A ballpark adds the home size (dwelling) and its volume range (cuft_low, cuft_high).
      - `cuft` number: Volume priced
      - `weight_lbs` integer
      - `distance_miles` number, nullable
      - `duration_minutes` number, nullable: Routed drive time, when a route was found
      - `move_date` string, date, nullable: YYYY-MM-DD
    - `assumptions` string[], required: Plain-language caveats. Show these to the human.
    - `rate_card_id` string: The mover's rate card this was priced from
    - `rate_card_name` string
    - `engine_version` string: Pricing engine version
  - `steps` PriceSteps: How far the inventory can move before the price does — the honest answer to 'what if I add the piano'.
    - `up` object, nullable: The nearest price break in one direction; null when none is close.
      - `cuft_delta` number: Cubic feet added (up) or removed (down) before the price changes
      - `total_cents` integer: The price at that point, in integer cents. Currency travels beside it — never assume USD.
      - `crew` integer
      - `labour_hours` number
    - `down` object, nullable: The nearest price break in one direction; null when none is close.
      - `cuft_delta` number: Cubic feet added (up) or removed (down) before the price changes
      - `total_cents` integer: The price at that point, in integer cents. Currency travels beside it — never assume USD.
      - `crew` integer
      - `labour_hours` number
  - `terms` Terms
    - `included` string, nullable: What the mover includes, in their words
    - `terms` string, nullable: The mover's terms, in their words
    - `frozen` boolean: false = live text; true = the snapshot the customer accepted at booking
    - `accepted_at` string, date-time, nullable: Only on a frozen copy

## Other responses

- `400`: `invalid_request` — A field is missing, malformed or not allowed in this state. `message` says which. `secret_key_in_url` — A secret key was sent as `?k=`. Secret keys go in the Authorization header only.
- `401`: `unauthorized` — No key, an unknown key, or a revoked key.
- `404`: `not_found` — No such job for this key, or no such endpoint.
- `429`: `rate_limited` — Per-key limit (60/min publishable, 300/min secret), the mover's daily ballpark or video-analysis cap, or a visitor's daily catalog-search cap. Obey `Retry-After`. Carries `Retry-After` in seconds.

---

[API](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api.md) · [All operations](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api/llms.txt) · [OpenAPI document](https://skmtc.dev/reelquoteai/apis/reelquoteai-us-api/revisions/56c304752192?raw)
