A CLI and Go library that tracks parcels across multiple shipping providers behind a single interface. Supports Shippo, EasyPost, 17Track, and TrackingMore.
go install msrl.dev/trackage/cmd/trackage@latest
# Save your API key to the OS keychain (no-echo prompt on a TTY).
trackage login shippo
# Track a package.
trackage track --backend=shippo 9400111899223067387543login writes the key to your OS keychain via the matching docker credential
helper (docker-credential-osxkeychain on macOS, -secretservice on Linux,
-wincred on Windows). If the helper isn't installed, or you want a different
storage mechanism, see Credentials — env var, config-file
plaintext, and explicit helper overrides all work.
Write default_backend = "shippo" to $XDG_CONFIG_HOME/trackage/config.toml to
skip --backend= on every call.
Other commands:
trackage track --json … # machine-readable output
trackage carriers # canonical carriers and how each backend names them
trackage backends # supported backends and their API-key env vars
trackage detect 1Z999… # try the local carrier detector on a numberSet TRACKAGE_TRACE=1 to dump the raw HTTP request and response (curl-style) to
stderr for any upstream API call. Every credential-bearing header is redacted:
Authorization, Proxy-Authorization, Cookie, Set-Cookie, and each
backend's own auth header (17Track's 17token, TrackingMore's
Tracking-Api-Key). Stdout is unchanged, so --json | jq … keeps working with
tracing on.
Pass --carrier=<id> to force a carrier hint. Omitted, trackage detects the
carrier from the tracking number's format when it can (UPS 1Z…, USPS IMpb,
FedEx 12/15-digit, DHL Express 10–11-digit, and any
UPU S10 national number),
and otherwise lets the backend auto-detect. Shippo requires an explicit carrier
and will error if neither the detector nor the caller supplies one.
package main
import (
"context"
"fmt"
"msrl.dev/trackage"
"msrl.dev/trackage/backend/easypost"
)
func main() {
t := easypost.New(easypost.Config{APIKey: "…"})
r, err := t.Track(context.Background(), trackage.CarrierUSPS, "EZ1000000001")
if err != nil {
panic(err)
}
fmt.Println(r.Status, r.Description)
}Every backend implements the same trackage.Tracker interface. Adapters live
under backend/shippo, backend/easypost, backend/seventeentrack, and
backend/trackingmore.
trackage resolves a backend's API key from the first source that fires, in this order:
--api-keyflag<BACKEND>_API_KEYenv var (SHIPPO_API_KEY,EASYPOST_API_KEY,SEVENTEENTRACK_API_KEY,TRACKINGMORE_API_KEY)creds_storeinconfig.toml— shells out todocker-credential-<store>with the URLhttps://trackage.invalid/<backend>. The same helpers Docker, Podman, and ko use (osxkeychain,secretservice,pass,wincred, etc.) work verbatim. Ifcreds_storeis unset, trackage auto-picks one by OS —osxkeychainon macOS,secretserviceon Linux,wincredon Windows — and silently falls through to rung 4 when that helper isn't installed.api_keys.<backend>inconfig.toml(plaintext)
To stash a key in your OS keychain (set creds_store explicitly to override the
per-OS default), run:
trackage login shippo # prompts without echo on a TTY
echo "$KEY" | trackage login shippo # accepts piped input for scripting
trackage logout shippo # idempotent; no error if not logged inPass --creds-store=<name> to either command to override the config value for a
single invocation. Both commands shell out to the helper named in creds_store,
using the synthetic URL https://trackage.invalid/<backend>.
trackage normalizes every backend's status enum into five canonical values:
pending, in_transit, delivered, exception, unknown. Backend-specific
detail is preserved on Tracking.Substatus (verbatim string) and Tracking.Raw
(the upstream JSON, untouched). See docs/design.md for why the
enum is small and where the load-bearing edges are.
make build # go build ./...
make test # go test -race ./...
make coverage-check # enforce 100% statement coverage
make lint # gofumpt + golangci-lint + Prettier on Markdown
make fmt # apply gofumpt + PrettierCross-cutting conventions follow
shields/right-answers.
Project-specific design notes are in docs/design.md;
per-backend research lives under docs/research/.
AGENTS.md is the short agent-facing pointer.