# RouteDesk Lead Intake API

> Documentation for the RouteDesk lead intake API: how lead vendors deliver leads straight into an auto transport broker's RouteDesk account.

This file is the complete reference, written for people and for coding agents. Human-readable pages: https://developer.routedesk.com. OpenAPI 3.1 spec: https://developer.routedesk.com/openapi.json.

## Quick facts

- Endpoint: `POST https://api.routedesk.com/api/v1/public/leads`
- Auth: `Authorization: Bearer <AUTH_KEY>` (the Auth Key of a lead source, from the broker; starts with `lsk_`)
- Body: one lead as a JSON object, `Content-Type: application/json`
- Success: `201` with `{ "success": true, "message": "...", "leadId": "..." }`
- Limits: 120 requests per minute per IP; 256 KB per body
- Idempotency: none; see the checklist

## Implementation checklist

1. Send `POST` to the endpoint over HTTPS with `Content-Type: application/json` and a JSON object body. One lead per request; there is no batch endpoint.
2. Authenticate with `Authorization: Bearer <AUTH_KEY>`, spelled exactly with a capital B. Read the key from server-side configuration (an environment variable). Never put it in browser or mobile code, a URL, or a repository.
3. Use the standard format for new work. Map your fields onto it; send `phone`, `email`, or both. Do not mix field names from the standard and legacy formats in one request.
4. Send dates as `YYYY-MM-DD`, `transportType` as `OPEN` or `ENCLOSED`, `inoperable` as a boolean, and countries as two-letter codes.
5. Treat only `201` as success; store `leadId` if you want a reference.
6. On `422`, read `errors` (one message per top-level field), fix the data, and do not resend unchanged. On `400`, `401`, `403` and `413`, fix the request or the key; resending the same request gets the same answer.
7. On `429`, wait the `Retry-After` seconds (also `retry_after` in the body), then resend. On `500`, retry with exponential backoff and give up after a few attempts; no lead was created.
8. There is no idempotency key. Allow at least 10 seconds before treating a request as timed out, retry a timeout once, and never resend a lead that got any response. Possible duplicates are flagged for the broker.
9. Stay under 120 requests per minute per IP address (two per second spreads a backlog safely). Keep bodies far below the 256 KB limit.
10. Field names the API does not know are ignored without an error, so check a test lead arrived with every value.

## Authentication

Each lead source in a broker's RouteDesk account has its own Auth Key. The broker copies it from Settings → Lead sources and gives it to you. A key decides which broker account a lead lands in, so a vendor working with several brokers holds one key per broker. Send it in the `Authorization` header as `Bearer <key>`, or as an `AuthKey` field at the top level of the JSON body (if both are sent, the body key is used). A lowercase `bearer`, a missing prefix, or another header such as `X-API-Key` is not read. When a broker regenerates a key, the old one stops working at once (`401 Invalid Auth Key`); when they switch a source off, its key gets `403`.

## Create a lead (standard format)

The minimum accepted lead:

```json
{
  "phone": "+13055550142",
  "origin": {
    "city": "Miami",
    "state": "FL"
  },
  "destination": {
    "city": "Dallas",
    "state": "TX"
  },
  "vehicles": [
    {
      "make": "Toyota",
      "model": "Camry"
    }
  ]
}
```

A lead using every field:

```json
{
  "firstName": "Jordan",
  "lastName": "Rivera",
  "email": "jordan.rivera@example.com",
  "phone": "+13055550142",
  "origin": {
    "city": "Miami",
    "state": "FL",
    "postalCode": "33101",
    "country": "US"
  },
  "destination": {
    "city": "Toronto",
    "state": "ON",
    "postalCode": "M5V 2T6",
    "country": "CA"
  },
  "shipDate": "2026-12-01",
  "transportType": "ENCLOSED",
  "notes": "Second vehicle is a project car and does not start.",
  "vehicles": [
    {
      "make": "Toyota",
      "model": "Camry",
      "modelYear": 2022,
      "type": "CAR",
      "inoperable": false
    },
    {
      "make": "Ford",
      "model": "Bronco",
      "modelYear": 1972,
      "type": "SUV",
      "inoperable": true
    }
  ]
}
```

### Fields

Length limits are enforced: a longer value is rejected with `422`, never cut short.

