# Carsan API reference

Customer and agent API. Quickstart: [https://app.carsan.com/agents](https://app.carsan.com/agents). Schema: [OpenAPI](https://api.carsan.com/openapi.json).

The older `/api/public/v1` routes are being replaced by these routes.

## Basics

- Base URL: `https://api.carsan.com/v1`. Responses use the `{"data": ...}` envelope unless noted.
- `No key` routes are anonymous. Send no credentials on them.
- `API key` routes take `Authorization: Bearer carsan_YOUR_API_KEY` or `X-API-Key: carsan_YOUR_API_KEY`. Do not send both.
- Money: integer USD cents, including quote fields without a `_cents` suffix. Fields ending in `_usd` are whole dollars.
- Dates: car-local `YYYY-MM-DDTHH:MM`, not UTC. Search needs both `date_from` and `date_to`.
- On 429, wait the `Retry-After` seconds.

| City | `city` value | Timezone |
| --- | --- | --- |
| Los Angeles | `la` | `America/Los_Angeles` |
| Miami | `miami` | `America/New_York` |

## Key capabilities

| Capability | Allows |
| --- | --- |
| `read_only` | Profile, verification status, trips and costs, invoices, masked saved card, customer action links. |
| `booking_write` | Everything in `read_only`, plus create and cancel reservations. Cancelling can charge a cancellation fee under the normal rules and releases an eligible deposit hold. |

No key can pay, charge, refund, add or remove a card, execute an extension, or start verification. Existing keys are `read_only`; create and cancel return 403 `api_key_write_upgrade_required` with a link to key settings.

## Retries

Create and cancel require an `Idempotency-Key` header: a UUID v4 generated once per new command.

Reuse the same value unchanged for every retry of that command. Never use values like `booking-1`, and never generate a new key after a timeout.

A retry with the same key and the same command returns the original result. The same key with a different command returns 409 `idempotency_mismatch`.

## Cars and quotes

### `GET /cars`

Search cars. With dates, each car gets trip totals for those dates.

- Auth: No key
- Capability: none

- `rent_trip_total_cents` is rent only. `display_trip_total_cents` is rent plus trip fee. The quote adds the selected options.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `city` | query | string | no | `la` or `miami`. |
| `date_from` | query | string | no | Car-local `YYYY-MM-DDTHH:MM`. Send together with `date_to`. |
| `date_to` | query | string | no | Car-local `YYYY-MM-DDTHH:MM`, after `date_from`. |
| `body_type` | query | string | no | `sedan`, `coupe`, `suv`, `hatchback`, `convertible`, `pickup`, `minivan`, `three-wheeler`, `other`. |
| `limit` | query | integer | no | Page size. |
| `offset` | query | integer | no | Page offset. Continue while `has_more` is true. |

```bash
curl "https://api.carsan.com/v1/cars?city=la&date_from=2026-11-10T10:00&date_to=2026-11-13T10:00&limit=20"
```

Result (sample data):

```json
{
  "data": {
    "cars": [
      {
        "car_id": 101,
        "car_alias": "Los_Angeles_Toyota_Camry_2023_SAMPLE",
        "name": "Toyota Camry 2023",
        "city": "la",
        "is_available": true,
        "trip_days": 3,
        "rent_trip_total_cents": 17100,
        "rent_trip_savings_cents": 900,
        "display_trip_total_cents": 21600
      }
    ],
    "total": 1,
    "has_more": false
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 400 |  | Invalid city, invalid date format, or `date_from` not before `date_to`. |

### `GET /cars/{alias}`

Car details, including blocked date ranges.

- Auth: No key
- Capability: none

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `alias` | path | string | yes | `car_alias` from search. |

```bash
curl "https://api.carsan.com/v1/cars/Los_Angeles_Toyota_Camry_2023_SAMPLE"
```

Result (sample data):

```json
{
  "data": {
    "car_alias": "Los_Angeles_Toyota_Camry_2023_SAMPLE",
    "name": "Toyota Camry 2023",
    "city": "la",
    "seats": 5,
    "blocked_ranges": []
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 404 |  | Unknown alias. |

### `POST /reservation/quote`

Price for dates and options. No reservation, no hold, no payment.

- Auth: No key
- Capability: none

- `final_total` excludes the deposit. `deposit_cents` is the refundable deposit, shown separately.
- A quote does not guarantee a future price or availability. Creation and payment check again.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `car_alias` | body | string | yes | Car alias. |
| `date_checkin` | body | string | yes | Car-local `YYYY-MM-DDTHH:MM`. |
| `date_checkout` | body | string | yes | Car-local `YYYY-MM-DDTHH:MM`. |
| `options.protection_plan` | body | string | no | `minimum` (default) or `standard`. |
| `options.mileage_type` | body | string | no | `limited` (default) or `unlimited`. |
| `options.delivery_type` | body | string | no | `pickup` (default) or `delivery`. Delivery needs `options.delivery_address`. |
| `extras` | body | string[] | no | Extra item types from the car's prices. |
| `promo_code` | body | string | no | Personal eligibility is not known without an account; it is checked again at booking. |

```bash
curl -X POST "https://api.carsan.com/v1/reservation/quote" \
  -H "Content-Type: application/json" \
  -d '{ "car_alias": "Los_Angeles_Toyota_Camry_2023_SAMPLE", "date_checkin": "2026-11-10T10:00", "date_checkout": "2026-11-13T10:00", "options": { "protection_plan": "minimum", "mileage_type": "limited", "delivery_type": "pickup" }, "extras": [] }'
```

Result (sample data):

```json
{
  "data": {
    "rent_subtotal": 18000,
    "rent_discount_amount": 900,
    "protection_total": 4500,
    "trip_fee_total": 4500,
    "final_total": 26100,
    "display_total": 26100,
    "display_mode": "trip_total",
    "display_trip_total": 26100,
    "blended_day_rate": 7500,
    "discounted_day_rate": 7200,
    "deposit_cents": 30000
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 400 |  | Invalid body, dates, or options. |
| 409 |  | The car is not available for those dates. |

## Account, verification, and cards

Every response belongs to the customer who owns the key. Nothing here changes data.

### `GET /my/profile`

The customer's profile.

- Auth: API key
- Capability: `read_only` or `booking_write`

```bash
curl "https://api.carsan.com/v1/my/profile" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "uuid": "00000000-0000-4000-8000-0000000000aa",
    "first_name": "Sample",
    "is_approved_to_drive": false,
    "payment_methods": []
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |

### `GET /my/verification`

Compact verification status. Read-only: it never starts or submits verification.

- Auth: API key
- Capability: `read_only` or `booking_write`

- Use this for routine polling. When verification is needed, send the customer to the link; only the customer can start it.

```bash
curl "https://api.carsan.com/v1/my/verification" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "status": "pending"
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |

### `GET /my/billing/cards`

The saved card: brand, last four digits, expiry. Zero or one card.

- Auth: API key
- Capability: `read_only` or `booking_write`

- An empty array means no saved card. A saved card does not guarantee a future payment succeeds.

```bash
curl "https://api.carsan.com/v1/my/billing/cards" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": [
    { "id": 1, "card_brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2028 }
  ]
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |

### `GET /my/trips`

All the customer's trips: `active` (upcoming and in progress) and `history` (completed and cancelled), each with a `financial_summary`.

- Auth: API key
- Capability: `read_only` or `booking_write`

- `trip_total_cents` is the current trip cost after discounts, extensions and fees, excluding the deposit. It can be `null` for old trips that cannot be classified. It does not say whether anything was paid.

```bash
curl "https://api.carsan.com/v1/my/trips" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "active": [
      {
        "uuid": "00000000-0000-4000-8000-000000000001",
        "status": "new",
        "date_checkin": "2026-11-10T10:00",
        "date_checkout": "2026-11-13T10:00",
        "financial_summary": { "currency": "USD", "trip_total_cents": 26100, "deposit_cents": 30000 }
      }
    ],
    "history": []
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |

### `GET /my/reservation/{uuid}`

One trip: dates, status, payment status, `financial_summary`, and `cancellation_outcome`. Read this to confirm any result.

- Auth: API key
- Capability: `read_only` or `booking_write`

- A cancelled trip does not mean money was returned. Read `cancellation_outcome.hold_release` and `cancellation_outcome.refunds[]` for that.
- If the customer owes money, reading this trip creates or refreshes the payable debt invoice, the same as opening the trip in the app. `payment_status.invoice_id` then points to it.
- A 500 while a payment is in progress is temporary. Wait a few seconds and read again; do not poll faster than every few seconds.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |

```bash
curl "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "uuid": "00000000-0000-4000-8000-000000000001",
    "status": "new",
    "is_booked": false,
    "financial_summary": { "currency": "USD", "trip_total_cents": 26100, "deposit_cents": 30000 },
    "cancellation_outcome": { "status": "not_cancelled" }
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such reservation for this customer. |

### `GET /my/reservation/{uuid}/invoices`

Every invoice of one trip, in all states, with line items. Raw array, no `data` envelope.

- Auth: API key
- Capability: `read_only` or `booking_write`

- Use `billing_status` for the actual billing state. An empty array is a valid result.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |

```bash
curl "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001/invoices" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
[
  {
    "id": "00000000-0000-4000-8000-000000000002",
    "created_at": "2026-11-01T09:30:00",
    "currency": "USD",
    "type": "RENT",
    "billing_status": "ISSUED",
    "amount": 26100,
    "description": "Rent",
    "items": [{ "id": "00000000-0000-4000-8000-000000000003", "amount": 26100, "description": "Rent" }]
  }
]
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such reservation for this customer. |

### `GET /my/billing/invoice/{uuid}`

One invoice, with the same fields as the trip invoice list.

- Auth: API key
- Capability: `read_only` or `booking_write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Invoice ID. |

```bash
curl "https://api.carsan.com/v1/my/billing/invoice/00000000-0000-4000-8000-000000000002" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "id": "00000000-0000-4000-8000-000000000002",
    "currency": "USD",
    "type": "RENT",
    "billing_status": "ISSUED",
    "amount": 26100
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such invoice for this customer. |

## Reservations

Create and cancel run the same rules as the app. They need a `booking_write` key and an `Idempotency-Key`.

### `POST /my/reservation`

Create an unpaid reservation. Returns the new reservation UUID.

- Auth: API key
- Capability: `booking_write`

- An unpaid reservation is not a paid booking and does not hold the car. Get a payment link for the customer next.
- Same body as the quote.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | yes | UUID v4, generated once for this booking and reused only for its retries. |

```bash
# Generate ONE UUID v4 per new booking and keep it.
IDEMPOTENCY_KEY=$(python3 -c 'import uuid; print(uuid.uuid4())')

curl -X POST "https://api.carsan.com/v1/my/reservation" \
  -H "Authorization: Bearer $CARSAN_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "car_alias": "Los_Angeles_Toyota_Camry_2023_SAMPLE", "date_checkin": "2026-11-10T10:00", "date_checkout": "2026-11-13T10:00", "options": { "protection_plan": "minimum", "mileage_type": "limited", "delivery_type": "pickup" }, "extras": [] }'

# Timeout, network error or 5xx: send the SAME request with the SAME $IDEMPOTENCY_KEY.
# Never generate a new key for a retry. A new key means a new booking attempt.
```

Result (sample data):

```json
{
  "data": "00000000-0000-4000-8000-000000000001"
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 403 | `api_key_write_upgrade_required` | The key is `read_only`. The customer creates a `booking_write` key in key settings. |
| 409 |  | Dates not available or a booking rule refused the request. |
| 409 | `idempotency_mismatch` | The same `Idempotency-Key` was used for a different command. Generate a new UUID only for a genuinely new command. |
| 409 | `operation_in_progress` | Another request with this key is being processed. Wait `Retry-After` seconds and retry with the same key. |
| 503 | `operation_outcome_unknown` | The outcome could not be confirmed. Retry with the same key; do not create a new one. |

### `DELETE /my/reservation/{uuid}`

Cancel the customer's reservation under the normal rules.

- Auth: API key
- Capability: `booking_write`

- A cancellation fee can apply. An eligible deposit hold is released after cancellation.
- Success means the cancellation committed. Read the reservation for hold release and refund state; do not report money returned until it shows so.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |
| `Idempotency-Key` | header | string | yes | UUID v4, generated once for this cancellation and reused only for its retries. |

```bash
# Generate ONE UUID v4 per cancellation command and keep it.
IDEMPOTENCY_KEY=$(python3 -c 'import uuid; print(uuid.uuid4())')

curl -X DELETE "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001" \
  -H "Authorization: Bearer $CARSAN_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"

# Retry only with the same $IDEMPOTENCY_KEY.
```

Result (sample data):

```json
{
  "data": "OK"
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 403 | `api_key_write_upgrade_required` | The key is `read_only`. |
| 404 |  | No such reservation for this customer. |
| 409 | `idempotency_mismatch` | The same `Idempotency-Key` was used for a different command. Generate a new UUID only for a genuinely new command. |
| 409 | `operation_in_progress` | Another request with this key is being processed. Wait `Retry-After` seconds and retry with the same key. |
| 503 | `operation_outcome_unknown` | The outcome could not be confirmed. Retry with the same key; do not create a new one. |

## Customer action links

These return a link for the customer. Nothing is paid, added, or extended. The customer confirms in the app; then read the reservation again. Action names: `pay_reservation`, `add_payment_card`, `extend_reservation`.

### `POST /my/reservation/{uuid}/payment-link`

Link for the customer to pay rent or deposit.

- Auth: API key
- Capability: `read_only` or `booking_write`
- Customer action: the response is a link for the customer. Nothing is executed.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |
| `payment_type` | body | string | no | Rent or deposit, as allowed by the payment flow. |

```bash
curl -X POST "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001/payment-link" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "action": "pay_reservation",
    "status": "requires_user_action",
    "url": "https://app.carsan.com/trips/00000000-0000-4000-8000-000000000001?intent=pay&payment_type=reservation"
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such reservation for this customer. |

### `POST /my/billing/cards/add-link`

Link for the customer to add or replace a card.

- Auth: API key
- Capability: `read_only` or `booking_write`
- Customer action: the response is a link for the customer. Nothing is executed.

```bash
curl -X POST "https://api.carsan.com/v1/my/billing/cards/add-link" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "action": "add_payment_card",
    "status": "requires_user_action",
    "url": "https://app.carsan.com/billing/card-add"
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |

### `POST /my/billing/reservation/{uuid}/extension/price`

Price preview for a new checkout time. Dates do not change.

- Auth: API key
- Capability: `read_only` or `booking_write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |
| `modification_type` | body | string | yes | `extend`. |
| `new_checkout_date_time_local` | body | string | yes | New car-local checkout `YYYY-MM-DDTHH:MM`. |

```bash
curl -X POST "https://api.carsan.com/v1/my/billing/reservation/00000000-0000-4000-8000-000000000001/extension/price" \
  -H "Authorization: Bearer $CARSAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"modification_type": "extend", "new_checkout_date_time_local": "2026-11-15T10:00"}'
```

Result (sample data):

```json
{
  "data": {
    "additional_days": 2,
    "total_amount": 17400
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such reservation for this customer. |

### `POST /my/reservation/{uuid}/extension-link`

Link for the customer to review and confirm an extension, including a zero-cost one.

- Auth: API key
- Capability: `read_only` or `booking_write`
- Customer action: the response is a link for the customer. Nothing is executed.

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `uuid` | path | string | yes | Reservation UUID. |
| `date_checkout` | body | string | no | Proposed car-local checkout `YYYY-MM-DDTHH:MM`. |

```bash
curl -X POST "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001/extension-link" \
  -H "Authorization: Bearer $CARSAN_API_KEY"
```

Result (sample data):

```json
{
  "data": {
    "action": "extend_reservation",
    "status": "requires_user_action",
    "url": "https://app.carsan.com/trips/00000000-0000-4000-8000-000000000001/modify"
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 |  | Missing, invalid, or revoked API key. |
| 429 |  | Rate limited. Wait the `Retry-After` seconds. |
| 404 |  | No such reservation for this customer. |

## Read-only account sequence

These private reads need the customer's key. Car search does not. Sample data only.

```bash
# 1. Saved card (brand, last four, expiry)
curl "https://api.carsan.com/v1/my/billing/cards" -H "Authorization: Bearer $CARSAN_API_KEY"

# 2. Trips with totals
curl "https://api.carsan.com/v1/my/trips" -H "Authorization: Bearer $CARSAN_API_KEY"

# 3. One trip's invoices
curl "https://api.carsan.com/v1/my/reservation/00000000-0000-4000-8000-000000000001/invoices" -H "Authorization: Bearer $CARSAN_API_KEY"

# 4. One invoice
curl "https://api.carsan.com/v1/my/billing/invoice/00000000-0000-4000-8000-000000000002" -H "Authorization: Bearer $CARSAN_API_KEY"
```
