> ## Documentation Index
> Fetch the complete documentation index at: https://terminal49.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Project44

> Map Project44 ocean tracking fields, parameters, and errors to their Terminal49 equivalents. Covers webhooks, container holds and fees, and a migration checklist.

If you are using Project44 for ocean visibility, this guide maps the ocean portion of Project44 onto Terminal49 field by field, so you can replace the ocean leg without reverse-engineering our schema.

Terminal49 is ocean and terminal focused. We do not cover road, rail, LTL, or air. If your Project44 integration spans modes, this migration handles only the ocean shipments.

There is no compatibility shim. You will change your authentication, request shape, and response parsing. For most integrations that is an afternoon.

<Note>
  Terminal49 self-serves. Create an account, generate a free Developer Key, and start tracking up to 10 active containers at no cost. Terminal data: holds, fees, and last free day are included on the container object out of the box.
</Note>

## Start in sixty seconds

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

<Steps>
  <Step title="Create an account">
    Go to [app.terminal49.com](https://app.terminal49.com). The free Developer Key tracks up to 10 active containers.
  </Step>

  <Step title="Generate a key">
    Visit [app.terminal49.com/developers/api-keys](https://app.terminal49.com/developers/api-keys). Copy the key immediately. It is shown once and then masked forever.
  </Step>

  <Step title="Send your first request">
    ```bash theme={null}
    curl -X POST https://api.terminal49.com/v2/tracking_requests \
      -H "Content-Type: application/vnd.api+json" \
      -H "Authorization: Token YOUR_API_KEY" \
      -d '{
        "data": {
          "type": "tracking_request",
          "attributes": {
            "request_number": "YOUR_BOL_NUMBER",
            "request_type": "bill_of_lading",
            "scac": "MAEU"
          }
        }
      }'
    ```
  </Step>
</Steps>

<Warning>
  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.
</Warning>

If you want to test without a live shipment, use the [test tracking numbers](/docs/api-docs/useful-info/test-numbers), which simulate success, failure, and edge-case outcomes.

## 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.

<CardGroup cols={2}>
  <Card title="Before — Project44" icon="rotate">
    OAuth client credentials to get a bearer token, then call `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.
  </Card>

  <Card title="After — Terminal49" icon="webhook">
    `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.
  </Card>
</CardGroup>

You can keep polling if you prefer — point your existing scheduler at `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

|                         | Project44                              | Terminal49                            |
| ----------------------- | -------------------------------------- | ------------------------------------- |
| Tracking model          | Poll on demand, multi-modal legs       | Register once, then push or poll      |
| Authentication          | OAuth2 client credentials bearer token | `Authorization: Token` header         |
| Base URL                | `api.project44.com`                    | `https://api.terminal49.com/v2`       |
| Content type            | `application/json`                     | `application/vnd.api+json`            |
| Response format         | Custom JSON                            | JSON:API                              |
| Webhooks                | Webhook subscriptions available        | 30+ events, HMAC-signed               |
| Carrier identification  | SCAC or carrier code                   | `scac`, or omit and use Infer         |
| Terminal holds and fees | Not available                          | Included on the container object      |
| Last free day           | Not available                          | Included, with per-source breakdown   |
| Rail milestones         | Limited                                | North American Class I and short-line |
| Multi-modal             | Ocean, road, rail, air, LTL            | Ocean and terminal only               |
| Getting an API key      | Account setup with Project44           | Self-serve                            |

## Authentication

Project44 uses OAuth2 client credentials. You request a bearer token from their token endpoint and include it in every call as `Authorization: Bearer`. Terminal49 uses a static API key as `Authorization: Token`.

<CodeGroup>
  ```bash Project44 theme={null}
  # 1. Request bearer token
  curl -X POST https://[your OAuth token endpoint] \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=YOUR_CLIENT_ID" \
    -d "client_secret=YOUR_CLIENT_SECRET"

  # 2. Use bearer token on every call
  curl https://api.project44.com/[shipment resource] \
    -H "Authorization: Bearer YOUR_BEARER_TOKEN"
  ```

  ```bash Terminal49 theme={null}
  curl -X POST https://api.terminal49.com/v2/tracking_requests \
    -H "Content-Type: application/vnd.api+json" \
    -H "Authorization: Token YOUR_T49_KEY" \
    -d '{"data":{"type":"tracking_request","attributes":{
         "request_number":"MRKU9465770",
         "request_type":"container",
         "scac":"MAEU"}}}'
  ```
</CodeGroup>

Note the `Token` prefix. It is not `Bearer`.

## Request parameter mapping

| Project44 parameter                 | Terminal49 equivalent                                                                       | Notes                                            |
| ----------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Your Project44 shipment identifier  | `request_number`                                                                            | Use BOL, booking, or container number            |
| Container number tracking           | `request_type: "container"`                                                                 |                                                  |
| Bill of lading tracking             | `request_type: "bill_of_lading"`                                                            | Master or house BOL                              |
| Booking number tracking             | `request_type: "booking_number"`                                                            |                                                  |
| SCAC / carrier code                 | `scac`                                                                                      | Same SCAC values for most carriers               |
| Omit carrier / auto-detect          | Omit `scac`, or call [Infer Tracking Number](/docs/api-docs/in-depth-guides/auto-detect-carrier) | Returns predicted SCAC and number type           |
| Force refresh / requery             | `POST /v2/containers/{id}/refresh`                                                          | Forces an immediate pull from all sources        |
| Shipment legs / routes              | Always included                                                                             | See [Routing](/docs/api-docs/in-depth-guides/routing) |
| OAuth `client_id` / `client_secret` | `Authorization: Token` header                                                               | Single static key, no token rotation             |

<Note>
  Project44 bundles multi-modal legs in one shipment. Terminal49 expects one identifier per tracking request. Track by BOL and we return every container on that bill of lading as related container resources. If you were tracking a single leg in Project44, send the corresponding container or BOL number to Terminal49.
</Note>

## 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](/docs/api-docs/in-depth-guides/including-resources) to sideload related resources in one call instead of chasing IDs.

### Shipment level

| Project44                   | Terminal49                                   |
| --------------------------- | -------------------------------------------- |
| Your Project44 shipment ID  | `shipment.id` (UUID)                         |
| Bill of lading number       | `shipment.attributes.bill_of_lading_number`  |
| Carrier / SCAC code         | `shipment.attributes.shipping_line_scac`     |
| Carrier name                | `shipment.attributes.shipping_line_name`     |
| Shipment status             | Derived from container status and milestones |
| Place of receipt            | `shipment.attributes.port_of_lading_*`       |
| Port of loading             | `shipment.relationships.port_of_lading`      |
| Port of discharge           | `shipment.relationships.port_of_discharge`   |
| Final destination (inland)  | `shipment.relationships.destination`         |
| Predictive ETA              | `shipment.attributes.pod_eta_at`             |
| Your Project44 cached state | No equivalent — refresh is managed for you   |

### Container level

| Project44               | Terminal49                                                                                                                         |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Container number        | `container.attributes.number`                                                                                                      |
| ISO code                | `container.attributes.equipment_type` + `equipment_length` + `equipment_height`                                                    |
| Container size / type   | Same three fields above                                                                                                            |
| Container status        | `container.attributes.current_status`                                                                                              |
| Transport events / legs | Container timestamps, [transport events](/docs/api-docs/api-reference/containers/get-a-containers-transport-events), and webhook events |

<Note>
  Project44 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.
</Note>

### Locations, facilities, and vessels

| Project44                 | Terminal49                                                                                  |
| ------------------------- | ------------------------------------------------------------------------------------------- |
| Location / port name      | `port.attributes.name`                                                                      |
| Location code / UN/LOCODE | `port.attributes.code`                                                                      |
| Latitude / longitude      | `port.attributes.latitude` / `.longitude`                                                   |
| Timezone                  | `port.attributes.time_zone`                                                                 |
| Country code              | `port.attributes.country_code`                                                              |
| Terminal / facility name  | `terminal.attributes.name`                                                                  |
| Terminal SMDG code        | `terminal.attributes.smdg_code`                                                             |
| Terminal BIC code         | `terminal.attributes.bic_code`                                                              |
| Vessel name               | `shipment.attributes.pod_vessel_name`                                                       |
| Vessel IMO                | `shipment.attributes.pod_vessel_imo`                                                        |
| Vessel MMSI               | Available via the [Vessels API](/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-imo) |
| Vessel call sign, flag    | Not returned                                                                                |

## 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.

| Project44 ocean event                   | Milestone                     | Terminal49 event                        |
| --------------------------------------- | ----------------------------- | --------------------------------------- |
| `[Project44 vessel loaded event]`       | Loaded on vessel at origin    | `container.transport.vessel_loaded`     |
| `[Project44 vessel departed event]`     | Vessel departed origin        | `container.transport.vessel_departed`   |
| `[Project44 vessel arrived event]`      | Vessel arrived at destination | `container.transport.vessel_arrived`    |
| `[Project44 vessel discharged event]`   | Discharged from vessel        | `container.transport.vessel_discharged` |
| `[Project44 container gated out event]` | Gated out at destination      | `container.transport.full_out`          |
| `[Project44 container gated in event]`  | Gated in at origin            | `container.transport.full_in`           |
| —                                       | Empty picked up at origin     | `container.transport.empty_out`         |
| —                                       | Empty returned at destination | `container.transport.empty_in`          |

Terminal49 also emits milestones Project44 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](/docs/api-docs/webhooks/event-catalog).

### Registering a webhook

```bash theme={null}
curl -X POST https://api.terminal49.com/v2/webhooks \
  -H "Content-Type: application/vnd.api+json" \
  -H "Authorization: Token YOUR_API_KEY" \
  -d '{
    "data": {
      "type": "webhook",
      "attributes": {
        "url": "https://your-endpoint.example.com/t49",
        "active": true,
        "events": [
          "container.transport.vessel_discharged",
          "container.transport.available",
          "container.pickup_lfd.changed"
        ]
      }
    }
  }'
```

Payloads are HMAC-signed. See [webhook setup](/docs/api-docs/in-depth-guides/webhooks) for signature verification, and [List webhook IPs](/docs/api-docs/api-reference/webhooks/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 Project44 equivalent.

### Holds

`holds_at_pod_terminal` is an array of active holds blocking pickup:

```json theme={null}
{
  "holds_at_pod_terminal": [
    { "name": "customs", "status": "hold", "description": "CBP HOLD" },
    { "name": "freight", "status": "hold", "description": null }
  ]
}
```

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.

<Warning>
  Hold names are case-sensitive. `USDA`, `VACIS`, and `TMF` are uppercase; `freight`, `customs`, and `other` are lowercase. Match exactly.
</Warning>

### Fees

`fees_at_pod_terminal` carries type, amount, and currency:

```json theme={null}
{
  "fees_at_pod_terminal": [
    { "type": "demurrage", "amount": 850.00, "currency_code": "USD" },
    { "type": "exam", "amount": 450.00, "currency_code": "USD" }
  ]
}
```

Fee types are `demurrage`, `extended_dwell_time`, `exam`, `total`, and `other`.

<Warning>
  Some terminals report a `total` line item alongside individual fees. Filter it out before summing or you will double-count.
</Warning>

### 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:

```javascript theme={null}
function isReadyForPickup(container) {
  const { available_for_pickup, holds_at_pod_terminal } = container.attributes;
  const hasActiveHolds = holds_at_pod_terminal.some(h => h.status === 'hold');
  return available_for_pickup === true && !hasActiveHolds;
}
```

Full detail in [Holds, Fees, and Release Readiness](/docs/api-docs/in-depth-guides/holds-and-fees).

<Info>
  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](/docs/api-docs/useful-info/entitlements) for the features that do require account enablement.
</Info>

## Gotchas that will bite you

<AccordionGroup>
  <Accordion title="OAuth token rotation vs static keys" icon="key">
    Project44 bearer tokens expire and must be refreshed. Terminal49 keys do not expire and do not rotate. Delete your token refresh logic. If you rotate for security, create a new key in the dashboard and replace the old one.
  </Accordion>

  <Accordion title="Multi-modal shipments must be split by mode" icon="split">
    Project44 bundles ocean, road, rail, and air legs in one shipment resource. Terminal49 is ocean only. You must extract the ocean leg and send the BOL or container number to Terminal49. Mode-crossing logic you built in Project44 will not carry over.
  </Accordion>

  <Accordion title="JSON:API vs REST/JSON structure" icon="brackets-curly">
    Project44 returns flat JSON objects. Terminal49 returns JSON:API with `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.
  </Accordion>

  <Accordion title="Tracking requests are asynchronous" icon="clock">
    `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](/docs/api-docs/in-depth-guides/tracking-request-lifecycle).
  </Accordion>

  <Accordion title="Timestamps are UTC with a separate timezone field" icon="globe">
    Project44 timestamp handling varies by API version and resource. Terminal49 stores event timestamps in UTC and returns the matching IANA timezone alongside. Convert for display rather than assuming local time. See [Event Timestamps](/docs/api-docs/in-depth-guides/event-timestamps).
  </Accordion>

  <Accordion title="Empty arrays are the normal state" icon="list-check">
    `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.
  </Accordion>

  <Accordion title="A fee amount of 0 is valid" icon="circle-dollar">
    It means the terminal reported the fee type but has not posted an amount yet. Common for demurrage in the first day or two after discharge.
  </Accordion>

  <Accordion title="container.updated carries a changeset" icon="arrows-rotate">
    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.
  </Accordion>
</AccordionGroup>

## 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.

| Status | Meaning                                                                    |
| ------ | -------------------------------------------------------------------------- |
| 400    | Malformed request or failed validation                                     |
| 401    | Missing or invalid API key                                                 |
| 403    | Key lacks permission, or the feature is not enabled on your plan           |
| 404    | Resource does not exist                                                    |
| 422    | Valid syntax, rejected content — for example a malformed container number  |
| 429    | Rate limited. See [Rate Limiting](/docs/api-docs/in-depth-guides/rate-limiting) |
| 5xx    | Terminal49 or an upstream carrier or terminal is unavailable               |

Rough equivalence for the Project44 errors you are handling today:

| Project44 error pattern                          | Terminal49                                                                                                            |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| Invalid or expired bearer token                  | HTTP 401                                                                                                              |
| Insufficient scope / unauthorized client         | HTTP 403                                                                                                              |
| Rate limit exceeded                              | HTTP 429                                                                                                              |
| Invalid request parameters, malformed identifier | HTTP 400 or 422                                                                                                       |
| Shipment not found, unsupported SCAC             | HTTP 422                                                                                                              |
| Carrier temporary unavailability, no response    | `tracking_request.failed`, or `awaiting_manifest` if not yet manifested. Not surfaced directly — we retry internally. |

The [TypeScript SDK](/docs/sdk/introduction) 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 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](/docs/coverage/ocean-carriers) 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](/docs/coverage/fields) 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

<Steps>
  <Step title="Get a key">
    Self-serve at [app.terminal49.com/developers/api-keys](https://app.terminal49.com/developers/api-keys). Copy it immediately — it is shown once.
  </Step>

  <Step title="Remove OAuth token refresh">
    Project44 bearer tokens expire. Terminal49 keys do not. Replace your token acquisition and refresh logic with a static `Authorization: Token` header.
  </Step>

  <Step title="Check your carrier mix">
    Compare your Project44 carrier codes against the [carrier list](/docs/coverage/ocean-carriers). Flag anything missing before you cut over.
  </Step>

  <Step title="Split multi-modal shipments">
    Extract ocean legs from your Project44 shipments. Send the corresponding BOL, booking, or container number to Terminal49.
  </Step>

  <Step title="Create tracking requests">
    One `POST /tracking_requests` per BOL, booking, or container, replacing the per-request lookup.
  </Step>

  <Step title="Handle the async lifecycle">
    Tracking requests start pending. Handle `succeeded`, `failed`, and `awaiting_manifest` rather than expecting data on creation.
  </Step>

  <Step title="Update response parsing">
    JSON:API structure, split equipment fields, UTC timestamps with a separate timezone.
  </Step>

  <Step title="Update error handling">
    Replace envelope checks with HTTP status codes.
  </Step>

  <Step title="Backfill active shipments">
    Submit tracking requests for everything currently in transit. Send us the list if it is large and we will load it.
  </Step>

  <Step title="Add the terminal fields">
    Holds, fees, and LFD are the reason to do this properly rather than porting like for like.
  </Step>
</Steps>

### Then pick one

<Tabs>
  <Tab title="Webhook path">
    1. Expose an HTTPS endpoint that accepts our POST payloads.
    2. Register a webhook and subscribe only to events you act on.
    3. Verify HMAC signatures.
    4. Whitelist our IPs if your firewall restricts inbound traffic.
    5. Trigger a test delivery before going live.
    6. Retire your polling job and your dedupe layer.

    See [webhook best practices](/docs/api-docs/webhooks/best-practices) for retries and idempotency.
  </Tab>

  <Tab title="Polling path">
    1. Store the tracking request ID and shipment ID from the creation response.
    2. Repoint your existing scheduler at `GET /v2/shipments` or `GET /v2/containers`.
    3. Keep your existing cadence.

    Skipped: endpoint setup, signature verification, IP whitelisting, delivery testing.

    Terminal data changes on a cadence polling tends to miss. If you only adopt webhooks for one thing, make it `container.updated` and `container.pickup_lfd.changed`.
  </Tab>
</Tabs>

## Migrate with an AI coding agent

Paste the prompt below into your AI coding assistant. It includes the full context of this migration, the field mapping, and the environment variables to swap.

```markdown Terminal49 migration agent prompt expandable icon=robot wrap theme={null}
You are migrating this codebase from the Project44 ocean tracking API to Terminal49.

