---
title: "Rate Limits & Quotas"
description: "How CarsXE usage limits work — included monthly volume, the 429 response shape, overage billing, usage alerts, and how to handle limits in code."
canonical_url: "https://docs.carsxe.com/docs/guides/rate-limits"
markdown_url: "https://docs.carsxe.com/docs/guides/rate-limits.md"
last_updated: "1980-01-01"
---

# Rate Limits & Quotas
URL: /docs/guides/rate-limits
LLM index: /llms.txt
Description: How CarsXE usage limits work — included monthly volume, the 429 response shape, overage billing, usage alerts, and how to handle limits in code.

CarsXE limits are **volume-based quotas** tied to your subscription, not per-second throttles. Each API has an included monthly volume for your tier; when you reach it, what happens next depends on your overage settings. This page explains how the quotas work, exactly what a `429` looks like, and how to handle limits gracefully in code.

<Note>
  There are no per-second request throttles on standard plans, but keep burst concurrency reasonable — extremely
  aggressive parallel traffic can still be rejected upstream before it reaches your quota.
</Note>

---

## How quotas work

- **Per-API quotas.** Each API (Specs, Market Value, History, …) has its own included monthly volume, determined by your subscription tier. A few related APIs share a combined quota.
- **Monthly reset.** Usage counters reset at the start of each billing period. Trial allowances are one-time — they do not reset monthly.
- **Units, not requests.** Most requests consume 1 unit. Bulk endpoints (for example [Recalls Batch](/api-reference/recalls/submit-a-bulk-recalls-batch)) consume **one unit per VIN**, so a 500-VIN batch uses 500 units in a single request.
- **Failed validation is free.** Requests rejected with a `400` validation error (missing VIN, bad parameter, etc.) are blocked before any lookup and don't count against your quota.
- **Plan-gated APIs.** Some APIs aren't included in lower tiers at all. Calling one returns a `403` — see [Errors](/docs/guides/errors) — not a `429`.