- `firstName` (string, up to 100 characters, optional): The customer's first name.
- `lastName` (string, up to 100 characters, optional): The customer's last name.
- `email` (string, up to 320 characters, optional): The customer's email address. Must be a valid address. Send `phone`, `email`, or both. A lead with neither is rejected.
- `phone` (string, up to 40 characters, optional): The customer's phone number, in any format. E.164 (`+13055550142`) is best. Send `phone`, `email`, or both. A lead with neither is rejected.
- `origin` (object, required): Where the vehicle is picked up.
  - `city` (string, up to 100 characters, required): City name.
  - `state` (string, up to 50 characters, required): State or province, ideally the two-letter code (`FL`, `ON`).
  - `postalCode` (string, up to 20 characters, optional): ZIP or postal code.
  - `country` (string, up to 40 characters, optional): Two-letter ISO code such as `US` or `CA`. `USA`, `United States`, `Canada` and `Mexico` are also understood. If it is missing or not recognized, `US` is saved.
- `destination` (object, required): Where the vehicle is delivered.
  - `city` (string, up to 100 characters, required): City name.
  - `state` (string, up to 50 characters, required): State or province, ideally the two-letter code (`FL`, `ON`).
  - `postalCode` (string, up to 20 characters, optional): ZIP or postal code.
  - `country` (string, up to 40 characters, optional): Two-letter ISO code such as `US` or `CA`. `USA`, `United States`, `Canada` and `Mexico` are also understood. If it is missing or not recognized, `US` is saved.
- `shipDate` (string, up to 30 characters, optional): The date the customer wants to ship, as `YYYY-MM-DD`. A time after the date is allowed and ignored. Any other format is rejected. A date that does not exist, such as `2026-02-30`, is ignored. Without a usable date the lead is saved as "first available".
- `transportType` (string, optional): `OPEN` or `ENCLOSED`, in any letter case. Anything else is rejected. Defaults to `OPEN`.
- `notes` (string, up to 2000 characters, optional): Anything the customer told you that does not fit another field. Shown to the broker.
- `vehicles` (array of objects, required): The vehicles to ship. At least one.
  - `make` (string, up to 50 characters, required): For example `Toyota`.
  - `model` (string, up to 50 characters, required): For example `Camry`.
  - `modelYear` (number or string, optional): A number or a numeric string, such as `2022` or `"2022"`. If it is missing, or earlier than 1900, or more than two years in the future, the current year is saved.
  - `type` (string, up to 50 characters, optional): One of `CAR`, `SUV`, `TRUCK`, `VAN`, `MOTORCYCLE`, `RV`, `ATV`, `BOAT`, `OTHER`, in any letter case. Anything else, or no value, is saved as `OTHER`.
  - `inoperable` (boolean, optional): `true` if the vehicle does not run. Must be a boolean, not `1`. Defaults to `false`.

Accepted vehicle types: CAR, SUV, TRUCK, VAN, MOTORCYCLE, RV, ATV, BOAT, OTHER (any letter case).

### Example requests

```bash
curl https://api.routedesk.com/api/v1/public/leads \
  -H "Authorization: Bearer YOUR_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jordan",
    "lastName": "Rivera",
    "email": "jordan.rivera@example.com",
    "phone": "+13055550142",
    "origin": {
      "city": "Miami",
      "state": "FL",
      "postalCode": "33101"
    },
    "destination": {
      "city": "Dallas",
      "state": "TX",
      "postalCode": "75201"
    },
    "shipDate": "2026-12-01",
    "transportType": "OPEN",
    "vehicles": [
      {
        "make": "Toyota",
        "model": "Camry",
        "modelYear": 2022
      }
    ]
  }'
```

```javascript
const response = await fetch('https://api.routedesk.com/api/v1/public/leads', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_AUTH_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "firstName": "Jordan",
    "lastName": "Rivera",
    "email": "jordan.rivera@example.com",
    "phone": "+13055550142",
    "origin": {
      "city": "Miami",
      "state": "FL",
      "postalCode": "33101"
    },
    "destination": {
      "city": "Dallas",
      "state": "TX",
      "postalCode": "75201"
    },
    "shipDate": "2026-12-01",
    "transportType": "OPEN",
    "vehicles": [
      {
        "make": "Toyota",
        "model": "Camry",
        "modelYear": 2022
      }
    ]
  }),
});

const result = await response.json();
console.log(response.status, result);
```

## Responses

Branch on the HTTP status code, not on message text. `201`, `401`, `403`, `422` and `500` use a JSON object with `success` and `message`. `400`, `413` and `429` are returned before the request is read as a lead and use Problem Details (RFC 9457) with `application/problem+json`.

### 201 Created

When: The lead is in the broker’s account.

What to do: Store the leadId if you want a reference. Do not send the lead again.

