API Reference

Base URL

https://shipshim.com/api/v1

The production API is at shipshim.com. If you're self-hosting, substitute your own deployment URL — the path layout is identical.

Authentication

All authenticated endpoints accept your API key three ways. Use whichever fits your client.

Authorization header (recommended)

Authorization: Bearer YOUR_API_KEY

X-API-Key header

X-API-Key: YOUR_API_KEY

Query parameter

?api_key=YOUR_API_KEY

Keys are stored hashed; ShipShim cannot recover a lost key. Generate a new one from the customer portal if you lose access to the original.


Endpoints

POST /track

Resolve a tracking number to a carrier and a direct tracking URL.

Authentication: required.

Request body

Field Type Required Description
tracking_number string (max 100 chars) Yes The tracking number to resolve.
carrier string (max 100 chars) No Carrier name or alias to lock the result (e.g. UPS, united parcel service). When the number also fits that carrier's format, ShipShim returns it with a confidence of 100. If the number doesn't fit, the carrier is still returned but at confidence 60 with hint_pattern_mismatch: true so you can catch a mistyped hint.
country string (max 10 chars) No ISO country code (e.g. US, DE). Used only as a tie-breaker between candidates that are otherwise equally likely — it never overrides a number's own structure. Most useful for disambiguating a number that several carriers in different countries could have issued.

Example request

curl -X POST https://shipshim.com/api/v1/track \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_number": "1Z999AA10123456784",
    "country": "US"
  }'

Response: success (200)

{
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "country": "US",
  "tracking_url": "https://www.ups.com/track?tracknum=1Z999AA10123456784",
  "confidence": 97,
  "matches": [
    {
      "carrier": "UPS",
      "country": "US",
      "confidence": 97,
      "tracking_url": "https://www.ups.com/track?tracknum=1Z999AA10123456784"
    }
  ]
}
Field Type Description
tracking_number string Echo of the input.
carrier string Best-matching carrier name.
country string Carrier's country (ISO code, or INT for international).
tracking_url string Direct link to the carrier's tracking page for this number.
confidence integer (0–100) A calibrated estimate that the top carrier actually issued this number — derived from verifiable structure, not country bias. 97–99: a UPU S10 country code or a verified check digit (UPS / USPS / FedEx) pins one carrier. 88–92: a single distinctive format with no rival. ≤70 with ambiguous: true: several carriers share the format — see matches.
matches array Up to 8 candidate carriers whose format matched, ranked best-first. For a clean match this is just the resolved carrier; for an ambiguous one it's the shortlist to choose from (or filter by country).
checkdigit boolean Present on formats that carry a check digit (S10, UPS, USPS, FedEx). false means the carrier was still identified (e.g. by its country code) but the check digit didn't validate — often a typo.

Response: ambiguous (200 with ambiguous: true)

Many carriers use plain numeric tracking numbers with no check digit or distinctive prefix, so a number alone genuinely can't always be pinned to one carrier. Rather than guess, ShipShim returns the most likely carrier flagged ambiguous: true, caps the confidence at 70, and lists the candidates in matches. Status is still 200. Supply a carrier or country hint to resolve it.

{
  "tracking_number": "123456789012",
  "carrier": "FedEx",
  "country": "US",
  "tracking_url": "https://www.fedex.com/fedextrack/?trknbr=123456789012",
  "confidence": 50,
  "ambiguous": true,
  "message": "Multiple carriers use this tracking-number format. Showing the most likely match. Pass `country` (e.g. US/DE) or `carrier` to disambiguate.",
  "matches": [
    {
      "carrier": "FedEx",
      "country": "US",
      "confidence": 50,
      "tracking_url": "https://www.fedex.com/fedextrack/?trknbr=123456789012"
    },
    {
      "carrier": "DHL Germany",
      "country": "DE",
      "confidence": 45,
      "tracking_url": "https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=123456789012"
    }
  ]
}

Pass carrier or country on the next call to disambiguate.

Response: not found (404)

No carrier pattern matched the number.

{
  "error": "not_found",
  "message": "No carrier found for this tracking number."
}

This still consumes one quota call — pattern matching ran, it just didn't find anything.


POST /demo/track

Public, unauthenticated endpoint used by the homepage demo card.

Authentication: none required. Rate limit: 10 requests per minute per IP. Excess requests return 429.

Accepts the same body shape as POST /track and returns the same response shape. Usage is attributed to the internal demo account and does not affect your quota — but the strict per-IP throttle makes it unsuitable for production use.

curl -X POST https://shipshim.com/api/demo/track \
  -H "Content-Type: application/json" \
  -d '{"tracking_number": "1Z999AA10123456784"}'

GET /health

Lightweight liveness probe.

Authentication: none required. This endpoint is served by nginx in production and never enters the Laravel framework — it's safe to hit from external uptime monitors at high frequency.

curl https://shipshim.com/api/v1/health

Returns 200 OK with the text body OK.


Error responses

401 Unauthorized

API key missing or invalid (or the key has been revoked).

{
  "error": "unauthorized",
  "message": "API key is required. Include it in the Authorization header as \"Bearer YOUR_API_KEY\""
}

403 Forbidden

Account is suspended. Contact support.

{
  "error": "account_suspended",
  "message": "Your account has been suspended. Please contact support."
}

422 Unprocessable Entity

The request body failed validation (e.g. tracking_number missing or too long, carrier exceeds 100 chars, country exceeds 10 chars).

{
  "message": "The tracking number field is required.",
  "errors": {
    "tracking_number": ["The tracking number field is required."]
  }
}

Validation failures do not consume quota — the reservation made at authentication is released when the controller returns 422.

429 Too Many Requests

For authenticated /track: monthly call limit exceeded. Upgrade the plan or wait until the next billing period.

{
  "error": "rate_limit_exceeded",
  "message": "Monthly API call limit exceeded. Please upgrade your plan.",
  "limit": 100,
  "used": 100
}

For /demo/track: per-IP rate limit (10/min) exceeded — Laravel's standard throttle response.

500 Internal Server Error

An unexpected server-side error. Includes a Sentry-trackable reference if persistent.


HTTP status codes

Code Meaning
200 Success — includes both clean matches and ambiguous: true responses
401 Missing, invalid, or revoked API key
403 Account suspended
404 No carrier pattern matched the tracking number
422 Request body failed validation (no quota consumed)
429 Monthly quota exceeded, or demo throttle hit
500 Server error