Skip to main content
If you have the Vizion tracking API in production, this page maps it onto Terminal49 field by field, so you can cut over without reverse-engineering our schema. There is no compatibility shim. You will change your request code and your response parsing. Because Vizion and Terminal49 are architecturally similar, this is mostly a rename and reshape migration.

Start in sixty seconds

You do not need to talk to anyone to try this.
1

Create an account

Sign up at app.terminal49.com. The free Developer Key tracks up to 10 active containers.
2

Generate an API key

3

Make your first request

The full API key is shown once, right after you create it. Copy it before you navigate away. After that it is masked and cannot be revealed. If you miss it, create a new key and delete the old one.
If you want to test without a live shipment, use the test tracking numbers, which simulate success, failure, and edge-case outcomes.

The architectural shift

Vizion and Terminal49 already think the same way: create a reference once, receive updates via webhooks or poll the reference. The shift is what we attach to that reference.

Before: Vizion

Create a reference via POST /references. Poll GET /references//updates or receive webhooks. Parse flat JSON responses and map Vizion event names to your internal model.

After: Terminal49

POST /tracking_requests once. Terminal49 polls carriers, terminals, and rail, then POSTs to your endpoint as things change. Container objects carry holds, fees, and last free day directly.
The biggest difference is not the workflow. It is the data surface. Terminal49 integrates directly with terminals, not just carriers, so the container object carries operational data that has no Vizion equivalent.

Quick comparison

Authentication

Move from Vizion’s key header to Terminal49’s Token prefix. Note the content type change.
Note the Token prefix. It is not Bearer.
Vizion’s exact header format varies by plan or integration type. If you pass the key in a custom header like x-api-key, the change is the same: move the value into Authorization: Token YOUR_T49_KEY.

Request parameter mapping

Vizion accepts a carrier reference or name alongside the number. Terminal49 takes a SCAC (request_type + scac) or omits the SCAC to use Infer. Track by BOL and we return every container on that bill of lading as related container resources.

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 the include parameter to sideload related resources in one call instead of chasing IDs.

Shipment level

Container level

Vizion returns a single ISO code string like 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

Vizion returns event arrays via updates or webhooks. Terminal49 exposes the same milestones as normalized transport events and pushes each one to your webhook. Terminal49 also emits milestones Vizion has no equivalent for:
  • Vessel berthed: container.transport.vessel_berthed
  • Available for pickup: container.transport.available and .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
See the full event catalog.

Registering a webhook

Payloads are HMAC-signed. See webhook setup for signature verification, and List webhook IPs if your firewall restricts inbound traffic.

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 Vizion equivalent.

Holds

holds_at_pod_terminal is an array of active holds blocking pickup:
Hold names are 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.
Hold names are case-sensitive. USDA, VACIS, and TMF are uppercase; freight, customs, and other are lowercase. Match exactly.

Fees

fees_at_pod_terminal carries type, amount, and currency:
Fee types are demurrage, extended_dwell_time, exam, total, and other.
Some terminals report a total line item alongside individual fees. Filter it out before summing or you will double-count.

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
Each has its own webhook event, so you can alert on whichever source your operation cares about.

Release readiness

Two fields answer “can I pick this up?” available_for_pickup and the holds array:
Full detail in Holds, Fees, and Release Readiness.
Holds, fees, LFD, and availability come back on the container object wherever the terminal is a supported source. They are not a paid add-on and they do not require a sales conversation. See Entitlements for the features that do require account enablement. Rail LFD and the embeddable widget are the main ones.

Gotchas that will bite you

Vizion calls them references. Terminal49 calls them tracking requests. The lifecycle is the same: create, poll or webhook, delete. Just rename your internal variable and store the tracking_request.id where you stored reference.id.
Relationships are ID references into an included array. 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.
Vizion may return local or UTC depending on the field. Terminal49 stores event timestamps in UTC and returns the matching IANA timezone alongside. Convert for display rather than assuming local time. See Event Timestamps.
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.
Terminal changes (fees, holds, LFD, appointment, availability) arrive on container.updated with a changeset showing old value first, new value second. Use it instead of diffing state yourself.
POST /tracking_requests returns immediately with a pending status. The shipment appears once the carrier responds. Subscribe to tracking_request.succeeded, tracking_request.failed, and tracking_request.awaiting_manifest rather than expecting shipment data in the creation response. See Tracking Request Lifecycle.

