Book a demo Open the ROI calculator

[soc 2 type ii · 14-day trial · no card]

[docs]

Documentation

[api v1 · rest + webhooks + edi] [last updated ] [illustrative]

[portfolio concept]

A slice, not a site. Throughline is fictional and this reference stops after six entries, on purpose: enough to judge how the system sets technical content. Payloads, plans and limits are illustrative, and api.throughline.example is a reserved domain that answers nowhere.

[soc 2 type ii · 14-day trial · no card]

[1]What is an exception?

An exception is a shipment whose observed state has left the tolerance for its lane. Nothing else enters the system. A shipment tracking inside tolerance is watched continuously but never shown, which is why the queue stays short enough to be read.

Every exception carries the same five fields from the second it is flagged:

  • shipment. The carrier reference it was flagged against, e.g. SHP-48211.
  • issue. One of LATE SHORT MISROUTE. The word carries the meaning; the hue is a mark beside it.
  • owner. A named person, attached by the first matching routing rule. An unmatched exception falls to a default owner, never to nobody.
  • deadline. Attached in the same second as the flag. Rule 07, for example, gives high-value late freight +4 h.
  • state. open until the close criteria are met: the carrier has a confirmed recovery plan and the customer accepted the revised date. Then the row leaves the queue on its own.

The lifecycle of one exception, end to end, is walked on the platform tour. What the queue replaces is on Throughline vs. spreadsheets and email.

[2]What is a lane tolerance?

A lane tolerance answers the question "how late is actually late, here". It is the number of hours a shipment on one origin-destination pair may run behind plan before it flags. Tolerance is set per lane, never globally, because a day of slack on a 2,100-mile linehaul is noise and a day on a 250-mile lane is a failed delivery.

Tolerances are fitted, not guessed: 90 days of history are backfilled in week one and fitted in week two, so day-one alerts are not noise. A manual value always wins over a fitted one.

GET /v1/lanes/{lane}/tolerance

[response 200] json
{
  "lane": "ORD-DFW",
  "flag_after_h": 8,
  "source": "fitted",
  "history_days": 90,
  "manual_override": null,
  "fitted_at": "2026-08-21T06:00:00Z"
}

This is the tolerance SHP-48211 broke in clause [3]: observed delay 38 h against a fitted 8 h. [illustrative]

[3]How do you read the queue over REST?

The base URL is https://api.throughline.example/v1. Every request carries a bearer token created in the workspace; tokens are scoped read or read-write. Limits are 120 requests per minute per token, with Retry-After on 429. [reserved .example domain · resolves nowhere]

List open exceptions

GET /v1/exceptions

[request] bash
curl "https://api.throughline.example/v1/exceptions?state=open&issue=late&limit=1" \
  --header "Authorization: Bearer tl_live_9f3c2a…"
[response 200] json
{
  "data": [
    {
      "id": "exc_01J9ZK7R2M",
      "shipment": "SHP-48211",
      "issue": "late",
      "state": "open",
      "lane": "ORD-DFW",
      "carrier": { "name": "Estes", "scac": "EXLA" },
      "declared_value_usd": 31400,
      "observed_delay_h": 38,
      "tolerance_h": 8,
      "rule": "07",
      "owner": { "initials": "KT", "name": "K. Tan" },
      "flagged_at": "2026-08-28T14:41:12Z",
      "due_by": "2026-08-28T18:41:12Z",
      "thread": "/v1/exceptions/exc_01J9ZK7R2M/thread"
    }
  ],
  "has_more": true,
  "next_cursor": "exc_01J9ZK7R2M"
}

The same shipment, the same rule, the same +4 h deadline as the thread on the platform tour. Timestamps are UTC; the queue renders them in the workspace timezone. [illustrative data]

Query parameters

[get /v1/exceptions · query parameters · illustrative]
Parameter Type Accepts Default
state string open · resolved · all open
issue string late · short · misroute all issues
lane string Origin-destination pair, e.g. ORD-DFW all lanes
carrier string SCAC, e.g. EXLA all carriers
limit integer 1 to 100 25
cursor string An id from a previous next_cursor first page

