← Carrier Connect TMS

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_xxxxxxxxxxxxxxxx

Base 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 /loads returns three example loads: booked, picked_up and invoiced.
  • POST /loads returns the load as it would be created (the same external_id always gives the same id).
  • POST /loads/{id}/status works on the example loads. The invoiced one returns 409, so you can test that error too.
  • GET /me returns "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)." } ] } }
  • 400 the body isn't valid JSON (or is missing Content-Type: application/json)
  • 401 missing, invalid or revoked key
  • 402 the carrier's TMS account is not active
  • 404 load not found (or it belongs to another company)
  • 409 status change not allowed (e.g. the load is already invoiced)
  • 422 invalid fields
  • 429 rate limit: 60 requests/minute and 10,000/day per key (see Retry-After)
  • 500 something failed on our side: retry later (creating a load with external_id is 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.