Error handling

Vizion returns errors in a response envelope or via HTTP status codes depending on the endpoint. Terminal49 uses standard HTTP status codes consistently. Replace envelope checks and message-string matching with status-code checks. Rough equivalence for the Vizion errors you are handling today: The TypeScript SDK maps these to typed errors (AuthenticationError, ValidationError, RateLimitError, UpstreamError, FeatureNotEnabledError) and retries rate-limit and server errors automatically with exponential backoff.

Where we are narrower than Vizion

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. Vizion lists substantially 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 air, parcel, or road tracking. If your integration covers those modes, this migration handles only the ocean portion. 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

1

Get a key

Self-serve at app.terminal49.com/developers/api-keys. Copy it immediately, it is shown once.
2

Switch authentication

Move from your Vizion API key header to Authorization: Token. Note the Token prefix.
3

Check your carrier mix

Compare your Vizion carrier values against the carrier list. Flag anything missing before you cut over.
4

Create tracking requests

One POST /tracking_requests per BOL, booking, or container, replacing the per-reference creation.
5

Handle the async lifecycle

Tracking requests start pending. Handle succeeded, failed, and awaiting_manifest rather than expecting data on creation.
6

Update response parsing

JSON:API structure, split equipment fields, UTC timestamps with a separate timezone.
7

Update error handling

Replace envelope checks with HTTP status codes.
8

Backfill active shipments

Submit tracking requests for everything currently in transit. Send us the list if it is large and we will load it.
9

Add the terminal fields

Holds, fees, and LFD are the reason to do this properly rather than porting like for like.

Then pick one

1

Expose an HTTPS endpoint

Accept our POST payloads at a public URL.
2

Register a webhook

Subscribe only to events you act on.
3

Verify HMAC signatures

Reject any payload whose signature does not match.
4

Whitelist our IPs

Only needed if your firewall restricts inbound traffic.
5

Trigger a test delivery

Confirm end-to-end before going live.
6

Retire your polling job

Remove your dedupe layer along with it.
See webhook best practices for retries and idempotency.

Migrate with an AI coding agent

If you use Cursor, Claude Code, Windsurf, Copilot, or another AI coding assistant, hand it the prompt below. It is written to run a side-by-side migration: the agent stands up a Terminal49 client next to your existing Vizion code, shadows every Vizion call with a Terminal49 call, diffs the responses, and only cuts over once parity is proven.
Point your agent at this page as context (paste the URL or add it as a doc source). The prompt references the mappings above, so the more of this page the agent can see, the better it does.
  1. Open your repo in your AI coding tool.
  2. Add this page as a documentation source, or paste its URL into the chat.
  3. Copy the prompt below into a new chat and send it.
  4. Answer the agent’s discovery questions (Vizion client location, env var names, carrier mix).
  5. Review each PR the agent opens. It should ship in small, reviewable steps: client, shadow, parity harness, cutover, cleanup.
  • A Terminal49Client alongside your existing VizionClient, sharing the same interface where possible.
  • A shadow-mode wrapper that calls both providers and logs response diffs without changing behavior.
  • A parity report per shipment: matched fields, diverged fields, and Terminal49-only fields (holds, fees, LFD).
  • A feature-flagged cutover: route reads to Terminal49, keep Vizion as fallback until you flip the flag off.
  • A webhook receiver with HMAC verification, or a polling scheduler, depending on which path you pick.
  • A cleanup PR that removes Vizion code, env vars, dependencies, and dedupe logic.

The prompt

Copy this into your agent. Replace the bracketed placeholders in the Repo context block before sending.
Terminal49 migration agent prompt
The prompt is deliberately opinionated on side-by-side migration and small PRs. If your team prefers a big-bang cutover or a different branching model, edit the Plan section before sending it to your agent.

Getting help

Send us your list of active container and bill of lading numbers and we will load them rather than making you script the backfill. If something in this mapping is wrong or incomplete, tell us. We would rather fix the page than have you work around it.

API reference

Every endpoint, with request and response schemas

TypeScript SDK

Typed client with retries and pagination built in

Coverage

Carriers, terminals, rail, and field availability

Test numbers

Simulate success, failure, and edge cases