> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.tryenvoy.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.tryenvoy.ai/_mcp/server.

# Validate loads without saving

POST https://tryenvoy.ai/api/v1/loads/validate
Content-Type: application/json

Dry run of POST /v1/loads/batch: checks each load and reports per-item errors (the batch route would reject the load) and warnings (accepted, but missing a field the AI agent needs, or a status Envoy doesn't recognize). Nothing is written. Takes the same body; each load is checked on its own, so one malformed load doesn't fail the request.

**Authentication:** Supports API key and bearer token authentication.

Reference: https://docs.tryenvoy.ai/api-reference/endpoints/api/validate-loads-api-v-1-loads-validate-post

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Servers

- `https://tryenvoy.ai/api/v1` (Production, default)
- `https://staging.tryenvoy.ai/api/v1` (Staging)

## Request

### Headers

- `X-Organization-ID` (string, optional, nullable)
- `Idempotency-Key` (string, optional) — A unique key (up to 255 printable ASCII characters, e.g. a UUID) that makes a retry of this request safe. Envoy stores the response for 24 hours; a retry with the same key and the same request returns it again with `Idempotent-Replayed: true` instead of running twice. The same key with a different request returns 422 `idempotency_key_reused`; while the first request is still running, 409 `idempotency_key_in_progress`. 5xx and 429 responses are not stored.

### Body (application/json)

This endpoint expects a LoadValidateRequest.

- `loads` (list of TMSLoadRequest, required) — Loads in the /v1/loads/batch shape

## Response

### 200

Successful Response

- `valid` (boolean, required) — True when every item is valid (warnings allowed)
- `data` (list of ValidationItemResult, required)
- `summary` (ValidationSummary, required) — Counts for a dry run: how many items would be applied, rejected, or applied with warnings.

## Errors

### 401 Unauthorized Error

Not authenticated - missing or invalid token/API key

- `any`

### 403 Forbidden Error

Insufficient permissions for this operation

- `any`

### 409 Conflict Error

`idempotency_key_in_progress`: a request with this Idempotency-Key is still running; retry later

- `any`

### 422 Unprocessable Entity Error

Validation Error; or `idempotency_key_reused`: this Idempotency-Key was already used for a different request

- `detail` (list of ValidationError, optional)

### 429 Too Many Requests Error

Rate limit exceeded - see Retry-After header

- `any`

## Types

### TMSLoadRequest

Request model for TMS load ingestion

- `load_number` (string, required) — Unique load reference number
- `equipment_type` (enum, required) — Equipment type used for transport
  - Allowed values: `VAN`, `REEFER`, `FLATBED`, `STEPDECK`, `RGN`, `CONESTOGA`, `BOX_TRUCK`, `CONTAINER`, `INTERMODAL`, `POWER_ONLY`, `TANKER`, `LOWBOY`, `HOTSHOT`, `AUTO_CARRIER`, `DRY_BULK`, `DRY_TEAM`, `REEFER_TEAM`, `LTL`, `OTHER`, `VAN/REEFER`
- `stops` (list of Stop, required) — Ordered list of stops (pickup and dropoff) - minimum 2 required
- `equipment_type_raw` (string, optional, nullable) — Original equipment-type string from the source TMS (e.g. 'Van/Reefer 53'', 'Dry Van 53FT'). Stored verbatim for audit; not used for matching.
- `mode` (enum, optional, nullable) — Transport mode, when supplied by the source
  - Allowed values: `FTL`, `PTL`, `LTL`, `DED`, `RAIL`, `PASS`
- `tms_load_id` (string, optional, nullable) — Your TMS's ID for the load. Envoy matches updates on it (then on load_number), so keep it stable for the life of the load.
- `tms_status` (string, optional, nullable) — Status supplied by the source TMS; no status is inferred when omitted
- `weight` (double, optional, nullable) — Weight of the load (must be non-negative)
- `distance` (double, optional, nullable) — Number of miles to drive (must be non-negative)
- `commodity` (string, optional, nullable) — Commodity being hauled
- `handling_unit_count` (integer, optional, nullable) — Number of freight handling units
- `handling_unit_type` (string, optional, nullable) — Type of freight handling unit, e.g. pallet or case
- `po_numbers` (list of string, optional, nullable) — List of PO or pickup numbers
- `reference_numbers` (list of ReferenceNumber, optional, nullable) — Typed reference numbers for the load, as sent by the source TMS. po_numbers remains the PO-only projection used by matching/search.
- `posted_rate` (integer, optional, nullable) — Posted rate in USD
- `max_buy_rate` (integer, optional, nullable) — Maximum buy rate in USD
- `suggested_market_rate` (integer, optional, nullable) — Suggested market rate in USD
- `length_feet` (integer, optional, nullable) — Length in feet (must be non-negative)
- `carrier_completed_rate` (double, optional, nullable) — Rate the carrier completed the load at, in USD
- `customer_rate` (double, optional, nullable) — Rate quoted to the customer, in USD
- `margin` (double, optional, nullable) — Margin in USD as reported by the source TMS
- `hold_load` (boolean, optional, default: false) — Whether the source TMS has this load on hold; held loads are hidden from web/extension
- `action` (enum, optional, nullable) — Caller intent for this row (CSV upload). NEW = create (error if it already exists); UPDATE = update (error if it does not exist); INACTIVATE = update + hold_load=true. When omitted, the row is upserted (legacy behavior).
  - Allowed values: `NEW`, `UPDATE`, `INACTIVATE`
- `temperature_setting_minimum` (integer, optional, nullable) — Minimum temperature setting in degrees
- `temperature_setting_maximum` (integer, optional, nullable) — Maximum temperature setting in degrees
- `temperature_run_type` (string, optional, nullable) — Temperature run type (e.g., continuous, cycle)
- `last_reported_state` (string, optional, nullable) — Last reported state for tracking
- `last_reported_city` (string, optional, nullable) — Last reported city for tracking
- `last_reported_long` (double, optional, nullable) — Last reported longitude (-180 to 180, nullable when not moving)
- `last_reported_lat` (double, optional, nullable) — Last reported latitude (-90 to 90, nullable when not moving)
- `tms_carrier_id` (string, optional, nullable) — Your TMS's ID for the carrier hauling the load
- `carrier_name` (string, optional, nullable) — Name of the carrier
- `customer_name` (string, optional, nullable) — Name of the customer (shipper)
- `customer_ref` (string, optional, nullable) — Customer/shipper entity identifier from the TMS (the scheduling feed's customer_tms_id) — not a per-shipment reference number
- `tms_sales_rep_names` (string, optional, nullable) — Sales representative name(s) reported by the source TMS; not an Envoy user assignment
- `carrier_rep` (Contact, optional, nullable) — Carrier Sales Representative contact details
- `driver` (Contact, optional, nullable) — Driver contact details
- `special_instructions` (list of string, optional, nullable) — Special handling instructions provided by the shipper/broker; verbalized to carriers during voice calls
- `notes` (string, optional, nullable) — Internal notes for the load. Shown in Envoy; the AI agent reads them only if your organization turns that on (carrier-facing text belongs in special_instructions).
- `custom_fields` (map from string to any, optional, nullable) — Additional custom fields for source-specific data

### ValidationItemResult

The findings for one submitted item.

- `index` (integer, required) — Position of the item in the request array (0-based)
- `valid` (boolean, required) — True when the batch route would apply the item (no errors)
- `external_id` (string, optional, nullable) — The item's own ID (tms_load_id, tms_carrier_id or id)
- `errors` (list of ValidationIssue, optional) — Why the batch route would reject it
- `warnings` (list of ValidationIssue, optional) — Accepted, but missing something the AI agent needs

### ValidationSummary

Counts for a dry run: how many items would be applied, rejected, or applied with warnings.

- `total` (integer, required)
- `valid` (integer, required)
- `invalid` (integer, required)
- `with_warnings` (integer, required)

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `input` (any, optional)
- `ctx` (ValidationErrorCtx, optional)

### Stop

Stop information including location, timing, and contacts

- `location` (Location, required) — Location details of the stop
- `id` (string, optional, nullable) — Envoy's id for the stop (a UUID), set on every stop Envoy returns. Never send your own stop identifier here: put it in pickup_delivery_number or reference_numbers.
- `stop_number` (integer, optional, nullable) — Sequence number of the stop
- `is_pickup` (boolean, optional, nullable) — True if this stop is a pickup
- `is_dropoff` (boolean, optional, nullable) — True if this stop is a dropoff
- `expected_date` (datetime, optional, nullable) — Expected date of activity at this stop
- `contact` (Contact, optional, nullable) — Contact details at the stop (singular, for backward compatibility)
- `type` (enum, optional, nullable) — Type of stop: exactly `Pickup` or `Dropoff` (case-sensitive; other spellings are rejected)
  - Allowed values: `Pickup`, `Dropoff`
- `planned` (PlannedArrival, optional, nullable) — Planned arrival window
- `pickup_delivery_number` (string, optional, nullable) — PO or pickup number; also the place for the source TMS's own stop identifier
- `contacts` (list of Contact, optional, nullable) — List of contacts at this stop
- `actual_stop_times` (ActualStopTimes, optional, nullable) — Actual arrival and departure times
- `carrier_eta` (datetime, optional, nullable) — Carrier's estimated time of arrival
- `reference_numbers` (list of ReferenceNumber, optional, nullable) — Typed reference numbers for this stop, as sent by the source TMS
- `notes` (string, optional, nullable) — Stop notes from the source TMS. DISPLAY ONLY — kept out of the voice/email agent context; carrier-facing text belongs in special_instructions.
- `special_instructions` (list of string, optional, nullable) — Stop-level handling instructions (titled sections), verbalized to carriers

### ReferenceNumber

One typed reference number from a source TMS (stop- or load-level). ``type`` is the source's own label ("Shipper Reference Number", "PO Number", "Shipment Id", ...) kept as display text rather than an enum: every TMS names these differently and customers define their own beyond any standard table, so an enum would silently drop the custom ones. Callers that need typed routing resolve the label at the source boundary (see ``tai/reference_types.py``) and keep the raw string here.

- `value` (string, required) — The reference number itself
- `type` (string, optional, nullable) — Source TMS label for this reference (display text)

### Contact

Contact information for various parties involved in the load

- `type` (enum, optional, nullable) — Role of the contact (for TMS compatibility)
  - Allowed values: `CARRIER`, `DISPATCHER`, `DRIVER`, `TENDERING`, `QUOTING`, `OTHER`
- `first_name` (string, optional, nullable) — First name of the contact
- `last_name` (string, optional, nullable) — Last name of the contact
- `phone` (string, optional, nullable) — Phone number — stored as E.164 when parseable, None otherwise
- `email` (string, optional, nullable) — Email address

### ValidationIssue

One problem found in an item.

- `code` (enum, required) — validation_error or rule_violation (in errors: the batch would reject the item); missing_required_field or unrecognized_status (in warnings)
  - Allowed values: `validation_error`, `rule_violation`, `missing_required_field`, `unrecognized_status`
- `message` (string, required)
- `field` (string, optional, nullable) — Dotted path to the field, e.g. stops.0.location.city

### ValidationErrorLocItems

- string
- integer

### ValidationErrorCtx

### Location

Location information for stops

- `city` (string, required) — City of the location
- `state` (string, required) — State or province code
- `street` (string, optional, nullable) — Street address of the location
- `postal_code` (string, optional, nullable) — Postal or ZIP code
- `country` (string, optional, nullable, default: US) — Country code (ISO-3166-1 alpha-2), defaults to US
- `company_name` (string, optional, nullable) — Company name at this location
- `county` (string, optional, nullable) — County name
- `latitude` (double, optional, nullable) — Latitude coordinate (-90 to 90)
- `longitude` (double, optional, nullable) — Longitude coordinate (-180 to 180)

### PlannedArrival

Planned arrival window for a stop

- `start` (datetime, optional, nullable) — Earliest arrival time
- `end` (datetime, optional, nullable) — Latest arrival time

### ActualStopTimes

Actual stop times for tracking when arrival/departure occurred

- `arrival_datetime` (datetime, optional, nullable) — Actual arrival timestamp
- `departure_datetime` (datetime, optional, nullable) — Actual departure timestamp

## Examples

**Request**

```json
{
  "loads": [
    {
      "load_number": "string",
      "equipment_type": "VAN",
      "stops": [
        {
          "location": {
            "city": "string",
            "state": "string"
          }
        }
      ]
    }
  ]
}
```

**Response**

```json
{
  "valid": true,
  "data": [
    {
      "index": 1,
      "valid": true,
      "external_id": "string",
      "errors": [
        {
          "code": "validation_error",
          "message": "string",
          "field": "string"
        }
      ],
      "warnings": [
        {
          "code": "validation_error",
          "message": "string",
          "field": "string"
        }
      ]
    }
  ],
  "summary": {
    "total": 1,
    "valid": 1,
    "invalid": 1,
    "with_warnings": 1
  }
}
```

**SDK Code**

```python
import requests

url = "https://tryenvoy.ai/api/v1/loads/validate"

payload = { "loads": [
        {
            "load_number": "string",
            "equipment_type": "VAN",
            "stops": [{ "location": {
                        "city": "string",
                        "state": "string"
                    } }]
        }
    ] }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://tryenvoy.ai/api/v1/loads/validate';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"loads":[{"load_number":"string","equipment_type":"VAN","stops":[{"location":{"city":"string","state":"string"}}]}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://tryenvoy.ai/api/v1/loads/validate"

	payload := strings.NewReader("{\n  \"loads\": [\n    {\n      \"load_number\": \"string\",\n      \"equipment_type\": \"VAN\",\n      \"stops\": [\n        {\n          \"location\": {\n            \"city\": \"string\",\n            \"state\": \"string\"\n          }\n        }\n      ]\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://tryenvoy.ai/api/v1/loads/validate")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"loads\": [\n    {\n      \"load_number\": \"string\",\n      \"equipment_type\": \"VAN\",\n      \"stops\": [\n        {\n          \"location\": {\n            \"city\": \"string\",\n            \"state\": \"string\"\n          }\n        }\n      ]\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://tryenvoy.ai/api/v1/loads/validate")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"loads\": [\n    {\n      \"load_number\": \"string\",\n      \"equipment_type\": \"VAN\",\n      \"stops\": [\n        {\n          \"location\": {\n            \"city\": \"string\",\n            \"state\": \"string\"\n          }\n        }\n      ]\n    }\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://tryenvoy.ai/api/v1/loads/validate', [
  'body' => '{
  "loads": [
    {
      "load_number": "string",
      "equipment_type": "VAN",
      "stops": [
        {
          "location": {
            "city": "string",
            "state": "string"
          }
        }
      ]
    }
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://tryenvoy.ai/api/v1/loads/validate");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"loads\": [\n    {\n      \"load_number\": \"string\",\n      \"equipment_type\": \"VAN\",\n      \"stops\": [\n        {\n          \"location\": {\n            \"city\": \"string\",\n            \"state\": \"string\"\n          }\n        }\n      ]\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["loads": [
    [
      "load_number": "string",
      "equipment_type": "VAN",
      "stops": [["location": [
            "city": "string",
            "state": "string"
          ]]]
    ]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://tryenvoy.ai/api/v1/loads/validate")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```