Plan Search Reference
Search and Hand Off uses this call, and Send a Lead runs the same search on our side. This page covers it in full: how to identify a vehicle, what you can put in a request, what comes back, and how to make it faster.
If you have not picked a path yet, start with Choose Your Integration.
Which endpoint to call
There are more search endpoints than you need. In practice:
| Endpoint | Use it when |
|---|---|
POST /api/v2/Partners/PlansSearch |
Default choice. Accepts any vehicle identifier and works out which one you sent. One integration handles all three. |
POST /api/v2/Partners/PlansSearch/Vin |
You only ever search by VIN and want the request validated strictly against that. |
POST /api/v2/Partners/PlansSearch/MakeModelYear |
You only ever search by make, model, and year. |
POST /api/v2/Partners/PlansSearch/LicensePlate |
You only ever search by plate and state. |
POST /api/v2/Partners/PlansSearchWithUserInfo/Vin |
You have the customer's details and want a pre-filled checkout. VIN only. See Search and Hand Off. |
POST /api/v2/Partners/PlansSearchAsync/* |
You need non-blocking searches or instant cached results, and Chaiz has enabled it for you. See make searches faster. |
The generic and the specific endpoints return the same shape. The specific ones just reject a request that carries the wrong identifier, which can be useful as a guardrail.
The synchronous route is /PlansSearch/Vin and the asynchronous one is /PlansSearchAsync/VIN. Note
the different capitalization. It is inconsistent, and we are keeping it that way rather than breaking
existing integrations.
Identify the vehicle
Three ways, in descending order of accuracy.
Prefer VIN wherever you can get one. It prices the exact trim, and it removes a step from checkout later. Make/model/year works, but the customer will be asked for a VIN before the sale completes, and that is where you lose people.
Always required, whichever you use: the customer's stateShortName (two-letter US code) and the
current mileage. Pricing depends on both, so neither is optional in practice.
If you send more than one identifier, precedence is VIN, then license plate plus state, then make/model/year. Send exactly one to avoid surprises.
Resolving loose make and model text
make and model must match our catalog. Free text a customer typed usually will not. Resolve it
first:
curl "https://uat-api.chaiz.com/api/v2/Partners/Vehicles/Lookup?make=merc&model=f150&year=2019" \
-H "Authorization: Bearer YOUR_API_KEY"
Returns up to five matches ordered by confidence, plus suggestions when the input is ambiguous.
Responses are cached and come back in under 50 ms.
| Confidence | Meaning | Example |
|---|---|---|
HIGH |
Exact match, ignoring case | ford resolves to FORD |
MEDIUM |
Matched after normalizing punctuation | f150 resolves to F-150 |
LOW |
Partial or contains match | merc could be MERCURY or MERCEDES-BENZ |
Treat LOW as "ask the customer to confirm" rather than as an answer. Check yearsAvailable on the
match before searching a year we do not carry.
To build your own dropdowns instead, GET /api/v2/Partners/Vehicles/Makes and
GET /api/v2/Partners/Vehicles/Makes/{makeId}/Models return the full catalog.
Request anatomy
{
"searchCriteria": { "vin": "5XXG14J25MG015777", "stateShortName": "NY", "mileage": 42000 },
"filters": { "duration": { "from": 24, "to": 60 }, "returnResults": 10 },
"tracking": { "queryParams": "utm_source=partner&utm_campaign=your-campaign" },
"responseDetailLevel": "Full",
"resultOrder": "Recommended",
"planVariant": "Recommended",
"showProviderInfo": true
}
filters, tracking, responseDetailLevel, resultOrder, planVariant, and showProviderInfo are root-level
fields, siblings of searchCriteria, not properties inside it. Nesting them inside searchCriteria is
the single most common request mistake, and it fails silently: the fields are ignored and you get an
unfiltered search.
| Field | Required | Notes |
|---|---|---|
searchCriteria |
Yes | The vehicle, state, and mileage |
filters |
No | Narrow the results. See below. |
tracking |
No | queryParams and referrer, for attribution. See Search and Hand Off. |
responseDetailLevel |
No | Full (default) or Essential. See response shapes. |
resultOrder |
No | Recommended (default), Rating, or None |
planVariant |
No | Recommended (default), Highest, Longest, or All. See one row per plan. |
showProviderInfo |
No | Include provider detail in the results |
partner |
No | Leave it out. Resolved from your token, and a mismatch is rejected. |
searchId, userId |
No | For continuing an existing search session. Omit unless you have been told to use them. |
Filters
All optional, all under filters.
| Filter | Type | Notes |
|---|---|---|
providers |
array of strings | Only these providers, by name: "NAAC", "Omega", "CAPS", "ServiceContract" |
excludeProviders |
array of strings | Everything except these |
numberOfProviders |
integer | Cap on distinct providers in the results |
coverageTypes |
array of strings | See the vocabulary note below |
coverageType |
enum | Single value. Different vocabulary from coverageTypes. See below. |
categoryTypes |
array of strings | Only plans covering these components: Engine, TurboSuper, TransferCase, Steering, Transmission, DriveAxle, Suspension, AirCon, Brakes, Electricals, Cooling, Fuel, Hybrid, Body, Consumables, Exhaust, General, Interior, Services, Assist |
planChaizRating |
range | Chaiz quality rating, 0 to 10. { "from": 7, "to": 10 } |
duration |
range, months | { "from": 24, "to": 60 } |
miles |
range, thousands | { "from": 50, "to": 150 } is 50,000 to 150,000 miles. 999 means unlimited. |
monthlyPrice |
range, USD | { "from": 50, "to": 150 } |
totalPrice |
range, USD | { "from": 1000, "to": 5000 } |
returnResults |
integer | Maximum rows returned. It counts rows, so with planVariant: "All" one plan can use several of them. Default depends on your account. |
Provider names are real catalog values, and which of them you can see depends on your account. Filtering to a provider you are not configured for returns nothing rather than an error.
The two coverage filters do not speak the same language
coverageTypes (the array) accepts either the friendly aliases "basic", "basicplus", "best" or
the canonical names "Powertrain", "PowertrainPlus", "MostComprehensive".
coverageType (the singular) accepts only the canonical names. The aliases do not work there.
An unrecognized value in coverageTypes is silently dropped, not rejected, so a typo looks like a
filter that had no effect. Prefer the canonical names everywhere and you avoid the whole problem.
| Alias | Canonical name | Meaning |
|---|---|---|
basic |
Powertrain |
Core powertrain components |
basicplus |
PowertrainPlus |
Powertrain plus additional systems |
best |
MostComprehensive |
Exclusionary, the broadest coverage |
Results report coverage as the number: 1 Powertrain, 2 PowertrainPlus, 3 MostComprehensive. Both
filters also accept those numbers.
Response shapes
responseDetailLevel controls how much comes back.
Full (default) |
Essential |
|
|---|---|---|
| Coverage breakdown, component scores, add-ons, provider detail, highlights | Yes | No |
planContractUrl |
Yes | No |
| Maintenance program | Full detail, as maintenanceProgram |
Yes/no only, as hasMaintenanceProgram |
| Core fields: price, duration, deductible, rating, handoff URLs | Yes | Yes |
| Payload size | Large | Small |
Use Full when you are rendering a comparison UI where customers want the detail. Use Essential when
you only need price, name, rating, and a link, which keeps responses small and fast to parse.
One row per plan
A plan can be sold with several terms: different durations and mileage tiers. planVariant decides which
of these variants you get back. The default is Recommended: one row for each planId.
planVariant |
What you get |
|---|---|
Recommended (default) |
One row per plan. The shortest duration wins. If durations tie, the mileage closest to the baseline for that duration wins. If that ties, the lowest monthly price wins. If that ties, the higher mileage wins. |
Highest |
One row per plan: the variant with the highest mileage. |
Longest |
One row per plan: the variant with the longest duration. |
All |
Every variant, as before. |
The baseline mileage for each duration:
| Duration | Baseline mileage |
|---|---|
| 12 months | 15,000 |
| 24 months | 30,000 |
| 36 months | 60,000 |
| 48 months | 75,000 |
| 60 months | 100,000 |
| 72 months | 125,000 |
Rules to know:
- The rows are picked first. The limits apply after that, each with its own unit:
-
numberOfProviders(or your account's provider limit when you do not send it) counts providers. It keeps the providers whose best plan scores highest. -
Your account's result limit counts distinct plans, on every surface. With
Allit still counts plans: one plan with 3 variants is one plan and returns 3 rows. filters.returnResultsis applied last and counts rows. WithAllone plan can use several of them.
-
-
If your account groups results to one row per provider (for example
bestValuePerProvider), that grouping picks the row.planVariantthen has no visible effect. - The order of the results does not change.
resultOrderworks as before. - Your account can set a default for
planVariant. A value in the request overrides it. - Send
"planVariant": "All"to get the same rows you got before this option existed.
Fields you will use most
| Field | Notes |
|---|---|
searchResultPlanId |
This plan at this price, embedded in the handoff URLs. Search again when it is older than 30 days. |
planId |
Essential only. Shared across the duration and mileage variants of the same plan, so it is what you dedupe on. Not returned on the default Full shape; see below. |
totalPrice, downpayment, monthlyPrice |
Already reflect vehicle, mileage, state, and your account. Do not recompute. Plans of 36 months or less can be paid monthly. Longer plans are paid in full and return 0 for monthlyPrice and downpayment, so show totalPrice for those. |
duration, miles, deductible |
Contract terms |
planChaizRating, planRatingVerdict |
Chaiz quality score out of 10, and its label |
displayProviderName, planName |
What to show the customer |
providerLogoUrl |
Full only. The provider's logo |
resultLabels |
Badges such as BEST_VALUE and LOWEST_PRICE |
maintenanceProgram |
Full only. The bundled maintenance program, or null when the plan has none. See below. |
hasMaintenanceProgram |
Essential only. Whether the plan bundles a maintenance program. |
planSummaryUrl, planCheckoutUrl |
Handoff destinations. See Search and Hand Off. |
By default you get one row per planId, so a plan appears once. With planVariant: "All", the same plan
appears several times at different durations and mileage tiers. That is not duplication in the results; it
is the same product on different terms. See one row per plan.
Maintenance programs
Some plans bundle a prepaid maintenance program at no extra cost, covering routine servicing such as oil changes, brake pads, battery replacement and cooling system service. The two response shapes answer this differently, so check the right field for the shape you requested:
-
On
Full, readmaintenanceProgram. It isnullwhen the plan does not include one; when it does, it carries adescription, anitemsarray of{ label, info }describing each covered service and its reimbursement limit, and adocumentUrl. There is no boolean on this shape. -
On
Essential, read thehasMaintenanceProgramboolean. The program's contents are not included; requestFullfor those.
"maintenanceProgram": {
"description": "Covers essential repairs to keep your car running smoothly",
"items": [
{ "label": "3 oil changes", "info": "$40 towards a standard oil change." },
{ "label": "1 battery or light replacement", "info": null }
],
"documentUrl": "https://assets.chaiz.com/files/maintenance-program.pdf"
}
documentUrl is null when the administrator has not published a program document. That does not
mean the plan lacks a maintenance program: the program's presence is signaled by
maintenanceProgram being non-null, never by documentUrl. Likewise, info is null on items
that have no limit or condition to state.
Deduping variants: you no longer need to dedupe. The default planVariant returns one row per plan on
both response shapes. If you send planVariant: "All" and want to group variants yourself, use planId.
It is returned only on the Essential shape. On the default Full shape, group on
displayProviderName plus planName plus coverageType instead.
Make searches faster
A cold search takes 2 to 8 seconds because Chaiz is collecting live rates from multiple warranty administrators. That is unavoidable on the synchronous path.
If you know which vehicles a customer might ask about before they ask (a listing page, a saved vehicle, an inventory feed), you can move that wait off the customer's request entirely.
Cached and asynchronous search is opt-in per account. If it is not enabled for you, these endpoints return an error regardless of a valid request. Email dev-support@chaiz.com to have it turned on, and do not build against it until it is.
Pre-registering a vehicle
POST /api/v2/Partners/VehicleRegistration with a VIN and mileage starts the quoting work in the
background so a later search returns immediately.
| Status | Meaning |
|---|---|
200 |
Registration accepted, quoting started |
201 |
Already cached and ready to search. The body echoes vin and mileage. |
202 |
Already in progress, nothing more to do |
400 |
The request did not validate, or quoting failed. response is null. |
The cache is keyed on VIN plus mileage, so a meaningful mileage change means the entry no longer
matches; re-register when the odometer moves by roughly 5,000 miles. Entries last 30 days.
POST /api/v2/Partners/VehicleRegistration/CleanVehicleCache clears one explicitly if you know the
underlying data changed.
Searching asynchronously
POST /api/v2/Partners/PlansSearchAsync/VIN (or /MakeModelYear, /LicensePlate, or the bare route)
never blocks for the full quoting round trip.
| Status | Meaning | What to do |
|---|---|---|
200 |
Done. The body holds your plans. | Use the results. This is the terminal success state. |
202 |
Still quoting | Poll the same endpoint with the same body every 1 to 2 seconds |
400 |
Validation failed, or quoting errored. response is null. |
Read the errors; do not poll |
Asynchronous search completes with 200, not 201. Only VehicleRegistration returns 201, and
it means something different there (already cached). If you are polling for a 201 from a search
endpoint you will poll forever.
There is no Retry-After header on a 202. Use your own 1 to 2 second interval, and set a sane
overall ceiling so a stuck search does not hang your request.
Responses carry an X-Cache header of HIT or MISS, which is useful for confirming your
pre-registration strategy is actually working.
Just the starting price
POST /api/v2/Partners/PlansSearchAsync/GetStartPrice returns only the starting price for a
vehicle, for a "protection from $X" teaser without pulling a full result set. It follows the same
200 / 202 / 400 contract.
The starting price is the lowest first payment among plans of 36 months or less. When a vehicle only has longer plans, which are paid in full, there is no starting price.
When no price is available, including when a vehicle only has plans longer than 36 months, this returns
an empty string rather than a number or null. Handle the empty case explicitly.
Next steps
- Search and Hand Off for what to do with the results.
- Restrictions and limitations for why a search can come back empty.
- UI Requirements for what your results page must show.
- Errors & Rate Limits for validation codes and retry behavior.
- API Reference for complete schemas.