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 |