From ed2995d4f9372c225e70ebc7b87f2d3d8f095be7 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:34:44 +0000 Subject: [PATCH] Documentation edits made through Mintlify web editor --- .../list-shipments-and-containers.mdx | 280 ++++++++++++++++++ api-docs/getting-started/start-here.mdx | 23 ++ .../tracking-shipments-and-containers.mdx | 216 ++++++++++++++ api-docs/in-depth-guides/event-timestamps.mdx | 36 +++ api-docs/in-depth-guides/webhooks.mdx | 277 +++++++++++++++++ .../api-data-sources-availability.mdx | 198 +++++++++++++ datasync/overview.mdx | 57 ++++ mint.json | 220 ++++++++++++++ 8 files changed, 1307 insertions(+) create mode 100644 api-docs/getting-started/list-shipments-and-containers.mdx create mode 100644 api-docs/getting-started/start-here.mdx create mode 100644 api-docs/getting-started/tracking-shipments-and-containers.mdx create mode 100644 api-docs/in-depth-guides/event-timestamps.mdx create mode 100644 api-docs/in-depth-guides/webhooks.mdx create mode 100644 api-docs/useful-info/api-data-sources-availability.mdx create mode 100644 datasync/overview.mdx create mode 100644 mint.json diff --git a/api-docs/getting-started/list-shipments-and-containers.mdx b/api-docs/getting-started/list-shipments-and-containers.mdx new file mode 100644 index 00000000..c61beb5b --- /dev/null +++ b/api-docs/getting-started/list-shipments-and-containers.mdx @@ -0,0 +1,280 @@ +--- +title: 3. List Your Shipments & 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 and 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 at 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 an 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 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, 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 Shipments in Google Sheets + +Below is code written in Google Apps Script that lists the current shipments into the current sheet of a spreadsheet. Google Apps Script is very similar to JavaScript. + +Because Google Apps 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 Apps 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 JavaScript you would use console.log(), but Google Apps Script uses Logger.log(). + Logger.log("error communicating with Terminal49 / 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" + } + }, + ... + } + }, + ... +``` \ No newline at end of file diff --git a/api-docs/getting-started/start-here.mdx b/api-docs/getting-started/start-here.mdx new file mode 100644 index 00000000..89186bf8 --- /dev/null +++ b/api-docs/getting-started/start-here.mdx @@ -0,0 +1,23 @@ +--- +title: 1. Start Here +--- +Want to start tracking your ocean shipments and containers with a few Bill of Lading (BL) numbers? Follow this guide to get started. + +Our API responses use the [JSON:API](https://jsonapi.org/) schema. There are [client libraries](https://jsonapi.org/implementations/#client-libraries) available in almost every programming language, and our API works with these libraries out of the box. + +You can use our APIs with any HTTP client - choose your favorite! We recommend Postman, which provides a friendly graphical interface to a powerful cross-platform HTTP client. Best of all, it supports the OpenAPI specifications that we publish with all our APIs. We've created a collection of requests to help you easily test the API endpoints with your API key. You can access the collection using the link below. + + +**Run in Postman** + + +*** +## Get an API Key +Sign in to your Terminal49 account and visit your [developer portal](https://app.terminal49.com/developers/api-keys) page to get your API key. + +### Authentication +When passing your API key, prefix it with `Token`. For example, if your API key is 'ABC123', your Authorization header should look like: + +``` +"Authorization": "Token ABC123" +``` \ No newline at end of file diff --git a/api-docs/getting-started/tracking-shipments-and-containers.mdx b/api-docs/getting-started/tracking-shipments-and-containers.mdx new file mode 100644 index 00000000..b954291e --- /dev/null +++ b/api-docs/getting-started/tracking-shipments-and-containers.mdx @@ -0,0 +1,216 @@ +--- +title: 2. Tracking Shipments & Containers +description: Submitting a tracking request is how you tell Terminal49 to track a shipment for you. +og:title: Tracking Shipments and Containers | Terminal49 API +og:description: Track shipments and containers with ease via Terminal49's API. Optimize your shipping processes with seamless tracking. +--- + +## What is a Tracking Request? +A tracking request requires two pieces of data: + + - A 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 SCAC codes 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 varies across ocean carriers. Please refer to the Carrier Data Matrix to see which SCAC codes are compatible with container number tracking. + +**Unsupported numbers** + + - House Bill of Lading numbers (HBOL) + - Customs entry numbers + - Seal numbers + - Internally generated numbers (e.g., PO numbers or customer reference numbers) + +## How do I use Tracking Requests? +Terminal49 provides an event-based API that operates asynchronously. Here's how the data flow works: + + 1. You send a tracking request to the API with your Bill of Lading number and SCAC code. + 2. The API acknowledges receipt of your tracking request and returns any immediately available shipment data. + 3. Terminal49 automatically begins tracking the shipment and all associated containers. + 4. You receive updates for any changes or new information via webhook notifications. Updates typically occur when: + - Containers reach milestones + - ETA changes (can happen at any time) + - Terminal availability data becomes available + - Last Free Day information is updated + 5. You can request current status information at any time by querying the list of shipments and containers from Terminal49. This process is covered in a different guide. + +## How do you receive tracking request data? +You have two options: + +1. **Poll for updates**: Query the `GET /tracking_request/{id}` endpoint to check your request's status. You'll need to keep track of the request ID returned by the API. + +2. **Use webhooks** (recommended): Register a webhook to receive automatic updates. While this requires initial setup, it's more efficient than polling. + +A webhook is a web-based callback URL (HTTP Push API) that allows Terminal49 to send notifications to your service. When we successfully locate the Bill of Lading with the carrier's SCAC code, we: +- Create a shipment +- Send a `tracking_request.succeeded` event to your webhook endpoint with the associated record + +If we encounter any issues, we send a `tracking_request.failed` event. + +![](/images/create-shipment-flow.png) + + +## Authentication +Terminal49's API uses Bearer Token authentication. Send your API key as a token with every request. + +To get your API token, visit your [Terminal49 account API settings](https://app.terminal49.com/settings/api). + +Include the token in each API request's Authentication header: + +Support: [dev@terminal49.com](dev@terminal49.com) + +``` +Authorization: Token YOUR_API_KEY +``` + +## How to Create a Tracking Request + +Here's a JavaScript example of sending a tracking request: +```javascript +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 an example 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", + "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" + } + } +} +``` + +If you attempt to track a shipment that's already being tracked, you'll receive this error: +```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, enabling libraries to translate JSON into a fully-fledged object model for ORM use. While this requires a larger, more structured payload, it provides powerful functionality. For direct JSON parsing, this might seem less convenient. We recommend using a JSON:API library to maximize the API's capabilities, but we'll work with direct JSON for these introductory examples. + + +## Try It: Make a Tracking Request +Use the request maker below to try creating a tracking request: + +1. Enter your API token in the authorization header value +2. Provide values for `request_number` and `scac`: + - The request number must be a shipping line booking or master bill of lading number + - The SCAC must be a valid shipping line SCAC code (see data sources for valid codes) + +You can access sample code in multiple languages by clicking "Code Generation" below. + + +**Tracking Request Troubleshooting** + +The most common issue is using incorrect tracking numbers. + +Verify that you're using the Bill of Lading number, booking number, or container number - not an internal reference number from your company or freight forwarder. Test the number on the carrier's website first. If it works there and the SCAC is supported by Terminal49, you should be able to track it with us. + +Sometimes issues arise from the shipping line's side due to temporary network problems or unpopulated manifests. We handle these situations as described in the [Tracking Request Retrying](/api-docs/useful-info/tracking-request-retrying) section. + + + +For persistent issues, contact us at support@terminal49.com. + + +```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 +Since we haven't set up webhooks yet, we'll need to manually poll to check tracking request status. + +**Try it below. Replace `YOUR_API_KEY` with your API key in the headers.** + +```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 created a tracking request, let's explore how to list your shipments and retrieve their data. + + +Visit this [page](https://help.terminal49.com/en/articles/8074102-how-to-initiate-shipment-tracking-on-terminal49) to learn about different ways to initiate shipment tracking on Terminal49. + \ No newline at end of file diff --git a/api-docs/in-depth-guides/event-timestamps.mdx b/api-docs/in-depth-guides/event-timestamps.mdx new file mode 100644 index 00000000..d4b01168 --- /dev/null +++ b/api-docs/in-depth-guides/event-timestamps.mdx @@ -0,0 +1,36 @@ +--- +title: Event Timestamps +og:title: Event Timestamps Guide | Terminal49 API Documentation +og:description: Learn how event timestamps are captured and processed through Terminal49's API for improved shipment visibility. +--- +Throughout a container's lifecycle, events occur across multiple time zones. Whenever you see a timestamp for a transportation event, there should be a corresponding [IANA Time Zone](https://www.iana.org/time-zones) identifier. + +Event timestamps are stored and returned in UTC (Coordinated Universal Time). If you wish to present them in local time, you need to convert the UTC timestamp using the corresponding time zone. + +### Example + +When you receive a container model with these attributes: +``` + 'pod_arrived_at': '2022-12-22T07:00:00Z', + 'pod_timezone': 'America/Los_Angeles', +``` +The local time of the `pod_arrived_at` timestamp would be `2022-12-21T23:00:00 PST -08:00`. + + +## When the Corresponding Time Zone is Null +When an event occurs where Terminal49 cannot determine the location (and therefore the time zone) of the event, the system is unable to store the event in true UTC. + +In this scenario, we take the timestamp as given from the source and parse it in UTC. + +### Example +If you receive: +``` + 'pod_arrived_at': '2022-12-22T07:00:00Z', + 'pod_timezone': null, +``` + +The time would remain as `2022-12-22T07:00:00` and the time zone would be unknown. (This assumes 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 an associated time zone. \ No newline at end of file diff --git a/api-docs/in-depth-guides/webhooks.mdx b/api-docs/in-depth-guides/webhooks.mdx new file mode 100644 index 00000000..30dd5622 --- /dev/null +++ b/api-docs/in-depth-guides/webhooks.mdx @@ -0,0 +1,277 @@ +--- +title: Webhooks +--- + +## Creating Webhooks +You can subscribe to events through webhooks to be alerted when events are triggered. + +Visit https://app.terminal49.com/developers/webhooks and click the 'Create Webhook Endpoint' button to create your webhook through the UI. + +If you prefer to create webhooks programmatically, see the [webhooks post endpoint documentation](/api-docs/api-reference/webhooks/create-a-webhook). + + +## Available Webhook Events + +Each `WebhookNotification` event represents a change to a model that may trigger a notification. + +List of Supported Events: + +Event | Description +---------|---------- + `tracking_request.succeeded` | A shipment has been created and linked to a `TrackingRequest` + `tracking_request.failed` | The `TrackingRequest` failed and no shipment was created + `tracking_request.awaiting_manifest` | The `TrackingRequest` is awaiting a manifest + `tracking_request.tracking_stopped` | Terminal49 is no longer updating this `TrackingRequest` + `container.transport.empty_out` | Container is empty out at port of lading + `container.transport.full_in` | Container is full in at port of lading + `container.transport.vessel_loaded` | Container is loaded onto vessel at port of lading + `container.transport.vessel_departed` | Vessel has departed from port of lading + `container.transport.transshipment_arrived` | Container has arrived at transshipment port + `container.transport.transshipment_discharged` | Container is discharged at transshipment port + `container.transport.transshipment_loaded` | Container is loaded at transshipment port + `container.transport.transshipment_departed` | Container has departed from transshipment port + `container.transport.feeder_arrived` | Container has arrived on feeder vessel or barge + `container.transport.feeder_discharged` | Container is discharged from feeder vessel or barge + `container.transport.feeder_loaded` | Container is loaded on feeder vessel or barge + `container.transport.feeder_departed` | Container has departed on feeder vessel or barge + `container.transport.vessel_arrived` | Container has arrived on vessel at port of discharge (destination port) + `container.transport.vessel_berthed` | Container vessel has berthed at port of discharge (destination port) + `container.transport.vessel_discharged` | Container is discharged at port of discharge + `container.transport.full_out` | Container full out at port of discharge + `container.transport.empty_in` | Container empty returned at destination + `container.transport.rail_loaded` | Container is loaded onto rail + `container.transport.rail_departed` | Container has departed on rail + `container.transport.rail_arrived` | Container has arrived by rail + `container.transport.rail_unloaded` | Container is unloaded from rail + `shipment.estimated.arrival` | ETA change notification (for port of discharge) + `container.created` | New container added to shipment (helpful for tracking new containers on a booking or BL) + `container.updated` | Container attribute(s) have been updated (see example below) + `container.pod_terminal_changed` | Port of discharge terminal assignment changed for container + `container.transport.arrived_at_inland_destination` | Container has arrived at inland destination + `container.transport.estimated.arrived_at_inland_destination` | ETA change notification (for destination) + `container.pickup_lfd.changed` | Last Free Day (LFD) has changed for container + +## Receiving Webhooks + +When an event is triggered, Terminal49 will attempt to POST to the URL you provided with the webhook. + +The payload of every webhook is a `WebhookNotification`. Each `WebhookNotification` includes a `reference_object` in its relationships which is the subject of that notification (e.g., a tracking request or an updated container). + +Terminal49 expects the endpoint to return one of these HTTP status codes: +- [HTTP 200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) +- [HTTP 201 Created](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201) +- [HTTP 202 Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) +- [HTTP 204 No Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204) + +To ensure delivery of all webhook notifications, any other response (including timeouts) will result in up to twelve retry attempts. + +```json json_schema +{ + "type":"object", + "properties":{ + "data":{ + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "webhook_notification" + ] + }, + "attributes": { + "type": "object", + "properties": { + "event": { + "type": "string" + }, + "delivery_status": { + "type": "string", + "default": "pending", + "enum": [ + "pending", + "succeeded", + "failed" + ], + "description": "Whether the notification has been delivered to the webhook endpoint" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "event", + "delivery_status", + "created_at" + ] + }, + "relationships": { + "type": "object", + "properties": { + "webhook": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + } + } + } + } + }, + "reference_object": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "tracking_request", + "estimated_event", + "transport_event", + "container_updated_event" + ] + } + } + } + } + } + }, + "required": [ + "webhook" + ] + } + } + }, + "included":{ + "type":"array", + "items": { + "anyOf": [ + { + "type": "object", + "title": "Webhook" + }, + { + "type": "object", + "title": "Tracking Request" + }, + { + "type": "object", + "title": "Transport Event" + }, + { + "type": "object", + "title": "Estimated Event" + }, + { + "type": "object", + "title": "Container Updated Event" + }, + { + "type": "object", + "title": "Terminal" + }, + { + "type": "object", + "title": "Port" + } + ] + } + } + } +} +``` + +> [How to Troubleshoot Missing Webhook Notifications](https://help.terminal49.com/en/articles/7851422-missing-webhook-notifications) + + +## Security +Terminal49 provides two ways to verify webhook authenticity: + +1. Verify webhook signatures to confirm events are sent from Terminal49 +2. Verify the source IP address against Terminal49's list of webhook IPs + +### Webhook Notification Origin IP +Webhook notifications will only come from these IP addresses: + +``` +35.222.62.171 +3.230.67.145 +44.217.15.129 +``` + +### Verifying the Webhook Signature (Optional) +When you create or retrieve a webhook, the model includes a `secret` attribute. + +For each webhook notification, Terminal49 generates a signature using the webhook `secret` as the key to create an HMAC hex digest with SHA-256 on the body. + +This signature is included in the header as `X-T49-Webhook-Signature`. + +To verify that a third party has not tampered with the webhook payload, perform the same operation on the response body using the webhook secret and confirm the digests match. + +Here's an example implementation in Ruby: +```ruby +class WebhooksController < ApplicationController + def receive_tracking_request + secret = ENV.fetch('TRACKING_REQUEST_WEBHOOK_SECRET') + raise 'InvalidSignature' unless valid_signature?(request, secret) + + # continue processing webhook payload... + end + + private + + def valid_signature?(request, secret) + hmac = OpenSSL::HMAC.hexdigest('SHA256', secret, request.body.read) + request.headers['X-T49-Webhook-Signature'] == hmac + end +end +``` + +## Webhook Notification Examples + +### container.updated + +The `container.updated` event notifies you about changes to container properties at the terminal or changes to which terminal the container is (or will be) located at. + +The `changeset` attribute contains a hash of all modified container properties. For each changed property: +- The hash key is the property name +- The array contains two values: the previous value and the new value + +For example: +```json +"changeset": { + "pickup_lfd": [null, "2020-05-20 00:00:00"] +} +``` +This shows the pickup last free day changed from not being set (null) to May 20, 2020. + +Monitored properties include: +- fees_at_pod_terminal +- holds_at_pod_terminal +- pickup_lfd +- pickup_appointment_at +- available_for_pickup +- pod_terminal + +The `container_updated.timestamp` attribute indicates when Terminal49 detected the changes at the terminal. + +When container availability status changes at the POD Terminal, you'll receive `container.updated` events with the `available_for_pickup` key in the `changeset`. \ No newline at end of file diff --git a/api-docs/useful-info/api-data-sources-availability.mdx b/api-docs/useful-info/api-data-sources-availability.mdx new file mode 100644 index 00000000..4d710986 --- /dev/null +++ b/api-docs/useful-info/api-data-sources-availability.mdx @@ -0,0 +1,198 @@ +# Data Sources + +- **Ocean Carriers (aka Steamship Lines):** Bill of lading/booking details, vessel ETA, containers and milestones +- **Container Terminal Operators:** Container availability, last free day, holds, fees etc. +- **Container Rail Carriers:** Container milestones via rail +- **AIS Data:** Vessel details and real-time location tracking (coming soon!) + +## Supported Ocean Carriers +View a complete list of supported carriers and attributes on [Google Sheets](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=0) + +[![Carriers Screenshot](../../assets/images/carriers_screenshot.png "Carriers Screenshot")](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=0) + +## Ports and Terminals +Currently, the Terminal49 API integrates with terminals at the following ports: +- Baltimore +- Boston +- Charleston +- Fraser Surrey (CA) +- Halifax (CA) +- Houston +- Jacksonville +- London Gateway (UK) +- Long Beach +- Los Angeles +- Miami +- Mobile +- New Orleans +- New York / New Jersey +- Oakland +- Philadelphia +- Port Everglades +- Portland +- Prince Rupert (CA) +- Savannah +- Seattle +- Southampton (UK) +- Tacoma +- Tampa +- Vancouver (CA) +- Virginia + +You can view a complete list of supported terminals and attributes on [Google Sheets](https://docs.google.com/spreadsheets/d/1cWK8sNpkjY5V-KlXe1fHi8mU_at2HcJYqjCvGQgixQk/edit#gid=1406366493) + +## Rail Carriers + +- BNSF Railway +- Canadian National Railway (CN) +- Canadian Pacific Railway (CP) +- CSX Transportation +- Norfolk Southern Railway (NS) +- Union Pacific Railroad (UP) + +## Known Issues (Ocean Carriers) +Shipment data is populated from requests to the shipping lines. + +Below is a list of known issues with our data sources: + +### CMA CGM, APL, ANL +- No container weight +- No container seal number + +### Maersk, Sealand, Safmarine +- Shipment departure/arrival events are not always available depending on when the bill of lading is entered into the system +- No container seal number + +### Hamburg Süd +- No estimated departure time +- No container weight +- No container seal number + +### MSC +- No container seal number + +### Hapag-Lloyd +- No container weight +- No container seal number + +### Evergreen +- All dates are provided as dates, not datetimes. We record and return them all as midnight at the location the event happened (when location is available) or midnight UTC +- Only Dry, Reefer, and Flatpack container types are mapped to our system + +### COSCO +- No departure or arrival events (does not affect departure/arrival times) + +### OOCL +- No container seal number + +### ONE +- Only Dry and Reefer container types are mapped to our system + +### Yang Ming +- When bill of lading has multiple containers, the container weight returned is the average of the shipment (i.e., the bill of lading gross weight / number of containers) + +### Hyundai Merchant Marine +- No container type + +### ZIM +- No container weight +- No container seal number + +### Westwood Shipping +- No container weight +- Only Dry container types are mapped to our system + +# Data Fields & Availability + +Below is a list of data that can be retrieved via the API, including whether it is always available, or whether it is only supported by certain carriers (Carrier Dependent), certain Terminals (Terminal Dependent) or on certain types of journeys (Journey Dependent). + +## Shipment Data +Shipment Data is the primary data that comes from the Carrier. It contains the details of the shipment retrieved from the Bill of Lading and references multiple container objects. + +| Data | Availability | More details | Notes | +| ------ |-----|-----|-----| +| Port of Lading | Always | Port of Lading name, Port of Lading UN/LOCODE, Port of Lading Timezone | | +| Port of Discharge | Always | Port of Discharge name, Port of Discharge UN/LOCODE, Port of Discharge Timezone | | +| Final Destination beyond Port of Discharge | Carrier Dependent, Journey Dependent | Destination name, Destination UN/LOCODE, Destination Timezone | Only for shipments with inland moves provided by or booked by the carrier | +| Listing of Container Numbers | Always | A list of container numbers with data attributes listed below | | +| Bill of Lading Number | Always (inputted by user) | BOL | | +| Shipping Line Details | Always | SCAC, SSL Name | | +| Voyage Details | Milestone-based | Vessel Name, Vessel IMO, Voyage Number | | +| Estimated Time of Departure | Carrier Dependent | Timestamp | | +| Actual Time of Departure | Always | Timestamp | After departure | +| Estimated Time of Arrival at Port of Discharge | Carrier Dependent | Timestamp | | +| Actual Time of Arrival at Port of Discharge | Always | Timestamp | Available after arrival | +| Estimated Time of Arrival at Final Destination | Carrier Dependent, Journey Dependent | Timestamp | Only for vessels with inland moves | + +## Container Data +At the container level, the following data is available. Container data is combined from all sources to create a single data view of the container. Some of this data will only be available when certain milestones have passed. + +| Data | Availability | More Details | Notes | +| ---------------- | ----------------- | ------------------------------------------------ | -------------------- | +| Container Number | Always | number | | +| Seal Number | Carrier Dependent | number | | +| Equipment Type | Always | Dry, Reefer, Open Top, Flat Rack, Tank, Hard Top | Enumerated data type | +| Equipment Length | Always | 20, 40, 45, 50 | Enumerated Data Type | +| Equipment Height | Always | Standard, High Cube | Enumerated Data Type | +| Weight | Carrier Dependent | Number | | +| Terminal Availability | Always | Availability Known, Availability for Pickup | | +| Holds | Terminal Dependent| Array of statuses | Each status includes the hold name (one of: Customs, Freight, TMF, Other, USDA) and the status (Pending, Hold) as well as any extra description| +| Fees | Terminal Dependent| Array of statuses| Each status includes the fee type (one of: Demurrage, Exam, Other) and the amount the hold is for (a float)| +| Last Free Day | Terminal Dependent| Date of last free day | | +| Arrived at Port of Discharge | Always | Once Arrived | | +| Discharged at Port of Discharge | Always | Once discharged | | +| Full Out at Port of Discharge | Always | | | +| Full Out at Final Destination | Journey Dependent | Only if non-port final destination | | +| Rail Loaded at Port of Discharge | Journey Dependent | Only if non-port final destination | | +| Rail Departed at Port of Discharge | Journey Dependent | Only if non-port final destination | | +| Rail Carrier SCAC at Port of Discharge |Journey Dependent | Only if non-port final destination | | +| ETA for Final Destination | Carrier Dependent, Journey Dependent | Only if non-port final destination | | +| ATA for Final Destination | Journey Dependent | Only if non-port final destination | | +| LFD at Final Destination | Carrier Dependent, Journey Dependent | Only if non-port final destination | | + + +## Milestone Event Data +When a milestone passes, the Terminal49 API will send a webhook notification with a Milestone event. For each milestone, the following data is always provided. Container, Shipment, Vessel, Location and Terminal data will be provided as objects that contain the information listed above. + +| Milestone Data | Description | +| -------------- | ---------------------------------------------------------------- | +| Event Name | The name of the event, e.g., 'container.transport.vessel_loaded' | +| Created At | When the event was created in our system | +| Timestamp | When the event occurred | +| Timezone | Which timezone the event occurred in | +| Voyage Number | The voyage number of the vessel | +| Container | A link to the Container Data | +| Shipment | A link to the Shipment Data | +| Vessel | Which vessel the event occurred on | +| Location | Where the event occurred | +| Terminal | Which terminal this occurred at | + +## Milestone Events Supported +Below is a list of milestones that the API can track, along with the event name used in the API. Additional events may be supported in the future. + +| Milestone Event Name | Event Name | +| -------------------- | -------------------------------------- | +| Vessel Loaded | container.transport.vessel_loaded | +| Vessel Departed | container.transport.vessel_departed | +| Vessel Arrived | container.transport.vessel_arrived | +| Vessel Berthed | container.transport.vessel_berthed | +| Vessel Discharged | container.transport.vessel_discharged | +| Empty Out | container.transport.empty_out | +| Full In | container.transport.full_in | +| Full Out | container.transport.full_out | +| Empty In | container.transport.empty_in | +| Rail Departed | container.transport.rail_departed | +| Rail Arrived | container.transport.rail_arrived | +| Rail Loaded | container.transport.rail_loaded | +| Rail Unloaded | container.transport.rail_unloaded | +| Transshipment Arrived | container.transport.transshipment_arrived | +| Transshipment Discharged | container.transport.transshipment_discharged | +| Transshipment Loaded | container.transport.transshipment_loaded | +| Transshipment Departed | container.transport.transshipment_departed | +| Feeder Arrived | container.transport.feeder_arrived | +| Feeder Discharged | container.transport.feeder_discharged | +| Feeder Loaded | container.transport.feeder_loaded | +| Feeder Departed | container.transport.feeder_departed | +| Arrived at Inland Destination | container.transport.arrived_at_inland_destination | +| Estimated Arrived at Inland Destination | container.transport.estimated.arrived_at_inland_destination | +| Pickup LFD Changed | container.pickup_lfd.changed | \ No newline at end of file diff --git a/datasync/overview.mdx b/datasync/overview.mdx new file mode 100644 index 00000000..f86c2885 --- /dev/null +++ b/datasync/overview.mdx @@ -0,0 +1,57 @@ +--- +title: Overview +og:title: Datasync Overview | Terminal49 API Documentation +og:description: Get an overview of the Datasync feature in Terminal49's API to streamline container tracking. +--- +Terminal49 DataSync is the easiest way to get fresh, up-to-date container and shipment data into your system. + +DataSync will create three tables in your system, in the schema/dataset/folder/spreadsheet of your choice: [containers](/datasync/table-properties/containers_rail), [shipments](/datasync/table-properties/shipments), and [tracking_requests](/datasync/table-properties/tracking-requests). In addition to these three tables, a technical table named [_transfer_status](/datasync/table-properties/transfer-status) is created, which tells you when each table was last refreshed. + +We can send the data to almost any database, data warehouse, or object store, as well as to Google Sheets. See the [full list of supported systems](/datasync/supported-destinations). + + +## How often does the data update? +DataSync refreshes the data tables every hour. + +Each refresh reloads only the rows that have changed since the previous refresh, minimizing unnecessary writes to your system. + +To check when a table was last updated, check the [_transfer_status](/datasync/table-properties/transfer-status) table. For each row in that table, there is a unique table key and the time when the latest sync occurred for that table. + + +## How to use the data +You can use the container and shipment tracking data in many ways. Here are some common use cases: + + - Send data directly to your visualization/analytics/reports software like Power BI or Tableau + - Send data directly to your TMS or ERP system + - Join data with one of your existing tables + - Use a Database View or Pivot Table to narrow down what you're looking at or rename columns + - Use Database Triggers to respond to updates + + +## The setup process +For users already tracking shipments with Terminal49, the setup is a three-step process that typically takes less than two hours. Some simpler setups can be completed in as little as 20 minutes. + + 1. **Connect data systems**: This step may involve role-based authentication or sharing credentials for a single-purpose user. See our [security FAQ](https://help.terminal49.com/en/articles/7988732-security-considerations-for-terminal49-datasync) to learn how we keep your data secure. + + 2. **One-hour configuration call**: We ensure your data is configured properly to fit with your existing data structure and requirements. + + 3. **Start querying the data**: You're ready to go! There's nothing new to learn - simply use the tools you already know with your enhanced dataset. + +[Schedule a call with our Customer Success team](https://savvycal.com/Dylan-Sewell-1f9bae32/2c4b47c7) to get started. + + +## How to start tracking shipments +There are several ways to start tracking shipments with Terminal49. All methods require either a Booking Number or Master Bill of Lading number for the shipments you want to track: + + - [Send an email with a CSV to track@terminal49.com](https://help.terminal49.com/en/articles/5506959-how-to-add-shipments-via-email) + - Upload a CSV through [our dashboard](https://app.terminal49.com/shipments/imports/new) + - Input shipments directly through [our dashboard](https://app.terminal49.com/shipments/imports/new) + - [Use the Terminal49 API to create TrackingRequests](/api-docs/api-reference/tracking-requests/create-a-tracking-request) + + +## Getting Started +Schedule your call now! + +For current Terminal49 customers: [Schedule a call with our Customer Support team](https://savvycal.com/Dylan-Sewell-1f9bae32/2c4b47c7) and we'll help you get set up. + +If you're not yet a customer: [Schedule a demo with our sales team](https://www.terminal49.com/contact) - we'll help you find the solution that's best for you. \ No newline at end of file diff --git a/mint.json b/mint.json new file mode 100644 index 00000000..ec1f18c0 --- /dev/null +++ b/mint.json @@ -0,0 +1,220 @@ +{ + "name": "Terminal 49", + "logo": "/logos/light.svg", + "favicon": "/logos/favicon.svg", + "colors": { + "primary": "#00A2FF", + "light": "#52bfff", + "dark": "#00A2FF" + }, + "modeToggle": { + "default": "light", + "isHidden": false + }, + "feedback": { + "thumbsRating": true, + "raiseIssue": true, + "suggestEdit": true + }, + "topbarCtaButton": { + "name": "Sign in", + "url": "https://app.terminal49.com/" + }, + "topbarLinks": [ + { + "name": "Start your free trial", + "url": "https://app.terminal49.com/register" + } + ], + "anchors": [ + { + "name": "Contact", + "icon": "phone", + "url": "https://www.terminal49.com/contact/" + }, + { + "name": "Schedule Demo", + "icon": "calendar-days", + "url": "https://www.terminal49.com/demo/" + }, + { + "name": "Blog", + "icon": "newspaper", + "url": "https://www.terminal49.com/blog/" + } + ], + "primaryTab": { + "name": "API Docs" + }, + "tabs": [ + { + "name": "DataSync", + "url": "datasync" + } + ], + "navigation": [ + { + "group": " ", + "pages": [ + "home" + ] + }, + { + "group": "Getting Started", + "pages": [ + "api-docs/getting-started/start-here", + "api-docs/getting-started/tracking-shipments-and-containers", + "api-docs/getting-started/list-shipments-and-containers", + "api-docs/getting-started/receive-status-updates", + "api-docs/getting-started/start-here", + "api-docs/getting-started/tracking-shipments-and-containers", + "api-docs/getting-started/list-shipments-and-containers" + ] + }, + { + "group": "In Depth Guides", + "pages": [ + "api-docs/in-depth-guides/quickstart", + "api-docs/in-depth-guides/webhooks", + "api-docs/in-depth-guides/including-resources", + "api-docs/in-depth-guides/tracking-request-lifecycle", + "api-docs/in-depth-guides/event-timestamps", + "api-docs/in-depth-guides/terminal49-map", + "api-docs/in-depth-guides/terminal49-widget", + "api-docs/in-depth-guides/rail-integration-guide", + "api-docs/in-depth-guides/event-timestamps", + "api-docs/in-depth-guides/webhooks" + ] + }, + { + "group": "Useful Info", + "pages": [ + "api-docs/useful-info/api-data-sources-availability", + "api-docs/useful-info/pricing", + "api-docs/useful-info/test-numbers", + "api-docs/useful-info/tracking-request-retrying", + "api-docs/useful-info/webhook-events-examples", + "api-docs/useful-info/api-data-sources-availability" + ] + }, + { + "group": " ", + "pages": [ + "datasync/home", + "datasync/overview" + ] + }, + { + "group": " ", + "pages": [ + "datasync/overview", + "datasync/supported-destinations" + ] + }, + { + "group": "Table Properties", + "pages": [ + "datasync/table-properties/containers_rail", + "datasync/table-properties/shipments", + "datasync/table-properties/tracking-requests", + "datasync/table-properties/transfer-status", + "datasync/table-properties/containers" + ] + }, + { + "group": "Shipments", + "pages": [ + "api-docs/api-reference/shipments/list-shipments", + "api-docs/api-reference/shipments/get-a-shipment", + "api-docs/api-reference/shipments/edit-a-shipment", + "api-docs/api-reference/shipments/stop-tracking-shipment", + "api-docs/api-reference/shipments/resume-tracking-shipment" + ] + }, + { + "group": "Tracking Requests", + "pages": [ + "api-docs/api-reference/tracking-requests/list-tracking-requests", + "api-docs/api-reference/tracking-requests/create-a-tracking-request", + "api-docs/api-reference/tracking-requests/get-a-single-tracking-request" + ] + }, + { + "group": "Tracking requests", + "pages": [ + "api-docs/api-reference/tracking-requests/edit-a-tracking-request" + ] + }, + { + "group": "Webhooks", + "pages": [ + "api-docs/api-reference/webhooks/get-single-webhook", + "api-docs/api-reference/webhooks/delete-a-webhook", + "api-docs/api-reference/webhooks/edit-a-webhook", + "api-docs/api-reference/webhooks/list-webhooks", + "api-docs/api-reference/webhooks/create-a-webhook", + "api-docs/api-reference/webhooks/list-webhook-ips" + ] + }, + { + "group": "Webhook Notifications", + "pages": [ + "api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification", + "api-docs/api-reference/webhook-notifications/list-webhook-notifications", + "api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples" + ] + }, + { + "group": "Containers", + "pages": [ + "api-docs/api-reference/containers/list-containers", + "api-docs/api-reference/containers/edit-a-container", + "api-docs/api-reference/containers/get-a-container", + "api-docs/api-reference/containers/get-a-containers-raw-events", + "api-docs/api-reference/containers/get-a-containers-transport-events" + ] + }, + { + "group": "Shipping Lines", + "pages": [ + "api-docs/api-reference/shipping-lines/shipping-lines", + "api-docs/api-reference/shipping-lines/get-a-single-shipping-line" + ] + }, + { + "group": "Metro Areas", + "pages": [ + "api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id" + ] + }, + { + "group": "Ports", + "pages": [ + "api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id" + ] + }, + { + "group": "Vessels", + "pages": [ + "api-docs/api-reference/vessels/get-a-vessel-using-the-id", + "api-docs/api-reference/vessels/get-a-vessel-using-the-imo" + ] + }, + { + "group": "Terminals", + "pages": [ + "api-docs/api-reference/terminals/get-a-terminal-using-the-id" + ] + } + ], + "footerSocials": { + "twitter": "https://twitter.com/terminal49", + "linkedin": "https://www.linkedin.com/company/terminal49/" + }, + "redirects": [ + { + "source": "/api-docs/getting-started/recieve-status-updates", + "destination": "/api-docs/getting-started/receive-status-updates" + } + ] +} \ No newline at end of file