# Edit a container
Source: https://terminal49.com/docs/api-docs/api-reference/containers/edit-a-container
patch /containers
Update a container
# Get a container
Source: https://terminal49.com/docs/api-docs/api-reference/containers/get-a-container
get /containers/{id}
Retrieves the details of a container.
# Get a container's raw events
Source: https://terminal49.com/docs/api-docs/api-reference/containers/get-a-containers-raw-events
get /containers/{id}/raw_events
#### Deprecation warning
The `raw_events` endpoint is provided as-is.
For past events we recommend consuming `transport_events`.
---
Get a list of past and future (estimated) milestones for a container as reported by the carrier. Some of the data is normalized even though the API is called raw_events.
Normalized attributes: `event` and `timestamp` timestamp. Not all of the `event` values have been normalized. You can expect the the events related to container movements to be normalized but there are cases where events are not normalized.
For past historical events we recommend consuming `transport_events`. Although there are fewer events here those events go through additional vetting and normalization to avoid false positives and get you correct data.
# Get a container's transport events
Source: https://terminal49.com/docs/api-docs/api-reference/containers/get-a-containers-transport-events
get /containers/{id}/transport_events
Get a list of past transport events (canonical) for a container. All data has been normalized across all carriers. These are a verified subset of the raw events may also be sent as Webhook Notifications to a webhook endpoint.
This does not provide any estimated future events. See `container/:id/raw_events` endpoint for that.
# Get container route
Source: https://terminal49.com/docs/api-docs/api-reference/containers/get-container-route
get /containers/{id}/route
Retrieves the route details from the port of lading to the port of discharge, including transshipments. This is a paid feature. Please contact sales@terminal49.com.
# List containers
Source: https://terminal49.com/docs/api-docs/api-reference/containers/list-containers
get /containers
Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.
This API will return all containers associated with the account.
# Get a metro area using the un/locode or the id
Source: https://terminal49.com/docs/api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id
get /metro_areas/{id}
Return the details of a single metro area.
# null
Source: https://terminal49.com/docs/api-docs/api-reference/parties/create-a-party
post /parties
Creates a new party
# null
Source: https://terminal49.com/docs/api-docs/api-reference/parties/edit-a-party
patch /parties/{id}
Updates a party
# null
Source: https://terminal49.com/docs/api-docs/api-reference/parties/get-a-party
get /parties/{id}
Returns a party by it's given identifier
# null
Source: https://terminal49.com/docs/api-docs/api-reference/parties/list-parties
get /parties
Get a list of parties
# Get a port using the locode or the id
Source: https://terminal49.com/docs/api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id
get /ports/{id}
Return the details of a single port.
# Edit a shipment
Source: https://terminal49.com/docs/api-docs/api-reference/shipments/edit-a-shipment
patch /shipments/{id}
Update a shipment
# Get a shipment
Source: https://terminal49.com/docs/api-docs/api-reference/shipments/get-a-shipment
get /shipments/{id}
Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.
# List shipments
Source: https://terminal49.com/docs/api-docs/api-reference/shipments/list-shipments
get /shipments
Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.
This api will return all shipments associated with the account. Shipments created via the `tracking_request` API aswell as the ones added via the dashboard will be retuned via this endpoint.
# Resume tracking a shipment
Source: https://terminal49.com/docs/api-docs/api-reference/shipments/resume-tracking-shipment
patch /shipments/{id}/resume_tracking
Resume tracking a shipment. Keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.
# Stop tracking a shipment
Source: https://terminal49.com/docs/api-docs/api-reference/shipments/stop-tracking-shipment
patch /shipments/{id}/stop_tracking
We'll stop tracking the shipment, which means that there will be no more updates. You can still access the shipment's previously-collected information via the API or dashboard.
You can resume tracking a shipment by calling the `resume_tracking` endpoint, but keep in mind that some information is only made available by our data sources at specific times, so a stopped and resumed shipment may have some information missing.
# Get a single shipping line
Source: https://terminal49.com/docs/api-docs/api-reference/shipping-lines/get-a-single-shipping-line
get /shipping_lines/{id}
Return the details of a single shipping line.
# Shipping Lines
Source: https://terminal49.com/docs/api-docs/api-reference/shipping-lines/shipping-lines
get /shipping_lines
Return a list of shipping lines supported by Terminal49.
N.B. There is no pagination for this endpoint.
# Get a terminal using the id
Source: https://terminal49.com/docs/api-docs/api-reference/terminals/get-a-terminal-using-the-id
get /terminals/{id}
Return the details of a single terminal.
# Create a tracking request
Source: https://terminal49.com/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request
post /tracking_requests
To track an ocean shipment, you create a new tracking request.
Two attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`.
Once a tracking request is created we will attempt to fetch the shipment details and it's related containers from the shipping line. If the attempt is successful we will create in new shipment object including any related container objects. We will send a `tracking_request.succeeded` webhook notification to your webhooks.
If the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`.
A `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook.
# Edit a tracking request
Source: https://terminal49.com/docs/api-docs/api-reference/tracking-requests/edit-a-tracking-request
patch /tracking_requests/{id}
Update a tracking request
# Get a single tracking request
Source: https://terminal49.com/docs/api-docs/api-reference/tracking-requests/get-a-single-tracking-request
get /tracking_requests/{id}
Get the details and status of an existing tracking request.
# List tracking requests
Source: https://terminal49.com/docs/api-docs/api-reference/tracking-requests/list-tracking-requests
get /tracking_requests
Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.
# Get a vessel using the id
Source: https://terminal49.com/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-id
get /vessels/{id}
Returns a vessel by id. `show_positions` is a paid feature. Please contact sales@terminal49.com.
# Get a vessel using the imo
Source: https://terminal49.com/docs/api-docs/api-reference/vessels/get-a-vessel-using-the-imo
get /vessels/{imo}
Returns a vessel by the given IMO number. `show_positions` is a paid feature. Please contact sales@terminal49.com.
# Get vessel future positions
Source: https://terminal49.com/docs/api-docs/api-reference/vessels/get-vessel-future-positions
get /vessels/{id}/future_positions
Returns the estimated route between two ports for a given vessel. The timestamp of the positions has fixed spacing of one minute.This is a paid feature. Please contact sales@terminal49.com.
# Get vessel future positions from coordinates
Source: https://terminal49.com/docs/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates
get /vessels/{id}/future_positions_with_coordinates
Returns the estimated route between two ports for a given vessel from a set of coordinates. The timestamp of the positions has fixed spacing of one minute.This is a paid feature. Please contact sales@terminal49.com.
# Get a single webhook notification
Source: https://terminal49.com/docs/api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification
get /webhook_notifications/{id}
# Get webhook notification payload examples
Source: https://terminal49.com/docs/api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples
get /webhook_notifications/examples
Returns an example payload as it would be sent to a webhook endpoint for the provided `event`
# List webhook notifications
Source: https://terminal49.com/docs/api-docs/api-reference/webhook-notifications/list-webhook-notifications
get /webhook_notifications
Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down.
# Create a webhook
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/create-a-webhook
post /webhooks
You can configure a webhook via the API to be notified about events that happen in your Terminal49 account. These events can be realted to tracking_requests, shipments and containers.
This is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically.
# Delete a webhook
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/delete-a-webhook
delete /webhooks/{id}
Delete a webhook
# Edit a webhook
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/edit-a-webhook
patch /webhooks/{id}
Update a single webhook
# Get single webhook
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/get-single-webhook
get /webhooks/{id}
Get the details of a single webhook
# List webhook IPs
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/list-webhook-ips
get /webhooks/ips
Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.
# List webhooks
Source: https://terminal49.com/docs/api-docs/api-reference/webhooks/list-webhooks
get /webhooks
Get a list of all the webhooks
# 3. List Your Shipments & Containers
Source: https://terminal49.com/docs/api-docs/getting-started/list-shipments-and-containers
## Shipment and Container Data in Terminal49
After you've successfully made a tracking request, Terminal49 will begin to track shipments and store relevant information about that shipment on your behalf.
The initial tracking request starts this process, collecting available data from Carriers and Terminals. Then, Terminal49 periodically checks for new updates adn pulls data from the carriers and terminals to keep the data we store up to date.
You can access data about shipments and containers on your tracked shipments any time. We will introduce the basics of this method below.
Keep in mind, however, that apart from initialization code, you would not usually access shipment data in this way. You would use Webhooks (described in the next section). A Webhook is another name for a web-based callback URL, or a HTTP Push API. They provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API.
## List all your Tracked Shipments
If your tracking request was successful, you will now be able to list your tracked shipments.
**Try it below. Click "Headers" and replace YOUR\_API\_KEY with your API key.**
Sometimes it may take a while for the tracking request to show up, but usually no more than a few minutes.
If you had trouble adding your first shipment, try adding a few more.
**We suggest copy and pasting the response returned into a text editor so you can examine it while continuing the tutorial.**
```json http
{
"method": "get",
"url": "https://api.terminal49.com/v2/shipments",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
> ### Why so much JSON? (A note on JSON API)
>
> The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly.
## Authentication
The API uses HTTP Bearer Token authentication.
This means you send your API Key as your token in every request.
Webhooks are associated with API tokens, and this is how the Terminal49 knows who to return relevant shipment information to.
## Anatomy of Shipments JSON Response
Here's what you'll see come back after you get the /shipments endpoint.
Note that for clarity I've deleted some of the data that is less useful right now, and replaced them with ellipses (...). Bolded areas are also mine to point out important data.
The **Data** attribute contains an array of objects. Each object is of type "shipment" and includes attributes such as bill of lading number, the port of lading, and so forth. Each Shipment object also has Relationships to structured data objects, for example, Ports and Terminals, as well as a list of Containers which are on this shipment.
You can write code to access these structured elements from the API. The advantage of this approach is that Terminal49 cleans and enhances the data that is provided from the steamship line, meaning that you can access a pre-defined object definition for a specific port in Los Angeles.
```jsx
{
"data": [
{
/* this is an internal id that you can use to query the API directly, i.e by hitting https://api.terminal49.com/v2/shipments/123456789 */
"id": "123456789",
// the object type is a shipment, per below.
"type": "shipment",
"attributes": {
// Your BOL number that you used in the tracking request
"bill_of_lading_number": "99999999",
...
"shipping_line_scac": "MAEU",
"shipping_line_name": "Maersk",
"port_of_lading_locode": "INVTZ",
"port_of_lading_name": "Visakhapatnam",
...
},
"relationships": {
"port_of_lading": {
"data": {
"id": "bde5465a-1160-4fde-a026-74df9c362f65",
"type": "port"
}
},
"port_of_discharge": {
"data": {
"id": "3d892622-def8-4155-94c5-91d91dc42219",
"type": "port"
}
},
"pod_terminal": {
"data": {
"id": "99e1f6ba-a514-4355-8517-b4720bdc5f33",
"type": "terminal"
}
},
"destination": {
"data": null
},
"containers": {
"data": [
{
"id": "593f3782-cc24-46a9-a6ce-b2f1dbf3b6b9",
"type": "container"
}
]
}
},
"links": {
// this is a link to this specific shipment in the API.
"self": "/v2/shipments/7f8c52b2-c255-4252-8a82-f279061fc847"
}
},
...
],
...
}
```
## Sample Code: Listing Tracked Shipment into a Google Sheet
Below is code written in Google App Script that lists the current shipments into the current sheet of a spreadsheet. App Script is very similar to Javascript.
Because Google App Script does not have native JSON API support, we need to parse the JSON directly, making this example an ideal real world application of the API.
```jsx
function listTrackedShipments(){
// first we construct the request.
var options = {
"method" : "GET",
"headers" : {
"content-type": "application/vnd.api+json",
"authorization" : "Token YOUR_API_KEY"
},
"payload" : ""
};
try {
// note that URLFetchApp is a function of Google App Script, not a standard JS function.
var response = UrlFetchApp.fetch("https://api.terminal49.com/v2/shipments", options);
var json = response.getContentText();
var shipments = JSON.parse(json)["data"];
var shipment_values = [];
shipment_values = extractShipmentValues(shipments);
listShipmentValues(shipment_values);
} catch (error){
//In JS you would use console.log(), but App Script uses Logger.log().
Logger.log("error communicating with t49 / shipments: " + error);
}
}
function extractShipmentValues(shipments){
var shipment_values = [];
shipments.forEach(function(shipment){
// iterating through the shipments.
shipment_values.push(extractShipmentData(shipment));
});
return shipment_values;
}
function extractShipmentData(shipment){
var shipment_val = [];
//for each shipment I'm extracting some of the key info i want to display.
shipment_val.push(shipment["attributes"]["shipping_line_scac"],
shipment["attributes"]["shipping_line_name"],
shipment["attributes"]["bill_of_lading_number"],
shipment["attributes"]["pod_vessel_name"],
shipment["attributes"]["port_of_lading_name"],
shipment["attributes"]["pol_etd_at"],
shipment["attributes"]["pol_atd_at"],
shipment["attributes"]["port_of_discharge_name"],
shipment["attributes"]["pod_eta_at"],
shipment["attributes"]["pod_ata_at"],
shipment["relationships"]["containers"]["data"].length,
shipment["id"]
);
return shipment_val;
}
function listShipmentValues(shipment_values){
// now, list the data in the spreadsheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var homesheet = ss.getActiveSheet();
var STARTING_ROW = 1;
var MAX_TRACKED = 500;
try {
// clear the contents of the sheet first.
homesheet.getRange(STARTING_ROW,1,MAX_TRACKED,shipment_values[0].length).clearContent();
// now insert all the shipment values directly into the sheet.
homesheet.getRange(STARTING_ROW,1,shipment_values.length,shipment_values[0].length).setValues(shipment_values);
} catch (error){
Logger.log("there was an error in listShipmentValues: " + error);
}
}
```
## List all your Tracked Containers
You can also list out all of your Containers. Container data includes Terminal availability, last free day, and other logistical information that you might use for drayage operations at port.
**Try it below. Click "Headers" and replace YOUR\_API\_KEY with your API key.**
**We suggest copy and pasting the response returned into a text editor so you can examine it while continuing the tutorial.**
```json http
{
"method": "get",
"url": "https://api.terminal49.com/v2/containers",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
## Anatomy of Containers JSON Response
Now that you've got a list of containers, let's examine the response you've received.
```jsx
// We have an array of objects in the data returned.
"data": [
{
//
"id": "internalid",
// this object is of type Container.
"type": "container",
"attributes": {
// Here is your container number
"number": "OOLU-xxxx",
// Seal Numbers aren't always returned by the carrier.
"seal_number": null,
"created_at": "2020-09-13T19:16:47Z",
"equipment_type": "reefer",
"equipment_length": null,
"equipment_height": null,
"weight_in_lbs": 54807,
//currently no known fees; this list will expand.
"fees_at_pod_terminal": [],
"holds_at_pod_terminal": [],
// here is your last free day.
"pickup_lfd": "2020-09-17T07:00:00Z",
"pickup_appointment_at": null,
"availability_known": true,
"available_for_pickup": false,
"pod_arrived_at": "2020-09-13T22:05:00Z",
"pod_discharged_at": "2020-09-15T05:27:00Z",
"location_at_pod_terminal": "CC1-162-B-3(Deck)",
"final_destination_full_out_at": null,
"pod_full_out_at": "2020-09-18T10:30:00Z",
"empty_terminated_at": null
},
"relationships": {
// linking back to the shipment object, found above.
"shipment": {
"data": {
"id": "894befec-e7e2-4e48-ab97-xxxxxxxxx",
"type": "shipment"
}
},
"pod_terminal": {
"data": {
"id": "39d09f18-cf98-445b-b6dc-xxxxxxxxx",
"type": "terminal"
}
},
...
}
},
...
```
# 4. How to Receive Status Updates
Source: https://terminal49.com/docs/api-docs/getting-started/receive-status-updates
## Using Webhooks to Receive Status Updates
Terminal49 posts status updates to a webhook that you register with us.
A Webhook is another name for a web-based callback URL, or a HTTP Push API. They provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API.
The HTTP Post request from Terminal49 has a JSON payload which you can parse to extract the relevant information.
## How do I use a Webhook with Terminal49?
First, you need to register a webhook. You can register as many webhooks as you like. Webhooks are associated with your account. All updates relating to that account are sent to the Webhook associated with it.
You can setup a new webhook by visiting [https://app.terminal49.com/developers/webhooks](https://app.terminal49.com/developers/webhooks) and clicking the 'Create Webhook Endpoint' button.

## Authentication
The API uses HTTP Bearer Token authentication.
This means you send your API Key as your token in every request.
Webhooks are associated with API tokens, and this is how the Terminal49 knows who to return relevant shipment information to.
## Anatomy of a Webhook Notification
Here's what you'll see in a Webhook Notification, which arrives as a POST request to your designated URL.
For more information, refer to the Webhook In Depth guide.
Note that for clarity I've deleted some of the data that is less useful right now, and replaced them with ellipses (...). Bolded areas are also mine to point out important data.
Note that there are two main sections:
**Data.** The core information being returned.
**Included**. Included are relevant objects that you are included for convenience.
```jsx
{
"data": {
"id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d",
"type": "webhook_notification",
"attributes": {
"id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d",
"event": "tracking_request.succeeded",
"delivery_status": "pending",
"created_at": "2020-09-13 14:46:37 UTC"
},
"relationships": {
...
}
},
"included":[
{
"id": "90873f19-f9e8-462d-b129-37e3d3b64c82",
"type": "tracking_request",
"attributes": {
"request_number": "MEDUNXXXXXX",
...
},
...
},
{
"id": "66db1d2a-eaa1-4f22-ba8d-0c41b051c411",
"type": "shipment",
"attributes": {
"created_at": "2020-09-13 14:46:36 UTC",
"bill_of_lading_number": "MEDUNXXXXXX",
"ref_numbers":[
null
],
"shipping_line_scac": "MSCU",
"shipping_line_name": "Mediterranean Shipping Company",
"port_of_lading_locode": "PLGDY",
"port_of_lading_name": "Gdynia",
....
},
"relationships": {
...
},
"links": {
"self": "/v2/shipments/66db1d2a-eaa1-4f22-ba8d-0c41b051c411"
}
},
{
"id": "4d556105-015e-4c75-94a9-59cb8c272148",
"type": "container",
"attributes": {
"number": "CRLUYYYYYY",
"seal_number": null,
"created_at": "2020-09-13 14:46:36 UTC",
"equipment_type": "reefer",
"equipment_length": 40,
"equipment_height": "high_cube",
...
},
"relationships": {
....
}
},
{
"id": "129b695c-c52f-48a0-9949-e2821813690e",
"type": "transport_event",
"attributes": {
"event": "container.transport.vessel_loaded",
"created_at": "2020-09-13 14:46:36 UTC",
"voyage_number": "032A",
"timestamp": "2020-08-07 06:57:00 UTC",
"location_locode": "PLGDY",
"timezone": "Europe/Warsaw"
},
...
}
]
}
```
> ### Why so much JSON? (A note on JSON API)
>
> The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly.
### What type of webhook event is this?
This is the first question you need to answer so your code can handle the webhook.
The type of update can be found in \["data"]\["attributes"].
The most common Webhook notifications are status updates on tracking requests, like **tracking\_request.succeeded** and updates on ETAs, shipment milestone, and terminal availability.
You can find what type of event you have received by looking at the "attributes", "event".
```jsx
"data" : {
...
"attributes": {
"id": "87d4f5e3-df7b-4725-85a3-b80acc572e5d",
"event": "tracking_request.succeeded",
"delivery_status": "pending",
"created_at": "2020-09-13 14:46:37 UTC"
},
}
```
### Inclusions: Tracking Requests & Shipment Data
When a tracking request has succeeded, the webhook event **includes** information about the shipment, the containers in the shipment, and the milestones for that container, so your app can present this information to your end users without making further queries to the API.
In the payload below (again, truncated by ellipses for clarity) you'll see a list of JSON objects in the "included" section. Each object has a **type** and **attributes**. The type tells you what the object is. The attributes tell you the data that the object carries.
Some objects have **relationships**. These are simply links to another object. The most essential objects in relationships are often included, but objects that don't change very often, for example an object that describes a teminal, are not included - once you query these, you should consider caching them locally.
```jsx
"included":[
{
"id": "90873f19-f9e8-462d-b129-37e3d3b64c82",
"type": "tracking_request",
"attributes" : {
...
}
},
{
"id": "66db1d2a-eaa1-4f22-ba8d-0c41b051c411",
"type": "shipment",
"attributes": {
"created_at": "2020-09-13 14:46:36 UTC",
"bill_of_lading_number": "MEDUNXXXXXX",
"ref_numbers":[
null
],
"shipping_line_scac": "MSCU",
"shipping_line_name": "Mediterranean Shipping Company",
"port_of_lading_locode": "PLGDY",
"port_of_lading_name": "Gdynia",
....
},
"relationships": {
...
},
"links": {
"self": "/v2/shipments/66db1d2a-eaa1-4f22-ba8d-0c41b051c411"
}
},
{
"id": "4d556105-015e-4c75-94a9-59cb8c272148",
"type": "container",
"attributes": {
"number": "CRLUYYYYYY",
"seal_number": null,
"created_at": "2020-09-13 14:46:36 UTC",
"equipment_type": "reefer",
"equipment_length": 40,
"equipment_height": "high_cube",
...
},
"relationships": {
....
}
},
{
"id": "129b695c-c52f-48a0-9949-e2821813690e",
"type": "transport_event",
"attributes": {
"event": "container.transport.vessel_loaded",
"created_at": "2020-09-13 14:46:36 UTC",
"voyage_number": "032A",
"timestamp": "2020-08-07 06:57:00 UTC",
"location_locode": "PLGDY",
"timezone": "Europe/Warsaw"
},
...
}
]
```
## Code Examples
### Registering a Webhook
```jsx
function registerWebhook(){
// Make a POST request with a JSON payload.
options = {
"method" : "POST"
"headers" : {
"content-type": "application/vnd.api+json",
"authorization" : "Token YOUR_API_KEY"
},
"payload" : {
"data": {
"type": "webhook",
"attributes": {
"url": "http://yourwebhookurl.com/webhook",
"active": true,
"events": ["tracking_request.succeeded"]
}
}
}
};
options.payload = JSON.stringify(data)
var response = UrlFetchApp.fetch('https://api.terminal49.com/v2/webhooks', options);
}
```
### Receiving a Post Webhook
Here's an example of some Javascript code that receives a Post request and parses out some of the desired data.
```
function receiveWebhook(postReq) {
try {
var json = postReq.postData.contents;
var webhook_raw = JSON.parse(json);
var webhook_data = webhook_raw["data"]
var notif_string = "";
if (webhook_data["type"] == "webhook_notification"){
if (webhook_data["attributes"]["event"] == "shipment.estimated.arrival"){
/* the webhook "event" attribute tell us what event we are being notified
* about. You will want to write a code path for each event type. */
var webhook_included = webhook_raw["included"];
// from the list of included objects, extract the information about the ETA update. This should be singleton.
var etas = webhook_included.filter(isEstimatedEvent);
// from the same list, extract the tracking Request information. This should be singleton.
var trackingReqs = webhook_included.filter(isTrackingRequest);
if(etas.length > 0 && trackingReqs.length > 0){
// therethis is an ETA updated for a specific tracking request.
notif_string = "Estimated Event Update: " + etas[0]["attributes"]["event"] + " New Time: " + etas[0]["attributes"]["estimated_timestamp"];
notif_string += " for Tracking Request: " + trackingReqs[0]["attributes"]["request_number"] + " Status: " + trackingReqs[0]["attributes"]["status"];
} else {
// this is a webhook type we haven't written handling code for.
notif_string = "Error. Webhook Returned Unexpected Data.";
}
if (webhook_data["attributes"]["event"] == "shipment.estimated.arrival"){
}
}
return HtmlService.createHtmlOutput(notf_string);
} catch (error){
return HtmlService.createHtmlOutput("Webhook failed: " + error);
}
}
// JS helper functions to filter events of certain types.
function isEstimatedEvent(item){
return item["type"] == "estimated_event";
}
function isTrackingRequest(item){
return item["type"] == "tracking_request";
}
```
## Try It Out & See More Sample Code
Update your API key below, and register a simple Webhook.
View the "Code Generation" button to see sample code.
```json http
{
"method": "post",
"url": "https://api.terminal49.com/v2/webhooks",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
},
"body": "{\r\n \"data\": {\r\n \"type\": \"webhook\",\r\n \"attributes\": {\r\n \"url\": \"https:\/\/webhook.site\/\",\r\n \"active\": true,\r\n \"events\": [\r\n \"tracking_request.succeeded\"\r\n ]\r\n }\r\n }\r\n}"
}
```
# 1. Start Here
Source: https://terminal49.com/docs/api-docs/getting-started/start-here
So you want to start tracking your ocean shipments and containers and you have a few BL numbers. Follow the guide.
Our API responses use [JSONAPI](https://jsonapi.org/) schema. There are [client libraries](https://jsonapi.org/implementations/#client-libraries) available in almost every language. Our API should work with these libs out of the box.
Our APIs can be used with any HTTP client; choose your favorite! We love Postman, it's a friendly graphical interface to a powerful cross-platform HTTP client. Best of all it has support for the OpenAPI specs that we publish with all our APIs. We have created a collection of requests for you to easily test the API endpoints with your API Key. Link to the collection below.
**Run in Postman**
***
## Get an API Key
Sign in to your Terminal49 account and go to your [developer portal](https://app.terminal49.com/developers/api-keys) page to get your API key.
### Authentication
When passing your API key it should be prefixed with `Token`. For example, if your API Key is 'ABC123' then your Authorization header would look like:
```
"Authorization": "Token ABC123"
```
# 2. Tracking Shipments & Containers
Source: https://terminal49.com/docs/api-docs/getting-started/tracking-shipments-and-containers
Submitting a tracking request is how you tell Terminal49 to track a shipment for you.
## What is a Tracking Request?
Your tracking request includes two pieces of data:
* Your Bill of Lading, Booking number, or container number from the carrier.
* The SCAC code for that carrier.
You can see a complete list of supported SCACs in row 2 of the Carrier Data Matrix.
## What sort of numbers can I track?
**Supported numbers**
1. Master Bill of Lading from the carrier (recommended)
2. Booking number from the carrier
3. Container number
* Container number tracking support across ocean carriers is sometimes more limited. Please refer to the Carrier Data Matrix to see which SCACs are compatible with Container number tracking.
**Unsupported numbers**
* House Bill of Lading numbers (HBOL)
* Customs entry numbers
* Seal numbers
* Internally generated numbers, for example PO numbers or customer reference numbers.
## How do I use Tracking Requests?
Terminal49 is an event-based API, which means that the API can be used asynchronously. In general the data flow is:
1. You send a tracking request to the API with your Bill of Lading number and SCAC.
2. The API will respond that it has successfully received your Tracking Request and return the Shipment's data that is available at that time.
3. After you have submitted a tracking request, the shipment and all of the shipments containers are tracked automatically by Terminal49.
4. You will be updated when anything changes or more data becomes available. Terminal49 sends updates relating to your shipment via posts to the webhook you have registered. Generally speaking, updates occur when containers reach milestones. ETA updates can happen at any time. As the ship approaches port, you will begin to receive Terminal Availability data, Last Free day, and so forth.
5. At any time, you can directly request a list of shipments and containers from Terminal49, and the API will return current statuses and information. This is covered in a different guide.
## How do you send me the data relating to the tracking request?
You have two options. First, you can poll for updates. This is the way we'll show you first.
You can poll the `GET /tracking_request/{id}` endpoint to see the status of your request. You just need to track the ID of your tracking request, which is returned to you by the API.
Second option is that you can register a webhook and the API will post updates when they happen. This is more efficient and therefore preferred. But it also requires some work to set up.
A Webhook is another name for a web-based callback URL, or a HTTP Push API. Webhooks provide a method for an API to post a notification to your service. Specifically, a webhook is simply a URL that can receive HTTP Post Requests from the Terminal49 API.
When we successfully lookup the Bill of Lading with the Carrier's SCAC, we will create a shipment, and send the event `tracking_request.succeeded` to your webhook endpoint with the associated record.
If we encounter a problem we'll send the event `tracking_request.failed`.

## Authentication
The API uses Bearer Token style authentication. This means you send your API Key as your token in every request.
To get your API token to Terminal49 and go to your [account API settings](https://app.terminal49.com/settings/api)
The token should be sent with each API request in the Authentication header:
Support [dev@terminal49.com](dev@terminal49.com)
```
Authorization: Token YOUR_API_KEY
```
## How to Create a Tracking Request
Here is javascript code that demonstates sending a tracking request
```json
fetch("https://api.terminal49.com/v2/tracking_requests", {
"method": "POST",
"headers": {
"content-type": "application/vnd.api+json",
"authorization": "Token YOUR_API_KEY"
},
"body": {
"data": {
"attributes": {
"request_type": "bill_of_lading",
"request_number": "",
"scac": ""
},
"type": "tracking_request"
}
}
})
.then(response => {
console.log(response);
})
.catch(err => {
console.error(err);
});
```
## Anatomy of a Tracking Request Response
Here's what you'll see in a Response to a tracking request.
```json
{
"data": {
"id": "478cd7c4-a603-4bdf-84d5-3341c37c43a3",
"type": "tracking_request",
"attributes": {
"request_number": "xxxxxx",
"request_type": "bill_of_lading",
"scac": "MAEU",
"ref_numbers": [],
"created_at": "2020-09-17T16:13:30Z",
"updated_at": "2020-09-17T17:13:30Z",
"status": "pending",
"failed_reason": null,
"is_retrying": false,
"retry_count": null
},
"relationships": {
"tracked_object": {
"data": null
}
},
"links": {
"self": "/v2/tracking_requests/478cd7c4-a603-4bdf-84d5-3341c37c43a3"
}
}
}
```
Note that if you try to track the same shipment, you will receive an error like this:
```json
{
"errors": [
{
"status": "422",
"source": {
"pointer": "/data/attributes/request_number"
},
"title": "Unprocessable Entity",
"detail": "Request number 'xxxxxxx' with scac 'MAEU' already exists in a tracking_request with a pending or created status",
"code": "duplicate"
}
]
}
```
**Why so much JSON? (A note on JSON API)**
The Terminal49 API is JSON API compliant, which means that there are nifty libraries which can translate JSON into a fully fledged object model that can be used with an ORM. This is very powerful, but it also requires a larger, more structured payload to power the framework. The tradeoff, therefore, is that it's less convenient if you're parsing the JSON directly. Ultimately we strongly recommend you set yourself up with a good library to use JSON API to its fullest extent. But for the purposes of understanding the API's fundamentals and getting your feet wet, we'll work with the data directly.
## Try It: Make a Tracking Request
Try it using the request maker below!
1. Enter your API token in the autorization header value.
2. Enter a value for the `request_number` and `scac`. The request number has to be a shipping line booking or master bill of lading number. The SCAC has to be a shipping line scac (see data sources to get a list of valid SCACs)
Note that you can also access sample code in multiple languages by clicking the "Code Generation" below.
**Tracking Request Troubleshooting**
The most common issue people encounter is that they are entering the wrong number.
Please check that you are entering the Bill of Lading number, booking number, or container number and not internal reference at your company or by your frieght forwarder. You can the number you are supplying by going to a carrier's website and using their tools to track your shipment using the request number. If this works, and if the SCAC is supported by T49, you should able to track it with us.
It is entirely possible that's neither us nor you but the shipping line is giving us a headache. Temporary network problems, not populated manifest and other things happen! You can read on how are we handling them in the [Tracking Request Retrying](/api-docs/useful-info/tracking-request-retrying) section.
You can always email us at [support@terminal49.com](mailto:support@terminal49.com) if you have persistent issues.
```json
{
"method": "post",
"url": "https://api.terminal49.com/v2/tracking_requests",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
},
"body": "{\r\n \"data\": {\r\n \"attributes\": {\r\n \"request_type\": \"bill_of_lading\",\r\n \"request_number\": \"\",\r\n \"scac\": \"\"\r\n },\r\n \"type\": \"tracking_request\"\r\n }\r\n}"
}
```
## Try It: List Your Active Tracking Requests
We have not yet set up a webook to receive status updates from the Terminal49 API, so we will need to manually poll to check if the Tracking Request has succeeded or failed.
**Try it below. Click "Headers" and replace `` with your API key.**
```json
{
"method": "get",
"url": "https://api.terminal49.com/v2/tracking_requests",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
## Next Up: Get your Shipments
Now that you've made a tracking request, let's see how you can list your shipments and retrieve the relevant data.
Go to this [page](https://help.terminal49.com/en/articles/8074102-how-to-initiate-shipment-tracking-on-terminal49) to see different ways of initiating shipment tracking on Terminal49.
# How to add a Customer to a Tracking Request?
Source: https://terminal49.com/docs/api-docs/in-depth-guides/adding-customer
## Why you would want to add a party to a tracking request?
Adding a party to a tracking request allows you to associate customer information with the tracking request. The customer added to the tracking request will be assigned to the shipment when it is created, just like reference numbers and tags. This can help in organizing and managing your shipments more effectively.
## How to get the party ID?
You can either find an existing party or create a new one.
* To find an existing party, jump to [Listing all parties](#listing-all-parties) section.
* To create a new party, jump to [Adding party for a customer](#adding-party-for-a-customer) section.
## Listing all parties
You can list all parties associated with your account through the [API](/api-docs/api-reference/parties/list-parties).
Endpoint: **GET** - [https://api.terminal49.com/v2/parties](/api-docs/api-reference/parties/list-parties)
```json Response
{
"data": [
{
"id": "PARTY_ID_1",
"type": "party",
"attributes": {
"company_name": "COMPANY NAME 1",
}
},
{
"id": "PARTY_ID_2",
"type": "party",
"attributes": {
"company_name": "COMPANY NAME 2",
}
}
],
"links": {
"last": "",
"next": "",
"prev": "",
"first": "",
"self": ""
},
"meta": {
"size": 2,
"total": 2
}
}
```
After you get all the parties you would filter the parties by `company_name` to find the correct ID, either by looking through the list manually or using code to automate the process.
## How to add party to tracking request if you have the party ID?
To add a customer to a tracking request, you need to add the party to the tracking request as a customer relationship while being created. **Note** that a party cannot be added to a tracking request that has already been created.
Endpoint: **POST** - [https://api.terminal49.com/v2/tracking\_requests](/api-docs/api-reference/tracking-requests/create-a-tracking-request)
```json Request
{
"data": {
"type": "tracking_request",
"attributes": {
"request_type": "bill_of_lading",
"request_number": "MEDUFR030802",
"ref_numbers": [
"PO12345",
"HBL12345",
"CUSREF1234"
],
"shipment_tags": [
"camembert"
],
"scac": "MSCU"
},
"relationships": {
"customer": {
"data": {
"id": "PARTY_ID",
"type": "party"
}
}
}
}
}
```
After you send a **POST** request to create a tracking request, you will receive a response with the Tracking Request ID and customer relationship. You can use this tracking request ID to track the shipment.
```json Response
{
"data": {
"id": "TRACKING_REQUEST_ID",
"type": "tracking_request",
"attributes": {
"request_type": "bill_of_lading",
"request_number": "MEDUFR030802",
"ref_numbers": [
"PO12345",
"HBL12345",
"CUSREF1234"
],
"shipment_tags": [
"camembert"
],
"scac": "MSCU"
},
"relationships": {
"tracked_object": {
"data": null
},
"customer": {
"data": {
"id": "PARTY_ID",
"type": "party"
}
}
},
"links": {
"self": "/v2/tracking_requests/TRACKING_REQUEST_ID"
}
}
}
```
## Adding party for a customer
For adding a customer to a tracking request, you need to create a party first. You can create a party through the [API](/api-docs/api-reference/parties/create-a-party).
Endpoint: **POST** - [https://api.terminal49.com/v2/parties](/api-docs/api-reference/parties/create-a-party)
```json Request
{
"data": {
"type": "party",
"attributes": {
"company_name": "COMPANY NAME"
}
}
}
```
After you send a **POST** request to create a party, you will receive a response with the Party ID. You can use this Party ID to add the customer to a tracking request.
```json Response
{
"data": {
"id": "PARTY_ID",
"type": "party",
"attributes": {
"company_name": "COMPANY NAME"
}
}
}
```
## Editing a party
You can update existing parties through the [API](/api-docs/api-reference/parties/edit-a-party).
Endpoint: **PATCH** - [https://api.terminal49.com/v2/parties/PARTY\_ID](/api-docs/api-reference/parties/edit-a-party)
## Reading a party
You can retrieve the details of an existing party through the [API](/api-docs/api-reference/parties/get-a-party).
Endpoint: **GET** - [https://api.terminal49.com/v2/parties/PARTY\_ID](/api-docs/api-reference/parties/get-a-party)
# Event Timestamps
Source: https://terminal49.com/docs/api-docs/in-depth-guides/event-timestamps
Through the typical container lifecycle events occur across multiple timezones. Wheverever you see a timestamp for some kind of transporation event, there should be a corresponding [IANA tz](https://www.iana.org/time-zones).
Event timestamps are stored and returned in UTC. If you wish to present them in the local time you need to convert that UTC timestamp using the corresponding timezone.
### Example
If you receive a container model with the attributes
```
'pod_arrived_at': '2022-12-22T07:00:00Z',
'pod_timezone': 'America/Los_Angeles',
```
then the local time of the `pod_arrived_at` timestamp would be `2022-12-21T23:00:00 PST -08:00`
## When the corresponding timezone is null
When there is event that occurs where Terminal49 cannot determine the location (and therefore the timezone) of the event the system is unable to store the event in true UTC.
In this scenario we take timestamp as given from the source and parse it in UTC.
### Example
```
'pod_arrived_at': '2022-12-22T07:00:00Z',
'pod_timezone': null,
```
then the local time of the `pod_arrived_at` timestamp would be `2022-12-22T07:00:00` and the timezone is unknown. (Assuming the source was returning localized timestamps)
## System Timestamps
Timestamps representing changes within the Terminal49 system (e.g. `created_at`, `updated_at`, `terminal_checked_at`) are stored and represented in UTC and do not have a TimeZone.
# Including Resources
Source: https://terminal49.com/docs/api-docs/in-depth-guides/including-resources
Throughout the documentation you will notice that many of the endpoints include a `relationships` object inside of the `data` attribute.
For example, if you are [requesting a container](/api/4c6091811c4e0-get-a-container) the relationships will include `shipment`, and possibly `pod_terminal` and `transport_events`
If you want to load the `shipment` and `pod_terminal` without making any additional requests you can add the query parameter `include` and provide a comma delimited list of the related resources:
```
containers/{id}?include=shipment,pod_terminal
```
You can even traverse the relationships up or down. For example if you wanted to know the port of lading for the container you could get that with:
```
containers/{id}?include=shipment,shipment.port_of_lading
```
# Quick Start Guide
Source: https://terminal49.com/docs/api-docs/in-depth-guides/quickstart
## Before You Begin
You'll need a four things to get started.
1. **A Bill of Lading (BOL) number.** This is issued by your carrier. BOL numbers are found on your [bill of lading](https://en.wikipedia.org/wiki/Bill_of_lading) document. Ideally, this will be a shipment that is currently on the water or in terminal, but this is not necessary.
2. **The SCAC of the carrier that issued your bill of lading.** The Standard Carrier Alpha Code of your carrier is used to identify carriers in computer systems and in shipping documents. You can learn more about these [here](https://en.wikipedia.org/wiki/Standard_Carrier_Alpha_Code).
3. **A Terminal49 Account.** If you don't have one yet, [sign up here.](https://app.terminal49.com/register)
4. **An API key.** Sign in to your Terminal49 account and go to your [developer portal page](https://app.terminal49.com/developers) to get your API key.
## Track a Shipment
You can try this using the embedded request maker below, or using Postman.
1. Try it below. Click "Headers" and replace YOUR\_API\_KEY with your API key. In the authorization header value.
2. Enter a value for the `request_number` and `scac`. The request number has to be a shipping line booking or master bill of lading number. The SCAC has to be a shipping line scac (see data sources to get a list of valid SCACs)
Note that you can also access sample code, include a cURL template, by clicking the "Code Generation" tab in the Request Maker.
```json http
{
"method": "post",
"url": "https://api.terminal49.com/v2/tracking_requests",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
},
"body": "{\r\n \"data\": {\r\n \"attributes\": {\r\n \"request_type\": \"bill_of_lading\",\r\n \"request_number\": \"\",\r\n \"scac\": \"\"\r\n },\r\n \"type\": \"tracking_request\"\r\n }\r\n}"
}
```
## Check Your Tracking Request Succeeded
We have not yet set up a webook to receive status updates from the Terminal49 API, so we will need to manually poll to check if the Tracking Request has succeeded or failed.
> ### Tracking Request Troubleshooting
>
> The most common issue people encounter is that they are entering the wrong number.
>
> Please check that you are entering the Bill of Lading number, booking number, or container number and not internal reference at your company or by your frieght forwarder. You can the number you are supplying by going to a carrier's website and using their tools to track your shipment using the request number. If this works, and if the SCAC is supported by T49, you should able to track it with us.
>
> You can always email us at [support@terminal49.com](mailto:support@terminal49.com) if you have persistent issues.
\*\* Try it below. Click "Headers" and replace `` with your API key.\*\*
```json http
{
"method": "get",
"url": "https://api.terminal49.com/v2/tracking_requests",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
## List your Tracked Shipments
If your tracking request was successful, you will now be able to list your tracked shipments.
**Try it below. Click "Headers" and replace YOUR\_API\_KEY with your API key.**
Sometimes it may take a while for the tracking request to show up, but usually no more than a few minutes.
If you had trouble adding your first shipment, try adding a few more.
```json http
{
"method": "get",
"url": "https://api.terminal49.com/v2/shipments",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
## List all your Tracked Containers
You can also list out all of your containers, if you'd like to track at that level.
Try it after replacing `` with your API key.
```json http
{
"method": "get",
"url": "https://api.terminal49.com/v2/containers",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
}
}
```
## Listening for Updates with Webhooks
The true power of Terminal49's API is that it is asynchronous. You can register a Webhook, which is essentially a callback URL that our systems HTTP Post to when there are updates.
To try this, you will need to first set up a URL on the open web to receive POST requests. Once you have done this, you'll be able to receive status updates from containers and shipments as they happen, which means you don't need to poll us for updates; we'll notify you.
\*\* Try it below. Click "Headers" and replace YOUR\_API\_KEY with your API key.\*\*
Once this is done, any changes to shipments and containers you're tracking in step 2 will now be sent to your webhook URL as Http POST Requests.
View the "Code Generation" button to see sample code.
```json http
{
"method": "post",
"url": "https://api.terminal49.com/v2/webhooks",
"headers": {
"Content-Type": "application/vnd.api+json",
"Authorization": "Token YOUR_API_KEY"
},
"body": "{\r\n \"data\": {\r\n \"type\": \"webhook\",\r\n \"attributes\": {\r\n \"url\": \"https:\/\/webhook.site\/\",\r\n \"active\": true,\r\n \"events\": [\r\n \"*\"\r\n ]\r\n }\r\n }\r\n}"
}
```
# Integrate Rail Container Tracking Data
Source: https://terminal49.com/docs/api-docs/in-depth-guides/rail-integration-guide
This guide provides a comprehensive, step-by-step approach for integrating North American Class-1 rail container tracking data into your systems. Whether you are a shipper or a logistics service provider, this guide will help you track all your rail containers via a single API.
This is a technical article about rail data within Terminal49's API and DataSync.
For a broader overview, including the reasons why you'd want rail visibility and how to use it in the Terminal49 dashboard,
[read our announcement post](https://www.terminal49.com/blog/launching-north-american-intermodal-rail-visibility-on-terminal49/).
## Table of Contents
* [Supported Rail Carriers](#supported-rail-carriers)
* [Supported Rail Events and Data Attributes](#supported-rail-events-and-data-attributes)
* [Rail-specific Transport Events](#rail-specific-transport-events)
* [Webhook Notifications](#webhook-notifications)
* [Rail Container Attributes](#rail-container-attributes)
* [Integration Methods](#integration-methods)
* [Integration via API](#a-integration-via-api)
* [Integration via DataSync](#b-integration-via-datasync)
## Supported Rail Carriers
Terminal49 container tracking platform integrates with all North American Class-1 railroads that handle container shipping, providing comprehensive visibility into your rail container movements.
* BNSF Railway
* Canadian National Railway (CN)
* Canadian Pacific Railway (CP)
* CSX Transportation
* Norfolk Southern Railway (NS)
* Union Pacific Railroad (UP)
By integrating with these carriers, Terminal49 ensures that you have direct access to critical tracking data, enabling better decision-making and operational efficiency.
## Supported Rail Events and Data Attributes
Terminal49 seamlessly tracks your containers as they go from container ship, to ocean terminal, to rail carrier.
We provide a [set of Transport Events](#webhook-notifications) that let you track the status of your containers as they move through the rail system. You can be notified by webhook whenever these events occur.
We also provide a set of attributes [on the container model](/api-docs/api-reference/containers/get-a-container) that let you know the current status of your container at any given time, as well as useful information such as ETA, pickup facility, and availability information.
### Rail-Specific Transport Events
There are several core Transport Events that occur on most rail journeys. Some rail carriers do not share all events, but in general these are the key events for a container.
```mermaid
graph LR
A[Rail Loaded] --> B[Rail Departed]
B --> C[Arrived at Inland Destination]
C --> D[Rail Unloaded]
D --> G[Available for Pickup]
G --> E[Full Out]
E --> F[Empty Return]
```
`Available for Pickup`, `Full Out` and `Empty Return` are not specific to rail, but are included here since they are a key part of the rail journey.
{/* However, not all shipments have a simple journey. You may want to track events such as when a train passes through a town, when the container switches trains or rail carriers at an interchange, and when the status of your container at the terminal changes.
This is a more complex diagram that shows the various events that can occur in a container's rail journey.
```mermaid
graph LR
Y[From ocean terminal or carrier] --> A
Z[Rail Interchange Received] --> B
A[Rail Loaded] --> B[Rail Departed]
B --> L[Train Passing]
B --> C[Rail Arrived]
L --> L
L --> C
C --> D[Rail Unloaded]
C --> X[Rail Interchange Delivered]
X --> Z
D --> G[Available for Pickup]
D --> H[Not Available]
H -- Holds and Fees Updated --> H
G --> H
H --> G
G --> E[Full Out]
E --> F[Empty Return]
``` */}
{/* ```mermaid
graph LR
C[Previous events] --> D[Rail Unloaded]
D --> G[Available for Pickup]
D --> H[Not Available]
G --> H
H --> G
H -- Holds and Fees Updated --> H
G --> E[Full Out]
``` */}
### Webhook Notifications
Terminal49 provides webhook notifications to keep you updated on key Transport Events in a container's rail journey. These notifications allow you to integrate near real-time tracking data directly into your applications.
Here's a list of the rail-specific events which support webhook notifications:
| Transport Event | Webhook Notification | Description | Example |
| ----------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Rail Loaded | `container.transport.rail_loaded` | The container is loaded onto a railcar. | Example |
| Rail Departed | `container.transport.rail_departed` | The container departs on the railcar (not always from port of discharge). | Example |
| Rail Arrived | `container.transport.rail_arrived` | The container arrives at a rail terminal (not always at the destination terminal). | Example |
| Arrived At Inland Destination | `container.transport.arrived_at_inland_destination` | The container arrives at the destination terminal. | Example |
| Rail Unloaded | `container.transport.rail_unloaded` | The container is unloaded from a railcar. | Example |
There's also a set of events that are triggered when the status of the container at the destination rail terminal changes. For containers without rail, they would have been triggered at the ocean terminal.
| Transport Event | Webhook Notification | Description | Example |
| --------------- | ------------------------------ | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Full Out | `container.transport.full_out` | The full container leaves the rail terminal. | Example |
| Empty In | `container.transport.empty_in` | The empty container is returned to the terminal. | Example |
Finally, we have a webhook notifications for when the destination ETA changes.
| Transport Event | Webhook Notification | Description | Example |
| ----------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Estimated Destination Arrival | `container.transport.estimated.arrived_at_inland_destination` | Estimated time of arrival for the container at the destination rail terminal. | Example |
Integrate these notifications by subscribing to the webhooks and handling the incoming data to update your systems.
#### Rail Container Attributes
The following are new attributes that are specific to rail container tracking.
* **pod\_rail\_loaded\_at**: Time when the container is loaded onto a railcar at the POD.
* **pod\_rail\_departed\_at**: Time when the container departs from the POD.
* **ind\_eta\_at**: Estimated Time of Arrival at the inland destination.
* **ind\_ata\_at**: Actual Time of Arrival at the inland destination.
* **ind\_rail\_unloaded\_at**: Time when the container is unloaded from rail at the inland destination.
* **ind\_facility\_lfd\_on**: Last Free Day for demurrage charges at the inland destination terminal.
* **pod\_rail\_carrier\_scac**: SCAC code of the rail carrier that picks up the container from the POD (this could be different than the rail carrier that delivers to the inland destination).
* **ind\_rail\_carrier\_scac**: SCAC code of the rail carrier that delivers the container to the inland destination.
{/* TODO: Look at the other container attributes that could be fed via rail but are currently shipping-line-only. Such as :current_issues, :pickup_appointment_at, :availability_known, :available_for_pickup */}
These attributes can be found on [container objects](/api-docs/api-reference/containers/get-a-container).
## Integration Methods
There are two methods to integrate Terminal49's rail tracking data programmatically: via API and DataSync.
### A. Integration via API
Terminal49 provides a robust API that allows you to programmatically access rail container tracking data and receive updates via webhooks. You will receive rail events and attributes alongside events and attributes from the ocean terminal and carrier.
[Here's a step-by-step guide to get started](/api-docs/getting-started/start-here)
### B. Integration via DataSync
Terminal49's DataSync service automatically syncs up-to-date tracking data with your system. The rail data will be in the same tables alongside the ocean terminal and carrier data.
[Learn more about DataSync](/datasync/overview)
# Vessel and Container Route Data
Source: https://terminal49.com/docs/api-docs/in-depth-guides/routing
This guide explains how to access detailed container routes and vessel positions data (historical and future positions) using Terminal49 APIs.
This is a technical article describing how to use our Routing Data feature, using the map as an example.
Routing Data (Container Route and Vessel Positions APIs) is a paid feature. These APIs are subject to additional terms of usage and pricing. If you are interested in using these APIs, please contact [sales@terminal49.com](mailto:sales@terminal49.com).
## Table of Contents
* [Overview of APIs for Mapping](#overview-of-apis-for-mapping)
* [Visualizing Your Container's Journey on a Map](#visualizing-your-container’s-journey-on-a-map)
* [Step 1: Plotting Port Locations](#step-1%3A-plotting-port-locations)
* [Step 2: Drawing Historical Vessel Paths (Actual Route Taken)](#step-2%3A-drawing-historical-vessel-paths-actual-route-taken)
* [Step 3: Drawing Predicted Future Vessel Paths](#step-3%3A-drawing-predicted-future-vessel-paths)
* [Using `GET /v2/vessels/{id}/future_positions_with_coordinates`](#using-get-%2Fv2%2Fvessels%2F%7Bid%7D%2Ffuture-positions-with-coordinates-for-vessels-currently-en-route)
* [Using `GET /v2/vessels/{id}/future_positions`](#using-get-%2Fv2%2Fvessels%2F%7Bid%7D%2Ffuture-positions-with-coordinates-for-vessels-currently-en-route)
* [Combining Data for a Complete Map](#combining-data-for-a-complete-map)
* [Use Cases](#use-cases)
* [Recommendations and Best Practices](#recommendations-and-best-practices)
* [Frequently Asked Questions](#frequently-asked-questions)
## Overview of APIs for Mapping
Terminal49 offers a suite of powerful APIs to provide granular details about your container shipments and vessel locations.
Two key components are:
* **Container Route API:** Offers detailed information about each part of your container's journey, including port locations (latitude, longitude), vessels involved, and key timestamps. This is foundational for placing port markers on your map.
* **Vessel Positions API:** Provides access to historical and predicted future positions for the vessels.
## Visualizing Your Container's Journey on a Map
To create a map visualization of a container's journey (similar to [the embeddable map](/api-docs/in-depth-guides/terminal49-map)), you'll typically combine data from several API endpoints. Here’s a step-by-step approach:
### Step 1: Plotting Port Locations
First, retrieve the overall route for the container. This will give you the sequence of ports the container will visit, along with their geographical coordinates.
Use the `GET /v2/containers/{id}/route` endpoint. (See: [Get Container Route API Reference](/api-docs/api-reference/containers/get-container-route))

```shell Request
curl --request GET \
--url https://api.terminal49.com/v2/containers/ae1c0b10-3ec2-4292-a95a-483cd2755433/route \
--header "Authorization: Token YOUR_API_TOKEN"
```
```json
{
"data": {
"id": "0a14f30f-f63b-4112-9aad-f52e3a1d9bdf",
"type": "route",
"relationships": {
"route_locations": {
"data": [
{ "id": "c781a624-a3bd-429a-85dd-9179c61eb57f", "type": "route_location" }, // POL: Pipavav
{ "id": "92258580-8706-478e-a6dc-24e11f972507", "type": "route_location" }, // TS1: Jebel Ali
{ "id": "7b6cc511-43f4-4037-9bdd-b0fe5fc0df8f", "type": "route_location" } // TS2: Colombo
// ... more route locations
]
}
}
},
"included": [
{
"id": "4115233f-10b7-4774-ad60-34c100b23760", // Matches a route_location's location data id
"type": "port",
"attributes": {
"name": "Pipavav (Victor) Port",
"code": "INPAV",
"latitude": "20.921010675",
"longitude": "71.509579681"
}
},
{
"id": "94892d07-ef8f-4f76-a860-97a398c2c177",
"type": "port",
"attributes": {
"name": "Jebel Ali",
"code": "AEJEA",
"latitude": "24.987353081",
"longitude": "55.059917502"
}
},
// ... other included items like vessels, other ports, and full route_location objects
{
"id": "c781a624-a3bd-429a-85dd-9179c61eb57f", // This is a route_location object
"type": "route_location",
"attributes": { /* ... ATD/ETA times, vessel info ... */ },
"relationships": {
"location": { // This links to the port object in 'included'
"data": { "id": "4115233f-10b7-4774-ad60-34c100b23760", "type": "port" }
},
"outbound_vessel": {
"data": { "id": "b868eaf8-9065-4fbe-9e72-f6154246b3c5", "type": "vessel" }
}
}
}
]
}
```
**How to use:**
1. Parse the `data.relationships.route_locations.data` array to get the sequence of stops.
2. For each `route_location` object (found in `included` using its ID from the previous step), find its corresponding physical `location` (port) by looking up the `relationships.location.data.id` in the `included` array (where `type` is `port`).
3. Use the `latitude` and `longitude` from the port attributes to plot markers on your map (e.g., POL, TS1, TS2 as shown in the image).
4. Each `route_location` in `included` also contains valuable data like `outbound_atd_at`, `inbound_ata_at`, `outbound_vessel.id`, `inbound_vessel.id` etc., which you'll need for the next steps.
### Step 2: Drawing Historical Vessel Paths (Actual Route Taken)
For segments of the journey that have already been completed, you can draw the vessel's actual path using its historical positions.
Use the `GET /v2/vessels/{id}?show_positions[from_timestamp]={departure_time}&show_positions[to_timestamp]={arrival_time}` endpoint. (See: [Get Vessel Positions API Reference](/api-docs/api-reference/vessels/get-a-vessel-using-the-id)

```shell Request (Example for MAERSK BALTIMORE from Pipavav ATD to Jebel Ali ATA)
# Vessel ID: b868eaf8-9065-4fbe-9e72-f6154246b3c5
# Pipavav (POL) ATD: 2025-05-18T00:48:06Z (from route_location c781a624...)
# Jebel Ali (TS1) ATA: 2025-05-21T09:50:00Z (from route_location 92258580...)
curl --request GET \
--url 'https://api.terminal49.com/v2/vessels/b868eaf8-9065-4fbe-9e72-f6154246b3c5?show_positions[from_timestamp]=2025-05-18T00:48:06Z&show_positions[to_timestamp]=2025-05-21T09:50:00Z' \
--header "Authorization: Token YOUR_API_TOKEN"
```
```json
{
"data": {
"id": "b868eaf8-9065-4fbe-9e72-f6154246b3c5",
"type": "vessel",
"attributes": {
"name": "MAERSK BALTIMORE",
"positions": [
{ "latitude": 20.885, "longitude": 71.498333333, "heading": 195, "timestamp": "2025-05-18T00:48:06Z", "estimated": false },
// ... many more positions between the two ports
{ "latitude": 25.026021667, "longitude": 55.067638333, "heading": 259, "timestamp": "2025-05-21T09:38:07Z", "estimated": false }
]
}
}
}
```
**How to use:**
1. From the `/containers/{id}/route` response, for each completed leg (i.e., both ATD from origin and ATA at destination are known):
* Identify the `outbound_vessel.data.id` from the departure `route_location`.
* Use the `outbound_atd_at` (Actual Time of Departure) from the departure `route_location` as the `from_timestamp`.
* Use the `inbound_ata_at` (Actual Time of Arrival) from the arrival `route_location` as the `to_timestamp`.
2. Call the `/vessels/{vessel_id}?show_positions...` endpoint with these details.
3. The `attributes.positions` array will contain a series of latitude/longitude coordinates. Plot these coordinates as a connected solid line on your map to represent the vessel's actual historical path for that leg (like the green line from POL to TS1 in the image).
### Step 3: Drawing Predicted Future Vessel Paths
For segments that are currently underway or planned for the future, you can display predicted vessel paths. These are typically shown as dashed lines.
#### Using `GET /v2/vessels/{id}/future_positions_with_coordinates` (For Vessels Currently En Route)
This endpoint is used when the vessel **is currently en route** between two ports (e.g., has departed Port A but not yet arrived at Port B). It requires the vessel's current coordinates as input, in addition to the port of departure and the port of arrival for the leg. The output is a predicted path from the vessel's current location to the destination port.
(See: [Get Vessel Future Positions with Coordinates API Reference](/api-docs/api-reference/vessels/get-vessel-future-positions-with-coordinates))

**How to use:**
1. **Determine if vessel is en route:** From the `/containers/{id}/route` response, check if the leg has an `outbound_atd_at` from the origin port but no `inbound_ata_at` at the destination port yet.
2. **Get Current Vessel Coordinates:**
* Identify the `outbound_vessel.data.id` from the departure `route_location`.
* Fetch the vessel's current details using `GET /v2/vessels/{vessel_id}`. The response will contain its latest `latitude`, `longitude`, and `position_timestamp` in the `attributes` section.
```shell Example: Fetch current vessel data
curl --request GET \
--url https://api.terminal49.com/v2/vessels/{vessel_id} \
--header "Authorization: Token YOUR_API_TOKEN"
```
```json
{
"data": {
"id": "50b58b30-acd6-45d3-a694-19664acb1518", // Example: TB QINGYUAN
"type": "vessel",
"attributes": {
"name": "TB QINGYUAN",
"latitude": 24.419361667, // Current latitude
"longitude": 58.567603333, // Current longitude
"position_timestamp": "2025-05-28T03:55:23Z"
// ... other attributes
}
}
}
```
3. **Call `future_positions_with_coordinates`:**
* Use the `location.data.id` of the original departure port for this leg (as `previous_port_id` or similar parameter, check API ref).
* Use the `location.data.id` of the final arrival port for this leg (as `port_id` or similar parameter).
* Include the fetched current `latitude` and `longitude` of the vessel in the request.
```shell Hypothetical Request (e.g., TB QINGYUAN en route from Jebel Ali to Colombo)
# Vessel ID: 50b58b30-acd6-45d3-a694-19664acb1518 (TB QINGYUAN)
# Original Departure Port (Jebel Ali) ID: 94892d07-ef8f-4f76-a860-97a398c2c177
# Final Arrival Port (Colombo) ID: 818ef299-aed3-49c9-b3f7-7ee205f697f6
# Current Coords (example): lat=24.4193, lon=58.5676
curl --request GET \
--url 'https://api.terminal49.com/v2/vessels/50b58b30-acd6-45d3-a694-19664acb1518/future_positions_with_coordinates?previous_port_id=94892d07-ef8f-4f76-a860-97a398c2c177&port_id=818ef299-aed3-49c9-b3f7-7ee205f697f6¤t_latitude=24.4193¤t_longitude=58.5676' \
--header "Authorization: Token YOUR_API_TOKEN"
```
```json
{
"data": {
"id": "50b58b30-acd6-45d3-a694-19664acb1518",
"type": "vessel",
"attributes": {
"name": "TB QINGYUAN",
"positions": [
// Path starts from near current_latitude, current_longitude
{ "latitude": 24.4193, "longitude": 58.5676, "timestamp": "...", "estimated": true },
// ... several intermediate estimated latitude/longitude points forming a path to Colombo
{ "latitude": 6.942742853, "longitude": 79.851136851, "timestamp": "...", "estimated": true } // Colombo
]
}
}
}
```
4. **Plot the path:** The `attributes.positions` array will provide a sequence of estimated coordinates starting from (or near) the vessel's current position. Plot these as a connected dashed line on your map (like the dashed line from the vessel's current position between TS1 and TS2, heading towards TS2 in the image).
#### Using `GET /v2/vessels/{id}/future_positions` (For Legs Not Yet Started)
This endpoint is used when the vessel **has not yet departed** from the origin port of a specific leg. It takes the origin port (Port A) and destination port (Port B) of the upcoming leg as input and predicts a path between them.
(See: [Get Vessel Future Positions API Reference](/api-docs/api-reference/vessels/get-vessel-future-positions))

**How to use:**
1. **Determine if leg has not started:** From the `/containers/{id}/route` response, check if the leg has no `outbound_atd_at` from the origin port (or `outbound_etd_at` is in the future).
2. **Identify vessel and ports:**
* Get the `outbound_vessel.data.id` that will perform this leg.
* Get the `location.data.id` of the departure port for this leg (as `previous_port_id`).
* Get the `location.data.id` of the arrival port for this leg (as `port_id`).
3. **Call `future_positions`:**
```shell Request (Example for CMA CGM COLUMBIA from Algeciras to Tanger Med - assuming not yet departed Algeciras)
# Vessel ID: 17189206-d585-4670-b6dd-0aa50fc30869 (CMA CGM COLUMBIA)
# Departure Port (Algeciras) ID: 0620b5e6-7621-408c-8b44-cf6f0d9a762c
# Arrival Port (Tanger Med) ID: f4ec11ea-8c5a-46f9-a213-9d976af04230
curl --request GET \
--url 'https://api.terminal49.com/v2/vessels/17189206-d585-4670-b6dd-0aa50fc30869/future_positions?port_id=f4ec11ea-8c5a-46f9-a213-9d976af04230&previous_port_id=0620b5e6-7621-408c-8b44-cf6f0d9a762c' \
--header "Authorization: Token YOUR_API_TOKEN"
```
```json
{
"data": {
"id": "17189206-d585-4670-b6dd-0aa50fc30869",
"type": "vessel",
"attributes": {
"name": "CMA CGM COLUMBIA",
"positions": [
// Path starts from Algeciras and goes to Tanger Med
{ "latitude": 36.142537873, "longitude": -5.438306296, "heading": null, "timestamp": "...", "estimated": true }, // Algeciras
// ... intermediate points
{ "latitude": 35.893832072, "longitude": -5.490968974, "heading": null, "timestamp": "...", "estimated": true } // Tanger Med
]
}
}
}
```
4. **Plot the path:** The `attributes.positions` array will provide estimated coordinates for the full leg. Plot these as a connected dashed line on your map (like the dashed line from TS3 to TS4 in the image, assuming the vessel is still at TS3).
### Combining Data for a Complete Map
By iterating through the `route_locations` obtained from the initial `/containers/{id}/route` call:
1. Plot all port markers (Step 1).
2. For each leg of the journey:
* If the leg is completed (ATD and ATA are known), use the historical vessel positions API to draw a solid line (Step 2).
* If the leg is in progress or planned for the future (ATD known or ETD known, but ATA is not yet known or is in the future), use one of the future vessel positions APIs to draw a dashed line (Step 3).
This approach allows you to build a comprehensive map view, dynamically showing completed paths with solid lines and future/in-progress paths with dashed lines, providing a clear visualization of the entire shipment journey.
## Use Cases
Integrating Terminal49's Vessel and Container Route APIs enables a variety of advanced capabilities:
* **Track Complete Shipment Journeys Visually:** Monitor shipments across multiple legs on a map, from the port of lading to the port of discharge, including all transshipment points.
* **Identify Transshipment Details Geographically:** Clearly see where transshipments occur and the routes taken between them.
* **Correlate Timestamps with Locations:** Visually connect ETDs, ETAs, ATDs, and ATAs for every leg with their geographical points on the map for precise planning and exception management.
* **Improve Internal Logistics Dashboards:** Offer your operations team a clear visual overview of all ongoing shipments and their current locations.
## Recommendations and Best Practices
* **Polling Intervals:** For routing data and vessel positions we recommend refreshing up to once per hour.
* **Efficient Data Handling:** Cache previous vessel positions when possible, as it doesn't change. Focus polling on active vessel movements.
* **Error Handling:** Implement proper error handling for API requests, especially for future predictions which might not always be available for all routes or vessels.
If you decide to create your own map:
* **Data Layering:** Consider layering information on your map. Start with basic port markers and paths, then add details like vessel names, ETAs, or status on hover or click.
* **Map Library Integration:** Use a robust mapping library (e.g., Leaflet, Mapbox GL) to handle the rendering of markers, lines, and map interactivity.
## Frequently Asked Questions
**Q: How up-to-date is the vessel position data?**
A: Vessel location data is updated every 15 minutes, although that does not guarantee there will be a new position every 15 minutes to factors like whether the vessel is transmitting or within range of a satellite or base station.
**Q: How accurate are the future predictions?**
Predicted future positions are based on algorithms and current data, and their accuracy can vary based on many factors such as temporary deviations, seasonality, or how frequently the shipping lane is used.
**Q: What if a vessel deviates from the predicted path?**
A: Predicted paths are estimates. The historical path (once available) will show the actual route taken. Regularly refreshing data for active shipments is key to getting the most accurate information.
# Terminal49 Map Embed Guide
Source: https://terminal49.com/docs/api-docs/in-depth-guides/terminal49-map
The Terminal49 Map allows you to embed real-time visualized container tracking on your website with just a few lines of code.
### Prerequisites
* A Terminal49 account.
* A Publishable API key, you can get one by reaching out to us at [support@terminal49.com](mailto:support@terminal49.com).
* Familiarity with our [Shipments API](/api-docs/api-reference/shipments/list-shipments) and [Containers API](/api-docs/api-reference/containers/list-containers).
In the following examples we'll be passing a `containerId` and `shipmentId` variables to the embedded map.
They relate to `id` attributes of the container and shipment objects that are returned by the API.
### How do I embed the map on my website?
Once you have the API Key, you can embed the map on your website.
1. Copy and paste the code below and insert it on your website.
Once loaded, this will make the map code available through the global `window` object.
Just before the closing `` tag, add the following link tag to load the map styles.
```html
Document
```
Just before the closing `