You can see your current usage per API at any time on the [developer dashboard](https://api.carsxe.com/dashboard/developer).

---

## What happens when you hit the limit

There are two possible behaviors, controlled by your **overage billing preference** in the dashboard:

| Overage billing                                     | Behavior past the included quota                                                                                                                |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Enabled** (default on plans with overage pricing) | Requests keep succeeding. Each unit past your included volume is billed at your tier's per-call overage rate. You will never see a quota `429`. |
| **Disabled** (opt-out)                              | Requests are rejected with a `429` until the period resets or you re-enable overage.                                                            |

Enterprise plans use custom pricing and monthly invoicing — included volume and overage terms are set in your contract.

---

## The 429 response

Quota errors use the standard [error envelope](/docs/guides/errors) plus a `usage` object:

<PortableCode value={{
 language: "json",
 code: `{
  "success": false,
  "message": "API limit exceeded for market_value (6/2026). Subscription tier: starter. Current usage: 5000, Limit: 5000.",
  "usage": {
    "current": 5000,
    "limit": 5000,
    "remaining": 0
  }
}`
}} />

<Properties>
  <Property name="usage.current" type="number">
    Units consumed so far this billing period (or in total, for one-time trial allowances).
  </Property>
  <Property name="usage.limit" type="number">
    Your included volume for this API on your current tier.
  </Property>
  <Property name="usage.remaining" type="number">
    Units left before the limit. <code>0</code> when the request was rejected.
  </Property>
</Properties>

The `message` tells you which API hit its limit, the billing period (month/year), your tier, and the exact numbers. Variants you may see:

- `This request requires N units.` — appended when a bulk request would exceed the limit even though `current < limit`. Reduce the batch size to fit within `remaining`, or split it across billing periods.
- `Please upgrade your tier to continue using the API.` — appended for one-time (trial) allowances that never reset. Waiting won't help; upgrade to continue.
- `Enable overage billing in your dashboard billing preferences to continue with overage charges: …` — appended when you've opted out of overage but your plan supports it. Re-enable overage on the [developer dashboard](https://api.carsxe.com/dashboard/developer) and the same request will succeed immediately.

---

## Usage alerts

You don't have to discover a quota `429` in production. CarsXE emails the account owner when an API reaches **80%**, **90%**, and **100%** of its included volume, so you can upgrade or enable overage before requests start failing.

---

## Handling 429s in code

A quota `429` is **not transient** — unlike a `5xx`, retrying with backoff won't make it succeed. The right response depends on `usage`:

<CodeGroup title="Handle a 429" tag="GET" label="/specs">

```bash
CARSXE_API_KEY="YOUR_API_KEY"
URL="https://api.carsxe.com/v1/specs?vin=1HGCM82633A004352"

response=$(curl -s -w "\n%{http_code}" -H "x-api-key: $CARSXE_API_KEY" "$URL")
body=$(echo "$response" | sed '$d')
status=$(echo "$response" | tail -n 1)

if [ "$status" = "429" ]; then
  remaining=$(echo "$body" | jq -r '.usage.remaining')
  message=$(echo "$body" | jq -r '.message')
  if [ "$remaining" = "0" ]; then
    echo "CarsXE quota exhausted: $message" >&2
    exit 1
  fi
  # Bulk request too large — shrink and retry
  echo "Retry with at most $remaining VINs" >&2
fi
```

```js
import { CarsXE } from "carsxe-api";

const carsxe = new CarsXE("YOUR_API_KEY");

try {
  const result = await carsxe.specs({ vin });
  console.log(result);
} catch (error) {
  if (error.statusCode !== 429) throw error;

  if (error.usage?.remaining === 0) {
    // Quota exhausted: retrying is pointless this billing period.
    notifyOps(`CarsXE quota exhausted: ${error.message}`);
    throw error;
  }
  // Bulk request too large for what's left — shrink and retry.
  return sendSmallerBatch(error.usage.remaining);
}
```

```python
import asyncio
from carsxe_api import CarsXE

carsxe = CarsXE("YOUR_API_KEY")

try:
    result = asyncio.run(carsxe.specs({"vin": vin}))
    print(result)
except Exception as error:
    if getattr(error, "status_code", None) != 429:
        raise

    usage = getattr(error, "usage", {}) or {}
    if usage.get("remaining") == 0:
        # Quota exhausted: retrying is pointless this billing period.
        notify_ops(f"CarsXE quota exhausted: {error}")
        raise
    # Bulk request too large for what's left — shrink and retry.
    send_smaller_batch(usage["remaining"])
```

```php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use CarsxeDeveloper\Carsxe\Carsxe;

$carsxe = new Carsxe("YOUR_API_KEY");

try {
    $result = $carsxe->specs(["vin" => "1HGCM82633A004352"]);
    print_r($result);
} catch (Exception $error) {
    if ($error->getCode() !== 429) {
        throw $error;
    }

    $usage = method_exists($error, 'getUsage') ? $error->getUsage() : [];
    if (($usage['remaining'] ?? 1) === 0) {
        // Quota exhausted: retrying is pointless this billing period.
        notifyOps('CarsXE quota exhausted: ' . $error->getMessage());
        throw $error;
    }
    // Bulk request too large for what's left — shrink and retry.
    sendSmallerBatch($usage['remaining']);
}
```

```ruby
require "carsxe"

carsxe = Carsxe::CarsXE.new(api_key: "YOUR_API_KEY")

begin
  result = carsxe.specs("vin" => vin)
  puts result
rescue => error
  raise unless error.respond_to?(:status_code) && error.status_code == 429

  remaining = error.respond_to?(:usage) ? error.usage&.dig("remaining") : nil
  if remaining == 0
    # Quota exhausted: retrying is pointless this billing period.
    notify_ops("CarsXE quota exhausted: #{error.message}")
    raise
  end
  # Bulk request too large for what's left — shrink and retry.
  send_smaller_batch(remaining)
end
```

```go
package main

import (
	"fmt"
	carsxe "github.com/carsxe/carsxe-go-package"
)

func main() {
	client := carsxe.New("YOUR_API_KEY")
	result, err := client.Specs(map[string]string{"vin": vin})
	if err == nil {
		fmt.Println(result)
		return
	}

	apiErr, ok := err.(*carsxe.APIError)
	if !ok || apiErr.StatusCode != 429 {
		fmt.Println("Error:", err)
		return
	}

	if apiErr.Usage != nil && apiErr.Usage.Remaining == 0 {
		// Quota exhausted: retrying is pointless this billing period.
		notifyOps(fmt.Sprintf("CarsXE quota exhausted: %s", apiErr.Message))
		return
	}
	// Bulk request too large for what's left — shrink and retry.
	sendSmallerBatch(apiErr.Usage.Remaining)
}
```

```java
import io.github.carsxe.CarsXE;
import io.github.carsxe.CarsXEException;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        CarsXE carsxe = new CarsXE("YOUR_API_KEY");

        try {
            Map<String, String> params = new HashMap<>();
            params.put("vin", vin);
            System.out.println(carsxe.specs(params));
        } catch (CarsXEException e) {
            if (e.getStatusCode() != 429) throw e;

            int remaining = e.getUsage() != null ? e.getUsage().getRemaining() : -1;
            if (remaining == 0) {
                // Quota exhausted: retrying is pointless this billing period.
                notifyOps("CarsXE quota exhausted: " + e.getMessage());
                throw e;
            }
            // Bulk request too large for what's left — shrink and retry.
            sendSmallerBatch(remaining);
        }
    }
}
```

```swift
import carsxe

let carsxe = CarsXE(apiKey: "YOUR_API_KEY")

do {
    let result = try carsxe.specs(["vin": vin])
    print(result)
} catch let error as CarsXEError {
    guard error.statusCode == 429 else { throw error }

    let remaining = error.usage?["remaining"] as? Int ?? -1
    if remaining == 0 {
        // Quota exhausted: retrying is pointless this billing period.
        notifyOps("CarsXE quota exhausted: \(error.message)")
        throw error
    }
    // Bulk request too large for what's left — shrink and retry.
    sendSmallerBatch(remaining)
}
```

```csharp
using carsxe;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        CarsXE carsxe = new CarsXE("YOUR_API_KEY");

        try
        {
            var result = await carsxe.Specs(new Dictionary<string, string> { { "vin", vin } });
            Console.WriteLine(result);
        }
        catch (CarsXEException ex) when (ex.StatusCode == 429)
        {
            int remaining = ex.Usage?.Remaining ?? -1;
            if (remaining == 0)
            {
                // Quota exhausted: retrying is pointless this billing period.
                NotifyOps($"CarsXE quota exhausted: {ex.Message}");
                throw;
            }
            // Bulk request too large for what's left — shrink and retry.
            SendSmallerBatch(remaining);
        }
    }
}
```

</CodeGroup>

Two practical patterns:

- **Track `usage.remaining` proactively.** It's returned on quota errors, and your live numbers are always on the dashboard. Alerting at your own threshold (say 75%) gives you more lead time than the built-in emails.
- **Size batches to fit.** Before a bulk call, make sure the VIN count is at or below your remaining units — a rejected batch consumes nothing, but it also accomplishes nothing.

For generic retry/backoff handling of `5xx` errors, see the full code samples on the [Errors](/docs/guides/errors) page.

---

## Raising your limits

- **Upgrade your tier** for more included volume on every API — compare plans on the [pricing page](https://api.carsxe.com/pricing).
- **Enable overage billing** to never be blocked: usage past the included volume is billed per call at your tier's rate.
- **Go enterprise** for high or spiky volume: custom quotas, custom pricing, and consolidated monthly invoicing. [Contact us](https://api.carsxe.com/contact) to talk through your numbers.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
