cypress.DOCS
Log inRegister
Cypress Docs
Developers

Pushing inventory

Send your vehicles one at a time or 500 at a time, map your feed's fields, attach an inspection report by its link, and keep the whole inventory in step.

TL;DRAI summary

This chapter explains how to add or update vehicles in Cypress from your own system, one by one or 500 at a time, and how a daily feed updates only the fields it sends. It also covers your floor price, saved locations, an inspection report link so Cypress pulls the condition and photos, and pushing your whole inventory so the vehicles you no longer hold are taken off.

Dive deeper:

Your system is the source of truth for what you own; Cypress keeps a copy it can sell. A push writes that copy: one vehicle with POST /api/v1/vehicles, or up to 500 with POST /api/v1/vehicles/bulk. Both need the vehicles scope.

One vehicle or many

You want to…CallWhat happens
Add a vehiclePOST /api/v1/vehiclesCreated as a draft (201). A VIN already in your inventory answers 409 with its id
Add it or replace it, by VINPOST /api/v1/vehicles?mode=upsertNew VINs are created (201), known ones replaced (200): every field you leave out is cleared
Add it or update what you send, by VINPOST /api/v1/vehicles?mode=mergeNew VINs are created (201); on known ones only the fields you send change (200) (since 1.8.0)
Replace one you knowPUT /api/v1/vehicles/{id}Every field you leave out is cleared (see below)
Change a few fieldsPATCH /api/v1/vehicles/{id}Only the fields you send change
Send manyPOST /api/v1/vehicles/bulk (?mode=merge or ?mode=upsert)Up to 500 items, an answer per item; one bad item never fails the others
Send your whole inventory, and take off what leftPOST /api/v1/vehicles/bulk?mode=merge&removeMissing=trueThe vehicles the push leaves out are archived, at once or, when a buyer is involved, after Cypress's review: see below (since 1.8.0)

A daily feed usually sends everything with POST /api/v1/vehicles/bulk?mode=merge: new vehicles are added, the rest are brought up to date, and you never need to know which is which. Merge or upsert? merge changes only the fields each item sends, so the details your feed doesn't carry (a trim, a transmission, an interior color your team or Cypress filled in) stay. upsert makes each vehicle exactly what you send and clears the rest: use it when your feed carries every field. Every push takes ?dryRun=true (every check, nothing written); the single-vehicle writes also take an Idempotency-Key.

A bulk push answers one item per vehicle, in the order sent:

{
  "items": [
    { "vin": "1FTFW1ET5DFC10312", "status": 201, "id": "clx…", "href": "/api/v1/vehicles/clx…", "inspectionReport": "QUEUED" },
    { "vin": "1FAHP2E88DG100002", "status": 200, "id": "clx…", "href": "/api/v1/vehicles/clx…" },
    { "vin": "BAD", "status": 422, "errors": ["vin: …"] }
  ],
  "created": 1, "updated": 1, "conflicts": 0, "invalid": 1, "refused": 0
}

The fields

Four fields are required: vin, modelYear, make and model. Cypress does not decode the VIN of a pushed vehicle: your data is the record.

FieldWhat it is
vin, modelYear, make, modelThe vehicle. model may carry the trim ("228I AWD"); a known trim is lifted into trim
trim, bodyClass, driveType, transmission, fuelTypeOptional specs
exteriorColor, interiorColorAs your system names them
mileageWhole miles
conditionPreferably the industry 1.0–5.0 grade (3.9); EXCELLENT, GOOD, FAIR or POOR also work
stockNumberYour unit or stock number, shown to people
externalIdsYour systems' ids for the vehicle, to look it up by them: see Your own ids
locationname, address, city, state, zip. A yard code such as "DEN" goes in name: when it matches one of your saved locations (Settings › Locations, any case), Cypress fills the address, city, state and ZIP you leave out (since 1.8.0)
floorPriceYour floor: the lowest sale price you accept. Set at once on a vehicle not yet on the market; on one on the market or in a sale, a change Cypress reviews first (since 1.8.0)
imagesUp to 50 { "href", "type" } links Cypress can fetch; the first, or the one typed PRIMARY_IMAGE, is the cover
inspectionReportUrlA third-party inspection report's link: see Inspection reports (since 1.8.0)
listingPut it on the market in the same push: prices, channels and listingMode. See Pricing & listing rules
lifeStageOPERATION or PLANNING sends it to the plan instead of to market: see Planning API

