Skip to main content
If you have Gnosis Freight’s CLM platform 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. Gnosis and Terminal49 both track ocean containers through terminals and rail, so most of the work is a rename and reshape, not a redesign.

Start in sixty seconds

Signing up and getting a key is self-serve.
1

Create an account

Sign up at app.terminal49.com. The free plan 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.
The free plan tracks up to 10 active containers. Creating tracking requests through the API works right away; reading tracking data back through the API requires a free 7-day API trial (same 10-container limit) — contact us via in-app chat or support@terminal49.com and we enable it.
If you want to test without a live shipment, use the test tracking numbers, which simulate success and failure outcomes.
Migration offer: sign up now and the Vessels API — vessel schedules, AIS positions, and projected routes — is free for your first month.

The architectural shift

Gnosis’s CLM platform (tracking engine “Marlo”) and Terminal49 both integrate with ocean carriers, terminals, and rail. The difference is in how you fetch the result and how much of the operational picture arrives already computed for you.

Before: Gnosis Freight

Get an OAuth2 token via POST /api/auth/token. Create a tracking request via POST /api/v1/tracking_requests/ with an array of MBL numbers. Poll GET /api/v1/containers/ for state, or read a separate webhook schema for push updates.

After: Terminal49

POST /tracking_requests once, per bill of lading. 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 mechanical change is the tracking request shape: Gnosis accepts an array of MBL numbers in one call, while Terminal49 takes one bill of lading, booking, or container number per tracking request. If you batch MBLs today, you will loop over that array and issue one request per number.

Quick comparison

Authentication

Gnosis uses an OAuth2 password grant: exchange a username and password for a bearer token, then send that token on every request. Terminal49 uses a static API key sent as a token, with no separate token-exchange step.
Note the prefix. Gnosis uses Bearer. Terminal49 uses Token.
Gnosis tokens are issued from a username and password, and (depending on how your account is configured) may expire and need refreshing. Terminal49 API keys are static: generate one in the dashboard and use it until you rotate it yourself. There is no token-refresh flow to build.

Request parameter mapping

Gnosis’s POST /api/v1/tracking_requests/ accepts multiple MBL numbers in a single call and returns one tracking request per number internally. Terminal49’s POST /v2/tracking_requests is one call per bill of lading, booking, or container. 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. Gnosis returns a flat, paginated container list from GET /api/v1/containers/ ({metadata, containers: [...]}); Terminal49 splits the same information across shipment, container, port, and terminal resources. Use the include parameter to sideload related resources in one call instead of chasing IDs.

Shipment level

Container level

Terminal49 normalizes equipment into three fields: type (dry, reefer, open top, flat rack, bulk, tank), length (10, 20, 40, 45), and height (standard, high cube). If you parse a combined size/type code from Gnosis today, you can delete that parsing step.

Locations, facilities, and vessels

Milestone and event mapping

Gnosis exposes milestones as dated fields on the container object (loaded_on_vessel_dt, discharged_dt, and so on) plus a separate webhook schema endpoint. Terminal49 exposes the same milestones as normalized transport events and pushes each one to your webhook. Terminal49 also emits milestones Gnosis’s verified field list has no equivalent for:
  • Empty picked up / returned: container.transport.empty_out and .empty_in
  • Full gated in / out: container.transport.full_in and .full_out
  • Vessel berthed: container.transport.vessel_berthed
  • Transshipment: arrived, discharged, loaded, departed
  • Feeder vessel and barge: arrived, discharged, loaded, departed
  • ETA changes: container.transport.estimated.vessel_arrived, shipment.estimated.arrival
See the full event catalog.

Registering a webhook

Gnosis publishes its webhook shape as a schema dictionary at GET /api/v1/webhooks/containers/webhook_schema, which you inspect and subscribe to out of band. Terminal49 webhooks are self-service: register a URL and a list of named events directly.
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, and normalizes holds, fees, and last free day into a fixed shape you don’t have to reconcile yourself.

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.
Gnosis’s holds object is a dictionary of hold types where a true value means the container is held by that party. Terminal49’s shape is an array of objects, one per active hold, each carrying a name, a status, and a description. Rewrite any code that checks holds.customs === true to instead check whether an entry with name: "customs" exists in the array.

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.
Gnosis’s demurrage_amount maps to a demurrage entry in fees_at_pod_terminal. Gnosis also publishes predictive fields — gnosis_estimated_demurrage_amount, gnosis_estimated_detention_amount, and gnosis_estimated_next_day_demurrage_amount — that project charges before the terminal reports them. Terminal49 has no equivalent: fees_at_pod_terminal only reports amounts the terminal has actually posted. If your workflow depends on predictive fee amounts, budget time to either drop that logic or replace it with your own estimate.

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) — the Terminal49 equivalent of Gnosis’s last_free_detention_day_dt
  • pickup_lfd_terminal: the terminal’s LFD (demurrage deadline) — the equivalent of Gnosis’s last_free_demurrage_day_dt
  • 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. Gnosis’s gnosis_estimated_last_free_demurrage_day_dt is a predictive estimate; Terminal49 has no equivalent, since import_deadlines only carries dates the terminal or line has actually published.

