Shipment Intelligence

Interpret a shipment's tracking exceptions and route it to the right workflow.

Endpoint
POST https://kovca.io/api/v1/shipments/triage

Authenticate with your API key in the Authorization: Bearer header. Send JSON with Content-Type: application/json; bodies are limited to 16 KB.

You supply the tracking evidence; Kovca doesn't contact carriers. Dates and durations are computed in code from your timestamps. Carrier messages and customer messages are interpreted to identify the problem. The API never issues refunds, replacements, or claims.

Request body

FieldTypeDescription
shipment_id, carrierstring, requiredYour identifiers.
eventsobject[], optionalUp to 50 events: timestamp (ISO 8601), description, optional status and location. Any order; they're sorted.
latest_statusstring, optionalThe carrier's current status. Required if there are no events.
promised_delivery_datestring, optionalISO 8601 date or date-time.
as_ofstring, optionalThe time to evaluate at. Defaults to the time of the request.
orderobject, optionalorder_id and items, for context.
customer_messagestring, optionalUp to 2,000 characters.
service_policyobject, optionalno_scan_days (flag no_recent_scan after this many days without an event) and escalate_days_late (raise a delay to high priority at this many days late).
Example request
{
  "shipment_id": "SHP-48213",
  "carrier": "Example Parcel",
  "events": [
    {
      "timestamp": "2026-03-02T09:10:00Z",
      "status": "PU",
      "description": "Picked up",
      "location": "Columbus, OH"
    },
    {
      "timestamp": "2026-03-03T22:41:00Z",
      "status": "IT",
      "description": "Departed sort facility",
      "location": "Indianapolis, IN"
    },
    {
      "timestamp": "2026-03-05T14:05:00Z",
      "status": "EX",
      "description": "Delivery exception: weather delay at destination facility",
      "location": "Denver, CO"
    }
  ],
  "promised_delivery_date": "2026-03-05",
  "as_of": "2026-03-08T12:00:00Z",
  "service_policy": {
    "no_scan_days": 2,
    "escalate_days_late": 3
  }
}

Response

200 OK
{
  "request_id": "req_Tz7nW2qLr5YbK9cM",
  "exception_type": "delayed",
  "priority": "high",
  "recommended_action": "contact_carrier",
  "requires_human_review": false,
  "signals": [
    "past_promised_date",
    "no_recent_scan"
  ],
  "missing_information": [],
  "days_past_promised": 3,
  "days_since_last_event": 2
}
FieldTypeDescription
request_idstringUnique ID for this request.
exception_type"delayed" | "delivery_failed" | "address_issue" | "damaged" | "lost_or_missing" | "delivery_dispute" | "customs_hold" | "no_exception" | "other"See below.
priority"low" | "medium" | "high"From a fixed table, raised for delays past escalate_days_late.
recommended_action"monitor" | "contact_carrier" | "contact_customer" | "request_information" | "investigate" | "manual_review"The next workflow. Never an automatic refund or claim.
requires_human_reviewbooleanTrue for damage, loss, delivery disputes, other, contradictory tracking, and uncertain types.
signals"past_promised_date" | "no_recent_scan" | "loss_confirmed_by_carrier" | "loss_suspected" | "inconsistent_tracking" | "events_out_of_order" | "customer_reports_problem" | "exception_type_uncertain"[]See below.
missing_information"tracking_events" | "promised_delivery_date"[]promised_delivery_date is listed for delays without one.
days_past_promised, days_since_last_eventinteger, optionalWhole days, computed from your dates and as_of.

Exception types

  • delayed: The shipment is moving or waiting but is late, or the carrier reports a delay (weather, volume, missed connection).
  • delivery_failed: A delivery attempt failed, for example no one was available, access was blocked, or a signature was required.
  • address_issue: The address is wrong, incomplete, or undeliverable, or the package is being returned for an address problem.
  • damaged: The carrier or customer reports the package or its contents as damaged.
  • lost_or_missing: The carrier reports the package lost, or it has stopped moving with no explanation.
  • delivery_dispute: The carrier shows it delivered, but the customer says they didn't receive it.
  • customs_hold: The package is held in customs, for example for duties, documents, or inspection.
  • no_exception: Nothing is wrong: the shipment is moving normally or was delivered without a reported problem.
  • other: A problem not covered by the other options.

Routing

  • delayed → monitor: contact_carrier once past the promised date or without a recent scan. Priority medium, or high from escalate_days_late.
  • delivery_failed, address_issue, customs_hold → contact_customer: Priority medium.
  • damaged, lost_or_missing, delivery_dispute → investigate: Priority high, with review.
  • no_exception → monitor: Priority low; investigate (medium) if the tracking is contradictory.
  • other or uncertain → manual_review: With review.

Signals

  • past_promised_date: There is a problem and as_of is after promised_delivery_date.
  • no_recent_scan: There is a problem and no event for at least service_policy.no_scan_days.
  • loss_confirmed_by_carrier: Lost or missing, and a carrier event explicitly declares the loss.
  • loss_suspected: Lost or missing, without a carrier declaration.
  • inconsistent_tracking: Events contradict each other, such as a delivered scan followed by in-transit scans.
  • events_out_of_order: Events weren't in time order; they were sorted before evaluation.
  • customer_reports_problem: customer_message reports a problem.
  • exception_type_uncertain: The exception type isn't clear; recommended_action is manual_review.

Delivery dispute

Request
{
  "shipment_id": "SHP-61377",
  "carrier": "Example Parcel",
  "events": [
    {
      "timestamp": "2026-05-02T10:15:00Z",
      "description": "Out for delivery"
    },
    {
      "timestamp": "2026-05-02T16:48:00Z",
      "description": "Delivered, left at front door"
    }
  ],
  "customer_message": "Tracking says it was delivered yesterday but there's nothing at my door and my neighbors didn't get it either.",
  "as_of": "2026-05-03T11:00:00Z"
}

Code

curl https://kovca.io/api/v1/shipments/triage \
  -H "Authorization: Bearer $KOVCA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "shipment_id": "SHP-48213",
  "carrier": "Example Parcel",
  "events": [
    {
      "timestamp": "2026-03-02T09:10:00Z",
      "status": "PU",
      "description": "Picked up",
      "location": "Columbus, OH"
    },
    {
      "timestamp": "2026-03-03T22:41:00Z",
      "status": "IT",
      "description": "Departed sort facility",
      "location": "Indianapolis, IN"
    },
    {
      "timestamp": "2026-03-05T14:05:00Z",
      "status": "EX",
      "description": "Delivery exception: weather delay at destination facility",
      "location": "Denver, CO"
    }
  ],
  "promised_delivery_date": "2026-03-05",
  "as_of": "2026-03-08T12:00:00Z",
  "service_policy": {
    "no_scan_days": 2,
    "escalate_days_late": 3
  }
}'

Errors

  • 400 INVALID_REQUEST: a field is missing, too long, the wrong type, or not allowed. The message names the field.
  • 413 PAYLOAD_TOO_LARGE: the body is larger than 16 KB.
  • 401 UNAUTHORIZED, 429 RATE_LIMITED, 503 SERVICE_UNAVAILABLE, 504 TIMEOUT: see Errors and limits.

Limitations

  • Only the events you send are considered. The API doesn't fetch tracking data or know carrier service levels.
  • Lateness is measured against your promised_delivery_date; without one, a delay is reported but its lateness isn't.
  • A suspected loss is never reported as confirmed unless a carrier event says so.
  • Request data is processed to produce the result and is not stored by Kovca.