A field Cypress doesn't know is kept with the push and never refused, so your feed can send its own columns as they are.

A typical fleet feed maps like this:

{
  "vin": "1FTFW1ET5DFC10312",
  "modelYear": 2013, "make": "Ford", "model": "F-150 XLT",
  "exteriorColor": "BLACK",
  "mileage": 88500,
  "stockNumber": "101815",
  "externalIds": [{ "source": "fleet", "id": "101815" }],
  "location": { "name": "DEN" },
  "floorPrice": 24300,
  "inspectionReportUrl": "https://inspectionreport.manheim.com/?disclosureid=…"
}

With a saved location named DEN, the vehicle gets its address. A name that matches none of your saved locations is a warning on the item, and the vehicle keeps what you sent. The item says what became of the floor in floorPrice: SET, UNCHANGED, or STAGED when the vehicle is on the market and Cypress reviews the change first.

What a replace clears. A PUT, or mode=upsert on a VIN you already pushed, replaces the vehicle: a field you leave out is cleared, and the pictures you pushed are replaced by the ones you send (none sent, none kept). Two things a replace never touches: pictures added in the app or read from an inspection report, and — since 1.8.0 — a condition an inspection set, when the push carries no condition. A PATCH, or mode=merge, changes only what it names: your pictures stay unless it sends images.

Prices on a re-push. The floor travels on every push (floorPrice, above). The listing block creates the listing the first time; once a vehicle has an open listing, a re-push leaves the listing's own prices as they are: change them with PATCH /api/v1/listings/{id} (the vehicle's read names the listing in listing.id). A push that sends both floorPrice and listing.floorPrice must send the same amount.

Inspection reports

