Carrier Connect TMS API
Connect any load board, broker platform or your own software to a carrier's TMS. Create loads when they're booked, keep statuses in sync, and get notified by webhook when anything changes.
Authentication
The carrier creates an API key in their TMS under Company → API & Webhooks and gives it to you. Each key belongs to one carrier company and only sees that company's loads. Send it on every request:
Authorization: Bearer cctms_live_xxxxxxxxxxxxxxxxBase URL: https://tms.carrier-connect.center/api/v1. All bodies are JSON (Content-Type: application/json). Test your key:
curl https://tms.carrier-connect.center/api/v1/me \
-H "Authorization: Bearer $CCTMS_KEY"Sandbox
Build and test without touching real loads: the carrier creates a Test key (cctms_test_…) in the same screen. Same base URL, same endpoints, same validation and the same response shapes, plus "sandbox": true. Nothing is saved and no webhooks are sent.
GET /loadsreturns three example loads:booked,picked_upandinvoiced.POST /loadsreturns the load as it would be created (the sameexternal_idalways gives the sameid).POST /loads/{id}/statusworks on the example loads. The invoiced one returns409, so you can test that error too.GET /mereturns"environment": "sandbox".
Errors & limits
Errors always look like this:
{ "error": { "code": "validation_error", "message": "Some fields are invalid.",
"fields": [ { "field": "stops[0].state", "message": "Use a 2-letter US state (e.g. TX)." } ] } }400the body isn't valid JSON (or is missingContent-Type: application/json)401missing, invalid or revoked key402the carrier's TMS account is not active404load not found (or it belongs to another company)409status change not allowed (e.g. the load is already invoiced)422invalid fields429rate limit: 60 requests/minute and 10,000/day per key (seeRetry-After)500something failed on our side: retry later (creating a load withexternal_idis safe to retry)
Create a load
POST/loads
Send external_id with your own load ID: if you send the same one again (a retry), you get the existing load back with 200 instead of a duplicate. A new load returns 201. The customer is matched by MC (or name) or created automatically.
curl -X POST https://tms.carrier-connect.center/api/v1/loads \
-H "Authorization: Bearer $CCTMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "LB-48213",
"reference": "PO 55120",
"rate": 1850,
"miles": 1195,
"equipment": "Car hauler",
"notes": "Call 1h before pickup",
"customer": { "name": "Acme Logistics", "type": "broker", "mc": "987654",
"email": "ops@acme.com", "phone": "305-555-1234" },
"stops": [
{ "type": "pickup", "name": "Dealer A", "address": "1 Main St", "city": "Miami",
"state": "FL", "zip": "33174", "date": "2026-10-01", "time_from": "08:00", "time_to": "12:00" },
{ "type": "delivery", "city": "Houston", "state": "TX", "zip": "77001", "date": "2026-10-03" }
],
"vehicles": [ { "vin": "4T1B11HK5KU123456", "year": 2019, "make": "Toyota", "model": "Camry", "operable": true } ]
}'Required: rate and at least one pickup and one delivery stop (with city and a 2-letter US state). Optional: load_type (general, vehicles, ltl), commodity, weight_lb, pieces, temperature_f, up to 20 stops and 50 vehicles. Dates are YYYY-MM-DD, times HH:MM. New loads start as booked; the carrier assigns the driver and truck in the TMS.
List & get loads
GET/loads?status=picked_up&updated_since=2026-10-01T00:00:00Z&limit=50
GET/loads/{id}
Lists are ordered by last update, newest first. If next_cursor is not null, pass it as ?cursor= to get the next page. A load looks like this:
{
"id": "0b6c…", "number": 1042, "external_id": "LB-48213", "reference": "PO 55120",
"status": "picked_up", "load_type": "vehicles", "rate": 1850, "miles": 1195,
"source": "api",
"customer": { "name": "Acme Logistics", "type": "broker", "mc": "987654", "usdot": null },
"stops": [ { "type": "pickup", "city": "Miami", "state": "FL", "date": "2026-10-01", … }, … ],
"vehicles": [ { "vin": "4T1B11HK5KU123456", "year": 2019, "make": "Toyota", … } ],
"picked_up_at": "2026-10-01T14:02:11Z", "delivered_at": null,
"created_at": "…", "updated_at": "…"
}Statuses: booked → dispatched → picked_up → delivered → invoiced → paid, or cancelled.
Update status
POST/loads/{id}/status
curl -X POST https://tms.carrier-connect.center/api/v1/loads/$LOAD_ID/status \
-H "Authorization: Bearer $CCTMS_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "delivered" }'Allowed: picked_up, delivered, cancelled. Invoiced, paid and cancelled loads can't be changed (409). Sending the current status again is fine ("changed": false). Loads that came from the Carrier Connect Load Board can't be cancelled here (409 cancellation_requires_authorization): the cancellation is requested on the Load Board and the broker authorizes it.
Webhooks
The carrier adds your HTTPS URL under Company → API & Webhooks. We POST an event when a load is created (load.created) or changes status (load.status_changed) — whether the change came from the TMS, the driver, the Carrier Connect Load Board or the API.
POST https://your-system.com/webhooks/cctms
X-CCTMS-Event: load.status_changed
X-CCTMS-Signature: t=1791234567,v1=5f2b…
{ "id": "evt_381", "type": "load.status_changed", "created_at": "2026-10-01T14:02:11Z",
"data": { "previous_status": "dispatched", "load": { …same shape as GET /loads/{id}… } } }Answer with any 2xx within 10 seconds. Otherwise we retry after 1, 5, 15, 60, 180 and 360 minutes. Use id to ignore duplicates. Verify every request with the webhook's signing secret (whsec_…):
// Node.js
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 min
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));
}Use the "Send test" button in the TMS to receive a ping event.
Questions or a partnership? Email info@carrier-connect.center.