Start in sixty seconds
You do not need to talk to anyone to try this.Create an account
Generate a key
Send your first request
The architectural shift
Project44 tracks multi-modal shipments with legs. Terminal49 tracks ocean shipments and containers directly. You register a tracking request once, and we keep it updated and push changes to your webhook.Before — Project44
GET or polling endpoints for shipment status. Multi-modal legs are bundled in one shipment resource. You own the cache, the schedule, and the deduplication across road, rail, and ocean.After — Terminal49
POST /tracking_requests once → Terminal49 polls carriers, terminals, and rail → we POST to your endpoint as things change → write to your database. No cache layer, no dedupe logic.GET /v2/shipments or GET /v2/containers. But webhooks are the reason the API is shaped this way, and terminal data (holds, fees, last free day) changes on a cadence that polling tends to miss.
Quick comparison
Authentication
Project44 uses OAuth2 client credentials. You request a bearer token from their token endpoint and include it in every call asAuthorization: Bearer. Terminal49 uses a static API key as Authorization: Token.
Token prefix. It is not Bearer.
Request parameter mapping
Response field mapping
Terminal49 is JSON:API compliant, so relationships between shipments, containers, ports, and terminals are explicit rather than something you reassemble from ID references. Use theinclude parameter to sideload related resources in one call instead of chasing IDs.
Shipment level
Container level
45G1. Terminal49 splits this into three normalized fields: type (dry, reefer, open top, flat rack, tank, hard top), length (20, 40, 45, 50), and height (standard, high cube). If you were parsing ISO codes yourself, you can delete that code.Locations, facilities, and vessels
Milestone and event mapping
Project44 uses event codes or status fields on shipment legs. Terminal49 exposes the same milestones as normalized transport events and pushes each one to your webhook.- Vessel berthed —
container.transport.vessel_berthed - Available for pickup —
container.transport.availableand.not_available - Transshipment — arrived, discharged, loaded, departed
- Feeder vessel and barge — arrived, discharged, loaded, departed
- Rail — loaded, departed, arrived, unloaded, plus
arrived_at_inland_destination
Registering a webhook
What you gain
This is the part worth reading even if the rest is mechanical. Terminal49 integrates with terminals directly, not only carriers, so the container object carries operational data that has no Project44 equivalent.Holds
holds_at_pod_terminal is an array of active holds blocking pickup:
freight, customs, USDA, VACIS, TMF, and other. Status is hold or pending. When a hold clears, the object is removed from the array — there is no released state.
Fees
fees_at_pod_terminal carries type, amount, and currency:
demurrage, extended_dwell_time, exam, total, and other.
Last free day
pickup_lfd is a coalesced value that follows a fixed source priority: shipping line, then terminal, then rail. It does not pick the earliest date. The individual sources are available separately on import_deadlines:
pickup_lfd_line— the shipping line’s LFD (per diem deadline)pickup_lfd_terminal— the terminal’s LFD (demurrage deadline)pickup_lfd_rail— the rail carrier’s LFD at the inland destination
Release readiness
Two fields answer “can I pick this up?” —available_for_pickup and the holds array:
Gotchas that will bite you
OAuth token rotation vs static keys
OAuth token rotation vs static keys
Multi-modal shipments must be split by mode
Multi-modal shipments must be split by mode
JSON:API vs REST/JSON structure
JSON:API vs REST/JSON structure
data, relationships, and included. Use a JSON:API client library, or use include to sideload exactly what you need. Parsing raw JSON works but you will write more code than you expect.Tracking requests are asynchronous
Tracking requests are asynchronous
POST /tracking_requests returns immediately with a pending status. The shipment appears once the carrier responds. Subscribe to tracking_request.succeeded and tracking_request.failed rather than expecting shipment data in the creation response. A request may also land in awaiting_manifest if the carrier has not manifested the shipment yet — we retry automatically. See Tracking Request Lifecycle.Timestamps are UTC with a separate timezone field
Timestamps are UTC with a separate timezone field
Empty arrays are the normal state
Empty arrays are the normal state
holds_at_pod_terminal: [] and fees_at_pod_terminal: [] mean no active holds or fees. This is the common case. Do not treat it as missing data.A fee amount of 0 is valid
A fee amount of 0 is valid
container.updated carries a changeset
container.updated carries a changeset
container.updated with a changeset showing old value first, new value second. Use it instead of diffing state yourself.Error handling
Project44 returns errors within the response body, sometimes alongside partial data, sometimes with an error envelope. Terminal49 uses standard HTTP status codes. Replace envelope checks with status-code checks.AuthenticationError, ValidationError, RateLimitError, UpstreamError, FeatureNotEnabledError, AuthorizationError, NotFoundError) and retries rate-limit and server errors automatically with exponential backoff.
Where we are narrower than Project44
Worth knowing before you commit. Carrier count. Terminal49 integrates directly with 36 ocean carriers, plus 2 more enabled on request — as of 14 August 2026. Project44 lists more. Ours are direct integrations covering the lines that move volume into North America, and each is normalized into one schema. Check your carrier mix against the ocean carrier list before cutover, and read the known issues section there — we publish the per-carrier field gaps. Terminal data is North America. Holds, fees, LFD, and availability come from direct terminal integrations concentrated in the US and Canada, with European ports expanding. Ocean milestones work globally; terminal-level operational data does not yet. No road, rail, air, or LTL tracking. If your Project44 integration covers those modes, this migration handles only the ocean portion. You will need to keep Project44 or another provider for the non-ocean legs. No freight rates or sailing schedules. We do not offer a rate calculator, rate index, or schedule search. Some fields are source-dependent. Seal number, container weight, and departure or arrival events vary by carrier. The field availability reference says which fields are always present and which depend on the carrier, terminal, or journey.Migration checklist
Everyone does the base path. Then pick a branch.Base path
Get a key
Remove OAuth token refresh
Authorization: Token header.Check your carrier mix
Split multi-modal shipments
Create tracking requests
POST /tracking_requests per BOL, booking, or container, replacing the per-request lookup.Handle the async lifecycle
succeeded, failed, and awaiting_manifest rather than expecting data on creation.Update response parsing
Update error handling
Backfill active shipments
Add the terminal fields
Then pick one
- Webhook path
- Polling path
- Expose an HTTPS endpoint that accepts our POST payloads.
- Register a webhook and subscribe only to events you act on.
- Verify HMAC signatures.
- Whitelist our IPs if your firewall restricts inbound traffic.
- Trigger a test delivery before going live.
- Retire your polling job and your dedupe layer.