[4]How do webhooks arrive?

Every state change POSTs to your endpoint as it happens; webhooks ship on the Operations plan and up. Four event types cover the lifecycle: exception.flagged, exception.routed, exception.escalated and exception.resolved. Delivery retries on a backoff for 24 h, and every payload is signed.

[event payload] json
{
  "id": "evt_01J9ZK7R9T",
  "type": "exception.flagged",
  "created_at": "2026-08-28T14:41:12Z",
  "data": {
    "exception": "exc_01J9ZK7R2M",
    "shipment": "SHP-48211",
    "issue": "late",
    "lane": "ORD-DFW",
    "observed_delay_h": 38,
    "tolerance_h": 8
  }
}

Verify the signature

Each delivery carries a timestamped HMAC of the raw body:

Throughline-Signature: t=1787928072,v1=6b3f9c04a1…

Recompute it with your endpoint secret over t plus the unparsed body, and refuse anything older than five minutes:

[verification] node
import { createHmac, timingSafeEqual } from "node:crypto";

// header: "t=1787928072,v1=6b3f..." · body: the raw request bytes, unparsed
export function verifySignature(header, body, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((pair) => pair.split("="))
  );
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.${body}`)
    .digest("hex");
  const got = Buffer.from(parts.v1 ?? "", "utf8");
  const want = Buffer.from(expected, "utf8");
  return got.length === want.length && timingSafeEqual(got, want);
}

[5]How does a 214 become an event?

On the Network plan Throughline reads X12 214 shipment status messages directly off the wire and returns a 997 acknowledgment for every set received. The 204 load tender is read alongside so each 990 response pairs with its tender. The full set list is in the integrations directory.

A minimal 214, envelope omitted:

[x12 214] edi
ST*214*0001~
B10*0745912*SHP-48211*EXLA~
L11*8100448211*BM~
AT7*X1*NS***20260828*134100*LT~
MS1*DALLAS*TX*US~
MS2*EXLA*53124~
SE*7*0001~

Field mapping

[214 → exception event · field mapping · illustrative]
Element X12 name Example Becomes
B10-01 Reference identification, the carrier pro number 0745912 shipment.pro_number
B10-02 Shipment identification number SHP-48211 shipment.reference
B10-03 Standard carrier alpha code EXLA carrier.scac
L11-01/02 Reference number and qualifier; BM is bill of lading 8100448211 BM shipment.bol
AT7-01 Shipment status code; X1 is arrived at delivery X1 event.status_code
AT7-02 Status reason code; NS is normal status NS event.reason_code
AT7-05/06 Status date and time 20260828 134100 event.occurred_at
AT7-07 Time code; LT is local time LT event.occurred_at, offset resolution
MS1 Equipment or shipment location DALLAS TX US event.location
MS2 Equipment owner and number EXLA 53124 event.trailer

The event lands in the same tolerance model as every other feed: a 214 late against its lane flags exactly the way a polled API status does.

[6]How does data leave?

Your data is yours, queryable where you work:

  • CSV and Parquet, exported from any queue, attachments included. Every plan.
  • Snowflake secure data share of exception history and the event log. Network plan.
  • Read-only SQL workspace over the full exception schema. Network plan.
[sql workspace] sql
-- median minutes from carrier signal to flagged row, by lane, last 30 days
SELECT
  lane,
  approx_percentile(minutes_to_flag, 0.5) AS median_detect_min,
  count(*)                                AS exceptions
FROM exception_events
WHERE flagged_at >= dateadd('day', -30, current_date)
GROUP BY lane
ORDER BY exceptions DESC;

The 38 min median quoted across this site is the illustrative output of exactly this query. Retention and deletion terms live on the trust center.

[twenty minutes]

Watch your own feed become this queue.

[the reference stops here on purpose]

[next step]

Rather read your own payloads?

Twenty minutes, your carrier list, a week of your feed data live on the call.