Retry: no

Content-Type: application/json

```json
{
  "success": true,
  "message": "Lead created successfully",
  "leadId": "e763f6b3-946b-4980-83d6-859cbd636a98"
}
```

### 400 Bad request

When: The body is not valid JSON, or is empty, while Content-Type says application/json.

What to do: Fix how the body is built. Serialize it with your JSON library, not by hand.

Retry: no

Content-Type: application/problem+json

```json
{
  "type": "https://routedesk.com/errors/validation",
  "title": "Body is not valid JSON but content-type is set to 'application/json'",
  "status": 400,
  "detail": "Body is not valid JSON but content-type is set to 'application/json'",
  "instance": "/api/v1/public/leads",
  "request_id": "01a1d690-1a4c-74d6-9994-b633e772b591"
}
```

### 401 Auth Key is required

When: No Auth Key was found in the Authorization header or the body.

What to do: Send the header as exactly Authorization: Bearer <key>.

Retry: no

Content-Type: application/json

```json
{
  "success": false,
  "message": "Auth Key is required"
}
```

### 401 Invalid Auth Key

When: The key does not exist. It may have a typo, or the broker regenerated it.

What to do: Ask the broker for the current key.

Retry: no

Content-Type: application/json

```json
{
  "success": false,
  "message": "Invalid Auth Key"
}
```

### 403 Lead source is inactive

When: The key is right, but the broker has switched this lead source off.

What to do: Stop sending and contact the broker.

Retry: no

Content-Type: application/json

```json
{
  "success": false,
  "message": "Lead source is inactive"
}
```

### 413 Request body is too large

When: The body is over 256 KB. A normal lead is under 2 KB.

What to do: Send one lead per request and drop fields RouteDesk does not use.

Retry: no

Content-Type: application/problem+json

```json
{
  "type": "https://routedesk.com/errors/validation",
  "title": "Request body is too large",
  "status": 413,
  "detail": "Request body is too large",
  "instance": "/api/v1/public/leads",
  "request_id": "01a1d690-1a7c-75ef-a9a4-5623631d89f7"
}
```

### 422 Validation failed

When: The lead is missing a required field or a value is not accepted.

What to do: Read errors: each key is a field, each value says what is wrong. Fix and resend.

Retry: no

Content-Type: application/json

```json
{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "origin": "Required",
    "shipDate": "shipDate must be ISO-8601 (YYYY-MM-DD)",
    "transportType": "transportType must be OPEN or ENCLOSED",
    "vehicles": "At least one vehicle is required"
  }
}
```

### 429 Rate limit exceeded

When: More than 120 requests in a minute from your IP address.

What to do: Wait the number of seconds in the Retry-After header, then resend.

Retry: yes, after the Retry-After seconds

Content-Type: application/problem+json

```json
{
  "type": "https://routedesk.com/errors/rate-limited",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "Rate limit exceeded. Retry after 29 seconds.",
  "retry_after": 29,
  "instance": "/api/v1/public/leads",
  "request_id": "01a1d690-946b-75fb-8fa2-6b6106b06cb5"
}
```

### 500 Could not create the lead

When: Something failed on our side. The lead was not created.

What to do: Retry with backoff. The broker sees the failure in their submissions list.

Retry: yes, with backoff

Content-Type: application/json

```json
{
  "success": false,
  "message": "Could not create the lead"
}
```

### Reading a 422

Each key in `errors` is a top-level field and each value says what is wrong; there is one message per field. A problem inside a location or vehicle is reported on its parent (for example `"vehicles": "make is required"`). A missing phone and email is reported on `phone`. A body that is not a JSON object is reported under `_`; sending JSON with a Content-Type other than `application/json` causes this.

```json
{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "origin": "Required",
    "shipDate": "shipDate must be ISO-8601 (YYYY-MM-DD)",
    "transportType": "transportType must be OPEN or ENCLOSED",
    "vehicles": "At least one vehicle is required"
  }
}
```

## Testing: the shared demo account

Build and test against this account first. It needs no broker. It is shared with every developer, read-only when signed in, and all its leads are deleted every hour. Never send real customer names, phone numbers or emails.

- Auth Key (use it exactly like a live key): `lsk_pGN6Tcf8RAJBLzgmki8zhcbKlN0iI5jB5871KHZXFdw`
- Sign in to see your leads: https://app.routedesk.com/login
- Email: `test@routedesk.com`
- Password: `RouteDesk-Demo-xPztalnl`