What we are keeping:
- The overall flow: collect a tracking number, get shipment data, display or act on it.

What we are changing:
- Replace OAuth2 client-credentials token flow with a static `Authorization: Token` header.
  Remove all token refresh logic.
- Replace Project44 ocean shipment endpoints with `POST /v2/tracking_requests` and
  `GET /v2/shipments` or `GET /v2/containers`.
- Split multi-modal Project44 shipment resources: extract the ocean leg and send the BOL
  or container number to Terminal49.
- Replace Project44 REST/JSON responses with Terminal49 JSON:API responses.
- Replace polling with webhooks where possible. If polling is retained, point the scheduler
  at Terminal49 endpoints.
- Handle `application/vnd.api+json` content type.
- Handle async tracking request lifecycle: pending, succeeded, failed, awaiting_manifest.
- Update timestamp parsing: UTC with a separate timezone field instead of varying formats.
- Add Terminal49 terminal fields: `holds_at_pod_terminal`, `fees_at_pod_terminal`,
  `pickup_lfd`, `available_for_pickup`, `import_deadlines`.
- Update error handling to HTTP status codes (400, 401, 403, 404, 422, 429, 5xx).
- Replace or update typed SDK errors if using the Terminal49 TypeScript SDK.
- Environment variables: swap `PROJECT44_CLIENT_ID`, `PROJECT44_CLIENT_SECRET`, and
  `PROJECT44_BASE_URL` for `TERMINAL49_API_KEY`. Remove token refresh cron jobs.

Hold names are case-sensitive and exact: `freight`, `customs`, `USDA`, `VACIS`, `TMF`, `other`.
Fee types are: `demurrage`, `extended_dwell_time`, `exam`, `total`, `other`.
Terminal49 base URL: `https://api.terminal49.com/v2`.
Webhook events of interest: `container.transport.vessel_discharged`,
`container.transport.available`, `container.pickup_lfd.changed`,
`tracking_request.succeeded`, `tracking_request.failed`, `container.updated`.

Remove anything that only supported Project44 multi-modal legs (air, road, rail, LTL).
Keep only the ocean path.
```

## 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.

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/docs/api-docs/api-reference/introduction">
    Every endpoint, with request and response schemas
  </Card>

  <Card title="TypeScript SDK" icon="rectangle-terminal" href="/docs/sdk/introduction">
    Typed client with retries and pagination built in
  </Card>

  <Card title="Coverage" icon="ship" href="/docs/coverage/home">
    Carriers, terminals, rail, and field availability
  </Card>

  <Card title="Test numbers" icon="flask" href="/docs/api-docs/useful-info/test-numbers">
    Simulate success, failure, and edge cases
  </Card>
</CardGroup>