Release readiness

Two fields answer “can I pick this up?” available_for_pickup and the holds array:
Gnosis’s available_for_pickup field is a straight match — same name, same boolean. The difference is what you check alongside it: swap a dictionary lookup on holds for an array scan on holds_at_pod_terminal. 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. Routing Data (container map and vessel positions), rail LFD, container refresh, and the embeddable map and widget are the gated ones.

Gotchas that will bite you

Gnosis’s POST /api/v1/tracking_requests/ accepts an array of mbl_numbers in a single call. Terminal49’s POST /v2/tracking_requests takes one request_number per call. If you currently batch MBLs, loop over the array and fire one request per bill of lading; store the returned tracking_request.id per number, not per batch.
Gnosis requires exchanging a username and password for a bearer token before every session (and refreshing it as needed). Terminal49 API keys are static — generate one, send it as Authorization: Token YOUR_API_KEY, and delete the token-refresh logic entirely.
Gnosis returns a flat {metadata, containers: [...]} list. Terminal49 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.
Gnosis’s holds object marks each hold type true or false. Terminal49’s holds_at_pod_terminal is an array containing only the holds currently active, each with a name, status, and description. Rewrite holds.customs === true checks as array-membership checks.
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, the way you might diff two GET /api/v1/containers/ polls today.
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

Gnosis returns a 422 HTTPValidationError with an array of validation details (location, message, type) for malformed requests. Terminal49 uses standard HTTP status codes consistently across every endpoint. Rough equivalence for the Gnosis errors you are handling today: The TypeScript SDK maps these to typed errors (AuthenticationError, ValidationError, RateLimitError, UpstreamError, FeatureNotEnabledError, AuthorizationError, NotFoundError) and retries rate-limit and server errors automatically with exponential backoff.

Where we are narrower than Gnosis Freight

Worth knowing before you commit. No air cargo tracking. Gnosis’s CLM platform covers air cargo alongside ocean and rail. Terminal49 tracks ocean containers and their rail legs only. If your integration depends on air shipment visibility, this migration handles only the ocean and rail portion. No drayage execution features. Gnosis is built as an execution platform with import drayage workflows (import_drayage). Terminal49 is a tracking and visibility API — it tells you what happened and what’s next, but it does not book, dispatch, or execute drayage moves. No customs milestone tracking. Gnosis exposes customs milestones and a customs_clearance_dt field directly. Terminal49 has no direct equivalent beyond the customs entry on holds_at_pod_terminal, which tells you whether a customs hold is currently blocking pickup, not a full customs event timeline. No predictive fee or ETA estimates. Gnosis publishes gnosis_estimated_* fields for demurrage, detention, next-day demurrage, and LFD, plus a gnosis_vessel_eta_dt predictive ETA. Terminal49 reports what carriers and terminals have actually posted — pod_eta_at, fees_at_pod_terminal, and import_deadlines — and does not predict future values. Carrier count. Terminal49 integrates directly with 36 ocean carriers, plus 2 more enabled on request, as of 14 August 2026. 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. 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

Drop the OAuth2 password-grant token exchange. Move to Authorization: Token, a single static key.
3

Check your carrier mix

Compare your Gnosis-tracked carriers against the carrier list. Flag anything missing before you cut over.
4

Unbatch your MBL arrays

Replace one call with an array of mbl_numbers with one POST /tracking_requests per bill of lading, booking, or container.
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, holds as an array instead of a boolean map.
7

Update error handling

Replace 422 HTTPValidationError envelope parsing with HTTP status-code checks.
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

Decide what to do about predictive fields

Gnosis’s gnosis_estimated_* amounts and ETA have no Terminal49 equivalent. Decide whether to drop that logic or replace it with your own estimate before cutover.

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 the loop that called GET /api/v1/containers/ on a schedule.
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 Gnosis Freight code, shadows every Gnosis 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 (Gnosis 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 GnosisClient, 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 Gnosis 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 Gnosis code, env vars, the OAuth2 token-refresh logic, 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 and failure outcomes