API & EDI documentation
Quote, book and track freight from your own systems: a JSON API, signed webhooks and X12 EDI over HTTPS.
Quick start
API access is for approved customer accounts. Sign in to the customer portal and create a key under API access. Don't have an account? Contact us.
Base URL: https://www.c2klegacy.com All JSON requests use Content-Type: application/json.
curl -X POST https://www.c2klegacy.com/api/v1/quotes \
-H "Authorization: Bearer c2k_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"origin": { "city": "Toronto", "state": "ON" },
"destination": { "city": "Chicago", "state": "IL" },
"equipment": "Dry van",
"weight_lbs": 40000,
"pickup_date": "2026-10-10",
"accessorials": ["Liftgate"]
}'
Authentication
Send your key in the Authorization: Bearer header. Keys are shown once when created; store them like passwords. You can have up to 5 active keys and revoke any key at any time in the portal. Keep keys on your server, never in browser code.
Quotes
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/quotes | Create an instant quote. |
| GET | /api/v1/quotes/{ref} | Fetch one of your quotes. |
Request fields: origin and destination (city + 2-letter state), equipment (Dry van, Reefer, Flatbed, Step-deck, Power only), weight_lbs, optional pickup_date (YYYY-MM-DD) and accessorials (e.g. Hazmat, Liftgate, Inside delivery, Tarp, Team drivers, Customs / cross-border).
{
"ref": "Q-9A2945D1",
"origin": { "city": "Toronto", "state": "ON" },
"destination": { "city": "Chicago", "state": "IL" },
"equipment": "Dry van", "weight_lbs": 40000,
"miles": 522, "distance_method": "road", "currency": "USD",
"breakdown": { "linehaul": 1432.37, "fuel_surcharge": 257.83, "accessorials": 84 },
"total": 1774.2,
"valid_until": "2026-10-03T14:00:00.000Z"
}
Quotes are valid for a limited time (valid_until). Loads outside the instant-quote limits (for example overweight) return 422 and need a manual quote.
Bookings
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/bookings | Book a quote (quote_ref) or send the quote fields to price and book in one call. |
| GET | /api/v1/bookings | List your bookings. |
| GET | /api/v1/bookings/{ref} | Fetch by C2K Legacy booking ref or your customer_ref. |
Optional fields: customer_ref (your PO or load ID), commodity, delivery_date, contact_name, contact_phone, notes.
A new booking is pending until C2K Legacy accepts or declines it. When accepted, a shipment is created and shipment_ref appears on the booking. You get a booking.accepted or booking.declined webhook.
Shipments
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/shipments | List your shipments with status and events. |
| GET | /api/v1/shipments/{ref} | One shipment with its full event history. |
Tracking API for carriers (GPS / ELD feeds)
Carriers can push each load's position automatically. Create a key in the carrier portal under GPS / ELD integration (it starts with c2kt_ and is shown once), then send:
POST /api/tracking/ingest
Authorization: Bearer c2kt_...
Content-Type: application/json
{ "shipment_ref": "C2K2026000123", "lat": 42.33, "lon": -83.05, "speed_mph": 58, "heading": 270, "at": "2026-10-02T14:05:00Z" }
Or send up to 50 points at once: { "points": [ { "shipment_ref": "...", "lat": ..., "lon": ... }, ... ] }. The answer lists a result for each point. Rules: the load must be assigned to the key's carrier and moving (Picked up, In transit or Out for delivery); at is optional (default: now) and may be up to 24 hours old; points closer than 20 seconds apart are skipped; one point every 1 to 5 minutes is plenty. Locations are deleted 90 days after delivery.
Load board partners
For companies that show C2K Legacy's open loads on their own load board or TMS, and want their carriers to request or bid on them. This is a separate API from the customer API above, with its own keys (c2kp_live_...) that C2K Legacy issues. Base path: /api/partner/v1. Machine-readable spec: openapi.json.
How it works
- Get a key. C2K Legacy creates your partner account and key, and decides what it may do: read loads, request loads, bid.
- Onboard your carriers. Each carrier completes C2K Legacy onboarding (application, W-9, authority letter, insurance, agreement) and is cleared to haul. C2K Legacy then links the carrier to your account. You can only request or bid for linked carriers.
- Show our loads. Read the load feed and keep it in sync.
- Request or bid. Send the carrier's MC or USDOT number with each request or bid. C2K Legacy staff review them exactly like ones made in the carrier portal, and can accept, counter or decline.
- Follow the result. Poll your bids and requests. A winning bid or request comes back with the shipment reference.
curl https://www.c2klegacy.com/api/partner/v1/loads?origin_state=ON&equipment=Dry%20van \
-H "Authorization: Bearer c2kp_live_YOUR_KEY"
| Method | Path | Description |
|---|---|---|
| GET | /loads | Our loads. Filters: status (open, booked, closed, all), origin_state, destination_state, origin_city, destination_city, equipment, trailer_size, pickup_after, min_miles, max_miles, max_weight, updated_since, limit, cursor |
| GET | /loads/{ref} | One load, such as C2K2026000045 |
| GET | /carriers | The carriers linked to you and whether each is cleared to haul |
| GET | /carriers/status?mc= | Is one carrier cleared to request and bid? |
| POST | /loads/{ref}/requests | Request a load for a carrier (requests:write) |
| GET | /requests | Your requests with their status: pending, booked or closed |
| POST | /loads/{ref}/bids | Bid on a load for a carrier (bids:write) |
| GET | /bids | Your bids. Filters: status, load_ref, partner_ref, after_id |
| GET | /bids/{id} | One bid |
| POST | /bids/{id}/respond | Answer a counter-offer with { "accept": true } or false |
| POST | /bids/{id}/withdraw | Withdraw an open bid |
Keeping your board in sync
Call GET /loads?status=all&updated_since=<time of your last sync> every minute or two. Loads that were booked or closed come back with that status, so you can take them down. Page through results with next_cursor.
Example: a bid for a carrier
curl -X POST https://www.c2klegacy.com/api/partner/v1/loads/C2K2026000045/bids \
-H "Authorization: Bearer c2kp_live_YOUR_KEY" -H "Content-Type: application/json" \
-d '{ "carrier": { "mc": "1234567" }, "amount": 1850, "note": "Can pick up Monday", "partner_ref": "your-bid-77" }'
Errors come back as { "error": "...", "code": "..." }. Common codes: carrier_not_linked, carrier_not_cleared, load_unavailable, already_requested, missing_scope, rate_limited. Limit: 300 requests per minute per key.
Partner webhooks
Instead of polling /loads, you can have us push changes to you. Your key needs the Manage its webhook permission (ask C2K Legacy), and each event type needs the permission it relates to: loads need loads:read, requests requests:write, bids bids:write.
| Event | Sent when | data |
|---|---|---|
load.posted | A new load is posted | The load, as in GET /loads/{ref} |
load.updated | A load changes or closes | The load |
request.updated | Your request is booked or its load is taken down | The request |
bid.updated | C2K Legacy counters, accepts or declines a bid | The bid (a booked bid includes shipment_ref) |
| Method | Path | What it does |
|---|---|---|
| PUT | /webhook | Set url (https) and optionally events. The first call returns your signing secret, once. |
| GET | /webhook | Your settings and counts of waiting and failed deliveries |
| POST | /webhook/test | Send a webhook.test event now and report how it went |
| POST | /webhook/rotate-secret | New signing secret; the old one stops working |
| DELETE | /webhook | Stop sending |
| GET | /events | Every event from the last 30 days with delivery status. Filters: type, delivery (pending, delivered, failed), after_id. Use it to catch up after downtime. |
Requests are signed exactly as described under Webhooks below (X-C2K-Signature, X-C2K-Timestamp). Each event also carries X-C2K-Event, X-C2K-Event-Id and X-C2K-Delivery-Attempt. Answer with any 2xx within 8 seconds. A failed delivery is retried after 1, 5, 30 minutes, then 2, 6, 12 and 24 hours (eight tries) and is then marked failed; C2K Legacy staff can resend it. The same event id can arrive more than once, so ignore ids you have already handled. Events are not guaranteed to arrive in order: use the load's updated_at to decide which version is newer.
Webhooks
Register one HTTPS URL in the portal. We POST JSON for these events: booking.accepted, booking.declined and shipment.status. Respond with any 2xx within 8 seconds; failed deliveries are retried twice. Recent deliveries are listed in the portal.
Every request is signed. Verify X-C2K-Signature = sha256= + HMAC-SHA256 of {timestamp}.{raw body} using your signing secret, where the timestamp is the X-C2K-Timestamp header. Reject requests older than 5 minutes.
const crypto = require('crypto');
function verify(req, rawBody, secret) {
const ts = req.headers['x-c2k-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
const got = req.headers['x-c2k-signature'] || '';
return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
{ "id": "evt_1a2b3c4d5e6f7a8b", "type": "shipment.status", "created": "2026-10-11T15:04:05.000Z",
"data": { "shipment_ref": "C2K2026000123", "status": "In transit", "location": "Detroit, MI", "note": "On schedule", "eta": "Oct 12" } }
X12 EDI (HTTPS, SFTP, AS2)
For partners who exchange X12 (version 004010). This is the C2K Legacy baseline profile. Each partner usually needs a short mapping check: contact us to certify yours. The machine-readable API description is the OpenAPI 3.1 spec (also at /api/v1/openapi.json).
Three ways to connect
| Transport | How |
|---|---|
| HTTPS | The endpoints below, authenticated with your API key. The 997 is the response to the 204. |
| SFTP | Create a login in the portal (EDI connections). Upload 204 files to /in; they are processed as soon as the upload finishes. Collect the 997, 990, 214 and 210 files from /out and delete them after pickup. Password or SSH-key login; files up to 100 KB. If your client uploads under a temporary name (.tmp, .part) and renames, we process the file when it is renamed. |
| AS2 | POST signed (and optionally encrypted) messages to /as2 with AS2-From = your AS2 ID and AS2-To = C2KLEGACY. Register your AS2 ID and public certificate in the portal; download ours at /as2/cert.pem. We reply with a synchronous signed MDN (asynchronous MDNs are not supported). Messages must be signed. If you give a return URL we send our 997/990/214/210 documents to you over AS2. |
Test mode (sandbox)
Create a test API key (c2k_test_…), a test SFTP login or a test AS2 partner in the portal and certify your integration without booking real freight. Test traffic is completely separate: nothing reaches C2K Legacy's operations, no one is emailed, and test documents carry ISA15 = T. You drive the lifecycle yourself: POST /api/v1/test/bookings/{ref}/accept (or /decline) answers a tender with a 990 and a booking.accepted webhook; POST /api/v1/test/shipments/{ref}/advance moves the shipment through Picked up, In transit, Out for delivery and Delivered, producing 214s and finally a 210. DELETE /api/v1/test/reset clears your sandbox. Test webhooks are signed like live ones and marked X-C2K-Test: true. Quotes work in test mode even while live instant quotes are switched off.
Safety: an interchange marked as a test (ISA15 = T) sent to a live key, login or AS2 partner is rejected with a 997 and is not booked. Send ISA15 = P for live freight.
Endpoints and documents
| Document | Direction | Endpoint |
|---|---|---|
| 204 Load Tender | You send | POST /api/v1/edi/204 |
| 997 Functional Ack | Returned in the response to the 204 | (HTTP response body) |
| 990 Tender Response | You fetch once accepted or declined | GET /api/v1/edi/990/{booking or shipment id} |
| 214 Shipment Status | You fetch (or use the status webhook) | GET /api/v1/edi/214/{shipment ref} |
| 210 Freight Invoice | You fetch once Delivered | GET /api/v1/edi/210/{shipment ref} |
The 204 is priced with the same instant-quote engine, so the booking amount is the instant price at the time of tender. Tenders are pending until accepted or declined, then the 990 is available.
204 segments we read
| Segment | Use |
|---|---|
ISA / GS | Sender ID and qualifier (used as the receiver on our responses). |
B2-04 | Required. Shipment ID. Unique per tender: it makes re-sends safe (idempotent) and keys the 990. |
B2A-01 | 00 original or 01 cancel (pending tenders only). Others are rejected. |
L11 | Any reference is saved on the booking. Use qualifier EQ to set equipment: DRYVAN, REEFER, FLATBED, STEPDECK, POWERONLY (default Dry van). |
S5 + N4 | Required. First stop = pickup, last stop = delivery (city and state from N4-01, N4-02). |
G62 | Pickup date (qualifier 10) and delivery date (02), CCYYMMDD. |
AT8 | Required. Weight (AT8-03); pounds L or kilograms K. |
L5 | Commodity description. |
Example 204
ISA*00* *00* *ZZ*ACMESHIPPER *ZZ*C2KLEGACY *260101*1200*U*00401*000000001*0*T*>~
GS*SM*ACMESHIPPER*C2KLEGACY*20260101*1200*1*X*004010~
ST*204*0001~
B2**NQLG**ACME12345**PP~
B2A*00*LT~
L11*PO98765*PO~
L11*DRYVAN*EQ~
AT8*G*L*40000*10~
L5*1*Paper rolls~
S5*1*CL~
G62*10*20261010~
N1*SH*Acme Toronto~
N4*Toronto*ON*M5V2T6~
S5*2*UL~
G62*02*20261012~
N1*CN*Acme Chicago~
N4*Chicago*IL*60601~
SE*16*0001~
GE*1*1~
IEA*1*000000001~
curl -X POST https://www.c2klegacy.com/api/v1/edi/204 \
-H "Authorization: Bearer c2k_live_YOUR_KEY" \
-H "Content-Type: text/plain" --data-binary @tender.edi
A 200 response contains a 997 acknowledging the tender and an X-C2K-Booking header with our booking ref. A rejected tender returns 422 with a 997 (AK5*R) and an X-C2K-Error header explaining why.
214 status codes
Shipment events map to AT7 codes: Picked up → AF, In transit / Out for delivery / Delayed → X6 (delays add an NTE note), Delivered → D1. City and state from the event location are sent in MS1.
Test vs production: interchanges are flagged T (test) outside production and P in production (ISA15).
Errors & limits
Errors return JSON: { "error": "message" } with a standard status: 400 bad input, 401 bad key, 404 not found, 409 conflict (for example a 990 before the tender is answered), 410 quote expired, 422 can't be priced instantly, 429 too many requests, 503 instant quoting unavailable.
Limit: 120 requests per minute per API key.