Since 1.8.0. Send the link of a third-party inspection report with the vehicle, and Cypress reads the report for you. Today that is the Manheim condition report (https://inspectionreport.manheim.com/?disclosureid=…):

  • its grade becomes the vehicle's condition (unless a Cypress inspection was finalized for it),
  • each damage is recorded, with the title, keys, tires and warning lights,
  • its photos are added to the vehicle, so a feed with no pictures still lists with them.

The push answers right away and the report is read in the background. The push says what it did with the link, in inspectionReport:

AnswerMeans
QUEUEDA new link: Cypress is reading it
UNCHANGEDThe vehicle already has this link: nothing to do
LINK_ONLYNo provider Cypress reads: the link is kept on the vehicle for people to open

A Manheim link that names no report (no disclosureid) is a warning on the push, and the vehicle is written anyway. Because the same link changes nothing, a daily feed can send the link every time; a new link replaces the report, and a link whose report could not be read is tried again at most once a day. Null or no inspectionReportUrl leaves the vehicle's report as it is.

When the report lands, your endpoint hears VEHICLES.UPDATED with changedFields: ["inspectionReport"] and the report in inspectionReport. Its status is IMPORTING_PHOTOS when the grade and damages are in and the photos are on their way, COMPLETE when everything is in, FAILED when the report could not be read.

The event and the vehicle's read carry the report in the same shape:

"inspectionReport": {
  "url": "https://inspectionreport.manheim.com/?disclosureid=…",
  "provider": "MANHEIM",
  "status": "COMPLETE",
  "grade": 3.9, "gradeLabel": "Average",
  "damages": 3, "photos": 37,
  "updatedOn": "2026-10-22T14:02:11.000Z"
}

and GET /api/v1/vehicles/{id}/history-report has each damage. To attach a link to a vehicle on its own, POST /api/v1/vehicles/{id}/history-report with { "url": "…" } (MCP: attach_vehicle_history_report).

Keeping the whole inventory in step

Since 1.8.0. Add removeMissing=true and the push is your whole inventory: every active vehicle Cypress holds for you that the push leaves out is one you want off the platform. You don't keep track of what you sent or what left; the next push says it.

POST /api/v1/vehicles/bulk?mode=merge&removeMissing=true

What happens to a vehicle the push leaves out is what happens when you archive it in the app, or pick the "vehicles not in this file" on the Import screen:

  • With no open offer and no sale in progress, it is archived at once.
  • With an open offer, or a sale in progress on Cypress (an accepted offer, due diligence, ready to deliver), it waits for a Cypress Manager, flagged with liveDeal so the reviewer sees the deal. A vehicle on the market pauses while it waits (ON_HOLD); if the removal is rejected, it goes back on the market.
  • A vehicle whose removal Cypress rejected is not asked again on every push. Push it again and leave it out later, and it is filed again.
  • Active means not sold, not archived and not in Planning, whoever added it: a vehicle added in the app counts too.
  • A vehicle that already has a request waiting is not filed twice (alreadyPending).
  • An item refused for another reason (a bad field, an id another vehicle holds) still counts as in your inventory: a typo never takes a car off.
  • A vehicle you push that still has a request waiting (it was left out before) gets a warning on its item. The push does not withdraw the request; tell Cypress if the vehicle should stay. A vehicle you push that is already archived gets its details updated, and a warning: a push does not bring it back to Cypress.

The answer adds removals: one entry in items per vehicle taken off or filed, and one in kept per vehicle left out but not filed, with the reason (REVIEW_REJECTED, or NO_LONGER_ACTIVE for one sold or archived while the push was written):

"removals": {
  "activeInventory": 120, "missing": 3, "archived": 1, "staged": 1, "alreadyPending": 0,
  "items": [
    { "vin": "…", "id": "clx…", "requestId": "clx…", "archived": true, "statusBefore": "LISTED", "statusAfter": "ARCHIVED",
      "paused": false, "alreadyPending": false, "liveDeal": null },
    { "vin": "…", "id": "clx…", "requestId": "clx…", "archived": false, "statusBefore": "UNDER_OFFER", "statusAfter": "UNDER_OFFER",
      "paused": false, "alreadyPending": false, "liveDeal": { "kind": "OFFERS", "openOffers": 1, "topOffer": 21000 } }
  ],
  "kept": [
    { "vin": "…", "id": "clx…", "status": "APPROVED", "reason": "REVIEW_REJECTED" }
  ]
}

Your endpoint hears the same through webhooks:

  • A vehicle archived at once: VEHICLES.DISPOSITION.RESOLVED right away, with decision: "APPROVED", automatic: true, statusBefore and statusAfter: "ARCHIVED".
  • A vehicle that waits for review: VEHICLES.DISPOSITION.STAGED now (its requestId, statusBefore, statusAfter, paused, reviewReason and liveDeal), then VEHICLES.DISPOSITION.RESOLVED when Cypress decides: the decision (APPROVED or REJECTED) and statusAfter: ARCHIVED when approved; when rejected, LISTED again if the request had paused it, otherwise the status it had.
  • Both carry source: "api-sync".

Add syncId=<your label> (letters, digits, ., :, - or _, up to 64) and the push's answer and both events carry it, so your logs can tie a decision to the push that asked for it.

More than 500 vehicles. Send the pages as usual, without the flag. On the last page, add removeMissing=true and inventoryVins: every VIN you hold (up to 20,000). Cypress compares against that list and the page's own vehicles. Nothing is kept between calls, so a page that failed is simply sent again.

POST /api/v1/vehicles/bulk?mode=merge&removeMissing=true&syncId=feed-2026-10-22
{ "vehicles": [ …the last page… ], "inventoryVins": ["1FTFW1ET5DFC10312", "…"] }

A truncated feed never empties your fleet. A push that leaves out more than a fifth of your active vehicles (and more than five) is refused whole, items included (409 MASS_REMOVAL, with activeInventory and missing): a vehicle with no buyer is archived at once, so the guard stops a broken feed before anything is written. When the removal is meant, add allowMassRemoval=true.

Try it first. With dryRun=true the push answers what each item would do and what would happen to each vehicle it leaves out, writing nothing.

To take off a single vehicle, or to say why it left (sold elsewhere, back on rent), the calls of De-listing remain.