Accepted leads appear under Leads. Every request made with the key, including rejected ones with the body as received, appears under Settings → Lead sources → Developer sandbox.

```bash
curl https://api.routedesk.com/api/v1/public/leads \
  -H "Authorization: Bearer lsk_pGN6Tcf8RAJBLzgmki8zhcbKlN0iI5jB5871KHZXFdw" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+13055550142",
    "origin": {
      "city": "Miami",
      "state": "FL"
    },
    "destination": {
      "city": "Dallas",
      "state": "TX"
    },
    "vehicles": [
      {
        "make": "Toyota",
        "model": "Camry"
      }
    ]
  }'
```

Before going live, send one labelled test lead with the real broker key and have the broker confirm it.

## Legacy format (existing BATS integrations)

If you already send leads to BATS, send the same payload to RouteDesk and change only the URL and the Auth Key (the `AuthKey` body field works). New integrations should use the standard format. The API reads a request as the standard format when the body has an `origin` object or a lowercase `vehicles` array; anything else is read as the legacy format.

```json
{
  "first_name": "Jordan",
  "last_name": "Rivera",
  "email": "jordan.rivera@example.com",
  "phone": "(305) 555-0142",
  "origin_city": "Miami",
  "origin_state": "FL",
  "origin_postal_code": "33101",
  "origin_country": "USA",
  "destination_city": "Dallas",
  "destination_state": "TX",
  "destination_postal_code": "75201",
  "destination_country": "USA",
  "ship_date": "12/01/2026",
  "transport_type": 1,
  "Vehicles": [
    {
      "vehicle_make": "Toyota",
      "vehicle_model": "Camry",
      "vehicle_model_year": "2022",
      "vehicle_type": "Car",
      "vehicle_inop": 0
    }
  ]
}
```

- `first_name` (string, up to 100 characters, optional): The customer's first name.
- `last_name` (string, up to 100 characters, optional): The customer's last name.
- `email` (string, up to 320 characters, optional): The customer's email address. Must be a valid address. Send `phone`, `email`, or both. A lead with neither is rejected.
- `phone` (string, up to 40 characters, optional): The customer's phone number, in any format. Send `phone`, `email`, or both. A lead with neither is rejected.
- `origin_city` (string, up to 100 characters, required): City of the pickup location.
- `origin_state` (string, up to 50 characters, required): State or province of the pickup location.
- `origin_postal_code` (string, up to 20 characters, optional): ZIP or postal code of the pickup location.
- `origin_country` (string, up to 40 characters, optional): Two-letter ISO code such as `US` or `CA`. `USA`, `United States`, `Canada` and `Mexico` are also understood. If it is missing or not recognized, `US` is saved.
- `destination_city` (string, up to 100 characters, required): City of the delivery location.
- `destination_state` (string, up to 50 characters, required): State or province of the delivery location.
- `destination_postal_code` (string, up to 20 characters, optional): ZIP or postal code of the delivery location.
- `destination_country` (string, up to 40 characters, optional): Two-letter ISO code such as `US` or `CA`. `USA`, `United States`, `Canada` and `Mexico` are also understood. If it is missing or not recognized, `US` is saved.
- `ship_date` (string, up to 30 characters, optional): `MM/DD/YYYY` (month first) or `YYYY-MM-DD`. Anything else is ignored and the lead is saved as "first available".
- `transport_type` (number or string, optional): `1` for open, `2` for enclosed. The words `open` and `enclosed` also work. Anything else is saved as open.
- `Vehicles` (array of objects, required): The vehicles to ship. At least one. Note the capital `V`.
  - `vehicle_make` (string, up to 50 characters, required): For example `Toyota`.
  - `vehicle_model` (string, up to 50 characters, required): For example `Camry`.
  - `vehicle_model_year` (number or string, optional): A number or a numeric string, such as `2022` or `"2022"`. If it is missing, or earlier than 1900, or more than two years in the future, the current year is saved.
  - `vehicle_type` (string, up to 50 characters, optional): One of `CAR`, `SUV`, `TRUCK`, `VAN`, `MOTORCYCLE`, `RV`, `ATV`, `BOAT`, `OTHER`, in any letter case. Anything else, or no value, is saved as `OTHER`.
  - `vehicle_inop` (0, 1 or boolean, optional): `1` or `true` if the vehicle does not run.

Differences: an unrecognized `transport_type` is saved as open, and an unrecognized `ship_date` is ignored (the lead becomes "first available"); the standard format rejects both. There is no notes field. `ship_date` in `MM/DD/YYYY` is month first, so `11/08/2026` is November 8.
