---
title: "Errors & Rate Limits"
description: "The error envelope, every error code, and how rate limiting works"
---

# Errors and Rate Limits

## The error envelope

Partner API responses are wrapped, except the registration endpoints (`/api/v2/Partners/Register*`):
their success bodies are plain objects, and Register and Verify answer `400` as one plain-text sentence.
See [Registration and authentication endpoints](#registration-and-authentication-endpoints).
In a wrapped response, `response` carries the payload, `errorResponse` carries the problems, and
exactly one of them is populated.

```json
{
  "response": null,
  "errorResponse": {
    "errors": [
      {
        "errorMessage": "Can't parse vin: invalidvin",
        "errorField": "VIN",
        "errorCode": "CantParseVIN",
        "errorNumber": 4005,
        "suggestions": [
          {
            "action": "CheckInput",
            "message": "Verify the VIN is exactly 17 alphanumeric characters (no I, O, or Q)."
          },
          {
            "action": "RetryWithAlternative",
            "message": "Search by make, model, and year instead.",
            "endpoint": "/api/v2/Partners/PlansSearch/MakeModelYear"
          }
        ]
      }
    ]
  }
}
```

The field is `errorResponse`, and the payload field is `response`. Neither is called `data`.

| Field | Notes |
|---|---|
| `errorMessage` | Human readable. Safe to log, not always safe to show a customer verbatim. |
| `errorField` | Which input caused it. May be `null`. |
| `errorCode` | Machine readable identifier. Branch on this. |
| `errorNumber` | Numeric equivalent. Also stable. |
| `suggestions` | What to do about it. Often absent. |
| `affectedEntityIds` | Which entities the error relates to, where applicable. |

### Suggestions

Where present, `suggestions` tells you what to do next without you having to encode the rules yourself.

| Field | Notes |
|---|---|
| `action` | `CheckInput`, `RetryWithAlternative`, `UseDifferentEndpoint`, or `ContactSupport` |
| `message` | Written to be shown to an end user as-is |
| `endpoint` | An alternative endpoint to call, on `RetryWithAlternative` and `UseDifferentEndpoint` |

This is especially useful for AI agents and automated integrations: a `RetryWithAlternative` with an
`endpoint` is a machine-followable instruction.

## HTTP status codes

| Status | Meaning | What to do |
|---|---|---|
| `200` | Success | |
| `201` | Created. On `VehicleRegistration`, already cached. | |
| `202` | Accepted, still working. Async search only. | Poll the same endpoint every 1 to 2 seconds |
| `400` | Validation failed | Read `errorResponse.errors`. Do not retry unchanged. On plan search, `response` is always `null`, and `SystemError` (10000) reports a search that failed on our side; that one is safe to retry. Register and Verify answer with one plain-text sentence, see [Registration and authentication endpoints](#registration-and-authentication-endpoints). Two of those sentences report a server problem and are safe to retry. |
| `401` | Missing, invalid, rotated, or wrong-environment key (Rotate and Delete: any key but the current one) | See [Authentication](/getting-started/authentication#when-authentication-fails) |
| `403` | Authenticated but not permitted | Your account lacks the capability. Contact support. An HTML body instead of JSON means the firewall stopped the call, see [Authentication](/getting-started/authentication#calling-from-your-servers) |
| `404` | Not found | Check the path and any id you sent |
| `429` | Rate limited | Back off per `Retry-After` |
| `5xx` | Server-side failure | Safe to retry with backoff |

Only `5xx`, connection failures, a plan search `400` with `SystemError`, and the two registration
server-problem sentences (`An error occurred during registration. Please try again.` and
`An error occurred during verification. Please try again.`) are safe to retry blindly. **Never retry a slow plan search**: it has
not failed, and a retry starts a second independent search that quotes the vehicle twice.

## Registration and authentication endpoints

`POST /api/v2/Partners/Register` and `POST /api/v2/Partners/Register/Verify` do not use the error
envelope above. They answer `400` with one plain sentence and `Content-Type: text/plain`. There is no
`errorResponse`, so do not parse the body as JSON. If there are several problems, the sentences are
joined with a space. The `Partners/Authentication` endpoints answer a missing or invalid body the same way.

| Sentence | When |
|---|---|
| `The request body is missing, is not valid JSON, or has a field of the wrong type.` | The body is empty or is not valid JSON, for example a missing brace or colon. Or a field has the wrong type, for example `"agreedToTerms": "yes"` or `"email": ["a@b.c"]` |
| `Code must be a string of 6 digits.` | `code` is not a string of 6 digits |
| `Email is required.` | `email` and `contactEmail` are both missing or blank |
| `Verification code is required.` | `code` is missing or empty |
| `Invalid verification code. Please try again.` | Wrong code, attempts 1 to 4. Each one counts toward the limit of 5 attempts |
| `Verification code is invalid or expired. Please register again.` | The code has expired. Register again |
| `Verification failed. Please register again.` | The 5th wrong code. The registration is canceled. Register again to get a new code |
| `No pending registration found for this email. If you already verified it, use the API key you received (lost it? contact dev-support@chaiz.com); otherwise register first.` | No registration waits for this email: it was never registered, it is already verified, or it was canceled |
| `The ContactEmail field is required.` (and the same for other fields) | A required registration field is missing or empty |
| `The ContactEmail field is not a valid e-mail address.` | `contactEmail` is not a valid email address |
| `The field DisplayName must be a string or array type with a maximum length of '200'.` (and the same for other fields) | A registration field is too long. Limits: `contactEmail` 255, `displayName` 200, `organizationName` 200, `websiteUrl` 500, `intendedUse` 1000 characters |
| `You must agree to the terms of service.` | `agreedToTerms` is not `true` |
| `Registration limit reached. Please contact support.` | This IP address already has 3 active keys. Only Register checks this cap. The cap is checked first, so it also refuses a Register that would only re-send a pending code. Waiting does not free a slot. To free one, deactivate a key you do not use (`DELETE /api/v2/Partners/Register`), or contact dev-support@chaiz.com |
| `An error occurred during registration. Please try again.` | A server problem, not a problem with your request. Unlike other `400` answers, this one is safe to retry unchanged. Try again later. If it repeats, contact dev-support@chaiz.com |
| `An error occurred during verification. Please try again.` | A server problem, not a problem with your request. Unlike other `400` answers, this one is safe to retry unchanged. Try again later. If it repeats, contact dev-support@chaiz.com |

A `415 Unsupported Media Type` means the `Content-Type` is not `application/json`.

`POST /Register/Rotate` and `DELETE /Register` answer `400`, and `GET /Register/Usage` answers `404`, with a
JSON body whose `errors[0].errorMessage` is `No active registration found.` (full shape:
`{"errors":[{"errorMessage":"No active registration found.","errorField":null,"errorCode":null,"errorNumber":null}]}`)
when the key's partner has no active self-service registration, for example after a Delete that ran at the same
moment. This body has no `response`/`errorResponse` wrapper. The same status with an `errorMessage` that starts
with `An error occurred` is a server problem, not a missing registration: retry it later. A `401` means the key is
missing, invalid, rotated out or deleted. `POST /Register/Rotate` and `DELETE /Register` also answer `401` when the
key is valid but is not the registration's current key, for example a key that a parallel Rotate already replaced:
nothing is changed. The `401` body is empty or an `application/problem+json` object with `title`
`Unauthorized`, never the `errors` object.

## Validation errors (4xxx)

| Number | Code | Field | Meaning | Fix |
|---|---|---|---|---|
| 4001 | `ZipAndStateAreEmpty` | ZipAndState | Zip and state both missing | Send `stateShortName` or `zip` |
| 4002 | `CarDetailsAreEmpty` | CarDetails | No vehicle identifier at all | Send a VIN, a plate, or make/model/year |
| 4005 | `CantParseVIN` | VIN | VIN not valid | 17 alphanumeric characters, no I, O, or Q |
| 4006 | `CantParseLicensePlate` | LicensePlate | Plate not valid | Check the format, and send `stateShortName` with it |
| 4009 | `MileageIsLessThanZero` | Mileage | Negative mileage | Send a positive integer |
| 4013 | `StateShortNameIsInvalid` | StateShortName | Not a US state code | Two-letter code, for example `NY` |
| 4014 | `ModelIsInvalid` | Model | Model not valid for that make | Resolve it with [Vehicle Lookup](/reference/plan-search#resolving-loose-make-and-model-text) |
| 4015 | `MakeIsInvalid` | Make | Make not in the catalog | Resolve it with Vehicle Lookup |
| 4017 | `DurationFilterCantbeEmpty` | DurationFilter | Duration filter present but empty | Send `from` and `to`, or omit the filter |
| 4019 | `MileageIsRequired` | Mileage | Mileage was zero | Send the actual current mileage |

`4014` and `4015` are the most common errors in new integrations, and almost always mean you are
sending customer-typed text straight through. Resolve it first.

Some endpoints use a shorter form with no `errorNumber`, for example `errorCode: "EmptyValue"` with
`errorMessage: "VIN is required."`. Handle a missing `errorNumber` rather than assuming it is present.

## Coverage limits

These are hard limits. Within them, a vehicle no provider wants to cover comes back as an empty
`results` array, not an error. See [Restrictions and limitations](/reference/restrictions).

| Number | Code | Field | Meaning |
|---|---|---|---|
| 2000 | `ChaizRestrictedState` | StateShortName | We do not sell in this state (California) |
| 4007 | `YearIsMoreThan25YearsAgo` | Year | The vehicle is more than 25 model years old |
| 4008 | `YearIsInTheFuture` | Year | The model year is later than next year |
| 4010 | `MileageIsTooHigh` | Mileage | Over 250,000 miles |

Tell the customer what happened rather than retrying. These do not change with a different request.

## Account and plan errors (20xxx)

| Number | Code | Meaning | Fix |
|---|---|---|---|
| 20004 | `DurationFilterRestictedValues` | Your account allows only specific durations | Use an allowed value, or ask support to widen it |
| 20005 | `DurationFilterRestrictedRange` | Your account requires `from` to equal `to` | Send a single duration, for example `{ "from": 36, "to": 36 }` |
| 20006 | `VinHasNotBeenRegistered` | Async search on a VIN and mileage that was never pre-registered | Call `VehicleRegistration` first. See [make searches faster](/reference/plan-search#make-searches-faster) |

These are configuration talking to you, not bugs in your request. If a restriction does not match what
you expected to sell, contact dev-support@chaiz.com rather than working around it.

## Rate limits

Limits are per partner account, not per IP, and they apply as two simultaneous windows: hourly and
daily. Both must have room for a request to pass.

| Tier | Per hour | Per day |
|---|---|---|
| Free (self-service registration) | 50 | 500 |
| Standard (commercial agreement) | 1,000 | 10,000 |
| High volume (by arrangement) | 5,000 | 20,000 |

`GET /api/v2/Partners/Register/Usage` returns your current tier and limits. Ask dev-support@chaiz.com
about a higher tier before launch rather than after you start hitting `429`s.

Exceeding a limit returns `429` with a `Retry-After` header in seconds.

<scalar-callout type="info">
`Retry-After` is deliberately rounded and jittered, so it is a safe lower bound rather than an exact
countdown. Honor the value you are given and add your own jitter on top. Do not attempt to compute when
the window resets; several clients all retrying on the same computed second is how you get a second
`429`.
</scalar-callout>

`Retry-After` appears on `429` only. A `202` from async search does not carry one; poll on your own 1 to
2 second interval instead.

## Handling errors well

Read `errorResponse` before anything else, and use `suggestions` when they are there:

```javascript
const body = await response.json();
const errors = body.errorResponse?.errors;

if (errors?.length) {
  for (const e of errors) {
    console.error(`${e.errorCode ?? 'validation'} (${e.errorNumber ?? '-'}): ${e.errorMessage}`);

    for (const s of e.suggestions ?? []) {
      // s.message is written to be shown to the customer as-is
      console.log(`[${s.action}] ${s.message}${s.endpoint ? ` -> ${s.endpoint}` : ''}`);
    }
  }
  return;
}

const plans = body.response.results;
```

A short checklist that removes most avoidable failures:

- Resolve make and model through Vehicle Lookup instead of passing customer text through.
- Require state and mileage in your own form, and validate the VIN is 17 characters.
- Search again rather than reusing a result older than 30 days.
- Retry only on `5xx`, connection errors, `429` after `Retry-After`, a plan search `SystemError`,
  and the two registration server-problem sentences (`An error occurred during registration…` and
  `An error occurred during verification…`).
- Log `errorCode` and `errorNumber`, not just the message. The message wording can change; the codes
  are stable.

## Still stuck?

Email dev-support@chaiz.com with the full error response, the request body, and roughly when the call
happened. Remove card numbers and personal data first.
