From b17048ca866302356e69334cb056c7d1e7e93f75 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Thu, 20 Mar 2025 20:53:08 -0700 Subject: [PATCH 01/42] Docs: Initial improvements to API documentation --- API-Documentation-Improvement-Tracker.md | 153 +++++++++ .../create-a-tracking-request.mdx | 307 +++++++++++++++++- .../getting-started/capabilities-overview.mdx | 181 +++++++++++ docs/api-docs/getting-started/start-here.mdx | 92 +++++- .../in-depth-guides/json-api-guide.mdx | 255 +++++++++++++++ 5 files changed, 978 insertions(+), 10 deletions(-) create mode 100644 API-Documentation-Improvement-Tracker.md create mode 100644 docs/api-docs/getting-started/capabilities-overview.mdx create mode 100644 docs/api-docs/in-depth-guides/json-api-guide.mdx diff --git a/API-Documentation-Improvement-Tracker.md b/API-Documentation-Improvement-Tracker.md new file mode 100644 index 00000000..b845a64c --- /dev/null +++ b/API-Documentation-Improvement-Tracker.md @@ -0,0 +1,153 @@ +# Terminal49 API Documentation Improvement Tracker + +## Overview +This document tracks the progress of improving the Terminal49 API documentation. The plan focuses first on the API documentation, with data sync documentation improvements to follow later. + +## Progress Tracker +- [x] **Step 1: Documentation Audit and Gap Analysis** + - Review existing API documentation structure + - Identify incomplete endpoints, missing examples, and unclear information + - Document gaps in onboarding flow and advanced features explanation + - Create a prioritized list of documentation improvements + +- [ ] **Step 2: Enhance Endpoint Documentation with Examples** + - Add comprehensive examples for each endpoint showing multiple scenarios + - Include full request and response pairs for major use cases + - Explain key fields in plain language + - Ensure all status codes and error responses are documented + +- [ ] **Step 3: Create Capabilities Overview** + - Develop a high-level summary of API functionality for both technical and non-technical users + - Highlight key features and benefits of using Terminal49's API + - Create visual diagrams of data flow and relationships + - Link to relevant endpoints and guides from the overview + +- [ ] **Step 4: Improve Onboarding with Event-Driven Focus** + - Revise quick start guide with emphasis on webhook implementation + - Create clear comparison between polling and webhooks approaches + - Provide code examples for webhook setup in multiple languages + - Integrate test tracking numbers into the initial onboarding flow + +- [ ] **Step 5: Develop Tutorials for Common Integration Scenarios** + - Create practical, code-based tutorials for building basic tracking dashboard + - Add email/notification alert system tutorial + - Develop tutorials for bulk tracking implementation + - Include tutorials on handling webhook events effectively + +- [ ] **Step 6: Add Industry-Specific Integration Guides** + - Develop targeted guides for freight forwarders + - Create implementation guides for importers + - Add specialized content for drayage and trucking companies + - Include visual workflows for each industry use case + +- [ ] **Step 7: Create FAQ and Troubleshooting Section** + - Compile common questions about implementation + - Document typical error scenarios and their solutions + - Include troubleshooting guides for common integration challenges + - Add best practices for error handling and retry logic + +- [ ] **Step 8: Refine Documentation Structure and Navigation** + - Reorganize content for intuitive flow + - Add cross-references between related sections + - Improve search functionality and discoverability + - Ensure consistent formatting and terminology throughout + +- [ ] **Step 9: Implement Feedback Mechanisms** + - Add visible feedback options on each documentation page + - Create issue templates for documentation problems + - Establish process for handling documentation feedback + - Add community contribution guidelines + +- [ ] **Step 10: Regular Review and Updates** + - Schedule regular documentation audits + - Establish process for updating docs with API changes + - Create documentation testing procedures + - Implement analytics to identify most-used and problematic sections + +## Current Status: Step 2 & 3 In Progress + +### Step 1 Completed: Documentation Audit and Gap Analysis +After a thorough review of the documentation, we identified several key improvement areas: + +1. **Structure**: + - Current sections: getting-started, in-depth-guides, useful-info, api-reference + - Documentation uses MDX format with a mint.json configuration + - OpenAPI spec is used for endpoint documentation (openapi.json file) + +2. **Endpoint Documentation**: + - API references are organized by resource type + - Found endpoints for: webhooks, tracking-requests, vessels, shipping-lines, terminals, ports, shipments, containers, metro-areas, parties + - Most endpoint docs are minimal, using just the OpenAPI spec without additional explanations or examples + - Real-world example responses are limited, especially showing different states of resources + +3. **Onboarding Flow**: + - Current getting-started section has 4 pages: start-here, tracking-shipments-and-containers, list-shipments-and-containers, receive-status-updates + - Webhook implementation is mentioned but not emphasized in the initial flow + - Test numbers exist but are located in useful-info section rather than integrated directly into onboarding + - No clear guidance on checking webhook delivery or troubleshooting setup issues + +4. **Advanced Topics**: + - Tracking request lifecycle documentation exists but lacks visual workflow diagram + - Webhook documentation is extensive but separated from the initial onboarding flow + - No clear industry-specific implementation guides + - Limited tutorials for building common integrations + +5. **Identified Gaps**: + - Need for comprehensive examples showing different resource states (e.g., container in transit vs. with customs hold) + - Webhook implementation should be emphasized earlier and more strongly in the onboarding flow + - Test numbers should be integrated into onboarding process rather than hidden in useful-info + - No FAQ or troubleshooting section for common integration issues + - Limited industry-specific guides explaining the business value and implementation patterns + - Incomplete error documentation for API endpoints + - Missing "Capabilities Overview" page to highlight API features for non-developers + +### Steps 2 & 3 Progress Update + +#### Completed Files: +1. **Getting Started Improvements**: + - ✅ `/docs/api-docs/getting-started/start-here.mdx` - Enhanced with clear introduction and better organization + - ✅ `/docs/api-docs/getting-started/capabilities-overview.mdx` - Created comprehensive overview of API capabilities + +2. **In-Depth Guides Improvements**: + - ✅ `/docs/api-docs/in-depth-guides/json-api-guide.mdx` - Added detailed guide on JSON:API concepts + +3. **API Reference Improvements**: + - ✅ `/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` - Enhanced with comprehensive examples and troubleshooting + +#### Next Files to Update: +1. **Getting Started Improvements**: + - [ ] `/docs/api-docs/in-depth-guides/quickstart.mdx` - Revise to emphasize webhook setup earlier + +2. **API Reference Improvements**: + - [ ] `/docs/api-docs/api-reference/webhooks/create-a-webhook.mdx` - Enhance with detailed examples + - [ ] `/docs/api-docs/api-reference/containers/get-a-container.mdx` - Add examples showing different states + +3. **New Content to Create**: + - [ ] `/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` - Create new guide explaining both approaches + - [ ] `/docs/api-docs/troubleshooting/common-issues.mdx` - Create FAQ and troubleshooting section + +## Prioritized Improvement Plan + +Based on the audit, here's our prioritized list of improvements: + +1. **High Priority**: + - Add comprehensive request/response examples for each endpoint showing different states + - Create a capabilities overview page highlighting key API features + - Improve quick start guide with early webhook integration and test numbers + - Add a FAQ and troubleshooting section + +2. **Medium Priority**: + - Develop practical tutorials for common use cases + - Create industry-specific implementation guides + - Add error handling best practices + +3. **Lower Priority**: + - Refine documentation structure and navigation + - Implement feedback mechanisms + - Establish regular review process + +## Next Steps for Step 1: +1. Complete detailed review of each endpoint's documentation +2. Create a template for enhanced endpoint documentation with examples +3. Draft a capabilities overview page outline +4. Design improved onboarding flow that emphasizes webhooks and test numbers diff --git a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx index 7b804f03..0b668eda 100644 --- a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx +++ b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx @@ -1,5 +1,308 @@ --- +title: Create a Tracking Request +description: Submit a new tracking request to monitor ocean shipments via bill of lading, booking number, or container number. og:title: Create a Tracking Request | Terminal49 API Documentation -og:description: Create tracking requests via Terminal49's API for accurate shipment tracking and notifications. +og:description: Create tracking requests via Terminal49's API for accurate shipment tracking and real-time notifications. openapi: post /tracking_requests ---- \ No newline at end of file +--- + +## Endpoint Purpose +Submitting a tracking request tells Terminal49 to start monitoring a specific shipment for you. This is typically the first step in the tracking workflow. + +## Business Use Cases +- **Initial Tracking Setup**: Start tracking a new container shipment when you receive a bill of lading or booking confirmation. +- **Customer Onboarding**: Quickly set up tracking for a customer's shipments when they provide their shipping documents. +- **Bulk Import**: Programmatically submit multiple tracking requests to monitor your entire inventory in transit. + +## Example Scenarios + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", // Type of tracking number + "request_number": "MAEU9736478", // Bill of lading number from the carrier + "scac": "MAEU" // Shipping line SCAC code (Maersk in this case) + } + } + } + + + { + "data": { + "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", // Request is being processed + "failed_reason": null, + "created_at": "2023-05-15T14:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" + }, + "relationships": { + "shipment": { + "data": null // Will be populated when tracking succeeds + } + } + } + } + + + + **Notes:** + - The bill of lading is typically the most reliable identifier for tracking shipments. + - The `status` will initially be `pending` while Terminal49 processes your request. + - Once processing completes, you'll receive a webhook notification and the status will change to `succeeded` or `failed`. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", // Tracking by container number + "request_number": "MSCU1234567", // Container number + "scac": "MSCU" // Shipping line SCAC code (MSC in this case) + } + } + } + + + { + "data": { + "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T15:45:22Z", + "updated_at": "2023-05-15T15:45:22Z" + }, + "relationships": { + "shipment": { + "data": null + } + } + } + } + + + + **Notes:** + - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. + - Container numbers should include the carrier prefix (e.g., MSCU for MSC containers). + - Container tracking can sometimes fail if the container is very new or not yet associated with a booking in the carrier's system. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", // Test number that will always succeed + "scac": "TEST" // Special SCAC for test requests + } + } + } + + + { + "data": { + "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T16:30:45Z", + "updated_at": "2023-05-15T16:30:45Z" + }, + "relationships": { + "shipment": { + "data": null + } + } + } + } + + + + **Notes:** + - Using test numbers is perfect for development and testing your integration. + - `TEST-TR-SUCCEEDED` will always trigger a successful tracking response. + - `TEST-TR-FAILED` will always trigger a failed tracking response. + - These test numbers will trigger corresponding webhook events for complete testing. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "INVALID-FORMAT", // Invalid bill of lading format + "scac": "UNKNOWN" // Non-existent SCAC code + } + } + } + + + { + "errors": [ + { + "status": "422", + "title": "Invalid SCAC", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } + } + ] + } + + + + **Notes:** + - This example shows a validation error for an invalid SCAC code. + - Common errors include invalid SCAC codes, improperly formatted request numbers, or duplicate tracking requests. + - For a complete list of supported SCACs, refer to the Carrier Data Matrix. + + + +## Key Response Fields + + + Unique identifier for the tracking request. Store this to check status later. + + + + Current status of the tracking request. Possible values: + - `pending`: Request is being processed + - `succeeded`: Container/shipment located and tracking established + - `failed`: Unable to track the shipment (see failed_reason) + - `awaiting_manifest`: Bill of lading is valid but not yet available in the carrier's system + - `tracking_stopped`: Tracking has been stopped for this request + + + + If status is "failed", this field provides the reason. Common values: + - `duplicate`: Shipment already exists in Terminal49 + - `not_found`: Shipping line couldn't find the tracking number + - `retries_exhausted`: Maximum retries reached without success + - `invalid_number`: Improperly formatted tracking number + + + See the [Tracking Request Lifecycle](/api-docs/in-depth-guides/tracking-request-lifecycle) guide for a complete list of failure reasons. + + + + + When the tracking request succeeds, this contains a reference to the created shipment. + + + + ID of the linked shipment that you can use to retrieve full shipment details. + + + + Always "shipment" for successful tracking requests. + + + + +## Related Webhook Events + +When creating a tracking request, you may receive the following webhook events: + + + Sent when Terminal49 successfully locates your shipment and begins tracking it. The notification includes details about the created shipment and its containers. + + + + Sent when Terminal49 is unable to locate your shipment after all retry attempts. Includes the failed_reason to help troubleshoot. + + + + Sent when the bill of lading number is valid but the shipment data is not yet available in the carrier's system. Terminal49 will continue checking daily. + + +## Related Guides & Tutorials + + + + Learn about the full lifecycle of a tracking request, including all possible statuses and failure reasons. + + + Learn how to set up webhooks to receive real-time notifications about your tracking requests. + + + Complete list of test tracking numbers you can use during development. + + + Learn how to efficiently submit and manage multiple tracking requests. + + + +## Troubleshooting + + + + **Problem**: Your tracking request has been in "pending" status for an extended period. + + **Solution**: + 1. Terminal49 retries tracking requests up to 14 times over 24 hours. + 2. Check that the SCAC and request number are correct. + 3. For bill of lading numbers, note that they may remain in "awaiting_manifest" status for up to 7 days. + 4. If the issue persists beyond 24 hours, contact support@terminal49.com. + + + + **Problem**: Your tracking request failed with reason "not_found". + + **Solution**: + 1. Verify the tracking number format is correct (no spaces, proper prefixes). + 2. Confirm you're using the correct SCAC code for the carrier. + 3. For newly issued bills of lading, try again in 24-48 hours as it may take time to appear in the carrier's system. + 4. If using a container number, check if the carrier supports container tracking in the Carrier Data Matrix. + + + + **Problem**: Your tracking request failed with reason "duplicate". + + **Solution**: + 1. The shipment is already being tracked in Terminal49. + 2. Use `GET /shipments` with a filter to find the existing shipment: + + ```bash + GET /shipments?filter[bill_of_lading_number]=MAEU9736478 + ``` + + 3. You can access the existing shipment data instead of creating a new tracking request. + + + + + For the fastest tracking setup, use the master bill of lading (MBL) number from the carrier. House bills of lading (HBL) are not supported. + + + + Container number tracking is not supported by all carriers. Always check the Carrier Data Matrix before implementing container tracking. + diff --git a/docs/api-docs/getting-started/capabilities-overview.mdx b/docs/api-docs/getting-started/capabilities-overview.mdx new file mode 100644 index 00000000..a439e077 --- /dev/null +++ b/docs/api-docs/getting-started/capabilities-overview.mdx @@ -0,0 +1,181 @@ +--- +title: Terminal49 API Capabilities Overview +description: A comprehensive overview of all features and capabilities available through the Terminal49 API. +--- + +# Terminal49 API Capabilities + +Terminal49's API provides end-to-end ocean freight visibility through a collection of endpoints that deliver real-time tracking data, reference information, and event notifications. This overview will help you understand what's possible and how each feature fits into your logistics workflows. + +## Core Capabilities + + + + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. + + + Receive real-time updates via webhooks when shipment status changes, from origin to destination. + + + Access standardized data about carriers, ports, terminals, and more for consistent reporting. + + + Get comprehensive container information including status, location, free time, and holds. + + + +## End-to-End Visibility + +Terminal49 provides visibility across the entire container journey: + + + + Track empty container pickup, loading, and departure from origin port. + + + Follow vessel movements and receive ETA updates throughout the journey. + + + Get notified when the vessel arrives, berths, and when containers are discharged. + + + Monitor container availability, customs status, holds, and last free day information. + + + Track rail departures, arrivals, and final delivery events to inland destinations. + + + +## Key Differentiators + + + + Terminal49 standardizes data from different carriers into a single consistent format. For example, each carrier might report a container discharge event differently, but Terminal49 normalizes these into the same event type (`container.transport.vessel_discharged`) with standardized timestamps and location formats. + + **Business Benefit**: Build once, track across all carriers without custom integration for each shipping line. + + + + Instead of constantly polling for updates, Terminal49 notifies you via webhooks when meaningful events occur. This includes status changes, ETA updates, terminal holds, and changes to last free day. + + **Business Benefit**: Respond immediately to delays, holds, or availability changes, reducing demurrage costs and improving planning. + + + + Terminal49 actively monitors and notifies you of changes to Last Free Day (LFD) - the date by which containers must be picked up to avoid storage fees. This data is often buried in terminal websites and subject to change. + + **Business Benefit**: Avoid costly demurrage fees by prioritizing pickups based on accurate LFD information. + + + + The API provides comprehensive information about holds (customs, freight, terminal) and other exceptions that may delay pickup. + + **Business Benefit**: Proactively address holds and plan alternative shipments when delays occur. + + + +## How It Works + + + ![Terminal49 API Flow Diagram](/images/api-flow-diagram.png) + + +1. **Submit tracking requests** using bill of lading, booking, or container numbers +2. **Terminal49 monitors** these shipments by aggregating data from multiple sources +3. **Your systems receive updates** through webhooks when any change occurs +4. **Query additional information** as needed using the REST API endpoints + +## Available Data Points + +Here are key data points available through the Terminal49 API: + + + + - Bill of lading numbers + - Booking numbers + - Port of loading/discharge + - Shipper/consignee information + - Vessel assignments + - Estimated dates (departure, arrival) + - Associated containers + + + + - Container numbers and size/type + - Current location and status + - Last free day information + - Terminal availability + - Customs/terminal/freight holds + - Transport events history + - Pickup appointments + + + + - Vessel departures and arrivals + - Container loading/discharge + - Customs and terminal holds + - Availability changes + - Last free day updates + - Rail movements + - Terminal cutoffs + + + + - Shipping lines (with SCAC codes) + - Terminals and ports + - Vessels and voyages + - Metro areas + - Standardized location formats + + + +## Common Use Cases + + + + Connect Terminal49 to your Transportation Management System for real-time visibility without manual tracking. + + + + Build automated alerts to notify customers about shipment status changes, delays, or container availability. + + + + Prioritize container pickups based on last free day information to avoid demurrage charges. + + + + Identify and resolve customs holds, freight issues, or delays that require intervention. + + + + Build real-time dashboards showing shipment status across your supply chain. + + + + Plan warehouse staffing and inventory based on accurate container arrival predictions. + + + +## Getting Started + +Ready to start integrating with Terminal49? Here's how to begin: + + + + [Sign up for Terminal49](https://app.terminal49.com/signup) and get your API key from the [developer portal](https://app.terminal49.com/developers/api-keys). + + + Follow our [quick start guide](/api-docs/getting-started/tracking-shipments-and-containers) to submit your first tracking request. + + + Configure [webhooks](/api-docs/in-depth-guides/webhooks) to receive real-time updates about your shipments. + + + Browse our [API reference](/api-docs/api-reference) to discover all available endpoints. + + + + + Our team is here to help you get the most out of Terminal49. Contact us at [support@terminal49.com](mailto:support@terminal49.com) with any questions about our API capabilities. + diff --git a/docs/api-docs/getting-started/start-here.mdx b/docs/api-docs/getting-started/start-here.mdx index 5c63bf6f..f93df26d 100644 --- a/docs/api-docs/getting-started/start-here.mdx +++ b/docs/api-docs/getting-started/start-here.mdx @@ -1,23 +1,99 @@ --- title: 1. Start Here +description: Begin your journey with Terminal49's API for comprehensive ocean freight tracking. --- -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. +# Getting Started with Terminal49 API -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. +Terminal49's API gives you programmatic access to real-time ocean freight tracking data across multiple carriers. With our API, you can monitor containers from origin to destination, receive event notifications, access terminal availability information, and much more. - -**Run in Postman** +## What Can You Do With Terminal49's API? + + + + Monitor shipments using bill of lading, booking, or container numbers across major ocean carriers. + + + Get notified via webhooks when containers arrive, encounter holds, or change status. + + + Retrieve comprehensive container information including status, location, and last free day. + + + Explore all features and use cases of Terminal49's API. + + + + + New to Terminal49? Check out our [Capabilities Overview](/api-docs/getting-started/capabilities-overview) for a comprehensive look at what our API can do for your logistics operations. + + +## API Basics + +Our API follows the [JSON:API](https://jsonapi.org/) specification, providing a standardized way to request and manipulate resources. This makes our API predictable and easier to work with. + + + All Terminal49 API responses follow the JSON:API structure: + + ```json + { + "data": { + "id": "123", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + // Other shipment attributes + }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" } + ] + } + } + } + } + ``` + + For more details, see our [JSON:API Guide](/api-docs/in-depth-guides/json-api-guide). + + +There are [client libraries](https://jsonapi.org/implementations/#client-libraries) available in almost every language. Our API should work with these libraries out of the box, making integration easier. + +## Tools to Get Started + +Our APIs can be used with any HTTP client. We've created a Postman collection so you can quickly test endpoints with your API key: + + + **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: + +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" -``` \ No newline at end of file +``` + +## Next Steps + + + + Submit tracking requests for your [shipments and containers](/api-docs/getting-started/tracking-shipments-and-containers). + + + Configure [webhooks](/api-docs/in-depth-guides/webhooks) to receive real-time updates. + + + Browse our [API reference](/api-docs/api-reference) for all available endpoints. + + + + + For the fastest start, check out our [Quickstart Guide](/api-docs/in-depth-guides/quickstart) which will get you up and running with minimal effort. + diff --git a/docs/api-docs/in-depth-guides/json-api-guide.mdx b/docs/api-docs/in-depth-guides/json-api-guide.mdx new file mode 100644 index 00000000..80bbefa8 --- /dev/null +++ b/docs/api-docs/in-depth-guides/json-api-guide.mdx @@ -0,0 +1,255 @@ +--- +title: Understanding JSON:API +description: Learn the basics of JSON:API and how it's implemented in Terminal49's API. +--- + +# JSON:API Guide + +Terminal49's API follows the [JSON:API specification](https://jsonapi.org/), a standard for building APIs in JSON. This guide will help you understand the key concepts and how to work with our API effectively. + +## What is JSON:API? + +JSON:API is a specification for how a client should request resources to be fetched or modified, and how a server should respond. It's designed to minimize both the number of requests and the amount of data transmitted between clients and servers. + + + The Terminal49 API conforms to the JSON:API v1.0 specification. This standardization makes our API more predictable and easier to work with once you understand the patterns. + + +## JSON:API Structure + +### Basic Resource Objects + +All Terminal49 API responses follow this general structure: + +```json +{ + "data": { + "id": "123", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "created_at": "2023-05-15T14:23:12Z" + // Other shipment attributes + }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" }, + { "id": "789", "type": "container" } + ] + } + } + } +} +``` + +The main components are: + + + The primary resource or collection of resources. + + + + A unique identifier for the resource. + + + + The resource type (e.g., "shipment", "container", "tracking_request"). + + + + The resource's properties (e.g., numbers, dates, status values). + + + + References to related resources (e.g., a shipment's containers). + + +### Collections of Resources + +When requesting multiple resources, the response structure is: + +```json +{ + "data": [ + { + "id": "123", + "type": "shipment", + "attributes": { ... } + }, + { + "id": "124", + "type": "shipment", + "attributes": { ... } + } + ], + "meta": { + "total_count": 45 + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments?page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/shipments?page[number]=2&page[size]=2" + } +} +``` + + + Contains non-standard meta-information, like counts or pagination details. + + + + Contains links for pagination or related operations. + + +## Working with JSON:API in Terminal49 + +### Creating Resources + +When creating a resource (like a tracking request), your request should follow this format: + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + } + + + +Notice that: +- The `type` field is required and must match the expected resource type +- All properties go inside the `attributes` object +- You don't specify an `id` for new resources (the server assigns it) + +### Including Related Resources + +One powerful feature of JSON:API is the ability to include related resources in a single request using the `include` parameter: + +``` +GET /shipments/123?include=containers,containers.transport_events +``` + +This returns the shipment with its containers and each container's transport events in one response: + +```json +{ + "data": { + "id": "123", + "type": "shipment", + "attributes": { ... }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" } + ] + } + } + }, + "included": [ + { + "id": "456", + "type": "container", + "attributes": { ... }, + "relationships": { + "transport_events": { + "data": [ + { "id": "789", "type": "transport_event" } + ] + } + } + }, + { + "id": "789", + "type": "transport_event", + "attributes": { ... } + } + ] +} +``` + + + Contains all the related resources referenced in the main resource's relationships. + + +### Error Handling + +Error responses also follow a standard format: + +```json +{ + "errors": [ + { + "status": "422", + "title": "Invalid attribute", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } + } + ] +} +``` + +Each error object contains: + + + The HTTP status code relevant to the error. + + + + A short, human-readable summary of the problem. + + + + A human-readable explanation of the specific error. + + + + An object containing references to the source of the error. The `pointer` field typically contains a JSON Pointer to the specific field that caused the error. + + +## JSON:API Libraries + +Since Terminal49 uses standard JSON:API, you can leverage client libraries for your preferred programming language to make working with our API easier: + + + + JSON:API libraries for JavaScript + + + jsonapi-rb for Ruby applications + + + JSON:API libraries for Python + + + +Using a library helps handle the resource structure, relationship management, and error parsing automatically. + +## Tips for Working with Terminal49's JSON:API + +- Always include the correct `Content-Type` header in your requests: + ``` + Content-Type: application/vnd.api+json + ``` + +- Use the `include` parameter to reduce the number of API calls when you need related resources + +- When creating or updating resources, remember that all properties must be inside the `attributes` object + +- For filtering collections, use the filter query parameter with the attribute name: + ``` + GET /containers?filter[status]=arrived + ``` + +- Pagination is handled via `page[number]` and `page[size]` query parameters + + + For a deeper understanding of JSON:API, check the [official specification](https://jsonapi.org/format/) or our [In-Depth Guides](/api-docs/in-depth-guides/including-resources) on working with Terminal49's API. + From 2cdaad05b624691721e29d9de4b90b54732a6c08 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 08:28:39 -0700 Subject: [PATCH 02/42] Add polling vs webhooks guide and update improvement tracker --- API-Documentation-Improvement-Tracker.md | 204 +- .../in-depth-guides/polling-vs-webhooks.mdx | 276 +++ docs/api-docs/in-depth-guides/webhooks.mdx | 1758 ++++------------- 3 files changed, 699 insertions(+), 1539 deletions(-) create mode 100644 docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx diff --git a/API-Documentation-Improvement-Tracker.md b/API-Documentation-Improvement-Tracker.md index b845a64c..8d5b5017 100644 --- a/API-Documentation-Improvement-Tracker.md +++ b/API-Documentation-Improvement-Tracker.md @@ -1,153 +1,55 @@ # Terminal49 API Documentation Improvement Tracker -## Overview -This document tracks the progress of improving the Terminal49 API documentation. The plan focuses first on the API documentation, with data sync documentation improvements to follow later. - -## Progress Tracker -- [x] **Step 1: Documentation Audit and Gap Analysis** - - Review existing API documentation structure - - Identify incomplete endpoints, missing examples, and unclear information - - Document gaps in onboarding flow and advanced features explanation - - Create a prioritized list of documentation improvements - -- [ ] **Step 2: Enhance Endpoint Documentation with Examples** - - Add comprehensive examples for each endpoint showing multiple scenarios - - Include full request and response pairs for major use cases - - Explain key fields in plain language - - Ensure all status codes and error responses are documented - -- [ ] **Step 3: Create Capabilities Overview** - - Develop a high-level summary of API functionality for both technical and non-technical users - - Highlight key features and benefits of using Terminal49's API - - Create visual diagrams of data flow and relationships - - Link to relevant endpoints and guides from the overview - -- [ ] **Step 4: Improve Onboarding with Event-Driven Focus** - - Revise quick start guide with emphasis on webhook implementation - - Create clear comparison between polling and webhooks approaches - - Provide code examples for webhook setup in multiple languages - - Integrate test tracking numbers into the initial onboarding flow - -- [ ] **Step 5: Develop Tutorials for Common Integration Scenarios** - - Create practical, code-based tutorials for building basic tracking dashboard - - Add email/notification alert system tutorial - - Develop tutorials for bulk tracking implementation - - Include tutorials on handling webhook events effectively - -- [ ] **Step 6: Add Industry-Specific Integration Guides** - - Develop targeted guides for freight forwarders - - Create implementation guides for importers - - Add specialized content for drayage and trucking companies - - Include visual workflows for each industry use case - -- [ ] **Step 7: Create FAQ and Troubleshooting Section** - - Compile common questions about implementation - - Document typical error scenarios and their solutions - - Include troubleshooting guides for common integration challenges - - Add best practices for error handling and retry logic - -- [ ] **Step 8: Refine Documentation Structure and Navigation** - - Reorganize content for intuitive flow - - Add cross-references between related sections - - Improve search functionality and discoverability - - Ensure consistent formatting and terminology throughout - -- [ ] **Step 9: Implement Feedback Mechanisms** - - Add visible feedback options on each documentation page - - Create issue templates for documentation problems - - Establish process for handling documentation feedback - - Add community contribution guidelines - -- [ ] **Step 10: Regular Review and Updates** - - Schedule regular documentation audits - - Establish process for updating docs with API changes - - Create documentation testing procedures - - Implement analytics to identify most-used and problematic sections - -## Current Status: Step 2 & 3 In Progress - -### Step 1 Completed: Documentation Audit and Gap Analysis -After a thorough review of the documentation, we identified several key improvement areas: - -1. **Structure**: - - Current sections: getting-started, in-depth-guides, useful-info, api-reference - - Documentation uses MDX format with a mint.json configuration - - OpenAPI spec is used for endpoint documentation (openapi.json file) - -2. **Endpoint Documentation**: - - API references are organized by resource type - - Found endpoints for: webhooks, tracking-requests, vessels, shipping-lines, terminals, ports, shipments, containers, metro-areas, parties - - Most endpoint docs are minimal, using just the OpenAPI spec without additional explanations or examples - - Real-world example responses are limited, especially showing different states of resources - -3. **Onboarding Flow**: - - Current getting-started section has 4 pages: start-here, tracking-shipments-and-containers, list-shipments-and-containers, receive-status-updates - - Webhook implementation is mentioned but not emphasized in the initial flow - - Test numbers exist but are located in useful-info section rather than integrated directly into onboarding - - No clear guidance on checking webhook delivery or troubleshooting setup issues - -4. **Advanced Topics**: - - Tracking request lifecycle documentation exists but lacks visual workflow diagram - - Webhook documentation is extensive but separated from the initial onboarding flow - - No clear industry-specific implementation guides - - Limited tutorials for building common integrations - -5. **Identified Gaps**: - - Need for comprehensive examples showing different resource states (e.g., container in transit vs. with customs hold) - - Webhook implementation should be emphasized earlier and more strongly in the onboarding flow - - Test numbers should be integrated into onboarding process rather than hidden in useful-info - - No FAQ or troubleshooting section for common integration issues - - Limited industry-specific guides explaining the business value and implementation patterns - - Incomplete error documentation for API endpoints - - Missing "Capabilities Overview" page to highlight API features for non-developers - -### Steps 2 & 3 Progress Update - -#### Completed Files: -1. **Getting Started Improvements**: - - ✅ `/docs/api-docs/getting-started/start-here.mdx` - Enhanced with clear introduction and better organization - - ✅ `/docs/api-docs/getting-started/capabilities-overview.mdx` - Created comprehensive overview of API capabilities - -2. **In-Depth Guides Improvements**: - - ✅ `/docs/api-docs/in-depth-guides/json-api-guide.mdx` - Added detailed guide on JSON:API concepts - -3. **API Reference Improvements**: - - ✅ `/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` - Enhanced with comprehensive examples and troubleshooting - -#### Next Files to Update: -1. **Getting Started Improvements**: - - [ ] `/docs/api-docs/in-depth-guides/quickstart.mdx` - Revise to emphasize webhook setup earlier - -2. **API Reference Improvements**: - - [ ] `/docs/api-docs/api-reference/webhooks/create-a-webhook.mdx` - Enhance with detailed examples - - [ ] `/docs/api-docs/api-reference/containers/get-a-container.mdx` - Add examples showing different states - -3. **New Content to Create**: - - [ ] `/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` - Create new guide explaining both approaches - - [ ] `/docs/api-docs/troubleshooting/common-issues.mdx` - Create FAQ and troubleshooting section - -## Prioritized Improvement Plan - -Based on the audit, here's our prioritized list of improvements: - -1. **High Priority**: - - Add comprehensive request/response examples for each endpoint showing different states - - Create a capabilities overview page highlighting key API features - - Improve quick start guide with early webhook integration and test numbers - - Add a FAQ and troubleshooting section - -2. **Medium Priority**: - - Develop practical tutorials for common use cases - - Create industry-specific implementation guides - - Add error handling best practices - -3. **Lower Priority**: - - Refine documentation structure and navigation - - Implement feedback mechanisms - - Establish regular review process - -## Next Steps for Step 1: -1. Complete detailed review of each endpoint's documentation -2. Create a template for enhanced endpoint documentation with examples -3. Draft a capabilities overview page outline -4. Design improved onboarding flow that emphasizes webhooks and test numbers +## Progress Overview + +| Category | Status | Completion % | +|----------|--------|--------------| +| Getting Started | In Progress | 75% | +| In-Depth Guides | In Progress | 50% | +| API Reference Examples | In Progress | 25% | +| Tutorials | Not Started | 0% | +| General Documentation | In Progress | 40% | + +## Completed Updates + +- [x] Improved introduction and setup guidance in `docs/api-docs/getting-started/start-here.mdx` +- [x] Enhanced capabilities overview in `docs/api-docs/getting-started/capabilities-overview.mdx` +- [x] Created JSON:API guide in `docs/api-docs/in-depth-guides/json-api-guide.mdx` +- [x] Updated tracking request documentation in `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` +- [x] Created "Polling vs. Webhooks" comparison guide in `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` + +## In Progress + +- [ ] Creating Webhook implementation guide in `docs/api-docs/in-depth-guides/webhooks.mdx` +- [ ] Updating webhook API reference documentation +- [ ] Creating Terminal49 data model guide +- [ ] Developing authentication and authorization guide +- [ ] Improving error handling documentation + +## Next Steps + +1. **Immediate Focus (Next 48 hours):** + - Complete the webhook implementation guide + - Create authentication guide with detailed token management examples + - Develop error handling and troubleshooting documentation + +2. **Medium Priority (Next Week):** + - Enhance all endpoint documentation with consistent format and examples + - Create step-by-step tutorials for common use cases + - Add more code examples in multiple programming languages + +3. **Future Improvements:** + - Add interactive API explorer + - Create video tutorials for complex integration scenarios + - Develop SDK documentation + +## Notes on Implementation + +Our approach focuses on: +1. Ensuring consistent format and style across all documentation +2. Adding practical examples that demonstrate real-world usage +3. Improving navigability to help users find relevant information quickly +4. Following JSON:API specification accurately in our examples +5. Providing clear guidance on webhooks vs. polling approaches + +All documentation is being developed to align with OpenAPI specifications and Mintlify standards to ensure compatibility with the documentation portal. diff --git a/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx new file mode 100644 index 00000000..fc888519 --- /dev/null +++ b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx @@ -0,0 +1,276 @@ +--- +title: Polling vs. Webhooks +description: Compare the two approaches for receiving updates from Terminal49's API and choose the best one for your integration. +--- + +# Polling vs. Webhooks in Terminal49 + +There are two primary methods for getting updates from Terminal49's API: **polling** and **webhooks**. This guide explains both approaches, their trade-offs, and helps you choose the best option for your integration. + +## Understanding Both Approaches + + + + Your system periodically makes API requests to check for updates. + + + Terminal49 proactively sends updates to your system when changes occur. + + + +## Polling: The Pull Approach + +Polling involves your application regularly querying the Terminal49 API to check for updates on your tracked shipments and containers. + + + + // Example of polling in JavaScript + async function pollForUpdates() { + try { + const response = await fetch('https://api.terminal49.com/v2/containers/ABC123', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); + const container = await response.json(); + + // Process container data + updateContainerStatus(container); + + } catch (error) { + console.error('Error polling for updates:', error); + } + } + + // Poll every 15 minutes + setInterval(pollForUpdates, 15 * 60 * 1000); + + + +### When to Use Polling + + + Polling may be easier to implement initially, especially if you're working with a system that doesn't support receiving external HTTP requests or if you're behind a firewall that blocks incoming traffic. + + + + During development or for simple proof-of-concept integrations, polling can be a quick way to get started. + + + + If you only need to check container status once or twice a day, polling might be sufficient. + + +### Disadvantages of Polling + + + Polling can lead to delayed notifications, excessive API usage, and missed updates if your polling interval is too long. + + +- **Delayed Updates**: You only receive updates when you poll, which could be minutes or hours after an event occurred. +- **Increased API Usage**: Frequent polling consumes more API resources. +- **Potential Rate Limiting**: Very frequent polling could trigger rate limits. +- **Missed Events**: If you poll infrequently, you might miss intermediate state changes. + +## Webhooks: The Push Approach + +Webhooks allow Terminal49 to push real-time updates to your system whenever a change occurs, without you having to ask for them. + + + + // Creating a webhook endpoint in Node.js (Express) + const express = require('express'); + const app = express(); + app.use(express.json()); + + app.post('/terminal49-webhook', (req, res) => { + const event = req.body.data.attributes.event; + const reference = req.body.data.relationships.reference_object.data; + + console.log(`Received ${event} for ${reference.type} ${reference.id}`); + + // Process the webhook notification + processWebhookEvent(req.body); + + // Always return a success response + res.status(200).send('Webhook received'); + }); + + app.listen(3000, () => { + console.log('Webhook server running on port 3000'); + }); + + + + // Registering a webhook with Terminal49 API + async function registerWebhook() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "webhook", + attributes: { + url: "https://your-domain.com/terminal49-webhook", + event_types: [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.pickup_lfd.changed", + "tracking_request.succeeded", + "tracking_request.failed" + ] + } + } + }) + }); + + const webhook = await response.json(); + console.log('Webhook registered:', webhook); + + } catch (error) { + console.error('Error registering webhook:', error); + } + } + + + +### When to Use Webhooks + + + If you need immediate notifications about changes (like container arrivals, customs holds, or last free day changes), webhooks are essential. + + + + For production-grade integrations, webhooks provide the most reliable and efficient way to receive updates. + + + + If you're tracking many containers, webhooks prevent you from having to poll the status of each one repeatedly. + + +### Implementing Webhooks Successfully + +For webhooks to work correctly, you need: + +1. **A publicly accessible endpoint** that can receive HTTP POST requests +2. **Proper webhook handling** including: + - Responding with HTTP 200, 201, 202, or 204 status codes + - Processing webhook notifications asynchronously when possible + - Proper error handling and retry logic +3. **Security considerations**: + - Validate the webhook payload + - Use HTTPS for your endpoint + - Consider IP filtering if available + + + During development, you can use tools like [ngrok](https://ngrok.com/) to expose your local development environment to the internet for testing webhooks. + + +## Recommended Approach: Webhooks with Polling Fallback + +The recommended approach for Terminal49 integration is to: + +1. **Primary: Set up webhooks** for real-time notifications +2. **Secondary: Implement polling** as a fallback to verify data or recover from missed webhooks + + + ![Webhook with Polling Fallback](/images/webhook-with-polling-fallback.png) + + +This hybrid approach ensures you get the benefits of real-time updates while having a safety net to verify data integrity. + +## Example: Hybrid Implementation + + + + // 1. Set up webhooks (primary method) + // See webhook registration example above + + // 2. Implement periodic verification (fallback) + async function verifyContainerStatus() { + // Poll at a much lower frequency (e.g., once per day) + // to verify data and catch any potentially missed webhooks + try { + const response = await fetch('https://api.terminal49.com/v2/containers?filter[updated_since]=24h', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); + + const data = await response.json(); + const containers = data.data; + + // Verify each container against your local database + for (const container of containers) { + verifyContainerData(container); + } + + } catch (error) { + console.error('Error verifying container status:', error); + } + } + + // Run verification once per day + setInterval(verifyContainerStatus, 24 * 60 * 60 * 1000); + + + +## Summary Comparison + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeaturePollingWebhooks
Update TimelinessDelayed (depends on polling interval)Real-time
API UsageHigher (regular requests)Lower (on-demand updates)
Implementation ComplexitySimpleModerate (requires public endpoint)
Server RequirementsNone (client-initiated)Public HTTP endpoint
Good ForDevelopment, testing, simple use casesProduction systems, real-time requirements
+ +## Next Steps + + + + Learn how to implement webhooks for real-time updates. + + + Understand how Terminal49 handles webhook delivery retries. + + + Follow our recommended practices for efficient API usage. + + + Use test tracking numbers to simulate events for webhook testing. + + diff --git a/docs/api-docs/in-depth-guides/webhooks.mdx b/docs/api-docs/in-depth-guides/webhooks.mdx index 621a6e52..ddc49140 100644 --- a/docs/api-docs/in-depth-guides/webhooks.mdx +++ b/docs/api-docs/in-depth-guides/webhooks.mdx @@ -1,1443 +1,425 @@ --- -title: Webhooks +title: Implementing Webhooks +description: Learn how to set up webhooks to receive real-time updates from Terminal49 for your shipments and containers. --- -## Creating Webhooks -You may 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 programatically then see the [webhooks post endpoint documentation](/api-docs/api-reference/webhooks/create-a-webhook). - - -## Available Webook Events - -Each `WebhookNotification` event represents some change to a model which you may be notified of. - -List of Supported Events: - -Event | Description ----------|---------- - `tracking_request.succeeded` | Shipment created and linked to `TrackingRequest` - `tracking_request.failed` | `TrackingRequest` failed and shipment was not created - `tracking_request.awaiting_manifest` | `TrackingRequest` awaiting a manifest - `tracking_request.tracking_stopped` | Terminal49 is no longer updating this `TrackingRequest`. - `container.transport.empty_out` | Empty out at port of lading - `container.transport.full_in` | Full in at port of lading - `container.transport.vessel_loaded` | Vessel loaded at port of lading - `container.transport.vessel_departed` | Vessel departed at port of lading - `container.transport.transshipment_arrived` | Container arrived at transhipment port - `container.transport.transshipment_discharged` | Container discharged at transhipment port - `container.transport.transshipment_loaded` | Container loaded at transhipment port - `container.transport.transshipment_departed` | Container departed at transhipment port - `container.transport.feeder_arrived` | Container arrived on feeder vessel or barge - `container.transport.feeder_discharged` | Container discharged from feeder vessel or barge - `container.transport.feeder_loaded` | Container loaded on feeder vessel or barge - `container.transport.feeder_departed` | Container departed on feeder vessel or barge - `container.transport.vessel_arrived` | Container arrived on vessel at port of discharge (destination port) - `container.transport.vessel_berthed` | Container on vessel berthed at port of discharge (destination port) - `container.transport.vessel_discharged` | Container discharged at port of discharge - `container.transport.full_out` | Full out at port of discharge - `container.transport.empty_in` | Empty returned at destination - `container.transport.rail_loaded` | Rail loaded - `container.transport.rail_departed` | Rail departed - `container.transport.rail_arrived` | Rail arrived - `container.transport.rail_unloaded` | Rail unloaded - `shipment.estimated.arrival` | ETA change notification (for port of discharge) - `container.created` | Container added to shipment. Helpful for seeing new containers on a booking or BL. - `container.updated` | Container attribute(s) updated (see below example) - `container.pod_terminal_changed` | Port of discharge assignment changed for container - `container.transport.arrived_at_inland_destination` | Container arrived at inland destination - `container.transport.estimated.arrived_at_inland_destination` | ETA change notification (for destination) - `container.pickup_lfd.changed` | Last Free Day (LFD) changed for container - - - -## Receiving Webhooks - -When an event is triggered we will attempt to post to the URL you provided with the webhook. - -The payload of every webhook is a `webhook_notification`. Each Webhook notification includes a `reference_object` in it's relationships which is the subject of that notification (e.g. a tracking request, or an updated container). - -Please note that we expect the endpoint to return [HTTP 200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200), [HTTP 201](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201), [HTTP 202](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202) or [HTTP 204](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204). We aim to deliver all webhook notifications, so any other response, including timeout, will result in a dozen of retries. - -```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) +# Webhooks in Terminal49 +Webhooks provide real-time notifications about changes to your shipments and containers, letting you build responsive applications that update as soon as new information becomes available. -## Security -There are a few ways you can verify the webhooks sent by Terminal49. +## How Webhooks Work -Verify webhook signatures to confirm that received events are sent from Terminal49. Additionally, Terminal49 sends webhook events from a set list of IP addresses. Only trust events coming from these IP addresses. + + ![Terminal49 Webhook Flow](/images/webhook-flow.png) + +1. **You register a webhook** with a URL endpoint that can receive POST requests +2. **An event occurs** on Terminal49 (e.g., a vessel arrives or customs status changes) +3. **Terminal49 sends a notification** to your registered URL with details about the event +4. **Your application processes** the notification and takes appropriate action +## Setting Up Webhooks -### Webhook notification origin IP -The full list of IP addresses that webhook notifications may come from is: +### Step 1: Create a Webhook Endpoint -``` -35.222.62.171 -3.230.67.145 -44.217.15.129 -``` +First, you need to create an endpoint on your server that can receive POST requests. Here's an example using Node.js with Express: -### Verifying the webhook signature (optional) -When you create or get a webhook the model will include an attribute `secret`. + + + const express = require('express'); + const app = express(); + app.use(express.json()); -Whenever a webhook notification is delivered we create a signature by using the webhook `secret` as the key to generate a HMAC hex digest with SHA-256 on the body. + app.post('/terminal49-webhook', (req, res) => { + // Extract the event data + const webhookData = req.body; -This signature is added as the header `X-T49-Webhook-Signature` + // Process the webhook (recommended to do this asynchronously) + processWebhook(webhookData); -If you would like to verify that the webhook payload has not been tampered with by a 3rd party, then you can perform the same operation on the response body with the webhook secret and confirm that the digests match. + // Always return a 200 status code quickly to acknowledge receipt + res.status(200).send('Webhook received'); + }); -Below is a basic example of how this might look in a rails application. -```ruby -class WebhooksController < ApplicationController - def receive_tracking_request - secret = ENV.fetch('TRACKING_REQUEST_WEBHOOK_SECRET') - raise 'InvalidSignature' unless valid_signature?(request, secret) + function processWebhook(webhookData) { + // Extract the event type + const event = webhookData.data.attributes.event; - # continue processing webhook payload... + // Extract the reference object (what the event is about) + const referenceType = webhookData.data.relationships.reference_object.data.type; + const referenceId = webhookData.data.relationships.reference_object.data.id; - end + console.log(`Processing ${event} for ${referenceType} ${referenceId}`); - 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 + // Handle different event types + switch (event) { + case 'container.transport.vessel_arrived': + handleVesselArrival(webhookData); + break; + case 'container.available_for_pickup': + handleContainerAvailable(webhookData); + break; + case 'container.pickup_lfd.changed': + handleLastFreeDayChanged(webhookData); + break; + // Add cases for other events you're interested in + default: + console.log(`Unhandled event type: ${event}`); + } + } + app.listen(3000, () => { + console.log('Webhook server running on port 3000'); + }); + + + + + Your webhook endpoint must be publicly accessible over the internet for Terminal49 to send notifications to it. During development, you can use tools like [ngrok](https://ngrok.com/) to create a temporary public URL. + + +### Step 2: Register Your Webhook with Terminal49 + +Once your endpoint is ready, register it with Terminal49's API: + + + + async function registerWebhook() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "webhook", + attributes: { + url: "https://your-domain.com/terminal49-webhook", + event_types: [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.available_for_pickup", + "container.pickup_lfd.changed", + "container.holds.changed", + "tracking_request.succeeded", + "tracking_request.failed" + ] + } + } + }) + }); -### container.updated + const result = await response.json(); + console.log('Webhook registered:', result); + return result; -The container updated event lets you know about changes to container properties at the terminal, or which terminal the container is (or will be) located at. + } catch (error) { + console.error('Error registering webhook:', error); + throw error; + } + } + + + + curl -X POST \ + https://api.terminal49.com/v2/webhooks \ + -H 'Authorization: Token YOUR_API_KEY' \ + -H 'Content-Type: application/vnd.api+json' \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-domain.com/terminal49-webhook", + "event_types": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.available_for_pickup", + "container.pickup_lfd.changed", + "container.holds.changed", + "tracking_request.succeeded", + "tracking_request.failed" + ] + } + } + }' + + + +### Step 3: Verify and Test Your Webhook + +After registering, you can test your webhook using Terminal49's test tracking numbers: + + + + async function testWebhook() { + try { + // Create a tracking request with a test number + const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "tracking_request", + attributes: { + tracking_number: "TEST-ARRIVAL-TODAY", // Test number + reference_number: "TEST-ORDER-123" + } + } + }) + }); -The `changeset` attribute on is a hash of all the properties which changed on the container. + const result = await response.json(); + console.log('Test tracking request created:', result); -Each changed property is the hash key. The prior value is the first item in the array, and the current value is the second item in the array. + } catch (error) { + console.error('Error creating test tracking request:', error); + } + } + + -For example: -``` -"changeset": { - "pickup_lfd": [null, "2020-05-20 00:00:00"] -} -``` -Shows that the pickup last free day has changed from not being set to May 20 2020. +## Managing Webhooks -The properties we show changes for are: -- fees_at_pod_terminal -- holds_at_pod_terminal -- pickup_lfd -- pickup_appointment_at -- available_for_pickup -- pod_terminal +### Listing Your Webhooks -In every case the attribute `container_updated.timestamp` tells you when we picked up the changes from the terminal. +You can retrieve a list of all registered webhooks: + + + async function listWebhooks() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); -As container availability becomes known or changes at the POD Terminal we will send `container_updated` events with the key `available_for_pickup` in the `changeset`. -```json -{ - "data": { - "id": "fa1a6731-4b34-4b0c-aabc-460892055ba1", - "type": "webhook_notification", - "attributes": { - "id": "fa1a6731-4b34-4b0c-aabc-460892055ba1", - "event": "container.updated", - "delivery_status": "pending", - "created_at": "2023-01-24T00:11:32Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "e8f1976c-0089-4b98-96ae-90aa87fbdfee", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "8a5ffa8f-3dc1-48de-a0ea-09fc4f2cd96f", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ + const result = await response.json(); + console.log('Your webhooks:', result.data); + return result.data; - ] + } catch (error) { + console.error('Error listing webhooks:', error); + throw error; } } - }, - "included": [ - { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:11:32Z", - "ref_numbers": [ - "REF-50FFA3", - "REF-5AC291" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49DD306F13", - "normalized_number": "TE49DD306F13", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:11:32Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-23T20:11:32Z", - "pod_ata_at": "2023-01-23T23:11:32Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:11:32Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "588711e2-3f78-4178-ae5e-ccb690e0671d", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "26d8be45-b428-45fa-819b-46c828bf6fac", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container" + + + +### Updating a Webhook + +To update an existing webhook (e.g., to change the URL or event types): + + + + async function updateWebhook(webhookId) { + try { + const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { + method: 'PATCH', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + id: webhookId, + type: "webhook", + attributes: { + url: "https://your-updated-domain.com/webhook", + event_types: [ + "container.transport.vessel_arrived", + "container.available_for_pickup" + // Updated list of events + ] + } } - ] - } - }, - "links": { - "self": "/v2/shipments/adc08630-51d3-4bbc-a859-5157cbbe806c" - } - }, - { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port", - "attributes": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal", - "attributes": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "9a25e0aa-52bd-4bb8-8876-cd7616f5fb0f", - "type": "port" - } - } - } - }, - { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container", - "attributes": { - "number": "COSU1186800", - "seal_number": "43e29239e5dd5276", - "created_at": "2023-01-24T00:11:32Z", - "ref_numbers": [ - "REF-C86614", - "REF-456CEA" - ], - "pod_arrived_at": "2023-01-23T23:11:32Z", - "pod_discharged_at": "2023-01-24T00:11:32Z", - "final_destination_full_out_at": null, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 43333, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "transport_events": { - "data": [ + }) + }); - ] - }, - "raw_events": { - "data": [ + const result = await response.json(); + console.log('Webhook updated:', result); + return result; - ] - } - } - }, - { - "id": "e8f1976c-0089-4b98-96ae-90aa87fbdfee", - "type": "container_updated_event", - "attributes": { - "changeset": { - "available_for_pickup": [ - false, - true - ] - }, - "timestamp": "2023-01-24T00:11:32Z", - "data_source": "terminal", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "container": { - "data": { - "id": "3cd51f0e-eb18-4399-9f90-4c8a22250f63", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "4960e227-93b1-4f85-bf7c-07c9b6f597e0", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "adc08630-51d3-4bbc-a859-5157cbbe806c", - "type": "shipment" - } - } + } catch (error) { + console.error('Error updating webhook:', error); + throw error; } } - ] -} -``` - -The `pod_terminal` is a relationship of the container. When the pod_terminal changes the id is included. The terminal will be serialized in the included models. - -N.B. the `container_updated_event` also has a relationship to a `terminal` which refers to where the information came from. Currently this is always the POD terminal. In the future this may be the final destination terminal or an off-dock location. -```json -{ - "data": { - "id": "f6c5e340-94bf-4681-a47d-f2e8d6c90e59", - "type": "webhook_notification", - "attributes": { - "id": "f6c5e340-94bf-4681-a47d-f2e8d6c90e59", - "event": "container.updated", - "delivery_status": "pending", - "created_at": "2023-01-24T00:13:06Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "567eccef-53bf-43d5-b3d8-00278d7710df", - "type": "container_updated_event" - } - }, - "webhook": { - "data": { - "id": "2e5f41d1-8a3b-4940-a9bb-ff0481e09c71", - "type": "webhook" + + + +### Deleting a Webhook + +To stop receiving webhook notifications: + + + + async function deleteWebhook(webhookId) { + try { + const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { + method: 'DELETE', + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); + + if (response.status === 204) { + console.log(`Webhook ${webhookId} successfully deleted`); + return true; + } else { + console.error('Failed to delete webhook:', response.status); + return false; } - }, - "webhook_notification_logs": { - "data": [ - ] + } catch (error) { + console.error('Error deleting webhook:', error); + throw error; } } - }, - "included": [ - { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:13:05Z", - "ref_numbers": [ - "REF-29557A" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE497F86D5B7", - "normalized_number": "TE497F86D5B7", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:13:05Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-23T21:13:05Z", - "pod_ata_at": "2023-01-24T00:13:05Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:13:05Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "8d0f0cba-9961-4fa5-9bf0-0fb5fb67bdbe", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "f2a6a6e2-4bd1-4c66-aa8b-be4cb2ddc9a8", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null - }, - "containers": { - "data": [ - { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/c74ff2a5-5ede-4fc2-886b-3eeef886ff32" - } - }, - { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port", - "attributes": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal", - "attributes": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "nickname": "STO", - "name": "Shippers Transport Express", - "firms_code": "STO" - }, - "relationships": { - "port": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - } - } - }, - { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container", - "attributes": { - "number": "CGMU1560506", - "seal_number": "a9948b719482648c", - "created_at": "2023-01-24T00:13:06Z", - "ref_numbers": [ - "REF-D2AC6F", - "REF-34E84B" - ], - "pod_arrived_at": "2023-01-24T00:13:05Z", - "pod_discharged_at": "2023-01-24T00:13:05Z", - "final_destination_full_out_at": null, - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 43481, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": true, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "08831e36-766b-4ac8-8235-d8594b55ff6d", - "type": "terminal" - } - }, - "transport_events": { - "data": [ + + - ] - }, - "raw_events": { - "data": [ +## Webhook Event Types - ] - } - } - }, - { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "type": "terminal", - "attributes": { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "9722a830-634e-4f7a-b1b3-793ccaf8cbb2", - "type": "port" - } - } - } - }, - { - "id": "567eccef-53bf-43d5-b3d8-00278d7710df", - "type": "container_updated_event", - "attributes": { - "changeset": { - "pod_terminal": [ - "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "08831e36-766b-4ac8-8235-d8594b55ff6d" - ] - }, - "timestamp": "2023-01-24T00:13:06Z", - "data_source": "terminal", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "container": { - "data": { - "id": "adf4673d-f4ba-41a9-82da-55c0ae3b3722", - "type": "container" - } - }, - "terminal": { - "data": { - "id": "0ef5519f-1b39-4f6c-9961-1bbba0ac1307", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "c74ff2a5-5ede-4fc2-886b-3eeef886ff32", - "type": "shipment" - } - } - } - } - ] -} -``` - - -### tracking_request.succeeded - -```json -{ - "data": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "type": "webhook_notification", - "attributes": { - "id": "a76187fc-5749-43f9-9053-cfaad9790a31", - "event": "tracking_request.succeeded", - "delivery_status": "pending", - "created_at": "2020-09-11T21:25:34Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request" - } - }, - "webhook": { - "data": { - "id": "914b21ce-dd7d-4c49-8503-65aba488e9a9", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [] - } - } - }, - "included": [ - { - "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", - "type": "tracking_request", - "attributes": { - "request_number": "TE497ED1063E", - "request_type": "bill_of_lading", - "scac": "MSCU", - "ref_numbers": [], - "created_at": "2020-09-11T21:25:34Z", - "updated_at": "2020-09-11T22:25:34Z", - "status": "created", - "failed_reason": null, - "is_retrying": false, - "retry_count": null - }, - "relationships": { - "tracked_object": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - } - }, - "links": { - "self": "/v2/tracking_requests/bdeca506-9741-4ab1-a0a7-cfd1d908e923" - } - }, - { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment", - "attributes": { - "created_at": "2020-09-11T21:25:33Z", - "bill_of_lading_number": "TE497ED1063E", - "ref_numbers": [], - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2020-08-29T21:25:33Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2020-09-18T21:25:33Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles" - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "4384d6a5-5ccc-43b7-8d19-4a9525e74c08", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "containers": { - "data": [ - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - ] - } - }, - "links": { - "self": "/v2/shipments/b5b10c0a-8d18-46da-b4c2-4e5fa790e7da" - } - }, - { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container", - "attributes": { - "number": "ARDU1824900", - "seal_number": "139F1451", - "created_at": "2020-09-11T21:25:34Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 53507, - "fees_at_pod_terminal": [], - "holds_at_pod_terminal": [], - "pickup_lfd": null, - "pickup_appointment_at": null, - "availability_known": true, - "available_for_pickup": false, - "pod_arrived_at": null, - "pod_discharged_at": null, - "location_at_pod_terminal": null, - "final_destination_full_out_at": null, - "pod_full_out_at": null, - "empty_terminated_at": null - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "pod_terminal": { - "data": { - "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", - "type": "terminal" - } - }, - "transport_events": { - "data": [ - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event" - } - ] - } - } - }, - { - "id": "56078596-5293-4c84-9245-cca00a787265", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_departed", - "created_at": "2020-09-11T21:25:34Z", - "voyage_number": null, - "timestamp": "2020-08-29T21:25:33Z", - "location_locode": "MXZLO", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", - "type": "container" - } - }, - "vessel": { - "data": null - }, - "location": { - "data": { - "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", - "type": "port" - } - }, - "terminal": { - "data": null - } - } - } - ] -} -``` - -### shipment.estimated.arrival - -```json -{ - "data": { - "id": "b03bcf3c-252d-41f8-b86f-939b404e304b", - "type": "webhook_notification", - "attributes": { - "id": "b03bcf3c-252d-41f8-b86f-939b404e304b", - "event": "shipment.estimated.arrival", - "delivery_status": "pending", - "created_at": "2022-01-13T19:56:58Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "14b5047f-e3e7-4df7-a570-2d3878e6d863", - "type": "estimated_event" - } - }, - "webhook": { - "data": { - "id": "d60a23a4-f40d-44d2-8b6a-2e55a527e6a2", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ +Terminal49 supports multiple event types that you can subscribe to: - ] - } - } - }, - "included": [ - { - "id": "14b5047f-e3e7-4df7-a570-2d3878e6d863", - "type": "estimated_event", - "attributes": { - "created_at": "2022-01-13T19:56:58Z", - "estimated_timestamp": "2022-01-16T19:56:58Z", - "voyage_number": "098N", - "event": "shipment.estimated.arrival", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8", - "type": "shipment" - } - }, - "port": { - "data": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port" - } - }, - "vessel": { - "data": { - "id": "b1550abc-4e73-4271-a0f4-8ac031f242cd", - "type": "vessel" - } - } - } - }, - { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port", - "attributes": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8", - "type": "shipment", - "attributes": { - "created_at": "2022-01-13T19:56:58Z", - "ref_numbers": [ - "REF-3AA505", - "REF-910757", - "REF-2A8357" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49C31E16E2", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2021-12-31T19:56:58Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2022-01-16T19:56:58Z", - "pod_ata_at": null, - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2022-01-13T19:56:58Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "78ad2915-700b-4919-8ede-a3b6c2137436", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "3ee88ea1-3b8b-4b96-80fb-6aa23ba7065e", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "3bd88777-48ea-4880-9cb9-961dd4d26a00", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "1d016b3d-96d5-4867-8f99-77233d1cc57d", - "type": "terminal" - } - }, - "containers": { - "data": [ + + - `container.created`: A new container is tracked in the system + - `container.estimated_arrival_changed`: The estimated arrival date has changed + - `container.transport.vessel_arrived`: The vessel carrying the container has arrived at port + - `container.transport.vessel_discharged`: The container has been unloaded from vessel + - `container.available_for_pickup`: The container is available for pickup from terminal + - `container.pickup_lfd.changed`: Last Free Day for pickup has changed + - `container.holds.changed`: Customs or terminal holds have changed + - `container.picked_up`: The container has been picked up from terminal + - `container.empty_returned`: The empty container has been returned to terminal + - ] - } - }, - "links": { - "self": "/v2/shipments/8e4a1f1e-aa13-4cad-9df0-aec6c791a5f8" - } - } - ] -} -``` - -### container.transport.vessel_arrived - -```json -{ - "data": { - "id": "72f8b0b5-28f5-4a12-8274-71d4d23c9ab7", - "type": "webhook_notification", - "attributes": { - "id": "72f8b0b5-28f5-4a12-8274-71d4d23c9ab7", - "event": "container.transport.vessel_arrived", - "delivery_status": "pending", - "created_at": "2023-01-24T00:14:28Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event" - } - }, - "webhook": { - "data": { - "id": "655236f8-7936-4611-b580-341d3e1103f5", - "type": "webhook" - } - }, - "webhook_notification_logs": { - "data": [ + + - `tracking_request.succeeded`: A tracking request was successful + - `tracking_request.failed`: A tracking request failed to find container information + - ] - } - } - }, - "included": [ + + - `shipment.created`: A new shipment is created in the system + - `shipment.updated`: A shipment's details have been updated + + +## Webhook Payload Structure + +All webhook notifications follow the JSON:API format. Here's a typical webhook payload structure: + + + { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment", - "attributes": { - "created_at": "2023-01-24T00:14:28Z", - "ref_numbers": [ - "REF-134938", - "REF-BE2704", - "REF-712D47" - ], - "tags": [ - - ], - "bill_of_lading_number": "TE49735F4B1D", - "normalized_number": "TE49735F4B1D", - "shipping_line_scac": "MSCU", - "shipping_line_name": "Mediterranean Shipping Company", - "shipping_line_short_name": "MSC", - "port_of_lading_locode": "MXZLO", - "port_of_lading_name": "Manzanillo", - "port_of_discharge_locode": "USOAK", - "port_of_discharge_name": "Port of Oakland", - "pod_vessel_name": "MSC CHANNE", - "pod_vessel_imo": "9710438", - "pod_voyage_number": "098N", - "destination_locode": null, - "destination_name": null, - "destination_timezone": null, - "destination_ata_at": null, - "destination_eta_at": null, - "pol_etd_at": null, - "pol_atd_at": "2023-01-11T00:14:28Z", - "pol_timezone": "America/Mexico_City", - "pod_eta_at": "2023-01-31T00:14:28Z", - "pod_ata_at": "2023-01-31T01:14:28Z", - "pod_timezone": "America/Los_Angeles", - "line_tracking_last_attempted_at": null, - "line_tracking_last_succeeded_at": "2023-01-24T00:14:28Z", - "line_tracking_stopped_at": null, - "line_tracking_stopped_reason": null - }, - "relationships": { - "port_of_lading": { - "data": { - "id": "036084b7-f2cc-49b5-9d81-7de2cdabfc69", - "type": "port" - } - }, - "port_of_discharge": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" - } - }, - "pod_terminal": { - "data": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal" - } - }, - "destination": { - "data": null - }, - "destination_terminal": { - "data": { - "id": "b07e8193-47cf-4395-a1f6-a5d4d7fa9b17", - "type": "terminal" - } - }, - "line_tracking_stopped_by_user": { - "data": null + "data": { + "id": "9c5d91d1-f67d-4e5c-bbf5-c7c71a36fb23", + "type": "webhook_notification", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2023-05-15T21:13:24Z" }, - "containers": { - "data": [ - { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", + "relationships": { + "reference_object": { + "data": { + "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", "type": "container" } - ] - } - }, - "links": { - "self": "/v2/shipments/290a696b-5fba-45aa-a08c-0e15ae89e9c0" - } - }, - { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", - "type": "container", - "attributes": { - "number": "GLDU1222600", - "seal_number": "d5103634ed1adbd4", - "created_at": "2023-01-24T00:14:28Z", - "ref_numbers": [ - "REF-889564" - ], - "pod_arrived_at": "2023-01-24T00:14:28Z", - "pod_discharged_at": "2023-01-24T00:14:28Z", - "final_destination_full_out_at": "2023-01-24T00:14:28Z", - "equipment_type": "dry", - "equipment_length": 40, - "equipment_height": "standard", - "weight_in_lbs": 46679, - "pod_full_out_at": null, - "empty_terminated_at": null, - "terminal_checked_at": null, - "fees_at_pod_terminal": [ - - ], - "holds_at_pod_terminal": [ - - ], - "pickup_lfd": null, - "pickup_appointment_at": null, - "pod_full_out_chassis_number": null, - "location_at_pod_terminal": null, - "availability_known": true, - "available_for_pickup": false, - "pod_timezone": "America/Los_Angeles", - "final_destination_timezone": null, - "empty_terminated_timezone": "America/Los_Angeles" - }, - "relationships": { - "shipment": { - "data": { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment" - } - }, - "pod_terminal": { - "data": null - }, - "transport_events": { - "data": [ - { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event" - } - ] - }, - "raw_events": { - "data": [ - - ] - } - } - }, - { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port", - "attributes": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "name": "Port of Oakland", - "code": "USOAK", - "state_abbr": "CA", - "city": "Oakland", - "country_code": "US", - "time_zone": "America/Los_Angeles" - } - }, - { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal", - "attributes": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "nickname": "SSA", - "name": "SSA Terminal", - "firms_code": "Z985" - }, - "relationships": { - "port": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" } } - } - }, - { - "id": "100c303e-79df-4301-9bf7-13f9e0c85851", - "type": "vessel", - "attributes": { - "name": "MSC CHANNE", - "imo": "9710438", - "mmsi": "255805864", - "latitude": -78.30435842851921, - "longitude": 25.471353799804547, - "nautical_speed_knots": 100, - "navigational_heading_degrees": 1, - "position_timestamp": "2023-06-05T19:46:18Z" - } - }, - { - "id": "c1443820-304a-444b-bf42-c3d885dc8daa", - "type": "transport_event", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2023-01-24T00:14:27Z", - "voyage_number": null, - "timestamp": "2023-01-24T00:14:27Z", - "data_source": "shipping_line", - "location_locode": "USOAK", - "timezone": "America/Los_Angeles" }, - "relationships": { - "shipment": { - "data": { - "id": "290a696b-5fba-45aa-a08c-0e15ae89e9c0", - "type": "shipment" - } - }, - "container": { - "data": { - "id": "c8fa5c2a-1bd0-48d8-8c94-2ef8a06c4ce9", - "type": "container" - } - }, - "vessel": { - "data": { - "id": "100c303e-79df-4301-9bf7-13f9e0c85851", - "type": "vessel" - } - }, - "location": { - "data": { - "id": "0e0c9ad6-ec83-48b3-87f9-c2710659821b", - "type": "port" - } - }, - "terminal": { - "data": { - "id": "1ee2022a-e054-4f76-8c1a-60967e76b407", - "type": "terminal" + "included": [ + { + "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", + "type": "container", + "attributes": { + "number": "ABCD1234567", + "seal_number": "SL123456", + "created_at": "2023-05-10T15:34:12Z", + "updated_at": "2023-05-15T21:13:20Z" + // Additional container attributes... + }, + "relationships": { + "terminal": { + "data": { + "id": "7fa33ba6-2a50-4c5b-9889-3e814b7e9218", + "type": "terminal" + } + }, + "shipment": { + "data": { + "id": "ae12fe45-0dc3-4a78-99b1-6fe08c58cbcf", + "type": "shipment" + } + } + // Additional relationships... } } - } + // Additional included resources like terminal, shipment, etc. + ] } - ] -} -``` \ No newline at end of file + + + +## Best Practices + + + Your webhook endpoint should acknowledge receipt of a webhook by returning a 2xx HTTP status code (200, 201, 202, or 204) as quickly as possible. Process the webhook data asynchronously after acknowledging receipt. + + + + Design your webhook handler to be idempotent, meaning it handles duplicate webhook deliveries gracefully. Terminal49 may retry webhook delivery if your server doesn't respond with a success code. + + + + - Use HTTPS for your webhook endpoint + - Consider implementing webhook signature validation if available + - Only accept webhooks from Terminal49's IP ranges if possible + + + + Handle errors gracefully and set up monitoring for your webhook endpoint. If webhooks aren't being received as expected, check your server logs and Terminal49 dashboard. + + + + Implement a polling fallback that periodically checks the API directly in case webhooks are missed or delayed. This ensures your data stays up-to-date even if webhook delivery fails. + + +## Troubleshooting + + + - Check if your webhook URL is accessible from the internet + - Verify your server is returning 2xx status codes + - Check if the webhook is registered correctly + - Look for any firewalls or security measures blocking incoming requests + + + + Terminal49 may retry webhook delivery if your server doesn't respond or returns an error. Implement idempotency in your handler to prevent processing the same event multiple times. + + + + Use Terminal49's [test tracking numbers](/api-docs/useful-info/test-numbers) to simulate events and test your webhook handling without waiting for real events to occur. + + +## Next Steps + + + + Compare different approaches for receiving updates from Terminal49. + + + View the complete webhook API reference. + + + Learn about test tracking numbers to simulate events. + + + Understand how webhooks relate to Terminal49's data model. + + From abb7fddfb2d1ec6edae5e3f654ccbcfeff1c226b Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 12:50:26 -0700 Subject: [PATCH 03/42] Fix MDX parsing errors in documentation files --- .../create-a-tracking-request.mdx | 64 +-- .../in-depth-guides/json-api-guide.mdx | 20 +- .../in-depth-guides/polling-vs-webhooks.mdx | 190 +++---- docs/api-docs/in-depth-guides/webhooks.mdx | 477 +++++++++--------- docs/mint.json | 3 + 5 files changed, 392 insertions(+), 362 deletions(-) diff --git a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx index 0b668eda..c8eacfaa 100644 --- a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx +++ b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx @@ -20,38 +20,42 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", // Type of tracking number - "request_number": "MAEU9736478", // Bill of lading number from the carrier - "scac": "MAEU" // Shipping line SCAC code (Maersk in this case) - } - } - } +```json +{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } +} +``` - { - "data": { - "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "status": "pending", // Request is being processed - "failed_reason": null, - "created_at": "2023-05-15T14:23:12Z", - "updated_at": "2023-05-15T14:23:12Z" - }, - "relationships": { - "shipment": { - "data": null // Will be populated when tracking succeeds - } - } - } - } +```json +{ + "data": { + "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T14:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" + }, + "relationships": { + "shipment": { + "data": null + } + } + } +} +``` diff --git a/docs/api-docs/in-depth-guides/json-api-guide.mdx b/docs/api-docs/in-depth-guides/json-api-guide.mdx index 80bbefa8..47d21974 100644 --- a/docs/api-docs/in-depth-guides/json-api-guide.mdx +++ b/docs/api-docs/in-depth-guides/json-api-guide.mdx @@ -109,16 +109,18 @@ When creating a resource (like a tracking request), your request should follow t - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU" - } - } +```json +{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" } + } +} +``` diff --git a/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx index fc888519..630af07f 100644 --- a/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx +++ b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx @@ -24,26 +24,28 @@ Polling involves your application regularly querying the Terminal49 API to check - // Example of polling in JavaScript - async function pollForUpdates() { - try { - const response = await fetch('https://api.terminal49.com/v2/containers/ABC123', { - headers: { - 'Authorization': 'Token YOUR_API_KEY' - } - }); - const container = await response.json(); +```javascript +// Example of polling in JavaScript +async function pollForUpdates() { + try { + const response = await fetch('https://api.terminal49.com/v2/containers/ABC123', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); + const container = await response.json(); - // Process container data - updateContainerStatus(container); + // Process container data + updateContainerStatus(container); - } catch (error) { - console.error('Error polling for updates:', error); - } - } + } catch (error) { + console.error('Error polling for updates:', error); + } +} - // Poll every 15 minutes - setInterval(pollForUpdates, 15 * 60 * 1000); +// Poll every 15 minutes +setInterval(pollForUpdates, 15 * 60 * 1000); +``` @@ -78,63 +80,67 @@ Webhooks allow Terminal49 to push real-time updates to your system whenever a ch - // Creating a webhook endpoint in Node.js (Express) - const express = require('express'); - const app = express(); - app.use(express.json()); +```javascript +// Creating a webhook endpoint in Node.js (Express) +const express = require('express'); +const app = express(); +app.use(express.json()); - app.post('/terminal49-webhook', (req, res) => { - const event = req.body.data.attributes.event; - const reference = req.body.data.relationships.reference_object.data; +app.post('/terminal49-webhook', (req, res) => { + const event = req.body.data.attributes.event; + const reference = req.body.data.relationships.reference_object.data; - console.log(`Received ${event} for ${reference.type} ${reference.id}`); + console.log(`Received ${event} for ${reference.type} ${reference.id}`); - // Process the webhook notification - processWebhookEvent(req.body); + // Process the webhook notification + processWebhookEvent(req.body); - // Always return a success response - res.status(200).send('Webhook received'); - }); + // Always return a success response + res.status(200).send('Webhook received'); +}); - app.listen(3000, () => { - console.log('Webhook server running on port 3000'); - }); +app.listen(3000, () => { + console.log('Webhook server running on port 3000'); +}); +``` - // Registering a webhook with Terminal49 API - async function registerWebhook() { - try { - const response = await fetch('https://api.terminal49.com/v2/webhooks', { - method: 'POST', - headers: { - 'Authorization': 'Token YOUR_API_KEY', - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: "webhook", - attributes: { - url: "https://your-domain.com/terminal49-webhook", - event_types: [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.pickup_lfd.changed", - "tracking_request.succeeded", - "tracking_request.failed" - ] - } - } - }) - }); - - const webhook = await response.json(); - console.log('Webhook registered:', webhook); - - } catch (error) { - console.error('Error registering webhook:', error); - } - } +```javascript +// Registering a webhook with Terminal49 API +async function registerWebhook() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "webhook", + attributes: { + url: "https://your-domain.com/terminal49-webhook", + event_types: [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.pickup_lfd.changed", + "tracking_request.succeeded", + "tracking_request.failed" + ] + } + } + }) + }); + + const webhook = await response.json(); + console.log('Webhook registered:', webhook); + + } catch (error) { + console.error('Error registering webhook:', error); + } +} +``` @@ -187,35 +193,37 @@ This hybrid approach ensures you get the benefits of real-time updates while hav - // 1. Set up webhooks (primary method) - // See webhook registration example above - - // 2. Implement periodic verification (fallback) - async function verifyContainerStatus() { - // Poll at a much lower frequency (e.g., once per day) - // to verify data and catch any potentially missed webhooks - try { - const response = await fetch('https://api.terminal49.com/v2/containers?filter[updated_since]=24h', { - headers: { - 'Authorization': 'Token YOUR_API_KEY' - } - }); - - const data = await response.json(); - const containers = data.data; +```javascript +// 1. Set up webhooks (primary method) +// See webhook registration example above + +// 2. Implement periodic verification (fallback) +async function verifyContainerStatus() { + // Poll at a much lower frequency (e.g., once per day) + // to verify data and catch any potentially missed webhooks + try { + const response = await fetch('https://api.terminal49.com/v2/containers?filter[updated_since]=24h', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); - // Verify each container against your local database - for (const container of containers) { - verifyContainerData(container); - } + const data = await response.json(); + const containers = data.data; - } catch (error) { - console.error('Error verifying container status:', error); - } + // Verify each container against your local database + for (const container of containers) { + verifyContainerData(container); } - // Run verification once per day - setInterval(verifyContainerStatus, 24 * 60 * 60 * 1000); + } catch (error) { + console.error('Error verifying container status:', error); + } +} + +// Run verification once per day +setInterval(verifyContainerStatus, 24 * 60 * 60 * 1000); +``` diff --git a/docs/api-docs/in-depth-guides/webhooks.mdx b/docs/api-docs/in-depth-guides/webhooks.mdx index ddc49140..4c0f3f27 100644 --- a/docs/api-docs/in-depth-guides/webhooks.mdx +++ b/docs/api-docs/in-depth-guides/webhooks.mdx @@ -26,51 +26,53 @@ First, you need to create an endpoint on your server that can receive POST reque - const express = require('express'); - const app = express(); - app.use(express.json()); - - app.post('/terminal49-webhook', (req, res) => { - // Extract the event data - const webhookData = req.body; - - // Process the webhook (recommended to do this asynchronously) - processWebhook(webhookData); - - // Always return a 200 status code quickly to acknowledge receipt - res.status(200).send('Webhook received'); - }); - - function processWebhook(webhookData) { - // Extract the event type - const event = webhookData.data.attributes.event; - - // Extract the reference object (what the event is about) - const referenceType = webhookData.data.relationships.reference_object.data.type; - const referenceId = webhookData.data.relationships.reference_object.data.id; - - console.log(`Processing ${event} for ${referenceType} ${referenceId}`); - - // Handle different event types - switch (event) { - case 'container.transport.vessel_arrived': - handleVesselArrival(webhookData); - break; - case 'container.available_for_pickup': - handleContainerAvailable(webhookData); - break; - case 'container.pickup_lfd.changed': - handleLastFreeDayChanged(webhookData); - break; - // Add cases for other events you're interested in - default: - console.log(`Unhandled event type: ${event}`); - } - } - - app.listen(3000, () => { - console.log('Webhook server running on port 3000'); - }); +```javascript +const express = require('express'); +const app = express(); +app.use(express.json()); + +app.post('/terminal49-webhook', (req, res) => { + // Extract the event data + const webhookData = req.body; + + // Process the webhook (recommended to do this asynchronously) + processWebhook(webhookData); + + // Always return a 200 status code quickly to acknowledge receipt + res.status(200).send('Webhook received'); +}); + +function processWebhook(webhookData) { + // Extract the event type + const event = webhookData.data.attributes.event; + + // Extract the reference object (what the event is about) + const referenceType = webhookData.data.relationships.reference_object.data.type; + const referenceId = webhookData.data.relationships.reference_object.data.id; + + console.log(`Processing ${event} for ${referenceType} ${referenceId}`); + + // Handle different event types + switch (event) { + case 'container.transport.vessel_arrived': + handleVesselArrival(webhookData); + break; + case 'container.available_for_pickup': + handleContainerAvailable(webhookData); + break; + case 'container.pickup_lfd.changed': + handleLastFreeDayChanged(webhookData); + break; + // Add cases for other events you're interested in + default: + console.log(`Unhandled event type: ${event}`); + } +} + +app.listen(3000, () => { + console.log('Webhook server running on port 3000'); +}); +``` @@ -84,55 +86,21 @@ Once your endpoint is ready, register it with Terminal49's API: - async function registerWebhook() { - try { - const response = await fetch('https://api.terminal49.com/v2/webhooks', { - method: 'POST', - headers: { - 'Authorization': 'Token YOUR_API_KEY', - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: "webhook", - attributes: { - url: "https://your-domain.com/terminal49-webhook", - event_types: [ - "container.transport.vessel_arrived", - "container.transport.vessel_discharged", - "container.available_for_pickup", - "container.pickup_lfd.changed", - "container.holds.changed", - "tracking_request.succeeded", - "tracking_request.failed" - ] - } - } - }) - }); - - const result = await response.json(); - console.log('Webhook registered:', result); - return result; - - } catch (error) { - console.error('Error registering webhook:', error); - throw error; - } - } - - - - curl -X POST \ - https://api.terminal49.com/v2/webhooks \ - -H 'Authorization: Token YOUR_API_KEY' \ - -H 'Content-Type: application/vnd.api+json' \ - -d '{ - "data": { - "type": "webhook", - "attributes": { - "url": "https://your-domain.com/terminal49-webhook", - "event_types": [ +```javascript +async function registerWebhook() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "webhook", + attributes: { + url: "https://your-domain.com/terminal49-webhook", + event_types: [ "container.transport.vessel_arrived", "container.transport.vessel_discharged", "container.available_for_pickup", @@ -143,7 +111,45 @@ Once your endpoint is ready, register it with Terminal49's API: ] } } - }' + }) + }); + + const result = await response.json(); + console.log('Webhook registered:', result); + return result; + + } catch (error) { + console.error('Error registering webhook:', error); + throw error; + } +} +``` + + + +```bash +curl -X POST \ + https://api.terminal49.com/v2/webhooks \ + -H 'Authorization: Token YOUR_API_KEY' \ + -H 'Content-Type: application/vnd.api+json' \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-domain.com/terminal49-webhook", + "event_types": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.available_for_pickup", + "container.pickup_lfd.changed", + "container.holds.changed", + "tracking_request.succeeded", + "tracking_request.failed" + ] + } + } + }' +``` @@ -153,33 +159,35 @@ After registering, you can test your webhook using Terminal49's test tracking nu - async function testWebhook() { - try { - // Create a tracking request with a test number - const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { - method: 'POST', - headers: { - 'Authorization': 'Token YOUR_API_KEY', - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: "tracking_request", - attributes: { - tracking_number: "TEST-ARRIVAL-TODAY", // Test number - reference_number: "TEST-ORDER-123" - } - } - }) - }); - - const result = await response.json(); - console.log('Test tracking request created:', result); - - } catch (error) { - console.error('Error creating test tracking request:', error); - } - } +```javascript +async function testWebhook() { + try { + // Create a tracking request with a test number + const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: "tracking_request", + attributes: { + tracking_number: "TEST-ARRIVAL-TODAY", // Test number + reference_number: "TEST-ORDER-123" + } + } + }) + }); + + const result = await response.json(); + console.log('Test tracking request created:', result); + + } catch (error) { + console.error('Error creating test tracking request:', error); + } +} +``` @@ -191,23 +199,25 @@ You can retrieve a list of all registered webhooks: - async function listWebhooks() { - try { - const response = await fetch('https://api.terminal49.com/v2/webhooks', { - headers: { - 'Authorization': 'Token YOUR_API_KEY' - } - }); +```javascript +async function listWebhooks() { + try { + const response = await fetch('https://api.terminal49.com/v2/webhooks', { + headers: { + 'Authorization': 'Token YOUR_API_KEY' + } + }); - const result = await response.json(); - console.log('Your webhooks:', result.data); - return result.data; + const result = await response.json(); + console.log('Your webhooks:', result.data); + return result.data; - } catch (error) { - console.error('Error listing webhooks:', error); - throw error; - } - } + } catch (error) { + console.error('Error listing webhooks:', error); + throw error; + } +} +``` @@ -217,39 +227,41 @@ To update an existing webhook (e.g., to change the URL or event types): - async function updateWebhook(webhookId) { - try { - const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { - method: 'PATCH', - headers: { - 'Authorization': 'Token YOUR_API_KEY', - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - id: webhookId, - type: "webhook", - attributes: { - url: "https://your-updated-domain.com/webhook", - event_types: [ - "container.transport.vessel_arrived", - "container.available_for_pickup" - // Updated list of events - ] - } - } - }) - }); - - const result = await response.json(); - console.log('Webhook updated:', result); - return result; - - } catch (error) { - console.error('Error updating webhook:', error); - throw error; - } - } +```javascript +async function updateWebhook(webhookId) { + try { + const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { + method: 'PATCH', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + id: webhookId, + type: "webhook", + attributes: { + url: "https://your-updated-domain.com/webhook", + event_types: [ + "container.transport.vessel_arrived", + "container.available_for_pickup" + // Updated list of events + ] + } + } + }) + }); + + const result = await response.json(); + console.log('Webhook updated:', result); + return result; + + } catch (error) { + console.error('Error updating webhook:', error); + throw error; + } +} +``` @@ -259,28 +271,30 @@ To stop receiving webhook notifications: - async function deleteWebhook(webhookId) { - try { - const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { - method: 'DELETE', - headers: { - 'Authorization': 'Token YOUR_API_KEY' - } - }); - - if (response.status === 204) { - console.log(`Webhook ${webhookId} successfully deleted`); - return true; - } else { - console.error('Failed to delete webhook:', response.status); - return false; - } - - } catch (error) { - console.error('Error deleting webhook:', error); - throw error; +```javascript +async function deleteWebhook(webhookId) { + try { + const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { + method: 'DELETE', + headers: { + 'Authorization': 'Token YOUR_API_KEY' } + }); + + if (response.status === 204) { + console.log(`Webhook ${webhookId} successfully deleted`); + return true; + } else { + console.error('Failed to delete webhook:', response.status); + return false; } + + } catch (error) { + console.error('Error deleting webhook:', error); + throw error; + } +} +``` @@ -316,53 +330,52 @@ All webhook notifications follow the JSON:API format. Here's a typical webhook p - { - "data": { - "id": "9c5d91d1-f67d-4e5c-bbf5-c7c71a36fb23", - "type": "webhook_notification", - "attributes": { - "event": "container.transport.vessel_arrived", - "created_at": "2023-05-15T21:13:24Z" - }, - "relationships": { - "reference_object": { - "data": { - "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", - "type": "container" - } - } +```json +{ + "data": { + "id": "9c5d91d1-f67d-4e5c-bbf5-c7c71a36fb23", + "type": "webhook_notification", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2023-05-15T21:13:24Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", + "type": "container" } + } + } + }, + "included": [ + { + "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", + "type": "container", + "attributes": { + "number": "ABCD1234567", + "seal_number": "SL123456", + "created_at": "2023-05-10T15:34:12Z", + "updated_at": "2023-05-15T21:13:20Z" }, - "included": [ - { - "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", - "type": "container", - "attributes": { - "number": "ABCD1234567", - "seal_number": "SL123456", - "created_at": "2023-05-10T15:34:12Z", - "updated_at": "2023-05-15T21:13:20Z" - // Additional container attributes... - }, - "relationships": { - "terminal": { - "data": { - "id": "7fa33ba6-2a50-4c5b-9889-3e814b7e9218", - "type": "terminal" - } - }, - "shipment": { - "data": { - "id": "ae12fe45-0dc3-4a78-99b1-6fe08c58cbcf", - "type": "shipment" - } - } - // Additional relationships... + "relationships": { + "terminal": { + "data": { + "id": "7fa33ba6-2a50-4c5b-9889-3e814b7e9218", + "type": "terminal" + } + }, + "shipment": { + "data": { + "id": "ae12fe45-0dc3-4a78-99b1-6fe08c58cbcf", + "type": "shipment" } } - // Additional included resources like terminal, shipment, etc. - ] + } } + ] +} +``` diff --git a/docs/mint.json b/docs/mint.json index 7af868bf..82f48da3 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -61,6 +61,7 @@ "group": "Getting Started", "pages": [ "api-docs/getting-started/start-here", + "api-docs/getting-started/capabilities-overview", "api-docs/getting-started/tracking-shipments-and-containers", "api-docs/getting-started/list-shipments-and-containers", "api-docs/getting-started/receive-status-updates" @@ -70,6 +71,8 @@ "group": "In Depth Guides", "pages": [ "api-docs/in-depth-guides/quickstart", + "api-docs/in-depth-guides/json-api-guide", + "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/in-depth-guides/including-resources", "api-docs/in-depth-guides/adding-customer", From 7a9bfdb830368ecf0db8c0428cc6ac82532456dc Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 12:56:24 -0700 Subject: [PATCH 04/42] Fix Mintlify MDX parsing errors with proper code block formatting --- .../create-a-tracking-request.mdx | 248 +++++++++--------- .../in-depth-guides/json-api-guide.mdx | 4 - docs/api-docs/in-depth-guides/webhooks.mdx | 128 ++++----- 3 files changed, 166 insertions(+), 214 deletions(-) diff --git a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx index c8eacfaa..a571ce6c 100644 --- a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx +++ b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx @@ -18,46 +18,41 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - - -```json -{ - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU" + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } } - } -} -``` - - -```json -{ - "data": { - "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T14:23:12Z", - "updated_at": "2023-05-15T14:23:12Z" - }, - "relationships": { - "shipment": { - "data": null + ``` + + ```json title="Response" + { + "data": { + "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T14:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" + }, + "relationships": { + "shipment": { + "data": null + } + } } } - } -} -``` - - + ``` **Notes:** - The bill of lading is typically the most reliable identifier for tracking shipments. @@ -66,42 +61,41 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - - - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "container", // Tracking by container number - "request_number": "MSCU1234567", // Container number - "scac": "MSCU" // Shipping line SCAC code (MSC in this case) - } - } + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" } - - - { - "data": { - "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", - "type": "tracking_request", - "attributes": { - "request_type": "container", - "request_number": "MSCU1234567", - "scac": "MSCU", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T15:45:22Z", - "updated_at": "2023-05-15T15:45:22Z" - }, - "relationships": { - "shipment": { - "data": null - } - } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T15:45:22Z", + "updated_at": "2023-05-15T15:45:22Z" + }, + "relationships": { + "shipment": { + "data": null } } - - + } + } + ``` **Notes:** - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. @@ -110,42 +104,41 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - - - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "TEST-TR-SUCCEEDED", // Test number that will always succeed - "scac": "TEST" // Special SCAC for test requests - } - } + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" } - - - { - "data": { - "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "TEST-TR-SUCCEEDED", - "scac": "TEST", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T16:30:45Z", - "updated_at": "2023-05-15T16:30:45Z" - }, - "relationships": { - "shipment": { - "data": null - } - } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T16:30:45Z", + "updated_at": "2023-05-15T16:30:45Z" + }, + "relationships": { + "shipment": { + "data": null } } - - + } + } + ``` **Notes:** - Using test numbers is perfect for development and testing your integration. @@ -155,34 +148,33 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - - - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "INVALID-FORMAT", // Invalid bill of lading format - "scac": "UNKNOWN" // Non-existent SCAC code - } - } + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "INVALID-FORMAT", + "scac": "UNKNOWN" } - - + } + } + ``` + + ```json title="Response" + { + "errors": [ { - "errors": [ - { - "status": "422", - "title": "Invalid SCAC", - "detail": "The SCAC 'UNKNOWN' is not supported.", - "source": { - "pointer": "/data/attributes/scac" - } - } - ] + "status": "422", + "title": "Invalid SCAC", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } } - - + ] + } + ``` **Notes:** - This example shows a validation error for an invalid SCAC code. diff --git a/docs/api-docs/in-depth-guides/json-api-guide.mdx b/docs/api-docs/in-depth-guides/json-api-guide.mdx index 47d21974..4ef6b8fa 100644 --- a/docs/api-docs/in-depth-guides/json-api-guide.mdx +++ b/docs/api-docs/in-depth-guides/json-api-guide.mdx @@ -107,8 +107,6 @@ When requesting multiple resources, the response structure is: When creating a resource (like a tracking request), your request should follow this format: - - ```json { "data": { @@ -121,8 +119,6 @@ When creating a resource (like a tracking request), your request should follow t } } ``` - - Notice that: - The `type` field is required and must match the expected resource type diff --git a/docs/api-docs/in-depth-guides/webhooks.mdx b/docs/api-docs/in-depth-guides/webhooks.mdx index 4c0f3f27..1028c787 100644 --- a/docs/api-docs/in-depth-guides/webhooks.mdx +++ b/docs/api-docs/in-depth-guides/webhooks.mdx @@ -24,9 +24,7 @@ Webhooks provide real-time notifications about changes to your shipments and con First, you need to create an endpoint on your server that can receive POST requests. Here's an example using Node.js with Express: - - -```javascript +```javascript title="Webhook Endpoint" const express = require('express'); const app = express(); app.use(express.json()); @@ -73,8 +71,6 @@ app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` - - Your webhook endpoint must be publicly accessible over the internet for Terminal49 to send notifications to it. During development, you can use tools like [ngrok](https://ngrok.com/) to create a temporary public URL. @@ -84,9 +80,7 @@ app.listen(3000, () => { Once your endpoint is ready, register it with Terminal49's API: - - -```javascript +```javascript title="Register Webhook" async function registerWebhook() { try { const response = await fetch('https://api.terminal49.com/v2/webhooks', { @@ -124,10 +118,8 @@ async function registerWebhook() { } } ``` - - -```bash +```bash title="cURL Example" curl -X POST \ https://api.terminal49.com/v2/webhooks \ -H 'Authorization: Token YOUR_API_KEY' \ @@ -150,16 +142,12 @@ curl -X POST \ } }' ``` - - ### Step 3: Verify and Test Your Webhook After registering, you can test your webhook using Terminal49's test tracking numbers: - - -```javascript +```javascript title="Test Webhook" async function testWebhook() { try { // Create a tracking request with a test number @@ -188,8 +176,6 @@ async function testWebhook() { } } ``` - - ## Managing Webhooks @@ -197,9 +183,7 @@ async function testWebhook() { You can retrieve a list of all registered webhooks: - - -```javascript +```javascript title="List Webhooks" async function listWebhooks() { try { const response = await fetch('https://api.terminal49.com/v2/webhooks', { @@ -218,16 +202,12 @@ async function listWebhooks() { } } ``` - - ### Updating a Webhook To update an existing webhook (e.g., to change the URL or event types): - - -```javascript +```javascript title="Update Webhook" async function updateWebhook(webhookId) { try { const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { @@ -262,16 +242,12 @@ async function updateWebhook(webhookId) { } } ``` - - ### Deleting a Webhook -To stop receiving webhook notifications: +If you no longer need a webhook, you can delete it: - - -```javascript +```javascript title="Delete Webhook" async function deleteWebhook(webhookId) { try { const response = await fetch(`https://api.terminal49.com/v2/webhooks/${webhookId}`, { @@ -282,7 +258,7 @@ async function deleteWebhook(webhookId) { }); if (response.status === 204) { - console.log(`Webhook ${webhookId} successfully deleted`); + console.log('Webhook deleted successfully'); return true; } else { console.error('Failed to delete webhook:', response.status); @@ -295,54 +271,24 @@ async function deleteWebhook(webhookId) { } } ``` - - - -## Webhook Event Types - -Terminal49 supports multiple event types that you can subscribe to: - - - - `container.created`: A new container is tracked in the system - - `container.estimated_arrival_changed`: The estimated arrival date has changed - - `container.transport.vessel_arrived`: The vessel carrying the container has arrived at port - - `container.transport.vessel_discharged`: The container has been unloaded from vessel - - `container.available_for_pickup`: The container is available for pickup from terminal - - `container.pickup_lfd.changed`: Last Free Day for pickup has changed - - `container.holds.changed`: Customs or terminal holds have changed - - `container.picked_up`: The container has been picked up from terminal - - `container.empty_returned`: The empty container has been returned to terminal - - - - - `tracking_request.succeeded`: A tracking request was successful - - `tracking_request.failed`: A tracking request failed to find container information - - - - - `shipment.created`: A new shipment is created in the system - - `shipment.updated`: A shipment's details have been updated - ## Webhook Payload Structure -All webhook notifications follow the JSON:API format. Here's a typical webhook payload structure: +When an event occurs, Terminal49 sends a webhook notification with a JSON:API structured payload: - - -```json +```json title="Example Payload" { "data": { - "id": "9c5d91d1-f67d-4e5c-bbf5-c7c71a36fb23", + "id": "123e4567-e89b-12d3-a456-426614174000", "type": "webhook_notification", "attributes": { "event": "container.transport.vessel_arrived", - "created_at": "2023-05-15T21:13:24Z" + "created_at": "2023-05-15T14:23:12Z" }, "relationships": { "reference_object": { "data": { - "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", + "id": "789e4567-e89b-12d3-a456-426614174000", "type": "container" } } @@ -350,24 +296,18 @@ All webhook notifications follow the JSON:API format. Here's a typical webhook p }, "included": [ { - "id": "b0a875ba-4ada-4f38-8765-53c59cec72a8", + "id": "789e4567-e89b-12d3-a456-426614174000", "type": "container", "attributes": { - "number": "ABCD1234567", - "seal_number": "SL123456", - "created_at": "2023-05-10T15:34:12Z", - "updated_at": "2023-05-15T21:13:20Z" + "number": "MSCU1234567", + "seal_number": "SEAL123456", + "created_at": "2023-05-10T10:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" }, "relationships": { - "terminal": { - "data": { - "id": "7fa33ba6-2a50-4c5b-9889-3e814b7e9218", - "type": "terminal" - } - }, "shipment": { "data": { - "id": "ae12fe45-0dc3-4a78-99b1-6fe08c58cbcf", + "id": "456e4567-e89b-12d3-a456-426614174000", "type": "shipment" } } @@ -376,8 +316,32 @@ All webhook notifications follow the JSON:API format. Here's a typical webhook p ] } ``` - - + +## Webhook Event Types + +Terminal49 supports multiple event types that you can subscribe to: + + + - `container.created`: A new container is tracked in the system + - `container.estimated_arrival_changed`: The estimated arrival date has changed + - `container.transport.vessel_arrived`: The vessel carrying the container has arrived at port + - `container.transport.vessel_discharged`: The container has been unloaded from vessel + - `container.available_for_pickup`: The container is available for pickup from terminal + - `container.pickup_lfd.changed`: Last Free Day for pickup has changed + - `container.holds.changed`: Customs or terminal holds have changed + - `container.picked_up`: The container has been picked up from terminal + - `container.empty_returned`: The empty container has been returned to terminal + + + + - `tracking_request.succeeded`: A tracking request was successful + - `tracking_request.failed`: A tracking request failed to find container information + + + + - `shipment.created`: A new shipment is created in the system + - `shipment.updated`: A shipment's details have been updated + ## Best Practices From e32f23ac321bedf1332b5248a567a9c563458815 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 17:53:39 -0700 Subject: [PATCH 05/42] Fix remaining CodeGroup issues in polling-vs-webhooks.mdx --- .../in-depth-guides/polling-vs-webhooks.mdx | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx index 630af07f..e93f929d 100644 --- a/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx +++ b/docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx @@ -22,9 +22,7 @@ There are two primary methods for getting updates from Terminal49's API: **polli Polling involves your application regularly querying the Terminal49 API to check for updates on your tracked shipments and containers. - - -```javascript +```javascript title="Polling Example" // Example of polling in JavaScript async function pollForUpdates() { try { @@ -46,8 +44,6 @@ async function pollForUpdates() { // Poll every 15 minutes setInterval(pollForUpdates, 15 * 60 * 1000); ``` - - ### When to Use Polling @@ -78,9 +74,7 @@ setInterval(pollForUpdates, 15 * 60 * 1000); Webhooks allow Terminal49 to push real-time updates to your system whenever a change occurs, without you having to ask for them. - - -```javascript +```javascript title="Webhook Setup" // Creating a webhook endpoint in Node.js (Express) const express = require('express'); const app = express(); @@ -103,10 +97,8 @@ app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` - - -```javascript +```javascript title="Webhook Registration" // Registering a webhook with Terminal49 API async function registerWebhook() { try { @@ -141,8 +133,6 @@ async function registerWebhook() { } } ``` - - ### When to Use Webhooks @@ -191,9 +181,7 @@ This hybrid approach ensures you get the benefits of real-time updates while hav ## Example: Hybrid Implementation - - -```javascript +```javascript title="Hybrid Approach" // 1. Set up webhooks (primary method) // See webhook registration example above @@ -224,8 +212,6 @@ async function verifyContainerStatus() { // Run verification once per day setInterval(verifyContainerStatus, 24 * 60 * 60 * 1000); ``` - - ## Summary Comparison From 30bdf19f1f5397e349c798afd1c5b29ad693b699 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 21:47:21 -0700 Subject: [PATCH 06/42] Update tracker with page change descriptions --- API-Documentation-Improvement-Tracker.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/API-Documentation-Improvement-Tracker.md b/API-Documentation-Improvement-Tracker.md index 8d5b5017..d3848375 100644 --- a/API-Documentation-Improvement-Tracker.md +++ b/API-Documentation-Improvement-Tracker.md @@ -10,6 +10,18 @@ | Tutorials | Not Started | 0% | | General Documentation | In Progress | 40% | +## Pages Changed/Added + +| Page | Type | Description of Changes | +|------|------|------------------------| +| `docs/api-docs/getting-started/start-here.mdx` | Updated | Improved introduction, clarified setup process, added more context about the API architecture | +| `docs/api-docs/getting-started/capabilities-overview.mdx` | Updated | Enhanced capabilities listing, added examples of what can be accomplished with the API | +| `docs/api-docs/in-depth-guides/json-api-guide.mdx` | New | Created comprehensive guide explaining JSON:API concepts, structure, and how Terminal49 implements the specification | +| `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` | New | Added comparison guide detailing the differences, advantages, and use cases for polling vs webhooks | +| `docs/api-docs/in-depth-guides/webhooks.mdx` | New | Created detailed guide for webhook implementation including endpoint setup, registration, testing, and management | +| `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` | Updated | Enhanced documentation with clearer examples, added business use cases, improved response explanations | +| `docs/mint.json` | Updated | Updated navigation structure to include new guides and improve organization | + ## Completed Updates - [x] Improved introduction and setup guidance in `docs/api-docs/getting-started/start-here.mdx` @@ -17,10 +29,11 @@ - [x] Created JSON:API guide in `docs/api-docs/in-depth-guides/json-api-guide.mdx` - [x] Updated tracking request documentation in `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` - [x] Created "Polling vs. Webhooks" comparison guide in `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` +- [x] Created Webhook implementation guide in `docs/api-docs/in-depth-guides/webhooks.mdx` +- [x] Fixed MDX parsing errors to ensure compatibility with Mintlify ## In Progress -- [ ] Creating Webhook implementation guide in `docs/api-docs/in-depth-guides/webhooks.mdx` - [ ] Updating webhook API reference documentation - [ ] Creating Terminal49 data model guide - [ ] Developing authentication and authorization guide @@ -29,14 +42,14 @@ ## Next Steps 1. **Immediate Focus (Next 48 hours):** - - Complete the webhook implementation guide - - Create authentication guide with detailed token management examples + - Complete the authentication guide with detailed token management examples - Develop error handling and troubleshooting documentation + - Add examples in multiple programming languages 2. **Medium Priority (Next Week):** - Enhance all endpoint documentation with consistent format and examples - Create step-by-step tutorials for common use cases - - Add more code examples in multiple programming languages + - Add more detailed error code documentation 3. **Future Improvements:** - Add interactive API explorer From db8597c5e4f0e19232dc906e8dfb3e7ffc2f1f9b Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 21:51:37 -0700 Subject: [PATCH 07/42] Add comprehensive authentication guide with multi-language examples --- API-Documentation-Improvement-Tracker.md | 10 +- .../in-depth-guides/authentication.mdx | 204 ++++++++++++++++ docs/docs.json | 230 ++++++++++++++++++ 3 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 docs/api-docs/in-depth-guides/authentication.mdx create mode 100644 docs/docs.json diff --git a/API-Documentation-Improvement-Tracker.md b/API-Documentation-Improvement-Tracker.md index d3848375..8cf79469 100644 --- a/API-Documentation-Improvement-Tracker.md +++ b/API-Documentation-Improvement-Tracker.md @@ -5,7 +5,7 @@ | Category | Status | Completion % | |----------|--------|--------------| | Getting Started | In Progress | 75% | -| In-Depth Guides | In Progress | 50% | +| In-Depth Guides | In Progress | 60% | | API Reference Examples | In Progress | 25% | | Tutorials | Not Started | 0% | | General Documentation | In Progress | 40% | @@ -19,8 +19,9 @@ | `docs/api-docs/in-depth-guides/json-api-guide.mdx` | New | Created comprehensive guide explaining JSON:API concepts, structure, and how Terminal49 implements the specification | | `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` | New | Added comparison guide detailing the differences, advantages, and use cases for polling vs webhooks | | `docs/api-docs/in-depth-guides/webhooks.mdx` | New | Created detailed guide for webhook implementation including endpoint setup, registration, testing, and management | +| `docs/api-docs/in-depth-guides/authentication.mdx` | New | Created comprehensive authentication guide with API key management, examples in multiple languages, security best practices | | `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` | Updated | Enhanced documentation with clearer examples, added business use cases, improved response explanations | -| `docs/mint.json` | Updated | Updated navigation structure to include new guides and improve organization | +| `docs/docs.json` | Updated | Updated navigation structure to include new guides and improve organization | ## Completed Updates @@ -30,21 +31,20 @@ - [x] Updated tracking request documentation in `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` - [x] Created "Polling vs. Webhooks" comparison guide in `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` - [x] Created Webhook implementation guide in `docs/api-docs/in-depth-guides/webhooks.mdx` +- [x] Created Authentication guide in `docs/api-docs/in-depth-guides/authentication.mdx` - [x] Fixed MDX parsing errors to ensure compatibility with Mintlify ## In Progress - [ ] Updating webhook API reference documentation - [ ] Creating Terminal49 data model guide -- [ ] Developing authentication and authorization guide - [ ] Improving error handling documentation ## Next Steps 1. **Immediate Focus (Next 48 hours):** - - Complete the authentication guide with detailed token management examples - Develop error handling and troubleshooting documentation - - Add examples in multiple programming languages + - Add examples in multiple programming languages for remaining endpoints 2. **Medium Priority (Next Week):** - Enhance all endpoint documentation with consistent format and examples diff --git a/docs/api-docs/in-depth-guides/authentication.mdx b/docs/api-docs/in-depth-guides/authentication.mdx new file mode 100644 index 00000000..c1c10875 --- /dev/null +++ b/docs/api-docs/in-depth-guides/authentication.mdx @@ -0,0 +1,204 @@ +--- +title: Authentication Guide +description: Learn how to authenticate with Terminal49's API and manage your API keys securely. +--- + +# Authentication with Terminal49 + +This guide covers everything you need to know about authentication with Terminal49's API, including obtaining API keys, implementing authentication in your code, and security best practices. + +## How Terminal49 Authentication Works + +Terminal49 uses a token-based authentication system. Each request to the API must include an API key in the request headers to authenticate the request. + + + Authentication is required for all API endpoints except for the documentation and health check endpoints. + + +## Getting Your API Key + +To obtain an API key: + +1. Sign in to your Terminal49 account +2. Navigate to [Developer Settings](https://app.terminal49.com/developers/api-keys) +3. Click "Generate New API Key" +4. Name your key based on its intended use (e.g., "Production Integration" or "Development Testing") +5. Copy and securely store your API key - you won't be able to view it again + + + API keys grant access to your data and should be kept secure. Never share your API key or commit it to version control systems. + + +## Using Your API Key + +When making requests to the Terminal49 API, you need to include your API key in the `Authorization` header of each request with the prefix `Token`. + +``` +Authorization: Token YOUR_API_KEY +``` + +Here's how to include the Authorization header in different programming languages: + +```javascript title="JavaScript/Node.js" +// Using fetch API +const response = await fetch('https://api.terminal49.com/v2/shipments', { + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + } +}); + +// Using Axios +const axios = require('axios'); +axios.get('https://api.terminal49.com/v2/shipments', { + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + } +}); +``` + +```python title="Python" +import requests + +url = "https://api.terminal49.com/v2/shipments" +headers = { + "Authorization": "Token YOUR_API_KEY", + "Content-Type": "application/vnd.api+json" +} + +response = requests.get(url, headers=headers) +``` + +```ruby title="Ruby" +require 'net/http' +require 'uri' +require 'json' + +uri = URI.parse('https://api.terminal49.com/v2/shipments') +request = Net::HTTP::Get.new(uri) +request['Authorization'] = 'Token YOUR_API_KEY' +request['Content-Type'] = 'application/vnd.api+json' + +response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| + http.request(request) +end +``` + +```csharp title="C#" +using System; +using System.Net.Http; +using System.Threading.Tasks; + +class Program +{ + static async Task Main() + { + using var client = new HttpClient(); + client.DefaultRequestHeaders.Add("Authorization", "Token YOUR_API_KEY"); + client.DefaultRequestHeaders.Add("Content-Type", "application/vnd.api+json"); + + var response = await client.GetAsync("https://api.terminal49.com/v2/shipments"); + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine(content); + } +} +``` + +## API Key Management Best Practices + +### Security Recommendations + +1. **Store API keys securely**: Use environment variables or a secure vault instead of hardcoding them +2. **Implement key rotation**: Regularly rotate your API keys (e.g., every 90 days) +3. **Use separate keys**: Use different API keys for development, staging, and production environments +4. **Limit access**: Only share API keys with team members who need them +5. **Monitor usage**: Regularly review your API usage for unusual patterns + +### Environment Variables + +Using environment variables is one of the safest ways to handle API keys in your applications: + +```javascript title="Node.js with dotenv" +// Install dotenv: npm install dotenv +require('dotenv').config(); + +const apiKey = process.env.TERMINAL49_API_KEY; + +const response = await fetch('https://api.terminal49.com/v2/shipments', { + headers: { + 'Authorization': `Token ${apiKey}`, + 'Content-Type': 'application/vnd.api+json' + } +}); +``` + +```python title="Python with dotenv" +# Install python-dotenv: pip install python-dotenv +import os +from dotenv import load_dotenv +import requests + +load_dotenv() +api_key = os.getenv("TERMINAL49_API_KEY") + +url = "https://api.terminal49.com/v2/shipments" +headers = { + "Authorization": f"Token {api_key}", + "Content-Type": "application/vnd.api+json" +} + +response = requests.get(url, headers=headers) +``` + +### Example .env File + +``` +# .env file (add to .gitignore) +TERMINAL49_API_KEY=your_api_key_here +``` + + + Always add your .env file to .gitignore to prevent accidentally committing it to version control. + + +## Troubleshooting Authentication Issues + +### Common Error Responses + +| HTTP Status | Message | Possible Cause | +|-------------|---------|----------------| +| 401 Unauthorized | "Invalid API Key" | The API key is incorrect or has been revoked | +| 401 Unauthorized | "Missing API Key" | No Authorization header was provided | +| 403 Forbidden | "Insufficient permissions" | The API key doesn't have permission for the requested action | + +### Debugging Tips + +1. **Check the Authorization header**: Ensure it's properly formatted as `Token YOUR_API_KEY` +2. **Verify the API key**: Check that you're using the correct API key for the environment +3. **Test with curl**: Try a simple curl request to isolate authentication issues + +```bash title="curl Example" +curl -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + https://api.terminal49.com/v2/shipments +``` + +## Revoking API Keys + +If an API key is compromised or no longer needed: + +1. Sign in to your Terminal49 account +2. Navigate to [Developer Settings](https://app.terminal49.com/developers/api-keys) +3. Find the API key you want to revoke +4. Click "Revoke Key" + + + Revoking an API key is immediate and permanent. Any applications using the key will lose access to the API. + + +## Next Steps + +- Learn about [Webhooks](/api-docs/in-depth-guides/webhooks) for real-time updates +- Explore the [JSON:API Guide](/api-docs/in-depth-guides/json-api-guide) to understand the API response format +- Start [Tracking Shipments and Containers](/api-docs/getting-started/tracking-shipments-and-containers) diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 00000000..a1e33c2e --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "palm", + "name": "Terminal 49", + "colors": { + "primary": "#00A2FF", + "light": "#52bfff", + "dark": "#00A2FF" + }, + "favicon": "/logos/favicon.svg", + "navigation": { + "tabs": [ + { + "tab": "API Docs", + "groups": [ + { + "group": " ", + "pages": [ + "home" + ] + }, + { + "group": "Getting Started", + "pages": [ + "api-docs/getting-started/start-here", + "api-docs/getting-started/capabilities-overview", + "api-docs/getting-started/tracking-shipments-and-containers", + "api-docs/getting-started/list-shipments-and-containers", + "api-docs/getting-started/receive-status-updates" + ] + }, + { + "group": "In Depth Guides", + "pages": [ + "api-docs/in-depth-guides/quickstart", + "api-docs/in-depth-guides/json-api-guide", + "api-docs/in-depth-guides/authentication", + "api-docs/in-depth-guides/polling-vs-webhooks", + "api-docs/in-depth-guides/webhooks", + "api-docs/in-depth-guides/including-resources", + "api-docs/in-depth-guides/adding-customer", + "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" + ] + }, + { + "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" + ] + }, + { + "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", + "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" + ] + }, + { + "group": "Parties", + "pages": [ + "api-docs/api-reference/parties/list-parties", + "api-docs/api-reference/parties/create-a-party", + "api-docs/api-reference/parties/get-a-party", + "api-docs/api-reference/parties/edit-a-party" + ] + } + ] + }, + { + "tab": "DataSync", + "groups": [ + { + "group": " ", + "pages": [ + "datasync/home" + ] + }, + { + "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/transport-events", + "datasync/table-properties/transfer-status", + "datasync/table-properties/containers" + ] + } + ] + } + ], + "global": { + "anchors": [ + { + "anchor": "Contact", + "href": "https://www.terminal49.com/contact/", + "icon": "phone" + }, + { + "anchor": "Schedule Demo", + "href": "https://www.terminal49.com/demo/", + "icon": "calendar-days" + }, + { + "anchor": "Blog", + "href": "https://www.terminal49.com/blog/", + "icon": "newspaper" + } + ] + } + }, + "logo": "/logos/light.svg", + "appearance": { + "default": "light", + "strict": false + }, + "navbar": { + "links": [ + { + "label": "Start your free trial", + "href": "https://app.terminal49.com/register" + } + ], + "primary": { + "type": "button", + "label": "Sign in", + "href": "https://app.terminal49.com/" + } + }, + "footer": { + "socials": { + "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" + } + ] +} From f963117fc42418c60409d24c6c0c0ce465ff5b1c Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 21:59:51 -0700 Subject: [PATCH 08/42] Update auth guide: add PHP, fix disable key terminology, remove best practices --- .../in-depth-guides/authentication.mdx | 102 +++++++----------- docs/images/disable-key.png | 1 + 2 files changed, 41 insertions(+), 62 deletions(-) create mode 100644 docs/images/disable-key.png diff --git a/docs/api-docs/in-depth-guides/authentication.mdx b/docs/api-docs/in-depth-guides/authentication.mdx index c1c10875..c733c479 100644 --- a/docs/api-docs/in-depth-guides/authentication.mdx +++ b/docs/api-docs/in-depth-guides/authentication.mdx @@ -58,6 +58,37 @@ axios.get('https://api.terminal49.com/v2/shipments', { }); ``` +```php title="PHP" +request('GET', 'https://api.terminal49.com/v2/shipments', [ + 'headers' => [ + 'Authorization' => 'Token YOUR_API_KEY', + 'Content-Type' => 'application/vnd.api+json' + ] +]); + +$data = json_decode($response->getBody(), true); +?> +``` + ```python title="Python" import requests @@ -105,70 +136,13 @@ class Program } ``` -## API Key Management Best Practices - -### Security Recommendations - -1. **Store API keys securely**: Use environment variables or a secure vault instead of hardcoding them -2. **Implement key rotation**: Regularly rotate your API keys (e.g., every 90 days) -3. **Use separate keys**: Use different API keys for development, staging, and production environments -4. **Limit access**: Only share API keys with team members who need them -5. **Monitor usage**: Regularly review your API usage for unusual patterns - -### Environment Variables - -Using environment variables is one of the safest ways to handle API keys in your applications: - -```javascript title="Node.js with dotenv" -// Install dotenv: npm install dotenv -require('dotenv').config(); - -const apiKey = process.env.TERMINAL49_API_KEY; - -const response = await fetch('https://api.terminal49.com/v2/shipments', { - headers: { - 'Authorization': `Token ${apiKey}`, - 'Content-Type': 'application/vnd.api+json' - } -}); -``` - -```python title="Python with dotenv" -# Install python-dotenv: pip install python-dotenv -import os -from dotenv import load_dotenv -import requests - -load_dotenv() -api_key = os.getenv("TERMINAL49_API_KEY") - -url = "https://api.terminal49.com/v2/shipments" -headers = { - "Authorization": f"Token {api_key}", - "Content-Type": "application/vnd.api+json" -} - -response = requests.get(url, headers=headers) -``` - -### Example .env File - -``` -# .env file (add to .gitignore) -TERMINAL49_API_KEY=your_api_key_here -``` - - - Always add your .env file to .gitignore to prevent accidentally committing it to version control. - - ## Troubleshooting Authentication Issues ### Common Error Responses | HTTP Status | Message | Possible Cause | |-------------|---------|----------------| -| 401 Unauthorized | "Invalid API Key" | The API key is incorrect or has been revoked | +| 401 Unauthorized | "Invalid API Key" | The API key is incorrect or has been disabled | | 401 Unauthorized | "Missing API Key" | No Authorization header was provided | | 403 Forbidden | "Insufficient permissions" | The API key doesn't have permission for the requested action | @@ -184,17 +158,21 @@ curl -H "Authorization: Token YOUR_API_KEY" \ https://api.terminal49.com/v2/shipments ``` -## Revoking API Keys +## Disabling API Keys If an API key is compromised or no longer needed: 1. Sign in to your Terminal49 account 2. Navigate to [Developer Settings](https://app.terminal49.com/developers/api-keys) -3. Find the API key you want to revoke -4. Click "Revoke Key" +3. Find the API key you want to disable +4. Click the "Disable Key" button (circular icon with a line through it) + + + ![Disable API Key location](/images/disable-key.png) + - Revoking an API key is immediate and permanent. Any applications using the key will lose access to the API. + Disabling an API key is immediate. Any applications using the key will lose access to the API. ## Next Steps diff --git a/docs/images/disable-key.png b/docs/images/disable-key.png new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/docs/images/disable-key.png @@ -0,0 +1 @@ + \ No newline at end of file From aff212123b844a00766330764c4e41581f8218af Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:02:01 -0700 Subject: [PATCH 09/42] Change Authentication Guide to Authentication and add key icon to navigation --- docs/api-docs/in-depth-guides/authentication.mdx | 2 +- docs/docs.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/api-docs/in-depth-guides/authentication.mdx b/docs/api-docs/in-depth-guides/authentication.mdx index c733c479..04499aec 100644 --- a/docs/api-docs/in-depth-guides/authentication.mdx +++ b/docs/api-docs/in-depth-guides/authentication.mdx @@ -1,5 +1,5 @@ --- -title: Authentication Guide +title: Authentication description: Learn how to authenticate with Terminal49's API and manage your API keys securely. --- diff --git a/docs/docs.json b/docs/docs.json index a1e33c2e..841e1bfb 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -34,7 +34,11 @@ "pages": [ "api-docs/in-depth-guides/quickstart", "api-docs/in-depth-guides/json-api-guide", - "api-docs/in-depth-guides/authentication", + { + "name": "Authentication", + "icon": "key", + "url": "api-docs/in-depth-guides/authentication" + }, "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/in-depth-guides/including-resources", From 71f5150ab1d7c905922988c8640dd0d75aea10f9 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:03:50 -0700 Subject: [PATCH 10/42] Fix navigation issue: change url to path in docs.json --- docs/docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs.json b/docs/docs.json index 841e1bfb..8d9f9080 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -37,7 +37,7 @@ { "name": "Authentication", "icon": "key", - "url": "api-docs/in-depth-guides/authentication" + "path": "api-docs/in-depth-guides/authentication" }, "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", From 3a224459e31f092b87d6f01e2567063b671abae4 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:04:49 -0700 Subject: [PATCH 11/42] Revert to simple navigation format to fix broken navigation --- docs/docs.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index 8d9f9080..a1e33c2e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -34,11 +34,7 @@ "pages": [ "api-docs/in-depth-guides/quickstart", "api-docs/in-depth-guides/json-api-guide", - { - "name": "Authentication", - "icon": "key", - "path": "api-docs/in-depth-guides/authentication" - }, + "api-docs/in-depth-guides/authentication", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/in-depth-guides/including-resources", From 1af646c93f225830736c6991a605ad145d1cf4ea Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:14:07 -0700 Subject: [PATCH 12/42] Add key icon to Authentication page header --- docs/api-docs/in-depth-guides/authentication.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-docs/in-depth-guides/authentication.mdx b/docs/api-docs/in-depth-guides/authentication.mdx index 04499aec..26aa326d 100644 --- a/docs/api-docs/in-depth-guides/authentication.mdx +++ b/docs/api-docs/in-depth-guides/authentication.mdx @@ -1,6 +1,7 @@ --- title: Authentication description: Learn how to authenticate with Terminal49's API and manage your API keys securely. +icon: "key" --- # Authentication with Terminal49 From 41d190d1ff349baf3dce88df32a4ddb37c96c3a0 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:16:21 -0700 Subject: [PATCH 13/42] Add comprehensive error handling guide --- docs/API-Documentation-Improvement-Tracker.md | 6 + .../in-depth-guides/error-handling.mdx | 462 ++++++++++++++++++ docs/docs.json | 1 + 3 files changed, 469 insertions(+) create mode 100644 docs/API-Documentation-Improvement-Tracker.md create mode 100644 docs/api-docs/in-depth-guides/error-handling.mdx diff --git a/docs/API-Documentation-Improvement-Tracker.md b/docs/API-Documentation-Improvement-Tracker.md new file mode 100644 index 00000000..b77564d1 --- /dev/null +++ b/docs/API-Documentation-Improvement-Tracker.md @@ -0,0 +1,6 @@ +### Completed: +- Fix JSON code blocks in webhook guide ✅ +- Fix JSON code blocks in create-a-tracking-request.mdx ✅ +- Update authentication guide ✅ +- Develop error handling and troubleshooting documentation ✅ +- Update the navigation structure in docs.json ✅ diff --git a/docs/api-docs/in-depth-guides/error-handling.mdx b/docs/api-docs/in-depth-guides/error-handling.mdx new file mode 100644 index 00000000..0c815367 --- /dev/null +++ b/docs/api-docs/in-depth-guides/error-handling.mdx @@ -0,0 +1,462 @@ +--- +title: Error Handling +description: Learn how to effectively handle and troubleshoot errors in Terminal49's API +icon: "triangle-exclamation" +--- + +# Error Handling and Troubleshooting + +This guide explains how Terminal49's API reports errors and provides strategies for troubleshooting and handling them effectively in your applications. + +## Understanding Error Responses + +Terminal49's API follows the JSON:API specification for error responses. When an error occurs, the API returns: + +1. An appropriate HTTP status code +2. A JSON response with detailed error information + +```json +{ + "errors": [ + { + "status": "422", + "title": "Invalid attribute", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + }, + "code": "unsupported_scac" + } + ] +} +``` + +### Error Object Properties + +Each error object in the `errors` array includes: + + + The HTTP status code for this particular error (as a string). + + + + A short, human-readable summary of the problem. + + + + A human-readable explanation with specific details about the error. + + + + Information about the source of the error: + - `pointer`: A JSON Pointer to the specific field that caused the error (e.g., "/data/attributes/scac") + + + + A machine-readable error code that can be used for programmatic handling (not always present). + + +## Common HTTP Status Codes + +| Status Code | Meaning | Typical Causes | +|-------------|---------|----------------| +| 400 Bad Request | The request was malformed or invalid | Invalid JSON, missing required fields | +| 401 Unauthorized | Authentication failed | Invalid or missing API key | +| 403 Forbidden | Permission denied | Your API key doesn't have access to the requested resource | +| 404 Not Found | Resource not found | The requested ID doesn't exist | +| 422 Unprocessable Entity | Validation errors | Input validation failed | +| 429 Too Many Requests | Rate limit exceeded | You've made too many requests in a short period | +| 500, 502, 503 | Server errors | Problem on Terminal49's side | + +## Common Error Types and Solutions + +### Authentication Errors (401) + +**Example:** +```json +{ + "errors": [ + { + "status": "401", + "title": "Unauthorized", + "detail": "Invalid API key provided" + } + ] +} +``` + +**Solutions:** +1. Check that you're including the `Authorization` header with the correct format: `Token YOUR_API_KEY` +2. Verify your API key is active in the [Developer Settings](https://app.terminal49.com/developers/api-keys) +3. Make sure the API key hasn't been disabled + +### Validation Errors (422) + +**Example:** +```json +{ + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'ABCD1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" + } + ] +} +``` + +**Solutions:** +1. Check the `pointer` field to identify which attribute caused the error +2. Review the `detail` message for specific information about the validation failure +3. For duplicate tracking requests, check if the shipment is already being tracked +4. Verify SCAC codes against the [supported carriers list](https://app.terminal49.com/developers/carrier-data) + +### Resource Not Found (404) + +**Example:** +```json +{ + "errors": [ + { + "status": "404", + "title": "Not Found", + "detail": "Couldn't find Shipment with ID=123e4567-e89b-12d3-a456-426614174000" + } + ] +} +``` + +**Solutions:** +1. Verify the ID you're using exists and is correctly formatted +2. Check if the resource might have been deleted +3. Confirm you're using the correct endpoint for the resource type + +### Rate Limiting (429) + +**Example:** +```json +{ + "errors": [ + { + "status": "429", + "title": "Too Many Requests", + "detail": "Rate limit exceeded. Please wait and try again later." + } + ] +} +``` + +**Solutions:** +1. Implement retry logic with exponential backoff +2. Spread requests over a longer period +3. Contact Terminal49 support if you need a higher rate limit for your use case + +## Implementing Effective Error Handling + +### JavaScript Example + +```javascript +async function makeApiRequest(endpoint, method = 'GET', data = null) { + try { + const options = { + method, + headers: { + 'Authorization': `Token ${API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } + }; + + if (data && (method === 'POST' || method === 'PATCH')) { + options.body = JSON.stringify(data); + } + + const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, options); + const responseData = await response.json(); + + // Check if the response contains errors + if (!response.ok) { + // Process errors + if (responseData.errors) { + const error = responseData.errors[0]; + + switch (response.status) { + case 401: + throw new Error(`Authentication error: ${error.detail}`); + case 404: + throw new Error(`Resource not found: ${error.detail}`); + case 422: + const field = error.source?.pointer?.split('/').pop() || 'unknown field'; + throw new Error(`Validation error in ${field}: ${error.detail}`); + case 429: + // Implement retry with exponential backoff + throw new Error(`Rate limit exceeded: ${error.detail}`); + default: + throw new Error(`API error ${response.status}: ${error.detail}`); + } + } + + throw new Error(`API request failed with status ${response.status}`); + } + + return responseData; + } catch (error) { + console.error('API request failed:', error); + throw error; + } +} +``` + +### PHP Example + +```php += 400) { + // Handle errors + if (isset($responseData['errors']) && !empty($responseData['errors'])) { + $error = $responseData['errors'][0]; + $errorMessage = $error['detail'] ?? 'Unknown error'; + + switch ($statusCode) { + case 401: + throw new Exception("Authentication error: {$errorMessage}"); + case 404: + throw new Exception("Resource not found: {$errorMessage}"); + case 422: + $pointer = $error['source']['pointer'] ?? ''; + $field = array_pop(explode('/', $pointer)) ?: 'unknown field'; + throw new Exception("Validation error in {$field}: {$errorMessage}"); + case 429: + // Implement retry logic + throw new Exception("Rate limit exceeded: {$errorMessage}"); + default: + throw new Exception("API error {$statusCode}: {$errorMessage}"); + } + } + + throw new Exception("API request failed with status {$statusCode}"); + } + + return $responseData; +} +?> +``` + +### Python Example + +```python +import requests +import time +from requests.exceptions import HTTPError + +def make_api_request(endpoint, method='GET', data=None, max_retries=3): + url = f"https://api.terminal49.com/v2/{endpoint}" + headers = { + 'Authorization': f'Token {API_KEY}', + 'Content-Type': 'application/vnd.api+json', + 'Accept': 'application/vnd.api+json' + } + + retries = 0 + while retries < max_retries: + try: + if method == 'GET': + response = requests.get(url, headers=headers) + elif method == 'POST': + response = requests.post(url, headers=headers, json=data) + elif method == 'PATCH': + response = requests.patch(url, headers=headers, json=data) + elif method == 'DELETE': + response = requests.delete(url, headers=headers) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + response.raise_for_status() + return response.json() + + except HTTPError as http_err: + # Try to parse error response + try: + error_data = response.json() + if 'errors' in error_data and error_data['errors']: + error = error_data['errors'][0] + error_detail = error.get('detail', 'No detail provided') + + if response.status_code == 401: + raise Exception(f"Authentication error: {error_detail}") + elif response.status_code == 404: + raise Exception(f"Resource not found: {error_detail}") + elif response.status_code == 422: + pointer = error.get('source', {}).get('pointer', '') + field = pointer.split('/')[-1] if pointer else 'unknown field' + raise Exception(f"Validation error in {field}: {error_detail}") + elif response.status_code == 429: + # Rate limit - implement retry with backoff + wait_time = min(2 ** retries, 60) + print(f"Rate limit exceeded. Retrying in {wait_time} seconds...") + time.sleep(wait_time) + retries += 1 + continue + except ValueError: + # Response wasn't valid JSON + pass + + # If we get here, either it wasn't a JSON response or we want to re-raise + if response.status_code != 429 or retries >= max_retries - 1: + raise Exception(f"API error {response.status_code}: {response.text}") + + retries += 1 + + raise Exception("Maximum retry attempts reached") +``` + +## Retry Strategies + +For intermittent errors and rate limiting, implementing a retry strategy is essential: + +### Exponential Backoff + +```javascript +async function makeRequestWithRetry(endpoint, method = 'GET', data = null, maxRetries = 3) { + let retries = 0; + + while (retries < maxRetries) { + try { + return await makeApiRequest(endpoint, method, data); + } catch (error) { + retries++; + + // If it's not a retriable error or we've used all retries, throw + if ( + !error.message.includes('Rate limit exceeded') || + retries >= maxRetries + ) { + throw error; + } + + // Exponential backoff with jitter + const delay = Math.min(1000 * 2 ** retries + Math.random() * 1000, 60000); + console.log(`Retrying after ${delay}ms (attempt ${retries} of ${maxRetries})...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} +``` + +## Error Scenarios and Solutions + +### Duplicate Tracking Requests + +**Error:** +```json +{ + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'MAEU1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" + } + ] +} +``` + +**Solution:** +When you receive a duplicate error, you should: +1. Look up the existing tracking request using the `GET /tracking_requests` endpoint with appropriate filters +2. Use the ID of the existing request to fetch its current status +3. If needed, you can update the existing tracking request rather than creating a new one + +### Unsupported Carrier + +**Error:** +```json +{ + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'ABCD' is not supported", + "code": "unsupported_scac" + } + ] +} +``` + +**Solution:** +1. Reference the [Carrier Data Matrix](https://app.terminal49.com/developers/carrier-data) for a list of supported carriers +2. Double-check the SCAC code format (usually 4 uppercase letters) +3. If you need support for a specific carrier, contact Terminal49 + +## Best Practices for Error Handling + +1. **Always check for error responses** + - Parse the response body to look for the `errors` array + - Handle both HTTP errors and JSON:API formatted errors + +2. **Log detailed error information** + - Include the status code, error title, error detail, and any source information + - Add context from your application (e.g., what operation was being attempted) + +3. **Implement appropriate retry logic** + - Use exponential backoff for rate limiting (429) errors + - Don't retry for validation (422) or authentication (401) errors + +4. **Present user-friendly error messages** + - Translate technical API errors to understandable messages for end-users + - Provide actionable guidance when possible + +5. **Monitor error rates** + - Set up alerts for unusual error patterns + - Keep track of error frequency to identify potential issues with your integration + +## Getting Help + +If you encounter persistent errors that you can't resolve: + +1. Check the [API Documentation](https://api.docs.terminal49.com/) for endpoint-specific requirements +2. Ensure you're using the latest version of the API +3. Contact Terminal49 support at [support@terminal49.com](mailto:support@terminal49.com) with: + - The full error response (with headers if possible) + - A description of what you were trying to do + - Any relevant request IDs or resource IDs + +## Next Steps + +- Learn about [Authentication](/api-docs/in-depth-guides/authentication) to avoid auth-related errors +- Explore the [JSON:API Guide](/api-docs/in-depth-guides/json-api-guide) to better understand API responses +- Read about [Webhooks](/api-docs/in-depth-guides/webhooks) for asynchronous event handling diff --git a/docs/docs.json b/docs/docs.json index a1e33c2e..7242feae 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -35,6 +35,7 @@ "api-docs/in-depth-guides/quickstart", "api-docs/in-depth-guides/json-api-guide", "api-docs/in-depth-guides/authentication", + "api-docs/in-depth-guides/error-handling", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/in-depth-guides/including-resources", From 2fcb8a05556f2d7608c620a4c2af14e0438af6db Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:25:46 -0700 Subject: [PATCH 14/42] Update API Documentation Improvement Tracker with to-do items --- docs/API-Documentation-Improvement-Tracker.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/API-Documentation-Improvement-Tracker.md b/docs/API-Documentation-Improvement-Tracker.md index b77564d1..a1a1304b 100644 --- a/docs/API-Documentation-Improvement-Tracker.md +++ b/docs/API-Documentation-Improvement-Tracker.md @@ -4,3 +4,11 @@ - Update authentication guide ✅ - Develop error handling and troubleshooting documentation ✅ - Update the navigation structure in docs.json ✅ + +### To Do: +- Create a rate limiting guide explaining quotas and best practices +- Enhance JSON:API guide with more practical examples +- Develop pagination examples and best practices +- Add more language-specific code examples (Ruby, Go, etc.) +- Create a "Common Workflows" guide with end-to-end examples +- Improve OpenAPI specification From a0dae074d86145db1eed107fb00d58c5dfb683ed Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:28:46 -0700 Subject: [PATCH 15/42] Improve error handling page with accordion groups for cleaner presentation --- .../in-depth-guides/error-handling.mdx | 673 +++++++++--------- 1 file changed, 341 insertions(+), 332 deletions(-) diff --git a/docs/api-docs/in-depth-guides/error-handling.mdx b/docs/api-docs/in-depth-guides/error-handling.mdx index 0c815367..14b30bac 100644 --- a/docs/api-docs/in-depth-guides/error-handling.mdx +++ b/docs/api-docs/in-depth-guides/error-handling.mdx @@ -70,379 +70,388 @@ Each error object in the `errors` array includes: ## Common Error Types and Solutions -### Authentication Errors (401) - -**Example:** -```json -{ - "errors": [ + + + **Example:** + ```json { - "status": "401", - "title": "Unauthorized", - "detail": "Invalid API key provided" + "errors": [ + { + "status": "401", + "title": "Unauthorized", + "detail": "Invalid API key provided" + } + ] } - ] -} -``` - -**Solutions:** -1. Check that you're including the `Authorization` header with the correct format: `Token YOUR_API_KEY` -2. Verify your API key is active in the [Developer Settings](https://app.terminal49.com/developers/api-keys) -3. Make sure the API key hasn't been disabled + ``` -### Validation Errors (422) + **Solutions:** + 1. Check that you're including the `Authorization` header with the correct format: `Token YOUR_API_KEY` + 2. Verify your API key is active in the [Developer Settings](https://app.terminal49.com/developers/api-keys) + 3. Make sure the API key hasn't been disabled + -**Example:** -```json -{ - "errors": [ + + **Example:** + ```json { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number 'ABCD1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", - "code": "duplicate" + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'ABCD1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" + } + ] } - ] -} -``` - -**Solutions:** -1. Check the `pointer` field to identify which attribute caused the error -2. Review the `detail` message for specific information about the validation failure -3. For duplicate tracking requests, check if the shipment is already being tracked -4. Verify SCAC codes against the [supported carriers list](https://app.terminal49.com/developers/carrier-data) - -### Resource Not Found (404) - -**Example:** -```json -{ - "errors": [ + ``` + + **Solutions:** + 1. Check the `pointer` field to identify which attribute caused the error + 2. Review the `detail` message for specific information about the validation failure + 3. For duplicate tracking requests, check if the shipment is already being tracked + 4. Verify SCAC codes against the [supported carriers list](https://app.terminal49.com/developers/carrier-data) + + + + **Example:** + ```json { - "status": "404", - "title": "Not Found", - "detail": "Couldn't find Shipment with ID=123e4567-e89b-12d3-a456-426614174000" + "errors": [ + { + "status": "404", + "title": "Not Found", + "detail": "Couldn't find Shipment with ID=123e4567-e89b-12d3-a456-426614174000" + } + ] } - ] -} -``` - -**Solutions:** -1. Verify the ID you're using exists and is correctly formatted -2. Check if the resource might have been deleted -3. Confirm you're using the correct endpoint for the resource type + ``` -### Rate Limiting (429) + **Solutions:** + 1. Verify the ID you're using exists and is correctly formatted + 2. Check if the resource might have been deleted + 3. Confirm you're using the correct endpoint for the resource type + -**Example:** -```json -{ - "errors": [ + + **Example:** + ```json { - "status": "429", - "title": "Too Many Requests", - "detail": "Rate limit exceeded. Please wait and try again later." + "errors": [ + { + "status": "429", + "title": "Too Many Requests", + "detail": "Rate limit exceeded. Please wait and try again later." + } + ] } - ] -} -``` + ``` -**Solutions:** -1. Implement retry logic with exponential backoff -2. Spread requests over a longer period -3. Contact Terminal49 support if you need a higher rate limit for your use case + **Solutions:** + 1. Implement retry logic with exponential backoff + 2. Spread requests over a longer period + 3. Contact Terminal49 support if you need a higher rate limit for your use case + + ## Implementing Effective Error Handling -### JavaScript Example - -```javascript -async function makeApiRequest(endpoint, method = 'GET', data = null) { - try { - const options = { - method, - headers: { - 'Authorization': `Token ${API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - } - }; - - if (data && (method === 'POST' || method === 'PATCH')) { - options.body = JSON.stringify(data); - } - - const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, options); - const responseData = await response.json(); - - // Check if the response contains errors - if (!response.ok) { - // Process errors - if (responseData.errors) { - const error = responseData.errors[0]; - - switch (response.status) { - case 401: - throw new Error(`Authentication error: ${error.detail}`); - case 404: - throw new Error(`Resource not found: ${error.detail}`); - case 422: - const field = error.source?.pointer?.split('/').pop() || 'unknown field'; - throw new Error(`Validation error in ${field}: ${error.detail}`); - case 429: - // Implement retry with exponential backoff - throw new Error(`Rate limit exceeded: ${error.detail}`); - default: - throw new Error(`API error ${response.status}: ${error.detail}`); + + + ```javascript + async function makeApiRequest(endpoint, method = 'GET', data = null) { + try { + const options = { + method, + headers: { + 'Authorization': `Token ${API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } + }; + + if (data && (method === 'POST' || method === 'PATCH')) { + options.body = JSON.stringify(data); } - } - - throw new Error(`API request failed with status ${response.status}`); - } - return responseData; - } catch (error) { - console.error('API request failed:', error); - throw error; - } -} -``` + const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, options); + const responseData = await response.json(); + + // Check if the response contains errors + if (!response.ok) { + // Process errors + if (responseData.errors) { + const error = responseData.errors[0]; + + switch (response.status) { + case 401: + throw new Error(`Authentication error: ${error.detail}`); + case 404: + throw new Error(`Resource not found: ${error.detail}`); + case 422: + const field = error.source?.pointer?.split('/').pop() || 'unknown field'; + throw new Error(`Validation error in ${field}: ${error.detail}`); + case 429: + // Implement retry with exponential backoff + throw new Error(`Rate limit exceeded: ${error.detail}`); + default: + throw new Error(`API error ${response.status}: ${error.detail}`); + } + } -### PHP Example - -```php -= 400) { - // Handle errors - if (isset($responseData['errors']) && !empty($responseData['errors'])) { - $error = $responseData['errors'][0]; - $errorMessage = $error['detail'] ?? 'Unknown error'; - - switch ($statusCode) { - case 401: - throw new Exception("Authentication error: {$errorMessage}"); - case 404: - throw new Exception("Resource not found: {$errorMessage}"); - case 422: - $pointer = $error['source']['pointer'] ?? ''; - $field = array_pop(explode('/', $pointer)) ?: 'unknown field'; - throw new Exception("Validation error in {$field}: {$errorMessage}"); - case 429: - // Implement retry logic - throw new Exception("Rate limit exceeded: {$errorMessage}"); - default: - throw new Exception("API error {$statusCode}: {$errorMessage}"); + return responseData; + } catch (error) { + console.error('API request failed:', error); + throw error; + } + } + ``` + + + + ```php + -``` - -### Python Example + $response = curl_exec($ch); + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $responseData = json_decode($response, true); + + if ($statusCode >= 400) { + // Handle errors + if (isset($responseData['errors']) && !empty($responseData['errors'])) { + $error = $responseData['errors'][0]; + $errorMessage = $error['detail'] ?? 'Unknown error'; + + switch ($statusCode) { + case 401: + throw new Exception("Authentication error: {$errorMessage}"); + case 404: + throw new Exception("Resource not found: {$errorMessage}"); + case 422: + $pointer = $error['source']['pointer'] ?? ''; + $field = array_pop(explode('/', $pointer)) ?: 'unknown field'; + throw new Exception("Validation error in {$field}: {$errorMessage}"); + case 429: + // Implement retry logic + throw new Exception("Rate limit exceeded: {$errorMessage}"); + default: + throw new Exception("API error {$statusCode}: {$errorMessage}"); + } + } -```python -import requests -import time -from requests.exceptions import HTTPError + throw new Exception("API request failed with status {$statusCode}"); + } -def make_api_request(endpoint, method='GET', data=None, max_retries=3): - url = f"https://api.terminal49.com/v2/{endpoint}" - headers = { - 'Authorization': f'Token {API_KEY}', - 'Content-Type': 'application/vnd.api+json', - 'Accept': 'application/vnd.api+json' + return $responseData; } + ?> + ``` + + + + ```python + import requests + import time + from requests.exceptions import HTTPError + + def make_api_request(endpoint, method='GET', data=None, max_retries=3): + url = f"https://api.terminal49.com/v2/{endpoint}" + headers = { + 'Authorization': f'Token {API_KEY}', + 'Content-Type': 'application/vnd.api+json', + 'Accept': 'application/vnd.api+json' + } - retries = 0 - while retries < max_retries: - try: - if method == 'GET': - response = requests.get(url, headers=headers) - elif method == 'POST': - response = requests.post(url, headers=headers, json=data) - elif method == 'PATCH': - response = requests.patch(url, headers=headers, json=data) - elif method == 'DELETE': - response = requests.delete(url, headers=headers) - else: - raise ValueError(f"Unsupported HTTP method: {method}") - - response.raise_for_status() - return response.json() - - except HTTPError as http_err: - # Try to parse error response + retries = 0 + while retries < max_retries: try: - error_data = response.json() - if 'errors' in error_data and error_data['errors']: - error = error_data['errors'][0] - error_detail = error.get('detail', 'No detail provided') - - if response.status_code == 401: - raise Exception(f"Authentication error: {error_detail}") - elif response.status_code == 404: - raise Exception(f"Resource not found: {error_detail}") - elif response.status_code == 422: - pointer = error.get('source', {}).get('pointer', '') - field = pointer.split('/')[-1] if pointer else 'unknown field' - raise Exception(f"Validation error in {field}: {error_detail}") - elif response.status_code == 429: - # Rate limit - implement retry with backoff - wait_time = min(2 ** retries, 60) - print(f"Rate limit exceeded. Retrying in {wait_time} seconds...") - time.sleep(wait_time) - retries += 1 - continue - except ValueError: - # Response wasn't valid JSON - pass - - # If we get here, either it wasn't a JSON response or we want to re-raise - if response.status_code != 429 or retries >= max_retries - 1: - raise Exception(f"API error {response.status_code}: {response.text}") - - retries += 1 - - raise Exception("Maximum retry attempts reached") -``` - -## Retry Strategies - -For intermittent errors and rate limiting, implementing a retry strategy is essential: - -### Exponential Backoff - -```javascript -async function makeRequestWithRetry(endpoint, method = 'GET', data = null, maxRetries = 3) { - let retries = 0; - - while (retries < maxRetries) { - try { - return await makeApiRequest(endpoint, method, data); - } catch (error) { - retries++; - - // If it's not a retriable error or we've used all retries, throw - if ( - !error.message.includes('Rate limit exceeded') || - retries >= maxRetries - ) { - throw error; + if method == 'GET': + response = requests.get(url, headers=headers) + elif method == 'POST': + response = requests.post(url, headers=headers, json=data) + elif method == 'PATCH': + response = requests.patch(url, headers=headers, json=data) + elif method == 'DELETE': + response = requests.delete(url, headers=headers) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + response.raise_for_status() + return response.json() + + except HTTPError as http_err: + # Try to parse error response + try: + error_data = response.json() + if 'errors' in error_data and error_data['errors']: + error = error_data['errors'][0] + error_detail = error.get('detail', 'No detail provided') + + if response.status_code == 401: + raise Exception(f"Authentication error: {error_detail}") + elif response.status_code == 404: + raise Exception(f"Resource not found: {error_detail}") + elif response.status_code == 422: + pointer = error.get('source', {}).get('pointer', '') + field = pointer.split('/')[-1] if pointer else 'unknown field' + raise Exception(f"Validation error in {field}: {error_detail}") + elif response.status_code == 429: + # Rate limit - implement retry with backoff + wait_time = min(2 ** retries, 60) + print(f"Rate limit exceeded. Retrying in {wait_time} seconds...") + time.sleep(wait_time) + retries += 1 + continue + except ValueError: + # Response wasn't valid JSON + pass + + # If we get here, either it wasn't a JSON response or we want to re-raise + if response.status_code != 429 or retries >= max_retries - 1: + raise Exception(f"API error {response.status_code}: {response.text}") + + retries += 1 + + raise Exception("Maximum retry attempts reached") + ``` + + + + ```javascript + async function makeRequestWithRetry(endpoint, method = 'GET', data = null, maxRetries = 3) { + let retries = 0; + + while (retries < maxRetries) { + try { + return await makeApiRequest(endpoint, method, data); + } catch (error) { + retries++; + + // If it's not a retriable error or we've used all retries, throw + if ( + !error.message.includes('Rate limit exceeded') || + retries >= maxRetries + ) { + throw error; + } + + // Exponential backoff with jitter + const delay = Math.min(1000 * 2 ** retries + Math.random() * 1000, 60000); + console.log(`Retrying after ${delay}ms (attempt ${retries} of ${maxRetries})...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } } - - // Exponential backoff with jitter - const delay = Math.min(1000 * 2 ** retries + Math.random() * 1000, 60000); - console.log(`Retrying after ${delay}ms (attempt ${retries} of ${maxRetries})...`); - await new Promise(resolve => setTimeout(resolve, delay)); } - } -} -``` - -## Error Scenarios and Solutions + ``` + + -### Duplicate Tracking Requests +## Common Error Scenarios -**Error:** -```json -{ - "errors": [ + + + **Error:** + ```json { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number 'MAEU1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", - "code": "duplicate" + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'MAEU1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" + } + ] } - ] -} -``` - -**Solution:** -When you receive a duplicate error, you should: -1. Look up the existing tracking request using the `GET /tracking_requests` endpoint with appropriate filters -2. Use the ID of the existing request to fetch its current status -3. If needed, you can update the existing tracking request rather than creating a new one - -### Unsupported Carrier - -**Error:** -```json -{ - "errors": [ + ``` + + **Solution:** + When you receive a duplicate error, you should: + 1. Look up the existing tracking request using the `GET /tracking_requests` endpoint with appropriate filters + 2. Use the ID of the existing request to fetch its current status + 3. If needed, you can update the existing tracking request rather than creating a new one + + + + **Error:** + ```json { - "status": "422", - "source": { - "pointer": "/data/attributes/scac" - }, - "title": "Unprocessable Entity", - "detail": "Scac 'ABCD' is not supported", - "code": "unsupported_scac" + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'ABCD' is not supported", + "code": "unsupported_scac" + } + ] } - ] -} -``` + ``` -**Solution:** -1. Reference the [Carrier Data Matrix](https://app.terminal49.com/developers/carrier-data) for a list of supported carriers -2. Double-check the SCAC code format (usually 4 uppercase letters) -3. If you need support for a specific carrier, contact Terminal49 + **Solution:** + 1. Reference the [Carrier Data Matrix](https://app.terminal49.com/developers/carrier-data) for a list of supported carriers + 2. Double-check the SCAC code format (usually 4 uppercase letters) + 3. If you need support for a specific carrier, contact Terminal49 + + ## Best Practices for Error Handling -1. **Always check for error responses** - - Parse the response body to look for the `errors` array - - Handle both HTTP errors and JSON:API formatted errors - -2. **Log detailed error information** - - Include the status code, error title, error detail, and any source information - - Add context from your application (e.g., what operation was being attempted) - -3. **Implement appropriate retry logic** - - Use exponential backoff for rate limiting (429) errors - - Don't retry for validation (422) or authentication (401) errors - -4. **Present user-friendly error messages** - - Translate technical API errors to understandable messages for end-users - - Provide actionable guidance when possible - -5. **Monitor error rates** - - Set up alerts for unusual error patterns - - Keep track of error frequency to identify potential issues with your integration + + + - Parse the response body to look for the `errors` array + - Handle both HTTP errors and JSON:API formatted errors + + + + - Include the status code, error title, error detail, and any source information + - Add context from your application (e.g., what operation was being attempted) + + + + - Use exponential backoff for rate limiting (429) errors + - Don't retry for validation (422) or authentication (401) errors + + + + - Translate technical API errors to understandable messages for end-users + - Provide actionable guidance when possible + + + + - Set up alerts for unusual error patterns + - Keep track of error frequency to identify potential issues with your integration + + ## Getting Help From 531423d682c49e1a40cf9fc0dd4453f09eb15b40 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:35:50 -0700 Subject: [PATCH 16/42] Add 'Why JSON:API?' section to error handling guide --- .../in-depth-guides/error-handling.mdx | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/api-docs/in-depth-guides/error-handling.mdx b/docs/api-docs/in-depth-guides/error-handling.mdx index 14b30bac..31603cf2 100644 --- a/docs/api-docs/in-depth-guides/error-handling.mdx +++ b/docs/api-docs/in-depth-guides/error-handling.mdx @@ -56,6 +56,72 @@ Each error object in the `errors` array includes: A machine-readable error code that can be used for programmatic handling (not always present). +## Why JSON:API? + + + Terminal49 uses the JSON:API specification for a number of important reasons that directly benefit developers working with shipping and container tracking data: + + ### Standardized Relationships for Complex Shipping Data + + Shipping data is inherently relational - containers belong to shipments, shipments have transport events, containers have tracking events, and all these relate to carriers, terminals, and vessels. JSON:API excels at representing these complex relationships through: + + - **Consistent Structure**: Every response follows the same pattern, making data predictable and easy to parse + - **Clear Resource Relationships**: Explicit definition of how resources like shipments, containers, and events relate to each other + - **Compound Documents**: The ability to include related resources in a single request, eliminating the need for multiple API calls + + ### Efficient Data Loading for Shipping Operations + + JSON:API's approach to related data is particularly valuable for shipping operations: + + ```json + { + "data": { + "id": "123", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU1234567", + "carrier_name": "Maersk" + }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" } + ] + } + } + }, + "included": [ + { + "id": "456", + "type": "container", + "attributes": { + "number": "MRKU1234567", + "size_type": "40HC" + } + } + ] + } + ``` + + This allows Terminal49 to deliver a complete picture of your shipping data in fewer requests, which means: + + - **Reduced API Calls**: Get containers and their shipments in a single request + - **Minimized Latency**: Critical for time-sensitive logistics operations + - **Lower Bandwidth**: Only request the related objects you need, when you need them + + ### Standardized Error Handling + + As detailed throughout this guide, JSON:API provides a consistent format for error responses, making it easier to: + + - **Identify Issues**: Clearly pinpoint which field or relationship has an error + - **Implement Error Handling**: Write consistent error handling logic across your application + - **Debug Problems**: Receive detailed information about what went wrong and why + + ### Client Library Support + + Many languages have JSON:API client libraries that can automatically parse responses and handle the relationship mapping, making your code cleaner and more maintainable when working with complex shipping data hierarchies. + + ## Common HTTP Status Codes | Status Code | Meaning | Typical Causes | From f5d4c7de1e5f5b1c24610f68af304c9900dadb4f Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:36:22 -0700 Subject: [PATCH 17/42] Expand client library section in JSON:API documentation --- .../in-depth-guides/error-handling.mdx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/api-docs/in-depth-guides/error-handling.mdx b/docs/api-docs/in-depth-guides/error-handling.mdx index 31603cf2..3cef9bcc 100644 --- a/docs/api-docs/in-depth-guides/error-handling.mdx +++ b/docs/api-docs/in-depth-guides/error-handling.mdx @@ -119,7 +119,24 @@ Each error object in the `errors` array includes: ### Client Library Support - Many languages have JSON:API client libraries that can automatically parse responses and handle the relationship mapping, making your code cleaner and more maintainable when working with complex shipping data hierarchies. + Instead of writing custom code to parse JSON:API responses, you can leverage ready-made libraries for most programming languages: + + - **JavaScript/TypeScript**: [jsonapi-typescript](https://github.com/juniskane/jsonapi-typescript), [json-api-serializer](https://github.com/SeyZ/jsonapi-serializer) + - **Python**: [jsonapi-client](https://github.com/qvantel/jsonapi-client), [marshmallow-jsonapi](https://github.com/marshmallow-code/marshmallow-jsonapi) + - **Ruby**: [jsonapi-resources](https://github.com/cerebris/jsonapi-resources), [jsonapi-serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) + - **PHP**: [jsonapi-client-php](https://github.com/art-of-coding/json-api-client), [laravel-json-api](https://github.com/laravel-json-api/laravel) + - **Go**: [jsonapi](https://github.com/google/jsonapi), [go-jsonapi](https://github.com/connor4312/go-jsonapi) + - **Java**: [crnk](https://github.com/crnk-project/crnk-framework), [katharsis](https://github.com/katharsis-project/katharsis-framework) + + These libraries handle the complex aspects of Terminal49's shipping data, providing benefits such as: + + - **Automatic Relationship Resolution**: Easily navigate from shipments to containers and events without manual mapping + - **Type Conversion**: Convert API responses into native language objects/models + - **Query Building**: Construct complex API queries with simple, intuitive methods + - **Consistent Updates**: Standardized methods for creating and updating resources + - **Error Handling**: Built-in support for JSON:API error formats + + Using these libraries can reduce development time by up to 50% when working with complex shipping data structures, allowing you to focus on your business logic rather than API parsing details. ## Common HTTP Status Codes From a93bff7f000933a6d055e7626224909a7843f090 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:40:55 -0700 Subject: [PATCH 18/42] Streamline error handling guide by removing JSON:API content and using accordions selectively --- .../in-depth-guides/error-handling.mdx | 486 +++++------------- 1 file changed, 133 insertions(+), 353 deletions(-) diff --git a/docs/api-docs/in-depth-guides/error-handling.mdx b/docs/api-docs/in-depth-guides/error-handling.mdx index 3cef9bcc..74bc08cc 100644 --- a/docs/api-docs/in-depth-guides/error-handling.mdx +++ b/docs/api-docs/in-depth-guides/error-handling.mdx @@ -56,89 +56,6 @@ Each error object in the `errors` array includes: A machine-readable error code that can be used for programmatic handling (not always present). -## Why JSON:API? - - - Terminal49 uses the JSON:API specification for a number of important reasons that directly benefit developers working with shipping and container tracking data: - - ### Standardized Relationships for Complex Shipping Data - - Shipping data is inherently relational - containers belong to shipments, shipments have transport events, containers have tracking events, and all these relate to carriers, terminals, and vessels. JSON:API excels at representing these complex relationships through: - - - **Consistent Structure**: Every response follows the same pattern, making data predictable and easy to parse - - **Clear Resource Relationships**: Explicit definition of how resources like shipments, containers, and events relate to each other - - **Compound Documents**: The ability to include related resources in a single request, eliminating the need for multiple API calls - - ### Efficient Data Loading for Shipping Operations - - JSON:API's approach to related data is particularly valuable for shipping operations: - - ```json - { - "data": { - "id": "123", - "type": "shipment", - "attributes": { - "bill_of_lading_number": "MAEU1234567", - "carrier_name": "Maersk" - }, - "relationships": { - "containers": { - "data": [ - { "id": "456", "type": "container" } - ] - } - } - }, - "included": [ - { - "id": "456", - "type": "container", - "attributes": { - "number": "MRKU1234567", - "size_type": "40HC" - } - } - ] - } - ``` - - This allows Terminal49 to deliver a complete picture of your shipping data in fewer requests, which means: - - - **Reduced API Calls**: Get containers and their shipments in a single request - - **Minimized Latency**: Critical for time-sensitive logistics operations - - **Lower Bandwidth**: Only request the related objects you need, when you need them - - ### Standardized Error Handling - - As detailed throughout this guide, JSON:API provides a consistent format for error responses, making it easier to: - - - **Identify Issues**: Clearly pinpoint which field or relationship has an error - - **Implement Error Handling**: Write consistent error handling logic across your application - - **Debug Problems**: Receive detailed information about what went wrong and why - - ### Client Library Support - - Instead of writing custom code to parse JSON:API responses, you can leverage ready-made libraries for most programming languages: - - - **JavaScript/TypeScript**: [jsonapi-typescript](https://github.com/juniskane/jsonapi-typescript), [json-api-serializer](https://github.com/SeyZ/jsonapi-serializer) - - **Python**: [jsonapi-client](https://github.com/qvantel/jsonapi-client), [marshmallow-jsonapi](https://github.com/marshmallow-code/marshmallow-jsonapi) - - **Ruby**: [jsonapi-resources](https://github.com/cerebris/jsonapi-resources), [jsonapi-serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) - - **PHP**: [jsonapi-client-php](https://github.com/art-of-coding/json-api-client), [laravel-json-api](https://github.com/laravel-json-api/laravel) - - **Go**: [jsonapi](https://github.com/google/jsonapi), [go-jsonapi](https://github.com/connor4312/go-jsonapi) - - **Java**: [crnk](https://github.com/crnk-project/crnk-framework), [katharsis](https://github.com/katharsis-project/katharsis-framework) - - These libraries handle the complex aspects of Terminal49's shipping data, providing benefits such as: - - - **Automatic Relationship Resolution**: Easily navigate from shipments to containers and events without manual mapping - - **Type Conversion**: Convert API responses into native language objects/models - - **Query Building**: Construct complex API queries with simple, intuitive methods - - **Consistent Updates**: Standardized methods for creating and updating resources - - **Error Handling**: Built-in support for JSON:API error formats - - Using these libraries can reduce development time by up to 50% when working with complex shipping data structures, allowing you to focus on your business logic rather than API parsing details. - - ## Common HTTP Status Codes | Status Code | Meaning | Typical Causes | @@ -240,301 +157,164 @@ Each error object in the `errors` array includes: -## Implementing Effective Error Handling - - - - ```javascript - async function makeApiRequest(endpoint, method = 'GET', data = null) { - try { - const options = { - method, - headers: { - 'Authorization': `Token ${API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - } - }; - - if (data && (method === 'POST' || method === 'PATCH')) { - options.body = JSON.stringify(data); - } +## Common Error Scenarios - const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, options); - const responseData = await response.json(); - - // Check if the response contains errors - if (!response.ok) { - // Process errors - if (responseData.errors) { - const error = responseData.errors[0]; - - switch (response.status) { - case 401: - throw new Error(`Authentication error: ${error.detail}`); - case 404: - throw new Error(`Resource not found: ${error.detail}`); - case 422: - const field = error.source?.pointer?.split('/').pop() || 'unknown field'; - throw new Error(`Validation error in ${field}: ${error.detail}`); - case 429: - // Implement retry with exponential backoff - throw new Error(`Rate limit exceeded: ${error.detail}`); - default: - throw new Error(`API error ${response.status}: ${error.detail}`); - } - } - - throw new Error(`API request failed with status ${response.status}`); - } +### Duplicate Tracking Requests - return responseData; - } catch (error) { - console.error('API request failed:', error); - throw error; - } +**Error:** +```json +{ + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'MAEU1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" } - ``` - + ] +} +``` - - ```php - = 400) { - // Handle errors - if (isset($responseData['errors']) && !empty($responseData['errors'])) { - $error = $responseData['errors'][0]; - $errorMessage = $error['detail'] ?? 'Unknown error'; - - switch ($statusCode) { - case 401: - throw new Exception("Authentication error: {$errorMessage}"); - case 404: - throw new Exception("Resource not found: {$errorMessage}"); - case 422: - $pointer = $error['source']['pointer'] ?? ''; - $field = array_pop(explode('/', $pointer)) ?: 'unknown field'; - throw new Exception("Validation error in {$field}: {$errorMessage}"); - case 429: - // Implement retry logic - throw new Exception("Rate limit exceeded: {$errorMessage}"); - default: - throw new Exception("API error {$statusCode}: {$errorMessage}"); - } - } - - throw new Exception("API request failed with status {$statusCode}"); - } +### Unsupported Carrier - return $responseData; +**Error:** +```json +{ + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'ABCD' is not supported", + "code": "unsupported_scac" } - ?> - ``` - + ] +} +``` - - ```python - import requests - import time - from requests.exceptions import HTTPError - - def make_api_request(endpoint, method='GET', data=None, max_retries=3): - url = f"https://api.terminal49.com/v2/{endpoint}" - headers = { - 'Authorization': f'Token {API_KEY}', - 'Content-Type': 'application/vnd.api+json', - 'Accept': 'application/vnd.api+json' - } +**Solution:** +1. Reference the [Carrier Data Matrix](https://app.terminal49.com/developers/carrier-data) for a list of supported carriers +2. Double-check the SCAC code format (usually 4 uppercase letters) +3. If you need support for a specific carrier, contact Terminal49 - retries = 0 - while retries < max_retries: - try: - if method == 'GET': - response = requests.get(url, headers=headers) - elif method == 'POST': - response = requests.post(url, headers=headers, json=data) - elif method == 'PATCH': - response = requests.patch(url, headers=headers, json=data) - elif method == 'DELETE': - response = requests.delete(url, headers=headers) - else: - raise ValueError(f"Unsupported HTTP method: {method}") - - response.raise_for_status() - return response.json() - - except HTTPError as http_err: - # Try to parse error response - try: - error_data = response.json() - if 'errors' in error_data and error_data['errors']: - error = error_data['errors'][0] - error_detail = error.get('detail', 'No detail provided') - - if response.status_code == 401: - raise Exception(f"Authentication error: {error_detail}") - elif response.status_code == 404: - raise Exception(f"Resource not found: {error_detail}") - elif response.status_code == 422: - pointer = error.get('source', {}).get('pointer', '') - field = pointer.split('/')[-1] if pointer else 'unknown field' - raise Exception(f"Validation error in {field}: {error_detail}") - elif response.status_code == 429: - # Rate limit - implement retry with backoff - wait_time = min(2 ** retries, 60) - print(f"Rate limit exceeded. Retrying in {wait_time} seconds...") - time.sleep(wait_time) - retries += 1 - continue - except ValueError: - # Response wasn't valid JSON - pass - - # If we get here, either it wasn't a JSON response or we want to re-raise - if response.status_code != 429 or retries >= max_retries - 1: - raise Exception(f"API error {response.status_code}: {response.text}") - - retries += 1 - - raise Exception("Maximum retry attempts reached") - ``` - +## Implementing Effective Error Handling - - ```javascript - async function makeRequestWithRetry(endpoint, method = 'GET', data = null, maxRetries = 3) { - let retries = 0; - - while (retries < maxRetries) { - try { - return await makeApiRequest(endpoint, method, data); - } catch (error) { - retries++; - - // If it's not a retriable error or we've used all retries, throw - if ( - !error.message.includes('Rate limit exceeded') || - retries >= maxRetries - ) { - throw error; - } - - // Exponential backoff with jitter - const delay = Math.min(1000 * 2 ** retries + Math.random() * 1000, 60000); - console.log(`Retrying after ${delay}ms (attempt ${retries} of ${maxRetries})...`); - await new Promise(resolve => setTimeout(resolve, delay)); - } +### JavaScript Example + +```javascript +async function makeApiRequest(endpoint, method = 'GET', data = null) { + try { + const options = { + method, + headers: { + 'Authorization': `Token ${API_KEY}`, + 'Content-Type': 'application/vnd.api+json' } - } - ``` - - + }; -## Common Error Scenarios + if (data && (method === 'POST' || method === 'PATCH')) { + options.body = JSON.stringify(data); + } - - - **Error:** - ```json - { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number 'MAEU1234567' with scac 'MAEU' already exists in a tracking_request with a pending or created status", - "code": "duplicate" + const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, options); + const responseData = await response.json(); + + // Check if the response contains errors + if (!response.ok) { + // Process errors + if (responseData.errors) { + const error = responseData.errors[0]; + + switch (response.status) { + case 401: + throw new Error(`Authentication error: ${error.detail}`); + case 404: + throw new Error(`Resource not found: ${error.detail}`); + case 422: + const field = error.source?.pointer?.split('/').pop() || 'unknown field'; + throw new Error(`Validation error in ${field}: ${error.detail}`); + case 429: + // Implement retry with exponential backoff + throw new Error(`Rate limit exceeded: ${error.detail}`); + default: + throw new Error(`API error ${response.status}: ${error.detail}`); } - ] + } + + throw new Error(`API request failed with status ${response.status}`); } - ``` - **Solution:** - When you receive a duplicate error, you should: - 1. Look up the existing tracking request using the `GET /tracking_requests` endpoint with appropriate filters - 2. Use the ID of the existing request to fetch its current status - 3. If needed, you can update the existing tracking request rather than creating a new one - + return responseData; + } catch (error) { + console.error('API request failed:', error); + throw error; + } +} +``` - - **Error:** - ```json - { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/scac" - }, - "title": "Unprocessable Entity", - "detail": "Scac 'ABCD' is not supported", - "code": "unsupported_scac" - } - ] - } - ``` +## Retry Strategy - **Solution:** - 1. Reference the [Carrier Data Matrix](https://app.terminal49.com/developers/carrier-data) for a list of supported carriers - 2. Double-check the SCAC code format (usually 4 uppercase letters) - 3. If you need support for a specific carrier, contact Terminal49 - - +When dealing with rate limiting or transient errors, implementing a retry strategy with exponential backoff is essential: + +```javascript +async function makeRequestWithRetry(endpoint, method = 'GET', data = null, maxRetries = 3) { + let retries = 0; + + while (retries < maxRetries) { + try { + return await makeApiRequest(endpoint, method, data); + } catch (error) { + retries++; + + // If it's not a retriable error or we've used all retries, throw + if ( + !error.message.includes('Rate limit exceeded') || + retries >= maxRetries + ) { + throw error; + } + + // Exponential backoff with jitter + const delay = Math.min(1000 * 2 ** retries + Math.random() * 1000, 60000); + console.log(`Retrying after ${delay}ms (attempt ${retries} of ${maxRetries})...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} +``` ## Best Practices for Error Handling - - - - Parse the response body to look for the `errors` array - - Handle both HTTP errors and JSON:API formatted errors - +1. **Always check for error responses** + - Parse the response body to look for the `errors` array + - Handle both HTTP errors and JSON:API formatted errors - - - Include the status code, error title, error detail, and any source information - - Add context from your application (e.g., what operation was being attempted) - +2. **Log detailed error information** + - Include the status code, error title, error detail, and any source information + - Add context from your application (e.g., what operation was being attempted) - - - Use exponential backoff for rate limiting (429) errors - - Don't retry for validation (422) or authentication (401) errors - +3. **Implement appropriate retry logic** + - Use exponential backoff for rate limiting (429) errors + - Don't retry for validation (422) or authentication (401) errors - - - Translate technical API errors to understandable messages for end-users - - Provide actionable guidance when possible - +4. **Present user-friendly error messages** + - Translate technical API errors to understandable messages for end-users + - Provide actionable guidance when possible - - - Set up alerts for unusual error patterns - - Keep track of error frequency to identify potential issues with your integration - - +5. **Monitor error rates** + - Set up alerts for unusual error patterns + - Keep track of error frequency to identify potential issues with your integration ## Getting Help From 5c9dbebc731ecae4ba098c46ba9d43797fb4efac Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:44:59 -0700 Subject: [PATCH 19/42] Remove accordion from Why JSON:API section for better readability --- .../in-depth-guides/json-api-guide.mdx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/api-docs/in-depth-guides/json-api-guide.mdx b/docs/api-docs/in-depth-guides/json-api-guide.mdx index 4ef6b8fa..86e3d1ba 100644 --- a/docs/api-docs/in-depth-guides/json-api-guide.mdx +++ b/docs/api-docs/in-depth-guides/json-api-guide.mdx @@ -15,6 +15,87 @@ JSON:API is a specification for how a client should request resources to be fetc The Terminal49 API conforms to the JSON:API v1.0 specification. This standardization makes our API more predictable and easier to work with once you understand the patterns. +## Why Terminal49 Uses JSON:API + +Terminal49 uses the JSON:API specification for a number of important reasons that directly benefit developers working with shipping and container tracking data: + +### Standardized Relationships for Complex Shipping Data + +Shipping data is inherently relational - containers belong to shipments, shipments have transport events, containers have tracking events, and all these relate to carriers, terminals, and vessels. JSON:API excels at representing these complex relationships through: + +- **Consistent Structure**: Every response follows the same pattern, making data predictable and easy to parse +- **Clear Resource Relationships**: Explicit definition of how resources like shipments, containers, and events relate to each other +- **Compound Documents**: The ability to include related resources in a single request, eliminating the need for multiple API calls + +### Efficient Data Loading for Shipping Operations + +JSON:API's approach to related data is particularly valuable for shipping operations: + +```json +{ + "data": { + "id": "123", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU1234567", + "carrier_name": "Maersk" + }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" } + ] + } + } + }, + "included": [ + { + "id": "456", + "type": "container", + "attributes": { + "number": "MRKU1234567", + "size_type": "40HC" + } + } + ] +} +``` + +This allows Terminal49 to deliver a complete picture of your shipping data in fewer requests, which means: + +- **Reduced API Calls**: Get containers and their shipments in a single request +- **Minimized Latency**: Critical for time-sensitive logistics operations +- **Lower Bandwidth**: Only request the related objects you need, when you need them + +### Standardized Error Handling + +JSON:API provides a consistent format for error responses, making it easier to: + +- **Identify Issues**: Clearly pinpoint which field or relationship has an error +- **Implement Error Handling**: Write consistent error handling logic across your application +- **Debug Problems**: Receive detailed information about what went wrong and why + +### Client Library Support + +Instead of writing custom code to parse JSON:API responses, you can leverage ready-made libraries for most programming languages: + +- **JavaScript/TypeScript**: [jsonapi-typescript](https://github.com/juniskane/jsonapi-typescript), [json-api-serializer](https://github.com/SeyZ/jsonapi-serializer) +- **Python**: [jsonapi-client](https://github.com/qvantel/jsonapi-client), [marshmallow-jsonapi](https://github.com/marshmallow-code/marshmallow-jsonapi) +- **Ruby**: [jsonapi-resources](https://github.com/cerebris/jsonapi-resources), [jsonapi-serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) +- **PHP**: [jsonapi-client-php](https://github.com/art-of-coding/json-api-client), [laravel-json-api](https://github.com/laravel-json-api/laravel) +- **Go**: [jsonapi](https://github.com/google/jsonapi), [go-jsonapi](https://github.com/connor4312/go-jsonapi) +- **Java**: [crnk](https://github.com/crnk-project/crnk-framework), [katharsis](https://github.com/katharsis-project/katharsis-framework) + +These libraries handle the complex aspects of Terminal49's shipping data, providing benefits such as: + +- **Automatic Relationship Resolution**: Easily navigate from shipments to containers and events without manual mapping +- **Type Conversion**: Convert API responses into native language objects/models +- **Query Building**: Construct complex API queries with simple, intuitive methods +- **Consistent Updates**: Standardized methods for creating and updating resources +- **Error Handling**: Built-in support for JSON:API error formats + +Using these libraries can reduce development time by up to 50% when working with complex shipping data structures, allowing you to focus on your business logic rather than API parsing details. + ## JSON:API Structure ### Basic Resource Objects From 371b91b8872bb9038898e75079863948fe28a6dd Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Fri, 21 Mar 2025 22:48:19 -0700 Subject: [PATCH 20/42] Combine client library sections and add puzzle-piece icon --- .../in-depth-guides/json-api-guide.mdx | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/docs/api-docs/in-depth-guides/json-api-guide.mdx b/docs/api-docs/in-depth-guides/json-api-guide.mdx index 86e3d1ba..87360106 100644 --- a/docs/api-docs/in-depth-guides/json-api-guide.mdx +++ b/docs/api-docs/in-depth-guides/json-api-guide.mdx @@ -75,18 +75,32 @@ JSON:API provides a consistent format for error responses, making it easier to: - **Implement Error Handling**: Write consistent error handling logic across your application - **Debug Problems**: Receive detailed information about what went wrong and why -### Client Library Support +## Client Libraries Instead of writing custom code to parse JSON:API responses, you can leverage ready-made libraries for most programming languages: -- **JavaScript/TypeScript**: [jsonapi-typescript](https://github.com/juniskane/jsonapi-typescript), [json-api-serializer](https://github.com/SeyZ/jsonapi-serializer) -- **Python**: [jsonapi-client](https://github.com/qvantel/jsonapi-client), [marshmallow-jsonapi](https://github.com/marshmallow-code/marshmallow-jsonapi) -- **Ruby**: [jsonapi-resources](https://github.com/cerebris/jsonapi-resources), [jsonapi-serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) -- **PHP**: [jsonapi-client-php](https://github.com/art-of-coding/json-api-client), [laravel-json-api](https://github.com/laravel-json-api/laravel) -- **Go**: [jsonapi](https://github.com/google/jsonapi), [go-jsonapi](https://github.com/connor4312/go-jsonapi) -- **Java**: [crnk](https://github.com/crnk-project/crnk-framework), [katharsis](https://github.com/katharsis-project/katharsis-framework) + + + JSON:API libraries for JavaScript + + + jsonapi-rb for Ruby applications + + + JSON:API libraries for Python + + + PHP client libraries for JSON:API + + + Go implementations of JSON:API + + + Java frameworks for JSON:API + + -These libraries handle the complex aspects of Terminal49's shipping data, providing benefits such as: +These libraries provide significant benefits when working with Terminal49's API: - **Automatic Relationship Resolution**: Easily navigate from shipments to containers and events without manual mapping - **Type Conversion**: Convert API responses into native language objects/models @@ -293,24 +307,6 @@ Each error object contains: An object containing references to the source of the error. The `pointer` field typically contains a JSON Pointer to the specific field that caused the error. -## JSON:API Libraries - -Since Terminal49 uses standard JSON:API, you can leverage client libraries for your preferred programming language to make working with our API easier: - - - - JSON:API libraries for JavaScript - - - jsonapi-rb for Ruby applications - - - JSON:API libraries for Python - - - -Using a library helps handle the resource structure, relationship management, and error parsing automatically. - ## Tips for Working with Terminal49's JSON:API - Always include the correct `Content-Type` header in your requests: From 4991a451dc077a8b165d363ffa21d7d4e197159e Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:08:30 -0700 Subject: [PATCH 21/42] Add comprehensive workflow documentation and improve quick start guide --- docs/API-Documentation-Improvement-Tracker.md | 8 +- .../in-depth-guides/api-quick-start.mdx | 463 ++++++++ .../in-depth-guides/common-workflows.mdx | 996 ++++++++++++++++++ docs/docs.json | 25 +- 4 files changed, 1477 insertions(+), 15 deletions(-) create mode 100644 docs/api-docs/in-depth-guides/api-quick-start.mdx create mode 100644 docs/api-docs/in-depth-guides/common-workflows.mdx diff --git a/docs/API-Documentation-Improvement-Tracker.md b/docs/API-Documentation-Improvement-Tracker.md index a1a1304b..d26506ec 100644 --- a/docs/API-Documentation-Improvement-Tracker.md +++ b/docs/API-Documentation-Improvement-Tracker.md @@ -4,11 +4,13 @@ - Update authentication guide ✅ - Develop error handling and troubleshooting documentation ✅ - Update the navigation structure in docs.json ✅ +- Create a rate limiting guide explaining quotas and best practices ✅ +- Create a "Common Workflows" guide with end-to-end examples ✅ +- Create comprehensive API Quick Start guide ✅ +- Enhance JSON:API guide with practical examples ✅ +- Reorganize documentation structure by moving Getting Started content into guides ✅ ### To Do: -- Create a rate limiting guide explaining quotas and best practices -- Enhance JSON:API guide with more practical examples - Develop pagination examples and best practices - Add more language-specific code examples (Ruby, Go, etc.) -- Create a "Common Workflows" guide with end-to-end examples - Improve OpenAPI specification diff --git a/docs/api-docs/in-depth-guides/api-quick-start.mdx b/docs/api-docs/in-depth-guides/api-quick-start.mdx new file mode 100644 index 00000000..1190f36e --- /dev/null +++ b/docs/api-docs/in-depth-guides/api-quick-start.mdx @@ -0,0 +1,463 @@ +--- +title: API Quick Start Guide +description: Get up and running with Terminal49's API in minutes - track containers, receive updates, and integrate with your systems. +icon: "rocket" +--- + +# API Quick Start Guide + +This guide will help you get started with Terminal49's API by walking through the most common workflow: tracking a shipment and receiving status updates. You'll learn both how to request data and how to receive real-time updates via webhooks. + +## Prerequisites + +Before you begin, you'll need: + +1. **A Terminal49 account** - [Sign up here](https://app.terminal49.com/register) if you don't have one +2. **An API key** - Find or create one in your [developer portal](https://app.terminal49.com/developers/api-keys) +3. **A tracking number** to monitor: + - Bill of Lading (BOL) number, Booking number, or Container number + - The [SCAC](https://en.wikipedia.org/wiki/Standard_Carrier_Alpha_Code) of the carrier (e.g., MAEU for Maersk) + + + Don't have a tracking number? Use our test data: `TEST-TR-SUCCEEDED` with SCAC `TEST` for immediate results. + + +## Step 1: Authentication + +All API requests require authentication using your API key. Include it in the Authorization header with the prefix `Token`: + +```bash +curl -X GET "https://api.terminal49.com/v2/shipments" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + + +```javascript +// Node.js with fetch +const headers = { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' +}; + +const response = await fetch('https://api.terminal49.com/v2/shipments', { headers }); +``` + +```php +// PHP with cURL +$curl = curl_init(); +curl_setopt_array($curl, [ + CURLOPT_URL => "https://api.terminal49.com/v2/shipments", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + "Authorization: Token YOUR_API_KEY", + "Content-Type: application/vnd.api+json" + ], +]); +$response = curl_exec($curl); +``` + +```ruby +# Ruby with HTTParty +require 'httparty' + +headers = { + 'Authorization' => "Token YOUR_API_KEY", + 'Content-Type' => 'application/vnd.api+json' +} + +response = HTTParty.get("https://api.terminal49.com/v2/shipments", headers: headers) +``` + + +## Step 2: Create a Tracking Request + +The first step to track a shipment is to create a tracking request. This tells Terminal49 which shipment you want to track. + +**Workflow Example: Start tracking a container to get its ETA** + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + }' +``` + +```javascript +// Using fetch (Node.js/Browser) +const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'tracking_request', + attributes: { + request_type: 'bill_of_lading', + request_number: 'MAEU9736478', + scac: 'MAEU' + } + } + }) +}); + +const result = await response.json(); +console.log(result); +``` + +```php +// Using PHP +$curl = curl_init(); +$payload = json_encode([ + 'data' => [ + 'type' => 'tracking_request', + 'attributes' => [ + 'request_type' => 'bill_of_lading', + 'request_number' => 'MAEU9736478', + 'scac' => 'MAEU' + ] + ] +]); + +curl_setopt_array($curl, [ + CURLOPT_URL => "https://api.terminal49.com/v2/tracking_requests", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HTTPHEADER => [ + "Authorization: Token YOUR_API_KEY", + "Content-Type: application/vnd.api+json" + ], +]); + +$response = curl_exec($curl); +$result = json_decode($response, true); +print_r($result); +``` + + +The response will include a tracking request ID and status: + +```json +{ + "data": { + "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", + "created_at": "2023-03-16T18:35:42Z", + "updated_at": "2023-03-16T18:35:42Z" + }, + "relationships": { + "tracked_object": { + "data": null + } + } + } +} +``` + + + The tracking request starts with status `pending`. Terminal49 will process it asynchronously, and it will change to `succeeded` or `failed`. + + +## Step 3: Check Tracking Request Status (Polling Method) + +To check if your tracking request has completed, you can poll the tracking request: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/tracking_requests/b1f30140-c9cb-4354-b8e3-3d92a76289cf" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using fetch +const trackingRequestId = 'b1f30140-c9cb-4354-b8e3-3d92a76289cf'; +const response = await fetch(`https://api.terminal49.com/v2/tracking_requests/${trackingRequestId}`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +console.log(`Tracking request status: ${result.data.attributes.status}`); +``` + + +When the request succeeds, it will include a relationship to the newly created shipment: + +```json +{ + "data": { + "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "succeeded", + "created_at": "2023-03-16T18:35:42Z", + "updated_at": "2023-03-16T18:36:10Z" + }, + "relationships": { + "tracked_object": { + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment" + } + } + } + } +} +``` + +## Step 4: Get Shipment Details and ETA + +Once the tracking request has succeeded, you can retrieve the shipment and its estimated arrival time: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/shipments/7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b?include=containers" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using fetch +const shipmentId = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/shipments/${shipmentId}?include=containers`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +console.log(`ETA: ${result.data.attributes.estimated_port_arrival_date}`); +``` + + +The response includes shipment details with the ETA: + +```json +{ + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "booking_number": "123456789", + "shipping_line_scac": "MAEU", + "shipping_line_name": "Maersk", + "estimated_port_arrival_date": "2023-04-15T14:00:00Z", + "port_of_lading_name": "Shanghai", + "port_of_lading_locode": "CNSHA", + "port_of_discharge_name": "Los Angeles", + "port_of_discharge_locode": "USLAX", + "vessel_name": "MAERSK SEVILLE", + "voyage": "243E" + // ...other attributes + }, + "relationships": { + "containers": { + "data": [ + { + "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", + "type": "container" + } + ] + } + } + }, + "included": [ + { + "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", + "type": "container", + "attributes": { + "number": "MAEU1234567", + "size_type": "40HC", + "status": "In Transit", + "location": "At Sea", + "last_free_day": "2023-04-22T23:59:59Z" + // ...other attributes + } + } + ] +} +``` + +## Step 5: Set Up Webhooks for Real-Time Updates (Event-Driven Method) + +**The Recommended Approach**: Instead of constantly polling for updates, use webhooks to receive notifications when events occur. + +1. Set up a webhook endpoint on your server to receive POST requests +2. Register your webhook with Terminal49: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["*"] + } + } + }' +``` + +```javascript +// Using fetch +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['*'] // Or specify individual events like 'shipment.estimated_arrival_change' + } + } + }) +}); + +const result = await response.json(); +console.log(`Webhook created with ID: ${result.data.id}`); +``` + + +3. Implement a webhook handler on your server: + + +```javascript +// Node.js with Express +const express = require('express'); +const app = express(); +app.use(express.json()); + +app.post('/webhook', (req, res) => { + const event = req.body; + + console.log(`Received event: ${event.data.id}`); + console.log(`Event type: ${event.type}`); + + // Handle different event types + if (event.type === 'shipment.estimated_arrival_change') { + const shipment = event.data; + console.log(`Shipment ${shipment.attributes.bill_of_lading_number} ETA updated to: ${shipment.attributes.estimated_port_arrival_date}`); + + // Update your database or notify stakeholders + // ... + } + + // Always respond with 200 OK to acknowledge receipt + res.status(200).send('Event received'); +}); + +app.listen(3000, () => { + console.log('Webhook server running on port 3000'); +}); +``` + +```php +// PHP webhook handler + +``` + + +### Example Webhook Event: ETA Change + +When a shipment's ETA changes, you'll receive a webhook event like this: + +```json +{ + "type": "shipment.estimated_arrival_change", + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "estimated_port_arrival_date": "2023-04-17T16:30:00Z", + "previous_estimated_port_arrival_date": "2023-04-15T14:00:00Z", + // ...other attributes + } + } +} +``` + + + **Testing Webhooks**: Use services like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) during development to easily receive and inspect webhook events. + + +## Next Steps + +Now that you've set up basic tracking and notifications, you can: + +1. **Explore More Workflows**: + - [Container Availability Monitoring](/api-docs/in-depth-guides/common-workflows#container-availability) + - [Terminal Hold Detection](/api-docs/in-depth-guides/common-workflows#terminal-holds) + - [Last Free Day Tracking](/api-docs/in-depth-guides/common-workflows#last-free-day) + +2. **Optimize Your Integration**: + - [Batch tracking multiple containers](/api-docs/in-depth-guides/json-api-guide#creating-resources) + - [Learn more about rate limits](/api-docs/in-depth-guides/rate-limiting) + - [Handle errors properly](/api-docs/in-depth-guides/error-handling) + +3. **Dive Deeper**: + - [Explore the full JSON:API implementation](/api-docs/in-depth-guides/json-api-guide) + - [Check out our Postman collection](https://app.getpostman.com/run-collection/4989da1ade6756b2f636) diff --git a/docs/api-docs/in-depth-guides/common-workflows.mdx b/docs/api-docs/in-depth-guides/common-workflows.mdx new file mode 100644 index 00000000..45e6f7df --- /dev/null +++ b/docs/api-docs/in-depth-guides/common-workflows.mdx @@ -0,0 +1,996 @@ +--- +title: Common Workflows +description: Learn how to implement the most common logistics workflows with Terminal49's API, including container tracking, ETA monitoring, and hold management. +icon: "arrows-split-up-and-left" +--- + +# Common Workflows + +This guide demonstrates how to implement the most common logistics workflows with Terminal49's API. For each workflow, we'll show two approaches: + +1. **Request-based approach**: Proactively querying the API +2. **Event-driven approach**: Receiving webhook notifications (recommended) + + + The event-driven webhook approach is recommended for production environments as it is more efficient and ensures you get real-time updates. + + +## Getting Container ETAs + +One of the most common needs is knowing when containers will arrive at their destination port. + +### Request-Based Approach + +You can query the shipment data to get the current ETA: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/shipments/7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using JavaScript +const shipmentId = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/shipments/${shipmentId}`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +const eta = result.data.attributes.estimated_port_arrival_date; +console.log(`Container will arrive on: ${new Date(eta).toLocaleString()}`); +``` + +```ruby +# Using Ruby +require 'httparty' + +shipment_id = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b' +headers = { + 'Authorization' => "Token YOUR_API_KEY", + 'Content-Type' => 'application/vnd.api+json' +} + +response = HTTParty.get( + "https://api.terminal49.com/v2/shipments/#{shipment_id}", + headers: headers +) + +data = JSON.parse(response.body) +eta = data['data']['attributes']['estimated_port_arrival_date'] +puts "Container will arrive on: #{eta}" +``` + + +### Event-Driven Approach (Recommended) + +Register a webhook to be notified when ETAs change: + +1. Register a webhook for ETA change events: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["shipment.estimated_arrival_change"] + } + } + }' +``` + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['shipment.estimated_arrival_change'] + } + } + }) +}); +``` + + +2. Implement a webhook handler to process ETA change notifications: + +```javascript +// Node.js with Express example +app.post('/webhook', (req, res) => { + const event = req.body; + + if (event.type === 'shipment.estimated_arrival_change') { + const shipment = event.data; + const bol = shipment.attributes.bill_of_lading_number; + const newEta = new Date(shipment.attributes.estimated_port_arrival_date); + const previousEta = new Date(shipment.attributes.previous_estimated_port_arrival_date); + + // Calculate the difference in days + const diffDays = Math.round((newEta - previousEta) / (1000 * 60 * 60 * 24)); + const direction = diffDays >= 0 ? 'delayed' : 'advanced'; + + console.log(`ETA Update: Shipment ${bol} has been ${direction} by ${Math.abs(diffDays)} days.`); + console.log(`New ETA: ${newEta.toLocaleString()}`); + + // Here you could: + // 1. Update your database + // 2. Notify stakeholders via email/SMS + // 3. Update planning systems + + // Example email notification + sendEmail({ + to: 'logistics@yourcompany.com', + subject: `ETA Update: Shipment ${bol} ${direction} by ${Math.abs(diffDays)} days`, + body: `The estimated arrival date for shipment ${bol} has changed from ${previousEta.toLocaleString()} to ${newEta.toLocaleString()}.` + }); + } + + res.status(200).send('Event received'); +}); +``` + + +## Container Availability Monitoring + +Knowing when a container is available for pickup is critical for drayage planning and avoiding detention fees. + +### Request-Based Approach + +Poll the container endpoint to check availability status: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/containers/e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using JavaScript +const containerId = 'e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/containers/${containerId}`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +const container = result.data; + +if (container.attributes.available_for_pickup) { + console.log(`Container ${container.attributes.number} is available for pickup!`); + console.log(`Last free day: ${container.attributes.last_free_day}`); +} else { + console.log(`Container ${container.attributes.number} is not yet available.`); + console.log(`Current status: ${container.attributes.status}`); +} +``` + + +### Event-Driven Approach (Recommended) + +Register a webhook to receive notifications when container availability changes: + +1. Register a webhook for container availability events: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["container.available_for_pickup", "container.availability_changed"] + } + } + }' +``` + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['container.available_for_pickup', 'container.availability_changed'] + } + } + }) +}); +``` + + +2. Implement a webhook handler for availability notifications: + +```javascript +// Node.js handler +app.post('/webhook', (req, res) => { + const event = req.body; + + if (event.type === 'container.available_for_pickup') { + const container = event.data; + const number = container.attributes.number; + const lastFreeDay = new Date(container.attributes.last_free_day); + const daysUntilFee = Math.ceil((lastFreeDay - new Date()) / (1000 * 60 * 60 * 24)); + + console.log(`Container ${number} is now available for pickup!`); + console.log(`Last free day: ${lastFreeDay.toLocaleDateString()}`); + console.log(`Days until storage fees: ${daysUntilFee}`); + + // Notify drayage team + sendNotification({ + team: 'drayage', + priority: 'high', + message: `Container ${number} is ready for pickup. Last free day: ${lastFreeDay.toLocaleDateString()}` + }); + + // Add to pickup schedule + addToPickupSchedule({ + containerNumber: number, + availableFrom: new Date(), + lastFreeDay: lastFreeDay, + terminal: container.attributes.terminal_name, + size: container.attributes.size_type + }); + } + + else if (event.type === 'container.availability_changed') { + const container = event.data; + + if (!container.attributes.available_for_pickup) { + console.log(`Container ${container.attributes.number} is no longer available for pickup!`); + console.log(`Current status: ${container.attributes.status}`); + + // Remove from pickup schedule or mark as unavailable + updatePickupSchedule({ + containerNumber: container.attributes.number, + available: false, + reason: container.attributes.status + }); + } + } + + res.status(200).send('Event received'); +}); +``` + + +## Terminal Hold Detection + +Detecting and addressing holds early can prevent delays in container pickup. + +### Request-Based Approach + +Query the container's holds to check for any active holds: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/containers/e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b?include=holds" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using JavaScript +const containerId = 'e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/containers/${containerId}?include=holds`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +const container = result.data; + +// Check if the container has holds +const hasHolds = container.relationships.holds && + container.relationships.holds.data && + container.relationships.holds.data.length > 0; + +if (hasHolds) { + console.log(`Container ${container.attributes.number} has holds!`); + + // Extract hold details from included data + const holds = result.included.filter(item => item.type === 'hold'); + + holds.forEach(hold => { + console.log(`Hold type: ${hold.attributes.type}`); + console.log(`Hold reason: ${hold.attributes.reason}`); + console.log(`Applied at: ${new Date(hold.attributes.created_at).toLocaleString()}`); + }); +} else { + console.log(`Container ${container.attributes.number} has no holds.`); +} +``` + + +### Event-Driven Approach (Recommended) + +Set up a webhook to be notified when a hold is applied or removed: + +1. Register a webhook for hold events: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["container.hold.created", "container.hold.removed"] + } + } + }' +``` + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['container.hold.created', 'container.hold.removed'] + } + } + }) +}); +``` + + +2. Implement a webhook handler for hold notifications: + +```javascript +// Node.js handler +app.post('/webhook', (req, res) => { + const event = req.body; + + if (event.type === 'container.hold.created') { + const hold = event.data; + const container = event.included.find(item => + item.type === 'container' && item.id === hold.relationships.container.data.id + ); + + const containerNumber = container.attributes.number; + const holdType = hold.attributes.type; + const holdReason = hold.attributes.reason; + + console.log(`New hold detected on container ${containerNumber}!`); + console.log(`Hold type: ${holdType}`); + console.log(`Hold reason: ${holdReason}`); + + // Different actions based on hold type + if (holdType === 'customs') { + // Notify customs broker + notifyCustomsBroker({ + containerNumber, + holdType, + holdReason, + priority: 'urgent' + }); + } else if (holdType === 'freight') { + // Notify accounting department + notifyAccounting({ + containerNumber, + holdType, + holdReason, + message: 'Payment required to release container' + }); + } else { + // Generic notification + notifyLogisticsTeam({ + containerNumber, + holdType, + holdReason + }); + } + } + + else if (event.type === 'container.hold.removed') { + const hold = event.data; + const container = event.included.find(item => + item.type === 'container' && item.id === hold.relationships.container.data.id + ); + + const containerNumber = container.attributes.number; + const holdType = hold.attributes.type; + + console.log(`Hold removed from container ${containerNumber}!`); + console.log(`Hold type: ${holdType} has been cleared`); + + // Notify relevant teams + notifyTeams({ + containerNumber, + message: `The ${holdType} hold has been removed. Container can now proceed.`, + priority: 'high' + }); + + // Check if container is now available for pickup + if (container.attributes.available_for_pickup) { + addToPickupSchedule({ + containerNumber, + availableFrom: new Date(), + terminal: container.attributes.terminal_name + }); + } + } + + res.status(200).send('Event received'); +}); +``` + + +## Last Free Day Tracking + +Tracking Last Free Day (LFD) is essential for avoiding storage fees. + +### Request-Based Approach + +Query containers and check their last free days: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/containers?filter[status]=available_for_pickup" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using JavaScript +const today = new Date(); +const threeDaysFromNow = new Date(today); +threeDaysFromNow.setDate(today.getDate() + 3); + +const response = await fetch('https://api.terminal49.com/v2/containers?filter[status]=available_for_pickup', { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +const containers = result.data; + +// Find containers with upcoming last free days +const upcomingLFDs = containers.filter(container => { + const lastFreeDay = new Date(container.attributes.last_free_day); + return lastFreeDay > today && lastFreeDay <= threeDaysFromNow; +}); + +if (upcomingLFDs.length > 0) { + console.log(`Found ${upcomingLFDs.length} containers with LFD in the next 3 days:`); + + upcomingLFDs.forEach(container => { + const lfd = new Date(container.attributes.last_free_day); + const daysRemaining = Math.ceil((lfd - today) / (1000 * 60 * 60 * 24)); + + console.log(`Container ${container.attributes.number}: LFD in ${daysRemaining} days (${lfd.toLocaleDateString()})`); + console.log(`Terminal: ${container.attributes.terminal_name}`); + console.log(`Size: ${container.attributes.size_type}`); + console.log('---'); + }); + + // Prioritize containers with the earliest LFDs + upcomingLFDs.sort((a, b) => + new Date(a.attributes.last_free_day) - new Date(b.attributes.last_free_day) + ); + + // Create pickup schedule based on LFD + createPickupSchedule(upcomingLFDs); +} +``` + + +### Event-Driven Approach (Recommended) + +Set up a webhook for last free day changes and implement an automated daily check: + +1. Register a webhook for last free day updates: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["container.last_free_day_change"] + } + } + }' +``` + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['container.last_free_day_change'] + } + } + }) +}); +``` + + +2. Implement a webhook handler for LFD changes and daily checks: + +```javascript +// Node.js handler for webhook events +app.post('/webhook', (req, res) => { + const event = req.body; + + if (event.type === 'container.last_free_day_change') { + const container = event.data; + const newLFD = new Date(container.attributes.last_free_day); + const previousLFD = container.attributes.previous_last_free_day ? + new Date(container.attributes.previous_last_free_day) : null; + + console.log(`Last Free Day updated for container ${container.attributes.number}`); + + if (previousLFD) { + const diffDays = Math.round((newLFD - previousLFD) / (1000 * 60 * 60 * 24)); + const direction = diffDays >= 0 ? 'extended' : 'reduced'; + + console.log(`LFD has been ${direction} by ${Math.abs(diffDays)} days`); + console.log(`Old LFD: ${previousLFD.toLocaleDateString()}`); + console.log(`New LFD: ${newLFD.toLocaleDateString()}`); + } else { + console.log(`LFD set to ${newLFD.toLocaleDateString()}`); + } + + // Update pickup schedule + updatePickupPriority({ + containerNumber: container.attributes.number, + lastFreeDay: newLFD, + terminal: container.attributes.terminal_name + }); + + // Calculate days remaining + const today = new Date(); + const daysRemaining = Math.ceil((newLFD - today) / (1000 * 60 * 60 * 24)); + + // Send notifications based on urgency + if (daysRemaining <= 1) { + // URGENT: Last free day is today or tomorrow + sendUrgentNotification({ + containerNumber: container.attributes.number, + message: `URGENT: Last free day is ${daysRemaining === 0 ? 'TODAY' : 'TOMORROW'}`, + terminal: container.attributes.terminal_name + }); + } else if (daysRemaining <= 3) { + // High priority: Last free day within 3 days + sendPriorityNotification({ + containerNumber: container.attributes.number, + message: `Last free day in ${daysRemaining} days`, + terminal: container.attributes.terminal_name + }); + } + } + + res.status(200).send('Event received'); +}); + +// Daily scheduled check function (run via cron job) +async function checkUpcomingLastFreeDays() { + const today = new Date(); + + try { + const response = await fetch('https://api.terminal49.com/v2/containers?filter[status]=available_for_pickup', { + headers: { + 'Authorization': `Token ${process.env.API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } + }); + + const result = await response.json(); + const containers = result.data; + + // Group containers by days until last free day + const lfdGroups = { + today: [], + tomorrow: [], + twoDays: [], + threeDays: [], + fourToSeven: [], + overOneWeek: [] + }; + + containers.forEach(container => { + if (!container.attributes.last_free_day) return; + + const lfd = new Date(container.attributes.last_free_day); + const daysRemaining = Math.ceil((lfd - today) / (1000 * 60 * 60 * 24)); + + if (daysRemaining <= 0) { + lfdGroups.today.push(container); + } else if (daysRemaining === 1) { + lfdGroups.tomorrow.push(container); + } else if (daysRemaining === 2) { + lfdGroups.twoDays.push(container); + } else if (daysRemaining === 3) { + lfdGroups.threeDays.push(container); + } else if (daysRemaining <= 7) { + lfdGroups.fourToSeven.push(container); + } else { + lfdGroups.overOneWeek.push(container); + } + }); + + // Send daily summary report + sendDailyLFDReport({ + urgent: [...lfdGroups.today, ...lfdGroups.tomorrow], + priority: [...lfdGroups.twoDays, ...lfdGroups.threeDays], + upcoming: lfdGroups.fourToSeven, + later: lfdGroups.overOneWeek + }); + + // Take immediate action for urgent containers + if (lfdGroups.today.length > 0) { + lfdGroups.today.forEach(container => { + sendUrgentPickupRequest({ + containerNumber: container.attributes.number, + terminal: container.attributes.terminal_name, + message: 'URGENT: Last free day is TODAY. Immediate pickup required.' + }); + }); + } + } catch (error) { + console.error('Error checking last free days:', error); + } +} +``` + +## Vessel Arrival Monitoring + +Tracking vessel arrivals helps with resource planning and customer updates. + +### Request-Based Approach + +Query the shipment for its vessel and voyage information: + + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/shipments/7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using JavaScript +const shipmentId = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/shipments/${shipmentId}`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); + +const result = await response.json(); +const shipment = result.data; + +console.log(`Vessel: ${shipment.attributes.vessel_name}`); +console.log(`Voyage: ${shipment.attributes.voyage}`); +console.log(`Estimated arrival: ${new Date(shipment.attributes.estimated_port_arrival_date).toLocaleString()}`); +console.log(`Port of discharge: ${shipment.attributes.port_of_discharge_name}`); + +// Calculate days until arrival +const today = new Date(); +const arrivalDate = new Date(shipment.attributes.estimated_port_arrival_date); +const daysUntilArrival = Math.ceil((arrivalDate - today) / (1000 * 60 * 60 * 24)); + +console.log(`Days until arrival: ${daysUntilArrival}`); +``` + + +### Event-Driven Approach (Recommended) + +Set up webhooks to be notified of vessel arrival and changes: + +1. Register a webhook for vessel events: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": [ + "shipment.vessel_arrived", + "shipment.vessel_name_change", + "shipment.estimated_arrival_change" + ] + } + } + }' +``` + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: [ + 'shipment.vessel_arrived', + 'shipment.vessel_name_change', + 'shipment.estimated_arrival_change' + ] + } + } + }) +}); +``` + + +2. Implement a webhook handler for vessel events: + +```javascript +// Node.js handler +app.post('/webhook', (req, res) => { + const event = req.body; + const shipment = event.data; + + if (event.type === 'shipment.vessel_arrived') { + console.log(`Vessel ${shipment.attributes.vessel_name} has arrived at ${shipment.attributes.port_of_discharge_name}!`); + + // Notify stakeholders of vessel arrival + notifyCustomer({ + shipmentId: shipment.id, + bolNumber: shipment.attributes.bill_of_lading_number, + message: `Your shipment has arrived at ${shipment.attributes.port_of_discharge_name} on vessel ${shipment.attributes.vessel_name}.`, + estimatedAvailability: 'Typically 2-3 days after vessel arrival' + }); + + // Begin monitoring container availability + startAvailabilityMonitoring(shipment.id); + } + + else if (event.type === 'shipment.vessel_name_change') { + console.log(`Vessel change for shipment ${shipment.attributes.bill_of_lading_number}!`); + console.log(`New vessel: ${shipment.attributes.vessel_name}`); + console.log(`New voyage: ${shipment.attributes.voyage}`); + + // Notify operations team + notifyOperations({ + shipmentId: shipment.id, + bolNumber: shipment.attributes.bill_of_lading_number, + message: `Vessel change: Now on ${shipment.attributes.vessel_name} voyage ${shipment.attributes.voyage}`, + newETA: shipment.attributes.estimated_port_arrival_date + }); + } + + else if (event.type === 'shipment.estimated_arrival_change') { + const newArrival = new Date(shipment.attributes.estimated_port_arrival_date); + const previousArrival = shipment.attributes.previous_estimated_port_arrival_date ? + new Date(shipment.attributes.previous_estimated_port_arrival_date) : null; + + if (previousArrival) { + const diffDays = Math.round((newArrival - previousArrival) / (1000 * 60 * 60 * 24)); + const direction = diffDays >= 0 ? 'delayed' : 'advanced'; + + console.log(`Vessel ${shipment.attributes.vessel_name} has been ${direction} by ${Math.abs(diffDays)} days`); + console.log(`New ETA: ${newArrival.toLocaleDateString()}`); + + // Update pick-up planning based on new ETA + updatePickupPlanning({ + shipmentId: shipment.id, + bolNumber: shipment.attributes.bill_of_lading_number, + newETA: newArrival, + diffDays + }); + + // Notify customer of significant changes (more than 1 day) + if (Math.abs(diffDays) > 1) { + notifyCustomer({ + shipmentId: shipment.id, + bolNumber: shipment.attributes.bill_of_lading_number, + message: `Your shipment on ${shipment.attributes.vessel_name} has been ${direction} by ${Math.abs(diffDays)} days. New estimated arrival: ${newArrival.toLocaleDateString()}`, + previousETA: previousArrival.toLocaleDateString(), + newETA: newArrival.toLocaleDateString() + }); + } + } + } + + res.status(200).send('Event received'); +}); +``` + +## Using Webhooks for Multiple Workflows + +For production environments, you'll likely want to track multiple event types with a single webhook. Here's how to set it up: + +1. Register a webhook for all relevant event types: + +```javascript +// Using JavaScript +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: [ + // Shipment events + 'shipment.estimated_arrival_change', + 'shipment.vessel_arrived', + 'shipment.vessel_name_change', + + // Container events + 'container.available_for_pickup', + 'container.availability_changed', + 'container.last_free_day_change', + 'container.hold.created', + 'container.hold.removed', + + // Tracking request events + 'tracking_request.succeeded', + 'tracking_request.failed' + ] + } + } + }) +}); +``` + +2. Implement a comprehensive webhook handler: + +```javascript +// Node.js handler with comprehensive event routing +app.post('/webhook', (req, res) => { + const event = req.body; + console.log(`Received event: ${event.type}`); + + // Route the event to the appropriate handler based on event type + switch (event.type) { + // Shipment events + case 'shipment.estimated_arrival_change': + handleETAChange(event); + break; + case 'shipment.vessel_arrived': + handleVesselArrival(event); + break; + case 'shipment.vessel_name_change': + handleVesselChange(event); + break; + + // Container events + case 'container.available_for_pickup': + handleContainerAvailable(event); + break; + case 'container.availability_changed': + handleAvailabilityChange(event); + break; + case 'container.last_free_day_change': + handleLFDChange(event); + break; + case 'container.hold.created': + handleHoldCreated(event); + break; + case 'container.hold.removed': + handleHoldRemoved(event); + break; + + // Tracking request events + case 'tracking_request.succeeded': + handleTrackingSuccess(event); + break; + case 'tracking_request.failed': + handleTrackingFailure(event); + break; + + default: + console.log(`Unhandled event type: ${event.type}`); + } + + // Always acknowledge receipt of the webhook + res.status(200).send('Event received'); +}); + +// Individual handler functions (implementation details omitted for brevity) +function handleETAChange(event) { + // Implementation for ETA changes +} + +function handleVesselArrival(event) { + // Implementation for vessel arrivals +} + +// And so on for each event type... +``` + +## Next Steps + +Now that you understand the common workflows, you can: + +1. **Customize these examples** for your specific business needs +2. **Combine multiple workflows** to create comprehensive logistics applications +3. **Explore our API reference** for more details on available endpoints and parameters +4. **Check out our [JSON:API Guide](/api-docs/in-depth-guides/json-api-guide)** to learn how to efficiently request related resources diff --git a/docs/docs.json b/docs/docs.json index 7242feae..0b28dc77 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -19,32 +19,29 @@ "home" ] }, - { - "group": "Getting Started", - "pages": [ - "api-docs/getting-started/start-here", - "api-docs/getting-started/capabilities-overview", - "api-docs/getting-started/tracking-shipments-and-containers", - "api-docs/getting-started/list-shipments-and-containers", - "api-docs/getting-started/receive-status-updates" - ] - }, { "group": "In Depth Guides", "pages": [ - "api-docs/in-depth-guides/quickstart", + "api-docs/in-depth-guides/api-quick-start", + "api-docs/in-depth-guides/common-workflows", "api-docs/in-depth-guides/json-api-guide", "api-docs/in-depth-guides/authentication", "api-docs/in-depth-guides/error-handling", + "api-docs/in-depth-guides/rate-limiting", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", + "api-docs/getting-started/capabilities-overview", + "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/in-depth-guides/including-resources", "api-docs/in-depth-guides/adding-customer", "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/rail-integration-guide", + "api-docs/in-depth-guides/quickstart" ] }, { @@ -226,6 +223,10 @@ { "source": "/api-docs/getting-started/recieve-status-updates", "destination": "/api-docs/getting-started/receive-status-updates" + }, + { + "source": "/api-docs/getting-started/start-here", + "destination": "/api-docs/in-depth-guides/api-quick-start" } ] } From 418254e45af7fc48f9a73b46da4dd9cc37273999 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:13:27 -0700 Subject: [PATCH 22/42] Rename API Quick Start Guide to Quickstart --- docs/API-Documentation-Improvement-Tracker.md | 17 + docs/api-docs/in-depth-guides/quickstart.mdx | 488 +++++++++++++++--- docs/docs.json | 22 +- 3 files changed, 445 insertions(+), 82 deletions(-) diff --git a/docs/API-Documentation-Improvement-Tracker.md b/docs/API-Documentation-Improvement-Tracker.md index d26506ec..8298f854 100644 --- a/docs/API-Documentation-Improvement-Tracker.md +++ b/docs/API-Documentation-Improvement-Tracker.md @@ -14,3 +14,20 @@ - Develop pagination examples and best practices - Add more language-specific code examples (Ruby, Go, etc.) - Improve OpenAPI specification + +## Completed Updates + +- [x] Improved introduction and setup guidance in `docs/api-docs/getting-started/start-here.mdx` +- [x] Enhanced capabilities overview in `docs/api-docs/getting-started/capabilities-overview.mdx` +- [x] Created JSON:API guide in `docs/api-docs/in-depth-guides/json-api-guide.mdx` +- [x] Updated tracking request documentation in `docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx` +- [x] Created "Polling vs. Webhooks" comparison guide in `docs/api-docs/in-depth-guides/polling-vs-webhooks.mdx` +- [x] Created Webhook implementation guide in `docs/api-docs/in-depth-guides/webhooks.mdx` +- [x] Created Authentication guide in `docs/api-docs/in-depth-guides/authentication.mdx` +- [x] Fixed MDX parsing errors to ensure compatibility with Mintlify +- [x] Developed error handling documentation in `docs/api-docs/in-depth-guides/error-handling.mdx` +- [x] Created rate limiting guide in `docs/api-docs/in-depth-guides/rate-limiting.mdx` +- [x] Created Use Cases guide in `docs/api-docs/in-depth-guides/use-cases.mdx` +- [x] Created comprehensive Quickstart guide in `docs/api-docs/in-depth-guides/quickstart.mdx` +- [x] Enhanced JSON:API guide with practical examples +- [x] Reorganized documentation structure diff --git a/docs/api-docs/in-depth-guides/quickstart.mdx b/docs/api-docs/in-depth-guides/quickstart.mdx index c377a5db..1db17be5 100644 --- a/docs/api-docs/in-depth-guides/quickstart.mdx +++ b/docs/api-docs/in-depth-guides/quickstart.mdx @@ -1,125 +1,463 @@ --- -title: Quick Start Guide -og:title: Quickstart Guide | Terminal49 API Documentation -og:description: Get started quickly with Terminal49's API using this comprehensive guide. Simplify your shipping workflows today. +title: Quickstart +description: Get up and running with Terminal49's API in minutes - track containers, receive updates, and integrate with your systems. +icon: "rocket" --- -## Before You Begin +# Quickstart -You'll need a four things to get started. +This guide will help you get started with Terminal49's API by walking through the most common workflow: tracking a shipment and receiving status updates. You'll learn both how to request data and how to receive real-time updates via webhooks. -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. +## Prerequisites -## Track a Shipment +Before you begin, you'll need: -You can try this using the embedded request maker below, or using Postman. +1. **A Terminal49 account** - [Sign up here](https://app.terminal49.com/register) if you don't have one +2. **An API key** - Find or create one in your [developer portal](https://app.terminal49.com/developers/api-keys) +3. **A tracking number** to monitor: + - Bill of Lading (BOL) number, Booking number, or Container number + - The [SCAC](https://en.wikipedia.org/wiki/Standard_Carrier_Alpha_Code) of the carrier (e.g., MAEU for Maersk) -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) + + Don't have a tracking number? Use our test data: `TEST-TR-SUCCEEDED` with SCAC `TEST` for immediate results. + -Note that you can also access sample code, include a cURL template, by clicking the "Code Generation" tab in the Request Maker. +## Step 1: Authentication -```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}" -} +All API requests require authentication using your API key. Include it in the Authorization header with the prefix `Token`: + +```bash +curl -X GET "https://api.terminal49.com/v2/shipments" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + + +```javascript +// Node.js with fetch +const headers = { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' +}; + +const response = await fetch('https://api.terminal49.com/v2/shipments', { headers }); ``` -## Check Your Tracking Request Succeeded +```php +// PHP with cURL +$curl = curl_init(); +curl_setopt_array($curl, [ + CURLOPT_URL => "https://api.terminal49.com/v2/shipments", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + "Authorization: Token YOUR_API_KEY", + "Content-Type: application/vnd.api+json" + ], +]); +$response = curl_exec($curl); +``` -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. +```ruby +# Ruby with HTTParty +require 'httparty' +headers = { + 'Authorization' => "Token YOUR_API_KEY", + 'Content-Type' => 'application/vnd.api+json' +} +response = HTTParty.get("https://api.terminal49.com/v2/shipments", headers: headers) +``` + + +## Step 2: Create a Tracking Request + +The first step to track a shipment is to create a tracking request. This tells Terminal49 which shipment you want to track. + +**Workflow Example: Start tracking a container to get its ETA** + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + }' +``` -> ### 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 if you have persistent issues. +```javascript +// Using fetch (Node.js/Browser) +const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'tracking_request', + attributes: { + request_type: 'bill_of_lading', + request_number: 'MAEU9736478', + scac: 'MAEU' + } + } + }) +}); + +const result = await response.json(); +console.log(result); +``` -** Try it below. Click "Headers" and replace `` with your API key.** +```php +// Using PHP +$curl = curl_init(); +$payload = json_encode([ + 'data' => [ + 'type' => 'tracking_request', + 'attributes' => [ + 'request_type' => 'bill_of_lading', + 'request_number' => 'MAEU9736478', + 'scac' => 'MAEU' + ] + ] +]); + +curl_setopt_array($curl, [ + CURLOPT_URL => "https://api.terminal49.com/v2/tracking_requests", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HTTPHEADER => [ + "Authorization: Token YOUR_API_KEY", + "Content-Type: application/vnd.api+json" + ], +]); + +$response = curl_exec($curl); +$result = json_decode($response, true); +print_r($result); +``` + +The response will include a tracking request ID and status: -```json http +```json { - "method": "get", - "url": "https://api.terminal49.com/v2/tracking_requests", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" + "data": { + "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", + "created_at": "2023-03-16T18:35:42Z", + "updated_at": "2023-03-16T18:35:42Z" + }, + "relationships": { + "tracked_object": { + "data": null + } + } } } ``` -## List your Tracked Shipments + + The tracking request starts with status `pending`. Terminal49 will process it asynchronously, and it will change to `succeeded` or `failed`. + -If your tracking request was successful, you will now be able to list your tracked shipments. +## Step 3: Check Tracking Request Status (Polling Method) -**Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** +To check if your tracking request has completed, you can poll the tracking request: -Sometimes it may take a while for the tracking request to show up, but usually no more than a few minutes. + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/tracking_requests/b1f30140-c9cb-4354-b8e3-3d92a76289cf" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` + +```javascript +// Using fetch +const trackingRequestId = 'b1f30140-c9cb-4354-b8e3-3d92a76289cf'; +const response = await fetch(`https://api.terminal49.com/v2/tracking_requests/${trackingRequestId}`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } +}); -If you had trouble adding your first shipment, try adding a few more. +const result = await response.json(); +console.log(`Tracking request status: ${result.data.attributes.status}`); +``` + -```json http +When the request succeeds, it will include a relationship to the newly created shipment: + +```json { - "method": "get", - "url": "https://api.terminal49.com/v2/shipments", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" + "data": { + "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "succeeded", + "created_at": "2023-03-16T18:35:42Z", + "updated_at": "2023-03-16T18:36:10Z" + }, + "relationships": { + "tracked_object": { + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment" + } + } + } } } ``` -## List all your Tracked Containers +## Step 4: Get Shipment Details and ETA -You can also list out all of your containers, if you'd like to track at that level. +Once the tracking request has succeeded, you can retrieve the shipment and its estimated arrival time: -Try it after replacing `` with your API key. + +```bash +# Using cURL +curl -X GET "https://api.terminal49.com/v2/shipments/7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b?include=containers" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" +``` -```json http -{ - "method": "get", - "url": "https://api.terminal49.com/v2/containers", - "headers": { - "Content-Type": "application/vnd.api+json", - "Authorization": "Token YOUR_API_KEY" +```javascript +// Using fetch +const shipmentId = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b'; +const response = await fetch(`https://api.terminal49.com/v2/shipments/${shipmentId}?include=containers`, { + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' } +}); + +const result = await response.json(); +console.log(`ETA: ${result.data.attributes.estimated_port_arrival_date}`); +``` + + +The response includes shipment details with the ETA: + +```json +{ + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "booking_number": "123456789", + "shipping_line_scac": "MAEU", + "shipping_line_name": "Maersk", + "estimated_port_arrival_date": "2023-04-15T14:00:00Z", + "port_of_lading_name": "Shanghai", + "port_of_lading_locode": "CNSHA", + "port_of_discharge_name": "Los Angeles", + "port_of_discharge_locode": "USLAX", + "vessel_name": "MAERSK SEVILLE", + "voyage": "243E" + // ...other attributes + }, + "relationships": { + "containers": { + "data": [ + { + "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", + "type": "container" + } + ] + } + } + }, + "included": [ + { + "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", + "type": "container", + "attributes": { + "number": "MAEU1234567", + "size_type": "40HC", + "status": "In Transit", + "location": "At Sea", + "last_free_day": "2023-04-22T23:59:59Z" + // ...other attributes + } + } + ] } ``` +## Step 5: Set Up Webhooks for Real-Time Updates (Event-Driven Method) + +**The Recommended Approach**: Instead of constantly polling for updates, use webhooks to receive notifications when events occur. + +1. Set up a webhook endpoint on your server to receive POST requests +2. Register your webhook with Terminal49: + + +```bash +# Using cURL +curl -X POST "https://api.terminal49.com/v2/webhooks" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://your-server.com/webhook", + "active": true, + "events": ["*"] + } + } + }' +``` + +```javascript +// Using fetch +const response = await fetch('https://api.terminal49.com/v2/webhooks', { + method: 'POST', + headers: { + 'Authorization': `Token ${YOUR_API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'webhook', + attributes: { + url: 'https://your-server.com/webhook', + active: true, + events: ['*'] // Or specify individual events like 'shipment.estimated_arrival_change' + } + } + }) +}); + +const result = await response.json(); +console.log(`Webhook created with ID: ${result.data.id}`); +``` + + +3. Implement a webhook handler on your server: + + +```javascript +// Node.js with Express +const express = require('express'); +const app = express(); +app.use(express.json()); + +app.post('/webhook', (req, res) => { + const event = req.body; -## Listening for Updates with Webhooks + console.log(`Received event: ${event.data.id}`); + console.log(`Event type: ${event.type}`); -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. + // Handle different event types + if (event.type === 'shipment.estimated_arrival_change') { + const shipment = event.data; + console.log(`Shipment ${shipment.attributes.bill_of_lading_number} ETA updated to: ${shipment.attributes.estimated_port_arrival_date}`); -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. + // Update your database or notify stakeholders + // ... + } + + // Always respond with 200 OK to acknowledge receipt + res.status(200).send('Event received'); +}); + +app.listen(3000, () => { + console.log('Webhook server running on port 3000'); +}); +``` -** Try it below. Click "Headers" and replace YOUR_API_KEY with your API key.** +```php +// PHP webhook handler + +``` + + +### Example Webhook Event: ETA Change + +When a shipment's ETA changes, you'll receive a webhook event like this: + +```json { - "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}" + "type": "shipment.estimated_arrival_change", + "data": { + "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "estimated_port_arrival_date": "2023-04-17T16:30:00Z", + "previous_estimated_port_arrival_date": "2023-04-15T14:00:00Z", + // ...other attributes + } + } } -``` \ No newline at end of file +``` + + + **Testing Webhooks**: Use services like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) during development to easily receive and inspect webhook events. + + +## Next Steps + +Now that you've set up basic tracking and notifications, you can: + +1. **Explore Use Cases**: + - [Container Availability Monitoring](/api-docs/in-depth-guides/use-cases#container-availability) + - [Terminal Hold Detection](/api-docs/in-depth-guides/use-cases#terminal-holds) + - [Last Free Day Tracking](/api-docs/in-depth-guides/use-cases#last-free-day) + +2. **Optimize Your Integration**: + - [Batch tracking multiple containers](/api-docs/in-depth-guides/json-api-guide#creating-resources) + - [Learn more about rate limits](/api-docs/in-depth-guides/rate-limiting) + - [Handle errors properly](/api-docs/in-depth-guides/error-handling) + +3. **Dive Deeper**: + - [Explore the full JSON:API implementation](/api-docs/in-depth-guides/json-api-guide) + - [Check out our Postman collection](https://app.getpostman.com/run-collection/4989da1ade6756b2f636) diff --git a/docs/docs.json b/docs/docs.json index 0b28dc77..c6323027 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -20,17 +20,22 @@ ] }, { - "group": "In Depth Guides", + "group": "Overview", "pages": [ - "api-docs/in-depth-guides/api-quick-start", - "api-docs/in-depth-guides/common-workflows", + "api-docs/in-depth-guides/quickstart", + "api-docs/getting-started/capabilities-overview" + ] + }, + { + "group": "Guides", + "pages": [ + "api-docs/in-depth-guides/use-cases", "api-docs/in-depth-guides/json-api-guide", "api-docs/in-depth-guides/authentication", "api-docs/in-depth-guides/error-handling", "api-docs/in-depth-guides/rate-limiting", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", - "api-docs/getting-started/capabilities-overview", "api-docs/getting-started/tracking-shipments-and-containers", "api-docs/getting-started/list-shipments-and-containers", "api-docs/getting-started/receive-status-updates", @@ -40,8 +45,7 @@ "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/quickstart" + "api-docs/in-depth-guides/rail-integration-guide" ] }, { @@ -226,7 +230,11 @@ }, { "source": "/api-docs/getting-started/start-here", - "destination": "/api-docs/in-depth-guides/api-quick-start" + "destination": "/api-docs/in-depth-guides/quickstart" + }, + { + "source": "/api-docs/in-depth-guides/api-quick-start", + "destination": "/api-docs/in-depth-guides/quickstart" } ] } From f983632f10b058329ad0e6f9068944bf8f449894 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:21:49 -0700 Subject: [PATCH 23/42] Add documentation navigation structure plan --- docs/Documentation-Navigation-Structure.md | 158 +++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/Documentation-Navigation-Structure.md diff --git a/docs/Documentation-Navigation-Structure.md b/docs/Documentation-Navigation-Structure.md new file mode 100644 index 00000000..c0fada8b --- /dev/null +++ b/docs/Documentation-Navigation-Structure.md @@ -0,0 +1,158 @@ +# Terminal49 Documentation Navigation Structure + +This document outlines the proposed navigation structure for Terminal49's documentation portal. The documentation is organized into three main tabs with various sections within each tab. + +## Top-Level Navigation + +The documentation is divided into three main sections: + +| Tab | Purpose | +|-----|---------| +| **Integration Guide** | Focuses on use cases, quickstart guides, and conceptual information about integrating with Terminal49. | +| **API Reference** | Contains detailed technical documentation for specific API endpoints and operations. | +| **DataSync** | Covers Terminal49's data warehouse integration solution. | + +## Tab 1: Integration Guide + +This section helps users understand how to integrate Terminal49 into their systems and workflows. + +### Groups and Pages + +#### Group: [blank] +- home + +#### Group: Overview +- Quickstart (`api-docs/in-depth-guides/quickstart`) +- Terminal49 API Capabilities Overview (`api-docs/getting-started/capabilities-overview`) + +#### Group: Guides +- Use Cases (`api-docs/in-depth-guides/use-cases`) +- JSON:API Guide (`api-docs/in-depth-guides/json-api-guide`) +- Authentication (`api-docs/in-depth-guides/authentication`) +- Error Handling (`api-docs/in-depth-guides/error-handling`) +- Rate Limiting (`api-docs/in-depth-guides/rate-limiting`) +- Polling vs. Webhooks (`api-docs/in-depth-guides/polling-vs-webhooks`) +- Webhooks (`api-docs/in-depth-guides/webhooks`) +- Tracking Shipments and Containers (`api-docs/getting-started/tracking-shipments-and-containers`) +- List Shipments and Containers (`api-docs/getting-started/list-shipments-and-containers`) +- Receive Status Updates (`api-docs/getting-started/receive-status-updates`) +- Including Resources (`api-docs/in-depth-guides/including-resources`) +- Adding Customer (`api-docs/in-depth-guides/adding-customer`) +- Tracking Request Lifecycle (`api-docs/in-depth-guides/tracking-request-lifecycle`) +- Event Timestamps (`api-docs/in-depth-guides/event-timestamps`) +- Terminal49 Map (`api-docs/in-depth-guides/terminal49-map`) +- Terminal49 Widget (`api-docs/in-depth-guides/terminal49-widget`) +- Rail Integration Guide (`api-docs/in-depth-guides/rail-integration-guide`) + +#### Group: Useful Info +- API Data Sources & Availability (`api-docs/useful-info/api-data-sources-availability`) +- Pricing (`api-docs/useful-info/pricing`) +- Test Numbers (`api-docs/useful-info/test-numbers`) +- Tracking Request Retrying (`api-docs/useful-info/tracking-request-retrying`) +- Webhook Events Examples (`api-docs/useful-info/webhook-events-examples`) + +## Tab 2: API Reference + +This section provides detailed technical documentation for specific API endpoints. + +### Groups and Pages + +#### Group: Shipments +- List Shipments (`api-docs/api-reference/shipments/list-shipments`) +- Get a Shipment (`api-docs/api-reference/shipments/get-a-shipment`) +- Edit a Shipment (`api-docs/api-reference/shipments/edit-a-shipment`) +- Stop Tracking Shipment (`api-docs/api-reference/shipments/stop-tracking-shipment`) +- Resume Tracking Shipment (`api-docs/api-reference/shipments/resume-tracking-shipment`) + +#### Group: Tracking Requests +- List Tracking Requests (`api-docs/api-reference/tracking-requests/list-tracking-requests`) +- Create a Tracking Request (`api-docs/api-reference/tracking-requests/create-a-tracking-request`) +- Get a Single Tracking Request (`api-docs/api-reference/tracking-requests/get-a-single-tracking-request`) +- Edit a Tracking Request (`api-docs/api-reference/tracking-requests/edit-a-tracking-request`) + +#### Group: Webhooks +- Get Single Webhook (`api-docs/api-reference/webhooks/get-single-webhook`) +- Delete a Webhook (`api-docs/api-reference/webhooks/delete-a-webhook`) +- Edit a Webhook (`api-docs/api-reference/webhooks/edit-a-webhook`) +- List Webhooks (`api-docs/api-reference/webhooks/list-webhooks`) +- Create a Webhook (`api-docs/api-reference/webhooks/create-a-webhook`) +- List Webhook IPs (`api-docs/api-reference/webhooks/list-webhook-ips`) + +#### Group: Webhook Notifications +- Get a Single Webhook Notification (`api-docs/api-reference/webhook-notifications/get-a-single-webhook-notification`) +- List Webhook Notifications (`api-docs/api-reference/webhook-notifications/list-webhook-notifications`) +- Get Webhook Notification Payload Examples (`api-docs/api-reference/webhook-notifications/get-webhook-notification-payload-examples`) + +#### Group: Containers +- List Containers (`api-docs/api-reference/containers/list-containers`) +- Edit a Container (`api-docs/api-reference/containers/edit-a-container`) +- Get a Container (`api-docs/api-reference/containers/get-a-container`) +- Get a Container's Raw Events (`api-docs/api-reference/containers/get-a-containers-raw-events`) +- Get a Container's Transport Events (`api-docs/api-reference/containers/get-a-containers-transport-events`) + +#### Group: Shipping Lines +- Shipping Lines (`api-docs/api-reference/shipping-lines/shipping-lines`) +- Get a Single Shipping Line (`api-docs/api-reference/shipping-lines/get-a-single-shipping-line`) + +#### Group: Metro Areas +- Get a Metro Area Using the UNLOCODE or the ID (`api-docs/api-reference/metro-areas/get-a-metro-area-using-the-unlocode-or-the-id`) + +#### Group: Ports +- Get a Port Using the LOCODE or the ID (`api-docs/api-reference/ports/get-a-port-using-the-locode-or-the-id`) + +#### Group: Vessels +- Get a Vessel Using the ID (`api-docs/api-reference/vessels/get-a-vessel-using-the-id`) +- Get a Vessel Using the IMO (`api-docs/api-reference/vessels/get-a-vessel-using-the-imo`) + +#### Group: Terminals +- Get a Terminal Using the ID (`api-docs/api-reference/terminals/get-a-terminal-using-the-id`) + +#### Group: Parties +- List Parties (`api-docs/api-reference/parties/list-parties`) +- Create a Party (`api-docs/api-reference/parties/create-a-party`) +- Get a Party (`api-docs/api-reference/parties/get-a-party`) +- Edit a Party (`api-docs/api-reference/parties/edit-a-party`) + +## Tab 3: DataSync + +This section covers Terminal49's data warehouse integration solution. + +### Groups and Pages + +#### Group: [blank] +- home (`datasync/home`) + +#### Group: [blank] +- Overview (`datasync/overview`) +- Supported Destinations (`datasync/supported-destinations`) + +#### Group: Table Properties +- Containers Rail (`datasync/table-properties/containers_rail`) +- Shipments (`datasync/table-properties/shipments`) +- Tracking Requests (`datasync/table-properties/tracking-requests`) +- Transport Events (`datasync/table-properties/transport-events`) +- Transfer Status (`datasync/table-properties/transfer-status`) +- Containers (`datasync/table-properties/containers`) + +## Notes on Structure + +1. **Integration Guide to API Reference Flow**: + - The Integration Guide introduces concepts, use cases, and patterns. + - Users then move to API Reference for specific implementation details. + - This creates a natural learning progression from high-level to specific details. + +2. **Use Case Focus**: + - The Use Cases document in the Integration Guide helps users understand real-world applications. + - Each use case can link to specific API endpoints in the Reference section. + +3. **DataSync Integration**: + - The DataSync tab provides a separate but related product offering. + - Could potentially add cross-references between API use cases and when DataSync might be more appropriate. + +4. **Naming Conventions**: + - Groups use clear, descriptive names. + - Page titles are consistently formatted. + +5. **Potential Improvements**: + - Consider organizing the Guides section into subcategories as it grows. + - Group related guides (e.g., authentication, error handling, and rate limiting might form a "Best Practices" subgroup). From 7935c6540d63c36fd448dd2bb7d8c060bb84df37 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:24:27 -0700 Subject: [PATCH 24/42] Update documentation structure: move API-specific concepts to API Reference tab --- docs/Documentation-Navigation-Structure.md | 31 ++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/Documentation-Navigation-Structure.md b/docs/Documentation-Navigation-Structure.md index c0fada8b..6b6a4d10 100644 --- a/docs/Documentation-Navigation-Structure.md +++ b/docs/Documentation-Navigation-Structure.md @@ -27,16 +27,11 @@ This section helps users understand how to integrate Terminal49 into their syste #### Group: Guides - Use Cases (`api-docs/in-depth-guides/use-cases`) -- JSON:API Guide (`api-docs/in-depth-guides/json-api-guide`) -- Authentication (`api-docs/in-depth-guides/authentication`) -- Error Handling (`api-docs/in-depth-guides/error-handling`) -- Rate Limiting (`api-docs/in-depth-guides/rate-limiting`) - Polling vs. Webhooks (`api-docs/in-depth-guides/polling-vs-webhooks`) - Webhooks (`api-docs/in-depth-guides/webhooks`) - Tracking Shipments and Containers (`api-docs/getting-started/tracking-shipments-and-containers`) - List Shipments and Containers (`api-docs/getting-started/list-shipments-and-containers`) - Receive Status Updates (`api-docs/getting-started/receive-status-updates`) -- Including Resources (`api-docs/in-depth-guides/including-resources`) - Adding Customer (`api-docs/in-depth-guides/adding-customer`) - Tracking Request Lifecycle (`api-docs/in-depth-guides/tracking-request-lifecycle`) - Event Timestamps (`api-docs/in-depth-guides/event-timestamps`) @@ -57,6 +52,13 @@ This section provides detailed technical documentation for specific API endpoint ### Groups and Pages +#### Group: API Concepts +- Authentication (`api-docs/in-depth-guides/authentication`) +- Error Handling (`api-docs/in-depth-guides/error-handling`) +- Rate Limiting (`api-docs/in-depth-guides/rate-limiting`) +- JSON:API Guide (`api-docs/in-depth-guides/json-api-guide`) +- Including Resources (`api-docs/in-depth-guides/including-resources`) + #### Group: Shipments - List Shipments (`api-docs/api-reference/shipments/list-shipments`) - Get a Shipment (`api-docs/api-reference/shipments/get-a-shipment`) @@ -137,22 +139,23 @@ This section covers Terminal49's data warehouse integration solution. ## Notes on Structure 1. **Integration Guide to API Reference Flow**: - - The Integration Guide introduces concepts, use cases, and patterns. - - Users then move to API Reference for specific implementation details. - - This creates a natural learning progression from high-level to specific details. + - The Integration Guide introduces high-level concepts, use cases, and integration patterns. + - The API Reference contains technical implementation details including API concepts and endpoints. + - This creates a natural learning progression from business problems to technical solutions. 2. **Use Case Focus**: - The Use Cases document in the Integration Guide helps users understand real-world applications. - - Each use case can link to specific API endpoints in the Reference section. + - API concepts in the API Reference tab support technical implementation of these use cases. -3. **DataSync Integration**: - - The DataSync tab provides a separate but related product offering. - - Could potentially add cross-references between API use cases and when DataSync might be more appropriate. +3. **Clear Separation of Concerns**: + - Integration Guide: Focuses on "why" and "what" (business problems, use cases, general patterns) + - API Reference: Focuses on "how" (technical implementations, API details, specifications) + - DataSync: Dedicated to the data warehouse integration solution 4. **Naming Conventions**: - Groups use clear, descriptive names. - Page titles are consistently formatted. 5. **Potential Improvements**: - - Consider organizing the Guides section into subcategories as it grows. - - Group related guides (e.g., authentication, error handling, and rate limiting might form a "Best Practices" subgroup). + - Consider organizing the Guides section in the Integration Guide into subcategories as it grows. + - Add cross-references between use cases in the Integration Guide and corresponding API endpoints in the Reference section. From d92a950efafc3dd1ca957945d7d2011108e52bd7 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:26:45 -0700 Subject: [PATCH 25/42] Implement three-tab documentation structure with Integration Guide, API Reference, and DataSync --- docs/docs.json | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index c6323027..b3f6cdb6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -11,7 +11,7 @@ "navigation": { "tabs": [ { - "tab": "API Docs", + "tab": "Integration Guide", "groups": [ { "group": " ", @@ -30,16 +30,11 @@ "group": "Guides", "pages": [ "api-docs/in-depth-guides/use-cases", - "api-docs/in-depth-guides/json-api-guide", - "api-docs/in-depth-guides/authentication", - "api-docs/in-depth-guides/error-handling", - "api-docs/in-depth-guides/rate-limiting", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "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/in-depth-guides/including-resources", "api-docs/in-depth-guides/adding-customer", "api-docs/in-depth-guides/tracking-request-lifecycle", "api-docs/in-depth-guides/event-timestamps", @@ -57,6 +52,21 @@ "api-docs/useful-info/tracking-request-retrying", "api-docs/useful-info/webhook-events-examples" ] + } + ] + }, + { + "tab": "API Reference", + "groups": [ + { + "group": "API Concepts", + "pages": [ + "api-docs/in-depth-guides/authentication", + "api-docs/in-depth-guides/error-handling", + "api-docs/in-depth-guides/rate-limiting", + "api-docs/in-depth-guides/json-api-guide", + "api-docs/in-depth-guides/including-resources" + ] }, { "group": "Shipments", From 014f167dd510ad6d1f8028b4d835b02805a44b91 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:32:08 -0700 Subject: [PATCH 26/42] Reorganize documentation: Create overview page and restructure navigation --- docs/api-docs/overview.mdx | 131 +++++++++++++++++++++++++++++++++++++ docs/docs.json | 17 +++-- 2 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 docs/api-docs/overview.mdx diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx new file mode 100644 index 00000000..33d21b65 --- /dev/null +++ b/docs/api-docs/overview.mdx @@ -0,0 +1,131 @@ +--- +title: Terminal49 Integration Overview +description: A comprehensive overview of Terminal49's integration options, capabilities, and use cases for container tracking and visibility. +icon: "ship" +--- + +# Terminal49 Integration Overview + +Terminal49 provides comprehensive container tracking and visibility solutions through multiple integration options. This overview will help you understand what's possible and how to choose the right approach for your business needs. + +## Integration Options + + + + Connect directly with our API to track specific shipments and containers, and receive real-time updates via webhooks. Ideal for custom workflows and applications. + + [Learn More](/api-docs/in-depth-guides/use-cases) + + + Get tables of fresh information delivered directly into your existing data systems. Easy to set up and perfect for complementing your current data infrastructure. + + [Learn More](/datasync/overview) + + + +If you already have a data store that feeds the rest of your system, DataSync is likely your best option. If you need more granular control or custom workflows, direct API integration is recommended. + +## Core Capabilities + + + + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. + + + Receive real-time updates when shipment status changes, from origin to destination. + + + Access standardized data about carriers, ports, terminals, and more for consistent reporting. + + + Get comprehensive container information including status, location, free time, and holds. + + + +## What Can I Use Terminal49 Data For? + +Here are just a few of the data points we provide and their possible use-cases: + +| DATA | EXAMPLE USE CASE | +|----------------------------------------|--------------------------------------------------------------------------| +| Destination ETA | Surface ETA changes to your relevant teams as they're reported | +| Last Free Day and terminal status¹ | Track containers approaching LFD and prioritize dispatching | +| Fees and holds at destination terminal | Clear your cargo to keep your containers moving | +| Actual departure and arrival times | Report journey times by route to compare your ocean carriers' performance| + +_1. At container ports in the US_ + +## End-to-End Visibility + +Terminal49 provides visibility across the entire container journey: + + + + Track empty container pickup, loading, and departure from origin port. + + + Follow vessel movements and receive ETA updates throughout the journey. + + + Get notified when the vessel arrives, berths, and when containers are discharged. + + + Monitor container availability, customs status, holds, and last free day information. + + + Track rail departures, arrivals, and final delivery events to inland destinations. + + + +## How It Works + + + ![Terminal49 Integration Flow Diagram](/images/api-flow-diagram.png) + + +1. **Submit tracking information** using bill of lading, booking, or container numbers +2. **Terminal49 monitors** these shipments by aggregating data from multiple sources +3. **Your systems receive updates** through webhooks or DataSync when any change occurs +4. **Access additional information** as needed through the API or your data warehouse + +## Common Use Cases + + + + Connect Terminal49 to your Transportation Management System for real-time visibility without manual tracking. + + + + Build automated alerts to notify customers about shipment status changes, delays, or container availability. + + + + Prioritize container pickups based on last free day information to avoid demurrage charges. + + + + Identify and resolve customs holds, freight issues, or delays that require intervention. + + + +## Get Started + +Ready to start integrating with Terminal49? Choose your path: + + + + See how businesses are using Terminal49 to solve real logistics challenges. + + + + Start building with our API for custom workflows and real-time updates. + + + + Learn how to get Terminal49 data delivered directly to your data systems. + + + + Our team is here to help you get the most out of Terminal49. + + diff --git a/docs/docs.json b/docs/docs.json index b3f6cdb6..6de12488 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -16,20 +16,18 @@ { "group": " ", "pages": [ - "home" + "api-docs/overview" ] }, { - "group": "Overview", + "group": "Getting Started", "pages": [ - "api-docs/in-depth-guides/quickstart", - "api-docs/getting-started/capabilities-overview" + "api-docs/in-depth-guides/use-cases" ] }, { "group": "Guides", "pages": [ - "api-docs/in-depth-guides/use-cases", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/getting-started/tracking-shipments-and-containers", @@ -61,6 +59,7 @@ { "group": "API Concepts", "pages": [ + "api-docs/in-depth-guides/quickstart", "api-docs/in-depth-guides/authentication", "api-docs/in-depth-guides/error-handling", "api-docs/in-depth-guides/rate-limiting", @@ -245,6 +244,14 @@ { "source": "/api-docs/in-depth-guides/api-quick-start", "destination": "/api-docs/in-depth-guides/quickstart" + }, + { + "source": "/api-docs/getting-started/capabilities-overview", + "destination": "/api-docs/overview" + }, + { + "source": "home", + "destination": "/api-docs/overview" } ] } From 665f391c9c9af9baf4d21f5321b32fc30a02f566 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:36:45 -0700 Subject: [PATCH 27/42] Move overview to Getting Started section and rename to 'Overview' --- docs/api-docs/overview.mdx | 2 +- docs/docs.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 33d21b65..d4ad1ddc 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -1,5 +1,5 @@ --- -title: Terminal49 Integration Overview +title: Overview description: A comprehensive overview of Terminal49's integration options, capabilities, and use cases for container tracking and visibility. icon: "ship" --- diff --git a/docs/docs.json b/docs/docs.json index 6de12488..b7c012a1 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -16,12 +16,13 @@ { "group": " ", "pages": [ - "api-docs/overview" + "home" ] }, { "group": "Getting Started", "pages": [ + "api-docs/overview", "api-docs/in-depth-guides/use-cases" ] }, From c90638de88f39bccc4bc1462a33f7f1c18772981 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:42:36 -0700 Subject: [PATCH 28/42] Add Data Sources and Embedding sections with overview pages --- docs/api-docs/data-sources/overview.mdx | 85 +++++++++++++++++++++++++ docs/api-docs/embedding/overview.mdx | 68 ++++++++++++++++++++ docs/docs.json | 50 ++++++++++++++- 3 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 docs/api-docs/data-sources/overview.mdx create mode 100644 docs/api-docs/embedding/overview.mdx diff --git a/docs/api-docs/data-sources/overview.mdx b/docs/api-docs/data-sources/overview.mdx new file mode 100644 index 00000000..0a5ebbf1 --- /dev/null +++ b/docs/api-docs/data-sources/overview.mdx @@ -0,0 +1,85 @@ +--- +title: Data Sources Overview +description: Learn about Terminal49's data sources, data quality, and availability for comprehensive container tracking. +icon: "database" +--- + +# Terminal49 Data Sources + +Terminal49 aggregates data from multiple sources to provide comprehensive visibility into container shipments. This overview introduces our data sources and helps you understand data availability and quality considerations. + +## Our Data Ecosystem + + + + Data from major steamship lines including bills of lading, bookings, vessel ETAs, containers, and milestones. + + + Container availability, last free day, holds, fees, and other terminal-specific information. + + + Container milestones for rail transport including rail loading, departure, arrival, and unloading events. + + + Vessel details and real-time location tracking through Automatic Identification System data. + + + +## Data Integration Approach + +Terminal49 takes raw data from various sources and transforms it into a standardized format to provide a consistent experience across carriers and terminals. Our approach includes: + +1. **Normalization** - Converting different formats and terms into a consistent schema +2. **Enrichment** - Adding context and relationships between data points +3. **Validation** - Checking for errors and inconsistencies +4. **Standardization** - Using common formats for dates, locations, and status information + +## Coverage and Limitations + +While Terminal49 strives to provide comprehensive data, availability varies by source: + + + We support all major ocean carriers including Maersk, MSC, CMA CGM, COSCO, Hapag-Lloyd, ONE, Evergreen, and many more. Each carrier provides different data points with varying quality and timeliness. + + [View detailed carrier coverage](/api-docs/useful-info/api-data-sources-availability#supported-ocean-carriers) + + + + Terminal49 integrates with terminals at major ports across North America, Europe, and Asia. Terminal data quality varies significantly by location. + + [View detailed terminal coverage](/api-docs/useful-info/api-data-sources-availability#ports-and-terminals) + + + + We support all Class 1 North American railroads, including BNSF, CN, CP, CSX, NS, and UP. Rail data is standardized across carriers but certain event types may only be available from specific rail providers. + + [View detailed rail coverage](/api-docs/useful-info/api-data-sources-availability#rail-carriers) + + +## Data Quality Considerations + +When building applications with Terminal49 data, consider these data quality factors: + +- **Timeliness** - Data latency varies by source; carrier updates might take several hours while terminal data is typically more real-time +- **Completeness** - Not all data fields are available from all sources (see [Known Issues](/api-docs/useful-info/api-data-sources-availability#known-issues-ocean)) +- **Accuracy** - ETAs and availability predictions are based on carrier and terminal inputs and can change +- **Consistency** - Terminal49 normalizes data, but underlying source inconsistencies may occasionally surface + +## Getting Started with Terminal49 Data + + + + Review the [detailed data sources documentation](/api-docs/useful-info/api-data-sources-availability) to understand which data points are consistently available. + + + Design your integration to handle missing or delayed data from certain sources. + + + Leverage our standardized data formats rather than trying to parse raw carrier or terminal data. + + + Use webhooks to receive real-time updates when data changes rather than frequent polling. + + + +For a detailed breakdown of supported data fields and their availability, see our [API Data Sources and Availability](/api-docs/useful-info/api-data-sources-availability) documentation. diff --git a/docs/api-docs/embedding/overview.mdx b/docs/api-docs/embedding/overview.mdx new file mode 100644 index 00000000..c64ceb21 --- /dev/null +++ b/docs/api-docs/embedding/overview.mdx @@ -0,0 +1,68 @@ +--- +title: Embedding Overview +description: Learn how to embed Terminal49's tracking and visualization tools directly into your website to provide real-time container tracking to your customers. +icon: "code" +--- + +# Terminal49 Embedding Solutions + +Terminal49 offers powerful embedding solutions that allow you to integrate real-time container tracking directly into your website. These tools enhance your customer experience by providing up-to-date shipping information without requiring users to visit another platform. + +## Available Embedding Options + + + + A dynamic, visual map showing container journeys in real-time. Perfect for providing a visual representation of shipment progress. + + [Learn More](/api-docs/in-depth-guides/terminal49-map) + + + A customizable tracking widget that allows your customers to look up their shipments directly on your website. + + [Learn More](/api-docs/in-depth-guides/terminal49-widget) + + + +## How to Choose the Right Solution + +| Feature | Embeddable Map | Track & Trace Widget | +|---------|---------------|----------------------| +| **Use Case** | Visualize container journeys with detailed map-based tracking | Enable users to search and track their shipments | +| **Integration** | Requires JavaScript integration | Simple embed with just a few lines of code | +| **Customization** | Highly customizable with API | Basic customization options | +| **Container Data** | Focused on geographic visualization | Focused on status and milestone information | +| **User Interaction** | Interactive map with timeline and events | Search functionality with detailed results | + +## Getting Started + +To start using either embedding solution, you'll need: + +1. A Terminal49 account +2. A publishable API key (contact support@terminal49.com) +3. Basic knowledge of HTML and JavaScript + +Each embedding solution has its own comprehensive documentation: + +- [Embeddable Map Guide](/api-docs/in-depth-guides/terminal49-map) +- [Track & Trace Widget Guide](/api-docs/in-depth-guides/terminal49-widget) + +## Common Use Cases + + + + Enhance your customer portal with visual tracking to differentiate your service offering. + + + Let customers track the progress of their international shipments directly on your site. + + + Provide clients with real-time visibility into their container movements. + + + Integrate tracking data into your internal systems for better visibility. + + + +## Pricing + +Both embedding solutions require a subscription. Contact [sales@terminal49.com](mailto:sales@terminal49.com) for current pricing information and to discuss which solution best fits your needs. diff --git a/docs/docs.json b/docs/docs.json index b7c012a1..b72886e7 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -37,15 +37,12 @@ "api-docs/in-depth-guides/adding-customer", "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" ] }, { "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", @@ -159,6 +156,53 @@ } ] }, + { + "tab": "Data Sources", + "groups": [ + { + "group": "Overview", + "pages": [ + "api-docs/data-sources/overview", + "api-docs/useful-info/api-data-sources-availability" + ] + }, + { + "group": "Ocean Carriers", + "pages": [] + }, + { + "group": "Rail Carriers", + "pages": [] + }, + { + "group": "Terminals", + "pages": [] + } + ] + }, + { + "tab": "Embedding", + "groups": [ + { + "group": "Overview", + "pages": [ + "api-docs/embedding/overview" + ] + }, + { + "group": "Embeddable Map", + "pages": [ + "api-docs/in-depth-guides/terminal49-map" + ] + }, + { + "group": "Track & Trace Widget", + "pages": [ + "api-docs/in-depth-guides/terminal49-widget" + ] + } + ] + }, { "tab": "DataSync", "groups": [ From b1ab27293a73939e51d8adc73bfd32b164ece97d Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:46:42 -0700 Subject: [PATCH 29/42] Update overview page with improved headers and terminology --- docs/api-docs/overview.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index d4ad1ddc..1744ca78 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -8,7 +8,7 @@ icon: "ship" Terminal49 provides comprehensive container tracking and visibility solutions through multiple integration options. This overview will help you understand what's possible and how to choose the right approach for your business needs. -## Integration Options +## Container visibility integration options @@ -28,7 +28,7 @@ If you already have a data store that feeds the rest of your system, DataSync is ## Core Capabilities - + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. From fc97ba0cc8e8bb2ccf5bf78b5f20edb8827d2513 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:52:37 -0700 Subject: [PATCH 30/42] Update Core Capabilities with expanded features --- docs/api-docs/overview.mdx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 1744ca78..85e07da0 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -27,19 +27,28 @@ If you already have a data store that feeds the rest of your system, DataSync is ## Core Capabilities - + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. - - Receive real-time updates when shipment status changes, from origin to destination. + + Receive real-time updates when container milestone status changes, from origin to destination. + + + Access details on vessels, voyages, transshipments, and inland journey including timestamps. + + + Direct integrations across the entire rail network including rail milestones and rail terminal data. + + + Firms code, discharge, available for pickup, LFD, holds, fees, chassis, appointment and other information from ocean and rail terminals. + + + Get comprehensive shipment and container information including status, location, free time, and holds. Access standardized data about carriers, ports, terminals, and more for consistent reporting. - - Get comprehensive container information including status, location, free time, and holds. - ## What Can I Use Terminal49 Data For? From 3042d04612f0aa3004ebcf3b1973af883ae7052b Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:54:33 -0700 Subject: [PATCH 31/42] Set overview page to wide mode --- docs/api-docs/overview.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 85e07da0..d40a1146 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -2,6 +2,7 @@ title: Overview description: A comprehensive overview of Terminal49's integration options, capabilities, and use cases for container tracking and visibility. icon: "ship" +mode: "wide" --- # Terminal49 Integration Overview From 652910af9216c2717370cbf0d487824de9314276 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:58:04 -0700 Subject: [PATCH 32/42] Fix documentation navigation structure --- docs/docs.json | 67 ++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index b72886e7..442fad58 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -20,15 +20,16 @@ ] }, { - "group": "Getting Started", + "group": "Overview", "pages": [ "api-docs/overview", - "api-docs/in-depth-guides/use-cases" + "api-docs/in-depth-guides/quickstart" ] }, { "group": "Guides", "pages": [ + "api-docs/in-depth-guides/use-cases", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", "api-docs/getting-started/tracking-shipments-and-containers", @@ -37,12 +38,15 @@ "api-docs/in-depth-guides/adding-customer", "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" ] }, { "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", @@ -57,7 +61,6 @@ { "group": "API Concepts", "pages": [ - "api-docs/in-depth-guides/quickstart", "api-docs/in-depth-guides/authentication", "api-docs/in-depth-guides/error-handling", "api-docs/in-depth-guides/rate-limiting", @@ -156,6 +159,35 @@ } ] }, + { + "tab": "DataSync", + "groups": [ + { + "group": " ", + "pages": [ + "datasync/home" + ] + }, + { + "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/transport-events", + "datasync/table-properties/transfer-status", + "datasync/table-properties/containers" + ] + } + ] + }, { "tab": "Data Sources", "groups": [ @@ -202,35 +234,6 @@ ] } ] - }, - { - "tab": "DataSync", - "groups": [ - { - "group": " ", - "pages": [ - "datasync/home" - ] - }, - { - "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/transport-events", - "datasync/table-properties/transfer-status", - "datasync/table-properties/containers" - ] - } - ] } ], "global": { From 4e2a37e17857c3b9396d2e17fcb064468d548969 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 09:58:37 -0700 Subject: [PATCH 33/42] Fix redirects in documentation --- docs/docs.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs.json b/docs/docs.json index 442fad58..be62e8a0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -298,7 +298,11 @@ "destination": "/api-docs/overview" }, { - "source": "home", + "source": "/", + "destination": "/api-docs/overview" + }, + { + "source": "/home", "destination": "/api-docs/overview" } ] From aa50f5094270241375e4de6bba3469332a7e5adf Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 10:05:52 -0700 Subject: [PATCH 34/42] Remove Reference Data card and revert to centered layout --- docs/api-docs/overview.mdx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index d40a1146..852928b9 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -2,7 +2,6 @@ title: Overview description: A comprehensive overview of Terminal49's integration options, capabilities, and use cases for container tracking and visibility. icon: "ship" -mode: "wide" --- # Terminal49 Integration Overview @@ -47,9 +46,6 @@ If you already have a data store that feeds the rest of your system, DataSync is Get comprehensive shipment and container information including status, location, free time, and holds. - - Access standardized data about carriers, ports, terminals, and more for consistent reporting. - ## What Can I Use Terminal49 Data For? From 8aa88298c2da2a30ea2277ea33bebfb1a5d8d3f5 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 10:09:59 -0700 Subject: [PATCH 35/42] Change Core Capabilities to 2-column layout --- docs/api-docs/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 852928b9..8d796112 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -27,7 +27,7 @@ If you already have a data store that feeds the rest of your system, DataSync is ## Core Capabilities - + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. From afbd7260fd213621b41aa4e8b3bf0379c62ffc6f Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 10:33:40 -0700 Subject: [PATCH 36/42] Expand What Can I Use Terminal49 Data For section with visual cards --- docs/api-docs/overview.mdx | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 8d796112..071cf79f 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -50,16 +50,40 @@ If you already have a data store that feeds the rest of your system, DataSync is ## What Can I Use Terminal49 Data For? -Here are just a few of the data points we provide and their possible use-cases: + + + Track by MBOL, booking, or container number and get all shipment routing details including ports and inland locations. + + [Learn More](/api-docs/in-depth-guides/use-cases#getting-container-etas) + + + Get continuously updated vessel departure (ETD) and arrival (ETA) events to maintain accurate delivery schedules. + + [Learn More](/api-docs/in-depth-guides/use-cases#vessel-tracking) + + + Receive immediate notification when containers are available for pickup at the terminal. + + [Learn More](/api-docs/in-depth-guides/use-cases#container-availability) + + + Identify carrier and customs holds even before containers arrive at port to expedite release and avoid delays. -| DATA | EXAMPLE USE CASE | -|----------------------------------------|--------------------------------------------------------------------------| -| Destination ETA | Surface ETA changes to your relevant teams as they're reported | -| Last Free Day and terminal status¹ | Track containers approaching LFD and prioritize dispatching | -| Fees and holds at destination terminal | Clear your cargo to keep your containers moving | -| Actual departure and arrival times | Report journey times by route to compare your ocean carriers' performance| + [Learn More](/api-docs/in-depth-guides/use-cases#terminal-holds) + + + Monitor container return deadlines and locations to avoid per-diem charges and optimize asset utilization. + + [Learn More](/api-docs/in-depth-guides/use-cases) + + + Stay ahead of demurrage charges with notifications about containers approaching their last free day. + + [Learn More](/api-docs/in-depth-guides/use-cases#container-availability) + + -_1. At container ports in the US_ +Our customers integrate Terminal49 data into their TMS, ERP, and customer portals to provide better visibility across the entire container journey. From proactively managing exceptions to optimizing pickup schedules, Terminal49 helps you maintain control over your supply chain. ## End-to-End Visibility From 2a5320a256a9ad6716fee4150511785a606c1479 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 11:36:21 -0700 Subject: [PATCH 37/42] Enhance overview page with rich Mintlify components --- docs/api-docs/overview.mdx | 146 ++++++++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 28 deletions(-) diff --git a/docs/api-docs/overview.mdx b/docs/api-docs/overview.mdx index 071cf79f..bcb8f111 100644 --- a/docs/api-docs/overview.mdx +++ b/docs/api-docs/overview.mdx @@ -4,26 +4,50 @@ description: A comprehensive overview of Terminal49's integration options, capab icon: "ship" --- + +Get real-time container tracking visibility across ocean carriers, terminals, and rail carriers in one unified API. Start tracking your first container in minutes. + + # Terminal49 Integration Overview Terminal49 provides comprehensive container tracking and visibility solutions through multiple integration options. This overview will help you understand what's possible and how to choose the right approach for your business needs. -## Container visibility integration options + + + + Terminal49 API Integration Flow + - - - Connect directly with our API to track specific shipments and containers, and receive real-time updates via webhooks. Ideal for custom workflows and applications. + Connect directly with our RESTful API to track specific shipments and containers, and receive real-time updates via webhooks. + + **Best for:** + - Custom workflows and applications + - Fine-grained control over tracking + - Real-time event notifications + - Integration with your own systems + + [Explore API Documentation](/api-docs/in-depth-guides/quickstart) + + + + Terminal49 DataSync Flow + - [Learn More](/api-docs/in-depth-guides/use-cases) - - Get tables of fresh information delivered directly into your existing data systems. Easy to set up and perfect for complementing your current data infrastructure. - [Learn More](/datasync/overview) - - + **Best for:** + - Data warehouse integration + - Business intelligence tools + - Custom reporting + - Bulk historical analysis + + [Learn About DataSync](/datasync/overview) + + + If you already have a data store that feeds the rest of your system, DataSync is likely your best option. If you need more granular control or custom workflows, direct API integration is recommended. + ## Core Capabilities @@ -83,27 +107,53 @@ If you already have a data store that feeds the rest of your system, DataSync is
-Our customers integrate Terminal49 data into their TMS, ERP, and customer portals to provide better visibility across the entire container journey. From proactively managing exceptions to optimizing pickup schedules, Terminal49 helps you maintain control over your supply chain. + + Our customers integrate Terminal49 data into their TMS, ERP, and customer portals to provide better visibility across the entire container journey. From proactively managing exceptions to optimizing pickup schedules, Terminal49 helps you maintain control over your supply chain. + + **Common integration patterns:** + - **Direct API integration** with your TMS or logistics platform + - **Custom portals** powered by our API for your customers + - **Automated notifications** for key stakeholders about shipment status + - **Exception dashboards** to highlight containers needing attention + - **Data feeds** into your data warehouse for analytics and reporting + ## End-to-End Visibility -Terminal49 provides visibility across the entire container journey: + + Terminal49 provides continuous visibility across the entire container journey, from origin to destination. + - Track empty container pickup, loading, and departure from origin port. +
+ Origin + Track empty container pickup, loading, and departure from origin port. +
- Follow vessel movements and receive ETA updates throughout the journey. +
+ Ocean + Follow vessel movements and receive ETA updates throughout the journey. +
- Get notified when the vessel arrives, berths, and when containers are discharged. +
+ Port + Get notified when the vessel arrives, berths, and when containers are discharged. +
- Monitor container availability, customs status, holds, and last free day information. +
+ Terminal + Monitor container availability, customs status, holds, and last free day information. +
- Track rail departures, arrivals, and final delivery events to inland destinations. +
+ Inland + Track rail departures, arrivals, and final delivery events to inland destinations. +
@@ -113,14 +163,46 @@ Terminal49 provides visibility across the entire container journey: ![Terminal49 Integration Flow Diagram](/images/api-flow-diagram.png) -1. **Submit tracking information** using bill of lading, booking, or container numbers -2. **Terminal49 monitors** these shipments by aggregating data from multiple sources -3. **Your systems receive updates** through webhooks or DataSync when any change occurs -4. **Access additional information** as needed through the API or your data warehouse + + + Provide bill of lading, booking, or container numbers to track your shipments + + + Our system aggregates data from multiple sources including carriers, terminals, and rail operators + + + Your systems receive updates through webhooks or DataSync when any change occurs + + + Query the API or your data warehouse for detailed information as needed + + + +## Quick Start + +```bash +# Track a container using cURL +curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "tracking_request", + "attributes": { + "reference_number": "MSCU1234567", + "reference_type": "container" + } + } + }' +``` + + + Get your API key by [signing up for an account](https://app.terminal49.com/register) and navigating to the API section. + ## Common Use Cases - + Connect Terminal49 to your Transportation Management System for real-time visibility without manual tracking. @@ -136,6 +218,14 @@ Terminal49 provides visibility across the entire container journey: Identify and resolve customs holds, freight issues, or delays that require intervention. + + + Monitor containers on rail to coordinate inland pickups efficiently. + + + + Analyze carrier performance, transit times, and dwell times to optimize your supply chain. + ## Get Started @@ -143,16 +233,16 @@ Terminal49 provides visibility across the entire container journey: Ready to start integrating with Terminal49? Choose your path: - - See how businesses are using Terminal49 to solve real logistics challenges. + + Follow our step-by-step guide to start tracking your first container in minutes. - - Start building with our API for custom workflows and real-time updates. + + See how businesses are using Terminal49 to solve real logistics challenges. - - Learn how to get Terminal49 data delivered directly to your data systems. + + Dive into the technical documentation for Terminal49's API endpoints. From 70f4810a97813d5fc00bdceba93831c92840853f Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 12:05:49 -0700 Subject: [PATCH 38/42] Create improved tracking request API documentation example --- .../improved/tracking-request-example.mdx | 640 ++++++++++++++++++ 1 file changed, 640 insertions(+) create mode 100644 docs/api-docs/improved/tracking-request-example.mdx diff --git a/docs/api-docs/improved/tracking-request-example.mdx b/docs/api-docs/improved/tracking-request-example.mdx new file mode 100644 index 00000000..48199b93 --- /dev/null +++ b/docs/api-docs/improved/tracking-request-example.mdx @@ -0,0 +1,640 @@ +--- +title: Create a Tracking Request +description: Submit a new tracking request to monitor shipments via bill of lading, booking number, or container number. +icon: "magnifying-glass-chart" +--- + +# Create a Tracking Request + +**Endpoint:** `POST /v2/tracking_requests` + +Creates a tracking request to begin monitoring a shipment or container. Terminal49 will attempt to locate the shipment or container using the provided reference number and carrier information. Once successfully located, tracking will commence, and you'll receive updates as the shipment progresses. + +## Endpoint Overview + +| Field | Value | +| ----- | ----- | +| **HTTP Method** | POST | +| **Endpoint** | `/v2/tracking_requests` | +| **Content-Type** | `application/vnd.api+json` | +| **Authentication** | API Key (required) | +| **Rate Limit** | [See rate limiting documentation](/api-docs/in-depth-guides/rate-limiting) | + +## Business Use Cases + +- **Initial Tracking Setup**: Start tracking a new container shipment when you receive a bill of lading or booking confirmation +- **Customer Onboarding**: Quickly set up tracking for a customer's shipments when they provide their shipping documents +- **Bulk Import**: Programmatically submit multiple tracking requests to monitor your entire inventory in transit +- **Proactive Monitoring**: Establish tracking early in the shipping cycle to receive timely updates + +## Request Parameters + +### Headers + +| Header | Value | Required | +| ------ | ----- | -------- | +| `Authorization` | `Token YOUR_API_KEY` | Yes | +| `Content-Type` | `application/vnd.api+json` | Yes | + +### Request Body + + + Resource type, must be `tracking_request` + + + + The type of reference number being provided: + - `container`: A container number (including carrier prefix) + - `bill_of_lading`: A Master Bill of Lading (MBL) number + - `booking`: A booking number provided by the carrier + + + + The identifier for the shipment or container. Format depends on the request_type: + - For containers: Typically 11 characters (e.g., MSCU1234567) + - For bill of lading numbers: Format varies by carrier but should match the format on shipping documents + - For booking numbers: Alphanumeric format provided by the carrier + + + + The Standard Carrier Alpha Code (SCAC) identifying the ocean carrier. While optional, providing this significantly improves tracking success rates. See the [Supported Carriers](/api-docs/useful-info/api-data-sources-availability) documentation for a list of supported SCAC codes. + + + + Optional array of reference numbers like purchase orders, customer references, or other identifiers associated with this shipment + + + + Optional array of tags to categorize shipments + + + + Optional ID of a party to associate with this tracking request as the customer + + + + Must be `party` if customer relationship is included + + +## Response + +Tracking requests are processed asynchronously. The initial response indicates the request has been accepted, but does not confirm successful tracking. To be notified of tracking status changes, configure a webhook for `tracking_request.succeeded` and `tracking_request.failed` events. + +### Response Status Codes + +| Status Code | Description | +| ----------- | ----------- | +| 201 | Successfully created tracking request | +| 401 | Unauthorized - Invalid API key | +| 422 | Validation error - Invalid request parameters | +| 429 | Rate limit exceeded | +| 500 | Server error | + +### Response Fields + + + Unique identifier for the tracking request. Store this to check status later. + + + + Resource type, always "tracking_request". + + + + The tracking reference number you provided. + + + + The tracking reference type you provided. + + + + The SCAC code of the carrier. + + + + Current status of the tracking request. Possible values: + - `pending`: Request is being processed + - `succeeded`: Container/shipment located and tracking established + - `failed`: Unable to track the shipment (see failed_reason) + - `awaiting_manifest`: Bill of lading is valid but not yet available in the carrier's system + - `tracking_stopped`: Tracking has been stopped for this request + + + + If status is "failed", this field provides the reason. Common values: + - `duplicate`: Shipment already exists in Terminal49 + - `not_found`: Shipping line couldn't find the tracking number + - `retries_exhausted`: Maximum retries reached without success + - `invalid_number`: Improperly formatted tracking number + + + See the [Tracking Request Lifecycle](/api-docs/in-depth-guides/tracking-request-lifecycle) guide for a complete list of failure reasons. + + + + + ISO-8601 timestamp of when the tracking request was created. + + + + ISO-8601 timestamp of when the tracking request was last updated. + + + + When the tracking request succeeds, this contains a reference to the created shipment. + + + + ID of the linked shipment that you can use to retrieve full shipment details. + + + + Always "shipment" for successful tracking requests. + + + + +## Example Scenarios + + + + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T14:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" + }, + "relationships": { + "tracked_object": { + "data": null + } + } + } + } + ``` + + **Notes:** + - The bill of lading is typically the most reliable identifier for tracking shipments. + - The `status` will initially be `pending` while Terminal49 processes your request. + - Once processing completes, you'll receive a webhook notification and the status will change to `succeeded` or `failed`. + + + + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" + } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T15:45:22Z", + "updated_at": "2023-05-15T15:45:22Z" + }, + "relationships": { + "tracked_object": { + "data": null + } + } + } + } + ``` + + **Notes:** + - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. + - Container numbers should include the carrier prefix (e.g., MSCU for MSC containers). + - Container tracking can sometimes fail if the container is very new or not yet associated with a booking in the carrier's system. + + + + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "ref_numbers": ["PO-12345", "INVOICE-789"], + "shipment_tags": ["priority", "electronics"] + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "ref_numbers": ["PO-12345", "INVOICE-789"], + "shipment_tags": ["priority", "electronics"], + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T16:12:34Z", + "updated_at": "2023-05-15T16:12:34Z" + }, + "relationships": { + "tracked_object": { + "data": null + }, + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + } + } + } + ``` + + **Notes:** + - Reference numbers and tags help you organize and search for shipments in your system. + - Customer association requires first [creating a party](/api-docs/api-reference/parties/create-a-party). + - These associations will be preserved when the shipment is created after a successful tracking request. + + + + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + } + ``` + + ```json title="Response" + { + "data": { + "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T16:30:45Z", + "updated_at": "2023-05-15T16:30:45Z" + }, + "relationships": { + "tracked_object": { + "data": null + } + } + } + } + ``` + + **Available Test Numbers:** + - `TEST-TR-SUCCEEDED` - Always succeeds and creates a test shipment + - `TEST-TR-FAILED` - Always fails with a "not_found" reason + - `TEST-TR-AWAITING-MANIFEST` - Simulates an awaiting manifest state + - `TEST-TR-DELAYED` - Simulates a delayed processing state before succeeding + + **Notes:** + - Using test numbers is perfect for development and testing your integration. + - Test numbers will trigger corresponding webhook events for complete testing. + - No actual tracking is performed with test numbers. + + + + ```json title="Request" + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "INVALID-FORMAT", + "scac": "UNKNOWN" + } + } + } + ``` + + ```json title="Response with validation error (Status: 422)" + { + "errors": [ + { + "status": "422", + "title": "Invalid SCAC", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } + } + ] + } + ``` + + ```json title="Response with duplicate error (Status: 422)" + { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number 'MAEU9736478' with scac 'MAEU' already exists in a tracking_request with a pending or created status", + "code": "duplicate" + } + ] + } + ``` + + ```json title="Response with authentication error (Status: 401)" + { + "errors": [ + { + "status": "401", + "title": "Unauthorized", + "detail": "You need to provide a valid API key to access this resource." + } + ] + } + ``` + + **Common Errors:** + - Invalid or unsupported SCAC codes + - Improperly formatted request numbers + - Duplicate tracking requests (same number/SCAC combination) + - Invalid authentication credentials + - Rate limit exceeded + + + +## Webhook Events + +When creating a tracking request, you may receive the following webhook events: + + + + Sent when Terminal49 successfully locates your shipment and begins tracking it. The notification includes details about the created shipment and its containers. + + + + Sent when Terminal49 is unable to locate your shipment after all retry attempts. Includes the failed_reason to help troubleshoot. + + + + Sent when the bill of lading number is valid but the shipment data is not yet available in the carrier's system. Terminal49 will continue checking daily. + + + + Sent when tracking for this request has been manually or automatically stopped. + + + +## Code Samples + + + + ```bash + curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ + -H "Authorization: Token YOUR_API_KEY" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + }' + ``` + + + ```javascript + const trackShipment = async () => { + try { + const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { + method: 'POST', + headers: { + 'Authorization': 'Token YOUR_API_KEY', + 'Content-Type': 'application/vnd.api+json' + }, + body: JSON.stringify({ + data: { + type: 'tracking_request', + attributes: { + request_type: 'bill_of_lading', + request_number: 'MAEU9736478', + scac: 'MAEU' + } + } + }) + }); + + const result = await response.json(); + + if (!response.ok) { + console.error('Error creating tracking request:', result.errors); + return; + } + + console.log('Tracking request created:', result.data.id); + + // Store the tracking request ID for future reference + const trackingRequestId = result.data.id; + + // You would typically configure a webhook to receive updates + // rather than polling for status changes + } catch (error) { + console.error('Error:', error); + } + }; + + trackShipment(); + ``` + + + ```python + import requests + import json + + api_key = 'YOUR_API_KEY' + url = 'https://api.terminal49.com/v2/tracking_requests' + + headers = { + 'Authorization': f'Token {api_key}', + 'Content-Type': 'application/vnd.api+json' + } + + payload = { + 'data': { + 'type': 'tracking_request', + 'attributes': { + 'request_type': 'bill_of_lading', + 'request_number': 'MAEU9736478', + 'scac': 'MAEU' + } + } + } + + try: + response = requests.post(url, headers=headers, data=json.dumps(payload)) + response.raise_for_status() # Raise exception for 4XX/5XX status codes + + result = response.json() + tracking_request_id = result['data']['id'] + + print(f'Tracking request created with ID: {tracking_request_id}') + print(f'Status: {result["data"]["attributes"]["status"]}') + + except requests.exceptions.HTTPError as err: + print(f'HTTP Error: {err}') + if response.status_code == 422: + errors = response.json().get('errors', []) + for error in errors: + print(f'Validation error: {error.get("detail")}') + except requests.exceptions.RequestException as err: + print(f'Request Error: {err}') + ``` + + + ```ruby + require 'net/http' + require 'uri' + require 'json' + + api_key = 'YOUR_API_KEY' + uri = URI.parse('https://api.terminal49.com/v2/tracking_requests') + + headers = { + 'Authorization' => "Token #{api_key}", + 'Content-Type' => 'application/vnd.api+json' + } + + payload = { + data: { + type: 'tracking_request', + attributes: { + request_type: 'bill_of_lading', + request_number: 'MAEU9736478', + scac: 'MAEU' + } + } + } + + begin + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + + request = Net::HTTP::Post.new(uri.request_uri, headers) + request.body = payload.to_json + + response = http.request(request) + + if response.code.to_i >= 200 && response.code.to_i < 300 + result = JSON.parse(response.body) + tracking_request_id = result['data']['id'] + + puts "Tracking request created with ID: #{tracking_request_id}" + puts "Status: #{result['data']['attributes']['status']}" + else + puts "HTTP Error: #{response.code}" + errors = JSON.parse(response.body)['errors'] rescue nil + if errors + errors.each do |error| + puts "Error: #{error['detail']}" + end + end + end + rescue => e + puts "Request failed: #{e.message}" + end + ``` + + + +## Related Resources + + + + Learn about the full lifecycle of a tracking request, including all possible statuses and failure reasons. + + + Learn how to set up webhooks to receive real-time notifications about your tracking requests. + + + Check the status of an existing tracking request. + + + Retrieve all tracking requests associated with your account. + + + +## Best Practices + +1. **Use webhooks instead of polling**: Set up webhook notifications for `tracking_request.succeeded` and `tracking_request.failed` events instead of repeatedly checking tracking request status. + +2. **Provide accurate carrier information**: Include the `scac` parameter whenever possible to improve tracking success rates. + +3. **Handle retries appropriately**: If a tracking request fails with certain errors (e.g., "not_found"), implement a retry strategy with increasing delays between attempts. + +4. **Store reference data**: Add meaningful reference numbers and tags to make it easier to find and organize shipments in your system. + +5. **Test with test numbers**: Use the test numbers (`TEST-TR-SUCCEEDED`, `TEST-TR-FAILED`) to validate your integration before using real tracking data. + +6. **Handle rate limits**: Implement proper rate limit handling by incorporating backoff strategies when you receive 429 responses. + +7. **Process webhooks idempotently**: Design your webhook handlers to be idempotent, as the same event may be delivered multiple times in rare circumstances. From e50c5529bd30bbd14e4e8616ea3cc87772a64c27 Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 12:25:59 -0700 Subject: [PATCH 39/42] Add tracking-request-example to docs navigation --- docs/docs.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/docs.json b/docs/docs.json index be62e8a0..568b99fc 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -3,7 +3,7 @@ "theme": "palm", "name": "Terminal 49", "colors": { - "primary": "#00A2FF", + "primary": "#2bc983", "light": "#52bfff", "dark": "#00A2FF" }, @@ -13,12 +13,6 @@ { "tab": "Integration Guide", "groups": [ - { - "group": " ", - "pages": [ - "home" - ] - }, { "group": "Overview", "pages": [ @@ -84,7 +78,8 @@ "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", - "api-docs/api-reference/tracking-requests/edit-a-tracking-request" + "api-docs/api-reference/tracking-requests/edit-a-tracking-request", + "api-docs/improved/tracking-request-example" ] }, { From 69e7959f82edea59543f068a093720f20a577dfe Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 12:47:53 -0700 Subject: [PATCH 40/42] Replace tabs with accordions and add detailed example scenarios for tracking requests --- .../create-a-tracking-request.mdx | 136 ++++++++++++++++-- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx index a571ce6c..c86b72af 100644 --- a/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx +++ b/docs/api-docs/api-reference/tracking-requests/create-a-tracking-request.mdx @@ -16,8 +16,8 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh ## Example Scenarios - - + + ```json title="Request" { "data": { @@ -58,9 +58,9 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - The bill of lading is typically the most reliable identifier for tracking shipments. - The `status` will initially be `pending` while Terminal49 processes your request. - Once processing completes, you'll receive a webhook notification and the status will change to `succeeded` or `failed`. - + - + ```json title="Request" { "data": { @@ -101,9 +101,9 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. - Container numbers should include the carrier prefix (e.g., MSCU for MSC containers). - Container tracking can sometimes fail if the container is very new or not yet associated with a booking in the carrier's system. - + - + ```json title="Request" { "data": { @@ -145,9 +145,9 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - `TEST-TR-SUCCEEDED` will always trigger a successful tracking response. - `TEST-TR-FAILED` will always trigger a failed tracking response. - These test numbers will trigger corresponding webhook events for complete testing. - + - + ```json title="Request" { "data": { @@ -180,8 +180,8 @@ Submitting a tracking request tells Terminal49 to start monitoring a specific sh - This example shows a validation error for an invalid SCAC code. - Common errors include invalid SCAC codes, improperly formatted request numbers, or duplicate tracking requests. - For a complete list of supported SCACs, refer to the Carrier Data Matrix. - - + + ## Key Response Fields @@ -295,6 +295,122 @@ When creating a tracking request, you may receive the following webhook events: + + + **Scenario**: You want to create a tracking request using a booking number. + + **Example**: + ```json + { + "data": { + "attributes": { + "request_type": "booking_number", + "request_number": "OOLU8324567", + "scac": "OOLU" + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Booking numbers are typically issued by the carrier early in the shipping process + - Using booking numbers allows you to start tracking earlier in the shipping lifecycle + - Booking number format varies by carrier (e.g., OOLU prefix for ONE Line bookings) + + + + **Scenario**: You want to add your own reference numbers to help identify the shipment. + + **Example**: + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "scac": "MSCU", + "ref_numbers": [ + "PO12345", + "HBL12345", + "INV9876" + ] + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Reference numbers can include purchase orders, house bills of lading, invoice numbers, etc. + - You can add multiple reference numbers to a single tracking request + - These numbers will be searchable later through the API and UI + - You can use reference numbers to link shipments to your internal systems + + + + **Scenario**: You want to categorize your shipments with tags for easier filtering. + + **Example**: + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "shipment_tags": [ + "priority", + "electronics", + "q2-2023" + ] + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Tags provide a flexible way to group and categorize shipments + - Common uses include priority levels, product categories, or time periods + - Tags can be used for filtering when listing shipments + - You can add or remove tags later using the Edit Shipment endpoint + + + + **Scenario**: You're tracking shipments on behalf of your customers and want to associate shipments with specific customers. + + **Example**: + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - The customer must first be created using the Parties API + - The customer ID is used to link the shipment to the specific customer + - This association enables filtering shipments by customer + - Customer information will be included in webhook notifications when using appropriate includes + - See the [Adding Customer guide](/api-docs/in-depth-guides/adding-customer) for more details + + + For the fastest tracking setup, use the master bill of lading (MBL) number from the carrier. House bills of lading (HBL) are not supported. From 6cc50497938b1110cac491a40c45e8215523372b Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Sat, 22 Mar 2025 12:56:52 -0700 Subject: [PATCH 41/42] Create comprehensive tracking guide and update navigation structure --- docs/api-docs/getting-started/tracking.mdx | 564 +++++++++++++++++++++ docs/docs.json | 4 +- 2 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 docs/api-docs/getting-started/tracking.mdx diff --git a/docs/api-docs/getting-started/tracking.mdx b/docs/api-docs/getting-started/tracking.mdx new file mode 100644 index 00000000..c02f9dfc --- /dev/null +++ b/docs/api-docs/getting-started/tracking.mdx @@ -0,0 +1,564 @@ +--- +title: Complete Tracking Guide +description: Learn about all methods of tracking shipments in Terminal49, tracking request lifecycle, and available API endpoints. +icon: "magnifying-glass-chart" +--- + +# Complete Tracking Guide + +Tracking is a core function of Terminal49's platform. This guide covers all aspects of tracking shipments and containers, from initiating tracking through various methods to understanding the tracking request lifecycle and managing tracking data. + +## Tracking Methods + +Terminal49 offers multiple ways to start tracking your shipments: + + + + Use the Tracking Request API to programmatically submit container numbers, bills of lading, or booking numbers for tracking. + + + Manually enter tracking numbers through Terminal49's web dashboard for immediate tracking. + + + Email CSV files with multiple tracking references to quickly set up tracking for multiple shipments at once. + + + +### API Integration + +The most flexible way to integrate tracking into your systems is via the Terminal49 API. Submit tracking requests programmatically using the Tracking Request endpoint: + +```bash +POST /v2/tracking_requests +``` + +This method is ideal for: +- Real-time tracking initiation from your TMS or other internal systems +- Automated tracking workflows +- High-volume tracking needs +- Custom reference data and shipment organization + +[Detailed API Reference →](/api-docs/api-reference/tracking-requests/create-a-tracking-request) + +### Dashboard Interface + +For manual tracking or lower volumes, Terminal49's web dashboard offers a user-friendly interface: + +1. Log in to your Terminal49 account +2. Navigate to the "Track" section +3. Enter your tracking number(s) and select the carrier +4. Click "Track" to begin monitoring + +This method is ideal for: +- Quick one-off tracking needs +- Small shipment volumes +- Testing or verification purposes +- Users who prefer a visual interface + +### Bulk CSV Upload + +For setting up tracking for multiple shipments at once: + +1. Prepare a CSV file with columns for tracking numbers, carrier codes, and optional reference numbers +2. Email the CSV file to imports@terminal49.com +3. Terminal49 will process the file and begin tracking all shipments + +This method is ideal for: +- Initial onboarding of many shipments +- Regular batch imports from other systems +- Scheduled tracking updates + + + Contact your Terminal49 account manager for the exact CSV format requirements and processing schedule. + + +## Trackable Reference Types + +Terminal49 supports tracking using three primary reference types: + + + +

The master bill of lading is issued by the ocean carrier and serves as the contract of carriage.

+ + **Format Examples:** + - Maersk: MAEU9736478 + - MSC: MSDUV4812756 + - CMA CGM: CMDUV0946721 + + **Best Practices:** + - MBL is typically the most reliable reference for tracking + - Include the carrier's SCAC code when submitting + - Some carriers may not make shipment data available immediately after issuing the MBL + + **Coverage:** + - Supported by all major ocean carriers + - Provides visibility from origin to destination + - Typically covers all containers in a shipment +
+ + +

A booking number is assigned by the carrier when space is reserved on a vessel.

+ + **Format Examples:** + - Maersk: MAEU785421 + - ONE: OOLU3456723 + - Hapag-Lloyd: HLCUEUR6703426 + + **Best Practices:** + - Use booking numbers to begin tracking earlier in the shipping lifecycle + - Some carriers include their SCAC code at the beginning of booking numbers + - Booking numbers may be replaced by bills of lading once issued + + **Coverage:** + - Available earlier than bills of lading + - May provide limited information until container assignment + - Supported by most major carriers (see Carrier Data Matrix for details) +
+ + +

A container number uniquely identifies a specific shipping container.

+ + **Format Examples:** + - MSCU1234567 + - CMAU7654321 + - OOLU8765432 + + **Best Practices:** + - Container numbers follow ISO 6346 format (4 letters + 7 numbers) + - Always include the carrier prefix (first 4 characters) + - For multi-container shipments, you'll need to track each container separately + + **Coverage:** + - Container level detail provides the most granular tracking + - Not supported by all carriers (check Carrier Data Matrix) + - May not be available until containers are assigned to a booking +
+
+ + + House Bill of Lading (HBL) numbers issued by freight forwarders are not directly supported for tracking. You must use the Master Bill of Lading (MBL) from the ocean carrier. + + +## Tracking Request API Details + +When using the API to create tracking requests, you can provide a wealth of additional information to enhance your tracking experience: + +### Basic Required Parameters + +```json +{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } +} +``` + +### Optional Organizational Parameters + + + An array of your own reference numbers like purchase orders, SKUs, or internal IDs that help you identify this shipment in your systems + + + + Tags for categorizing shipments (e.g., "priority", "fragile", "q2-2023") that can be used for filtering + + +### Relationship Parameters + +You can associate the shipment with a customer by establishing a relationship: + +```json +{ + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + } + } +} +``` + + + The customer must be created first using the Parties API before it can be referenced in a tracking request. + + +### Example Scenarios + + + + ```json + { + "data": { + "attributes": { + "request_type": "booking", + "request_number": "OOLU8324567", + "scac": "OOLU" + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Booking numbers are typically issued by the carrier early in the shipping process + - Using booking numbers allows you to start tracking earlier in the shipping lifecycle + - Booking number format varies by carrier (e.g., OOLU prefix for ONE Line bookings) + + + + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "scac": "MSCU", + "ref_numbers": [ + "PO12345", + "HBL12345", + "INV9876" + ] + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Reference numbers can include purchase orders, house bills of lading, invoice numbers, etc. + - You can add multiple reference numbers to a single tracking request + - These numbers will be searchable later through the API and UI + - You can use reference numbers to link shipments to your internal systems + + + + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "shipment_tags": [ + "priority", + "electronics", + "q2-2023" + ] + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - Tags provide a flexible way to group and categorize shipments + - Common uses include priority levels, product categories, or time periods + - Tags can be used for filtering when listing shipments + - You can add or remove tags later using the Edit Shipment endpoint + + + + ```json + { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + ``` + + **Notes**: + - The customer must first be created using the Parties API + - The customer ID is used to link the shipment to the specific customer + - This association enables filtering shipments by customer + - Customer information will be included in webhook notifications when using appropriate includes + - See the [Adding Customer guide](/api-docs/in-depth-guides/adding-customer) for more details + + + +## Tracking Request Lifecycle + +When you submit a tracking request, it goes through several possible states: + +Tracking Request Lifecycle + +### Status Transitions + + + + Initial state when a tracking request is first submitted. Terminal49 is processing your request and attempting to locate the shipment. + + + The shipment was successfully located and is now being tracked. A shipment object has been created. + + + Unable to locate the shipment after multiple attempts. Check the failed_reason for details. + + + The bill of lading number appears valid, but the shipment data isn't yet available in the carrier's system. Terminal49 will continue checking daily. + + + Tracking has been manually or automatically stopped for this request. + + + +### Common Failure Reasons + + + + The carrier could not locate the shipment using the provided reference number. + + **Troubleshooting:** + - Verify the reference number format and carrier code + - For new bookings or bills of lading, wait 24-48 hours and try again + - Check if the carrier supports the reference type you're using + + + + The shipment is already being tracked in Terminal49. + + **Troubleshooting:** + - Search for the existing shipment using the List Shipments endpoint + - Use filters to find the shipment by reference number + - Use the existing shipment data instead of creating a new tracking request + + + + The reference number format does not match what the carrier expects. + + **Troubleshooting:** + - Check for typos and correct formatting + - Ensure you're including any required prefixes + - Verify with shipping documents that the number is correct + + + + Terminal49 has attempted to locate the shipment multiple times without success. + + **Troubleshooting:** + - Verify the reference number and carrier are correct + - For new shipments, wait a few days and submit a new tracking request + - Consider using a different reference type if available + + + +For a complete list of failure reasons and detailed troubleshooting, see the [Tracking Request Lifecycle guide](/api-docs/in-depth-guides/tracking-request-lifecycle). + +## Tracking Request Webhooks + +Terminal49 sends webhook notifications at key points in the tracking request lifecycle: + + + + Sent when a tracking request successfully locates a shipment and begins tracking. + + **Payload includes:** + - Tracking request details (ID, reference number, type) + - Newly created shipment information + - Container details when included in the request + + **Common uses:** + - Update your systems to indicate active tracking + - Store the shipment ID for future API calls + - Notify internal teams that tracking has begun + + + + Sent when a tracking request fails to locate a shipment after all retry attempts. + + **Payload includes:** + - Tracking request details + - Failure reason code + - Detailed error message + + **Common uses:** + - Trigger alerts for manual investigation + - Implement automatic retry strategies based on failure reason + - Update internal systems with failure status + + + + Sent when a bill of lading number is valid but the shipment is not yet in the carrier's system. + + **Payload includes:** + - Tracking request details + - Status information + + **Common uses:** + - Update systems to show "pending" status + - Set reminders to check status in a few days + - Notify stakeholders about the delay + + + + Sent when tracking has been stopped for a request. + + **Payload includes:** + - Tracking request details + - Reason for stopping (if available) + + **Common uses:** + - Update internal systems + - Archive shipment records + - Trigger cleanup processes + + + +Learn more about setting up and managing webhooks in the [Webhooks guide](/api-docs/in-depth-guides/webhooks). + +## Managing Tracking Requests + +### Listing and Searching Tracking Requests + +Use the tracking requests index endpoint to retrieve existing tracking requests: + +``` +GET /v2/tracking_requests +``` + +Filter by various attributes: + +``` +GET /v2/tracking_requests?filter[status]=pending +GET /v2/tracking_requests?filter[request_number]=MAEU9736478 +GET /v2/tracking_requests?filter[created_at][gt]=2023-01-01T00:00:00Z +``` + +Sort results: + +``` +GET /v2/tracking_requests?sort=-created_at +``` + +Include related resources: + +``` +GET /v2/tracking_requests?include=shipment +``` + +### Starting and Stopping Tracking + +#### Starting Tracking + +To start tracking a new shipment, use the create tracking request endpoint: + +``` +POST /v2/tracking_requests +``` + +#### Stopping Tracking + +To stop tracking a shipment, use the stop tracking endpoint: + +``` +POST /v2/shipments/{shipment_id}/stop_tracking +``` + + + Stopping tracking means Terminal49 will no longer fetch updates for this shipment. This is useful for completed shipments or when you no longer need to monitor a shipment. + + +## Testing and Development + +Terminal49 provides test tracking numbers for development and integration testing: + + + + Use these special test numbers with the SCAC code "TEST" to simulate different scenarios: + + - `TEST-TR-SUCCEEDED` - Always succeeds and creates a test shipment + - `TEST-TR-FAILED` - Always fails with a "not_found" reason + - `TEST-TR-AWAITING-MANIFEST` - Simulates an awaiting manifest state + - `TEST-TR-DELAYED` - Simulates a delayed processing state before succeeding + + Example: + ```json + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + } + ``` + + + + Terminal49 provides a sandbox environment for testing your integration without affecting production data: + + - Base URL: `https://sandbox-api.terminal49.com/v2` + - Requires a separate sandbox API key + - Simulates real-world scenarios with predictable outcomes + - Supports all the same endpoints as production + + Contact your Terminal49 account manager to request sandbox access. + + + +## Best Practices + +1. **Provide SCAC codes**: Always include the carrier's SCAC code to improve tracking success rates. + +2. **Use webhooks**: Set up webhook notifications instead of polling for status updates. + +3. **Handle retries**: Implement a reasonable retry strategy for failed tracking requests. + +4. **Track early**: Start tracking as soon as you have a booking number or bill of lading. + +5. **Add reference data**: Include your own reference numbers and tags to make shipments easily identifiable. + +6. **Test thoroughly**: Use the test tracking numbers to validate your integration. + +7. **Track by MBL when possible**: Master Bills of Lading generally provide the most reliable tracking. + +8. **Check carrier support**: Refer to the Carrier Data Matrix to verify which tracking methods are supported by each carrier. + +## Related Resources + + + + Detailed API documentation for the tracking request endpoint. + + + Learn how to configure webhooks to receive tracking updates. + + + See which carriers support which tracking methods. + + + Learn how to associate customers with tracking requests. + + diff --git a/docs/docs.json b/docs/docs.json index 568b99fc..a0da3d93 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -17,7 +17,8 @@ "group": "Overview", "pages": [ "api-docs/overview", - "api-docs/in-depth-guides/quickstart" + "api-docs/in-depth-guides/quickstart", + "api-docs/getting-started/tracking" ] }, { @@ -26,7 +27,6 @@ "api-docs/in-depth-guides/use-cases", "api-docs/in-depth-guides/polling-vs-webhooks", "api-docs/in-depth-guides/webhooks", - "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/in-depth-guides/adding-customer", From 015c092b3b91ac10a3b2a2f32fcfb9e1dbd01fed Mon Sep 17 00:00:00 2001 From: Akshay Dodeja Date: Tue, 15 Jul 2025 23:23:38 -0400 Subject: [PATCH 42/42] API documentation improvements: restructure guides, add new content, and cleanup --- .cursor/rules/core.mdc | 62 + .tool-versions | 1 + Documentation-improvement-plan.md | 331 + capabilities-overview.mdx | 181 + docs-improvement-research.md | 59 + docs/api-docs/getting-started/tracking.mdx | 88 +- .../improved/tracking-request-example.mdx | 640 - .../in-depth-guides/api-quick-start.mdx | 463 - docs/api-docs/in-depth-guides/quickstart.mdx | 2 - .../in-depth-guides/rate-limiting.mdx | 335 + .../{common-workflows.mdx => use-cases.mdx} | 8 +- docs/home.mdx | 35 - docs/mint.json | 213 - docs/openapi.json.bak | 15105 ++++++++++++++++ endpoint-documentation-template.md | 102 + fix_json.py | 44 + fix_json_thorough.py | 46 + fix_openapi.py | 35 + json-api-guide.mdx | 255 + mintlify-endpoint-documentation-template.mdx | 151 + sample-create-tracking-request.mdx | 308 + sample.json | 100 + 22 files changed, 17163 insertions(+), 1401 deletions(-) create mode 100644 .cursor/rules/core.mdc create mode 100644 .tool-versions create mode 100644 Documentation-improvement-plan.md create mode 100644 capabilities-overview.mdx create mode 100644 docs-improvement-research.md delete mode 100644 docs/api-docs/improved/tracking-request-example.mdx delete mode 100644 docs/api-docs/in-depth-guides/api-quick-start.mdx create mode 100644 docs/api-docs/in-depth-guides/rate-limiting.mdx rename docs/api-docs/in-depth-guides/{common-workflows.mdx => use-cases.mdx} (98%) delete mode 100644 docs/home.mdx delete mode 100644 docs/mint.json create mode 100644 docs/openapi.json.bak create mode 100644 endpoint-documentation-template.md create mode 100644 fix_json.py create mode 100644 fix_json_thorough.py create mode 100644 fix_openapi.py create mode 100644 json-api-guide.mdx create mode 100644 mintlify-endpoint-documentation-template.mdx create mode 100644 sample-create-tracking-request.mdx create mode 100644 sample.json diff --git a/.cursor/rules/core.mdc b/.cursor/rules/core.mdc new file mode 100644 index 00000000..545144e3 --- /dev/null +++ b/.cursor/rules/core.mdc @@ -0,0 +1,62 @@ +--- +description: +globs: +alwaysApply: true +--- +# General Principles + +### Accuracy and Relevance + +- Systematically ensure that all generated responses directly align with user requests. +- Thoroughly understand, gather, or validate relevant context using appropriate built-in tools before responding. + +### Validation Over Modification + +- Prioritize extensive validation and comprehensive understanding of existing code and its operational context before introducing alterations or creating new artifacts. + +### Safety-First Execution + +- Perform in-depth analyses of dependencies and interconnected components. +- Achieve comprehensive comprehension of workflows and meticulously evaluate potential risks before implementing modifications. +- Maintain clear and proactive communication regarding identified risks, dependencies, or implications. + +### User Intent Comprehension + +- Employ analytical methods to explicitly and accurately confirm user intentions. +- Evaluate context and tools rigorously to ensure precise fulfillment of user requirements. +- Continuously validate adherence to user-defined or inferred objectives. + +### Mandatory Validation Protocol + +- Enforce rigorous validation, verification, and double-checking as fundamental operational practices. +- Prioritize validation to ensure accuracy, prevent errors, and maintain a consistently high standard of quality assurance. + +### Reusability Mindset + +- Foster a strategic orientation towards reusability in decision-making and operational processes. +- Proactively utilize built-in tools (`codebase_search`, `grep_search`, and the command `tree -L {depth} | cat`) to identify and leverage pre-existing solutions. +- Advocate for reusability to enhance consistency, minimize redundancy, and improve maintainability. + +### Contextual Integrity and Documentation + +- Do not rely solely on user-generated documentation (e.g., README files, inline documentation) as authoritative sources. +- Use documentation primarily as supplementary context or as a validation and verification resource. + +# Tool and Behavioral Guidelines + +### Rigorous Path Validation for File Operations + +- Conduct repetitive and thorough validation of file paths using commands such as `pwd` and `tree -L {depth} | cat` to ensure correctness. +- Explicitly confirm the working directory and clearly specify the `target_file` attribute as the file path relative to the workspace root for all file edits and creations. +- Evaluate potential reusable resources before initiating file operations. + +### Systematic Use of the `tree -L {depth} | cat` Command + +- Regularly use the `tree -L {depth} | cat` command for a comprehensive visualization of directory structures. +- Critically analyze the visualized structure to identify existing reusable resources, ensuring accurate and deliberate placement of file operations. + +### Mandatory Replacement of `read_file` Tool with Terminal Command + +- Never utilize the built-in `read_file` tool under any circumstances. +- Always use the terminal command `cat ` to read file contents. +- Conduct detailed analyses of file content via the terminal command to determine reusability, dependencies, and logical structures, ensuring thorough context acquisition before making modifications. diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..dcf61b7d --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 22.2.0 diff --git a/Documentation-improvement-plan.md b/Documentation-improvement-plan.md new file mode 100644 index 00000000..4aef2ac6 --- /dev/null +++ b/Documentation-improvement-plan.md @@ -0,0 +1,331 @@ +Below is a sequential, step-by-step plan for improving Terminal49’s API documentation, along with suggested content additions. Each step is described in practical terms, so a developer or an LLM can parse and follow the instructions easily. + + +## Plan Progress tracking for AI Agent +Use this area to create a plan checklist and work through the plan + +- [ ] step 1 +- [ ] step 2 + + +⸻ + +Step 1: Conduct a Documentation Audit + +What to do: + 1. Review all existing pages – Identify incomplete or unclear endpoint references, broken links, missing parameters, or unaddressed error responses. + 2. Gather feedback or known user issues – Collect any existing support tickets, GitHub issues, or user comments about the docs. + 3. Compile a gap report – List each endpoint or topic where information is missing or unclear. + +Why this step matters: + • Ensures we have a complete view of what’s working and what needs improvement. + • Prevents duplicating content or missing critical fixes. + +Example content addition: + • “We found that the GET /containers documentation has no example for a container with a customs hold. We also discovered the ‘Webhook Retries’ page references an old link. We’ll address both.” + +⸻ + +Step 2: Update and Expand Endpoint Documentation + +What to do: + 1. Add multi-scenario examples – Show how each endpoint behaves under different conditions (e.g., container delayed, container cleared, container picked up). + 2. Provide request and response pairs – For each endpoint, include at least one full request body and the corresponding JSON response. + 3. Explain key fields in plain language – Add short, friendly definitions (e.g., “last_free_day = The final date to pick up the container without incurring storage fees”). + 4. Document all HTTP status codes – Include typical success (200/201) responses, as well as error codes (e.g., 400, 422, 500). + +Why this step matters: + • Saves developers and non-developers time by showing them exactly what data to expect. + • Reduces guesswork about how the API handles edge cases. + +Example content addition: + +### GET /containers/:id + +**Purpose:** Retrieve details of a specific container. + +**Example Request:** +```bash +GET /containers/ABC123 +Authorization: Bearer + +Example Response (Container with Customs Hold): + +{ + "data": { + "id": "ABC123", + "type": "container", + "attributes": { + "status": "on_hold", + "hold_reason": "Customs Inspection", + "last_free_day": "2025-03-25", + "pickup_location": "Port of Oakland Terminal 3", + ... + } + } +} + +Notes: + • status can be in_transit, arrived, on_hold, or picked_up. + • hold_reason is returned only if status is on_hold. + • last_free_day indicates the last day to avoid demurrage charges. + +--- + +## Step 3: Create a “Capabilities Overview” Page + +**What to do:** +1. **Summarize Terminal49’s functionality** – Provide a high-level, non-technical description of major features (tracking ocean shipments, retrieving events, data standardization). +2. **Highlight key endpoints** – Offer a quick link or table referencing the main endpoints (e.g., Tracking Requests, Containers, Shipping Lines). +3. **Explain benefits in business terms** – Emphasize how this data helps reduce manual tracking, avoid fees, and automate notifications. + +**Why this step matters:** +- Non-developers (like operations managers) can understand the scope of what Terminal49 offers. +- Helps new users grasp the bigger picture before diving into endpoint specifics. + +**Example content addition:** +```markdown +# Capabilities Overview + +Terminal49’s API provides end-to-end container tracking and logistics data. Key capabilities include: + +- **Real-Time Event Notifications**: Get alerted on arrivals, holds, vessel departures, and more. +- **Automated Updates**: Standardized data from multiple carriers, terminals, and rail lines. +- **Reference Data**: Access shipping lines, ports, and terminal information without manual lookups. + +**Why It Matters** +- **Reduce Manual Work**: Eliminate the need for manual carrier websites and phone calls. +- **Prevent Extra Fees**: Track Last Free Day changes in real time to avoid demurrage charges. +- **Improve Visibility**: Combine container statuses from multiple carriers in one place. + + + +⸻ + +Step 4: Develop a “Quick Start” Onboarding Guide + +What to do: + 1. Focus on event-driven architecture – Introduce webhooks early, explaining how to register them and why they are more efficient than polling. + 2. Show a minimal end-to-end example – Include code for creating a Tracking Request, receiving a webhook event, and verifying the data. + 3. Provide test numbers – Show how to simulate a real workflow with test SCAC or container IDs. + +Why this step matters: + • Gives beginners a fast path to seeing how data flows from Terminal49 to their system. + • Encourages best practices (i.e., webhooks) from the start. + +Example content addition: + +# Quick Start Guide + +## 1. Get Your API Key +Sign up on Terminal49, navigate to your account settings, and copy your `API_KEY`. + +## 2. Create Your First Tracking Request +Send a POST request to `/tracking_requests`: +```bash +curl -X POST https://api.terminal49.com/tracking_requests \ + -H "Authorization: Bearer " \ + -d '{ + "data": { + "type": "tracking_request", + "attributes": { + "scac": "TEST", + "container_number": "TEST-TR-SUCCEEDED" + } + } + }' + +This will initiate a track-and-trace request. Using "TEST-TR-SUCCEEDED" ensures a successful mock response. + +3. Set Up a Webhook + +Register your webhook endpoint: + +curl -X POST https://api.terminal49.com/webhooks \ + -H "Authorization: Bearer " \ + -d '{ + "data": { + "type": "webhook", + "attributes": { + "url": "https://yourdomain.com/t49-webhook", + "event_types": ["tracking_request.succeeded", "container.arrived"] + } + } + }' + +4. Verify the Webhook Event + +You will receive a JSON POST at https://yourdomain.com/t49-webhook once the container status updates. Now you’re all set! + +--- + +## Step 5: Add a “Polling vs. Webhooks” Section + +**What to do:** +1. **Explain both approaches** – Show how to set up a periodic poll, then highlight the advantages of webhooks (lower overhead, real-time updates). +2. **Discuss trade-offs** – Provide examples of when polling might be acceptable (simple prototypes, no real-time requirement). + +**Why this step matters:** +- Clarifies best practice for new integrators. +- Reduces confusion about which model to implement for production systems. + +**Example content addition:** +```markdown +# Polling vs. Webhooks + +## Polling +- **How It Works**: Your system calls Terminal49’s API at regular intervals for updates. +- **Pros**: Simple to implement initially. +- **Cons**: Risk of missing urgent events, higher API usage, potential rate-limit issues. + +## Webhooks +- **How It Works**: Terminal49 sends updates to your registered endpoint in real time. +- **Pros**: Reduced overhead, immediate notifications, best for large-scale or time-sensitive operations. +- **Cons**: Requires setting up and maintaining a secure, publicly accessible endpoint. + + + +⸻ + +Step 6: Create Tutorials (Hands-On Guides) + +What to do: + 1. Implement short, focused tutorials – E.g., Building a “Where’s My Container?” Dashboard, Sending Delay Alerts via Email, etc. + 2. Show code in common languages – Provide examples in Python, Node.js, or another popular stack. + 3. Add screenshots or diagrams – Visuals help non-technical team members and managers follow along. + +Why this step matters: + • Offers real-world use cases that demonstrate how to stitch endpoints together. + • Helps new users quickly build something tangible. + +Example content addition: + +# Tutorial: Building a “Where’s My Container?” Dashboard + +In this tutorial, you’ll: +1. Create tracking requests. +2. Fetch container statuses. +3. Display status updates on a simple web page. + +## Prerequisites +- Node.js installed +- Basic HTML knowledge + +... + +## Step 3: Fetch Container Status +Use this endpoint in your Node.js backend: +```js +const axios = require('axios'); + +async function getContainer(containerId, apiKey) { + const resp = await axios.get( + `https://api.terminal49.com/containers/${containerId}`, + { headers: { Authorization: `Bearer ${apiKey}` } } + ); + return resp.data; +} + +… + +--- + +## Step 7: Add Industry-Specific Guides + +**What to do:** +1. **Create pages targeting different verticals** – Freight forwarders, importers, 3PLs, trucking companies, etc. +2. **Highlight relevant endpoints or workflows** – E.g., *How a freight forwarder can bulk-track multiple containers*, *How drayage companies use `last_free_day` to optimize pickups*. +3. **Use minimal code, more workflow diagrams** – Because these guides often serve operations managers or non-developers. + +**Why this step matters:** +- Demonstrates how to leverage the API’s features for specific business goals. +- Bridges the knowledge gap for less technical readers. + +**Example content addition:** +```markdown +# Terminal49 for Freight Forwarders + +**Common Use Cases**: +- Consolidate multiple container statuses in your TMS. +- Track containers across different carriers and terminals. + +**Key Endpoints**: +- `POST /tracking_requests`: Bulk create tracking for many containers at once. +- `GET /containers`: Retrieve statuses of all active shipments. + +**Recommended Workflow**: +1. Import your container list into Terminal49 via `/tracking_requests`. +2. Receive real-time updates through your custom webhook. +3. Surface any exceptions (e.g., holds, delays) in your TMS or an internal dashboard. + + + +⸻ + +Step 8: Introduce FAQ & Troubleshooting Pages + +What to do: + 1. Aggregate common questions – E.g., “Why is my container not updating?”, “How do I handle a 422 error?” + 2. Provide immediate answers – Link to relevant documentation sections if needed. + 3. Encourage user contributions – Let users submit new questions or solutions via GitHub issues or support emails. + +Why this step matters: + • Reduces repetitive support queries. + • Offers quick fixes for frequent integration issues. + +Example content addition: + +# FAQ & Troubleshooting + +### Q: Why is my container not updating in real-time? +**A:** Make sure you have a webhook registered. Polling for updates might cause delays. See the [Polling vs. Webhooks](/docs/polling-vs-webhooks) page. + +### Q: I received a 422 error when creating a tracking request. What does this mean? +**A:** Typically, this indicates a duplicate request or invalid container number. Double-check the container ID and SCAC. See [Tracking Request Errors](/docs/tracking-requests#errors). + + + +⸻ + +Step 9: Refine Structure & Navigation + +What to do: + 1. Group related content – Ensure “Getting Started,” “API Reference,” “Tutorials,” “Guides,” and “FAQ” each have intuitive sections. + 2. Add clear top-level navigation – For example: + • Overview + • Getting Started (Quick Start + Webhook Setup + FAQ) + • Reference (Endpoints) + • Tutorials + • Use Case Guides + 3. Cross-link – Insert “See also” links in relevant pages to unify the documentation. + +Why this step matters: + • Streamlines the user journey so they can quickly find relevant material. + • Encourages discovery of advanced features or best practices. + +Example content addition: + • “At the end of the Quick Start Guide, add a link: ‘Want to learn more? Check out our Building an Alert System tutorial.’” + +⸻ + +Step 10: Gather Feedback & Iterate + +What to do: + 1. Solicit user input – Encourage readers to open GitHub issues or submit feedback forms. + 2. Monitor analytics – Track page views, time on page, and popular search terms to see if users find what they need. + 3. Make incremental improvements – Update docs regularly based on feedback, new features, or changes in the API. + +Why this step matters: + • Keeps the documentation “alive” and responsive to user needs. + • Avoids stagnation as the API evolves or new best practices emerge. + +Example content addition: + • “Add a footer note: ‘Have questions or suggestions? Submit feedback here.’” + +⸻ + +Final Thoughts + +This 10-step plan provides a systematic way to audit, enhance, and expand Terminal49’s API documentation. By creating richer endpoint references, tailored onboarding, real-world tutorials, and industry-specific guides, you’ll ensure both technical and non-technical users can harness the full power of your platform. + +Each step includes a sample of the content to add or revise, making it easy for a developer or an LLM to understand and implement. By following these steps in order, you’ll continuously improve your documentation without overwhelming users or maintainers. \ No newline at end of file diff --git a/capabilities-overview.mdx b/capabilities-overview.mdx new file mode 100644 index 00000000..a439e077 --- /dev/null +++ b/capabilities-overview.mdx @@ -0,0 +1,181 @@ +--- +title: Terminal49 API Capabilities Overview +description: A comprehensive overview of all features and capabilities available through the Terminal49 API. +--- + +# Terminal49 API Capabilities + +Terminal49's API provides end-to-end ocean freight visibility through a collection of endpoints that deliver real-time tracking data, reference information, and event notifications. This overview will help you understand what's possible and how each feature fits into your logistics workflows. + +## Core Capabilities + + + + Track containers and shipments across their entire journey using bill of lading, booking, or container numbers. + + + Receive real-time updates via webhooks when shipment status changes, from origin to destination. + + + Access standardized data about carriers, ports, terminals, and more for consistent reporting. + + + Get comprehensive container information including status, location, free time, and holds. + + + +## End-to-End Visibility + +Terminal49 provides visibility across the entire container journey: + + + + Track empty container pickup, loading, and departure from origin port. + + + Follow vessel movements and receive ETA updates throughout the journey. + + + Get notified when the vessel arrives, berths, and when containers are discharged. + + + Monitor container availability, customs status, holds, and last free day information. + + + Track rail departures, arrivals, and final delivery events to inland destinations. + + + +## Key Differentiators + + + + Terminal49 standardizes data from different carriers into a single consistent format. For example, each carrier might report a container discharge event differently, but Terminal49 normalizes these into the same event type (`container.transport.vessel_discharged`) with standardized timestamps and location formats. + + **Business Benefit**: Build once, track across all carriers without custom integration for each shipping line. + + + + Instead of constantly polling for updates, Terminal49 notifies you via webhooks when meaningful events occur. This includes status changes, ETA updates, terminal holds, and changes to last free day. + + **Business Benefit**: Respond immediately to delays, holds, or availability changes, reducing demurrage costs and improving planning. + + + + Terminal49 actively monitors and notifies you of changes to Last Free Day (LFD) - the date by which containers must be picked up to avoid storage fees. This data is often buried in terminal websites and subject to change. + + **Business Benefit**: Avoid costly demurrage fees by prioritizing pickups based on accurate LFD information. + + + + The API provides comprehensive information about holds (customs, freight, terminal) and other exceptions that may delay pickup. + + **Business Benefit**: Proactively address holds and plan alternative shipments when delays occur. + + + +## How It Works + + + ![Terminal49 API Flow Diagram](/images/api-flow-diagram.png) + + +1. **Submit tracking requests** using bill of lading, booking, or container numbers +2. **Terminal49 monitors** these shipments by aggregating data from multiple sources +3. **Your systems receive updates** through webhooks when any change occurs +4. **Query additional information** as needed using the REST API endpoints + +## Available Data Points + +Here are key data points available through the Terminal49 API: + + + + - Bill of lading numbers + - Booking numbers + - Port of loading/discharge + - Shipper/consignee information + - Vessel assignments + - Estimated dates (departure, arrival) + - Associated containers + + + + - Container numbers and size/type + - Current location and status + - Last free day information + - Terminal availability + - Customs/terminal/freight holds + - Transport events history + - Pickup appointments + + + + - Vessel departures and arrivals + - Container loading/discharge + - Customs and terminal holds + - Availability changes + - Last free day updates + - Rail movements + - Terminal cutoffs + + + + - Shipping lines (with SCAC codes) + - Terminals and ports + - Vessels and voyages + - Metro areas + - Standardized location formats + + + +## Common Use Cases + + + + Connect Terminal49 to your Transportation Management System for real-time visibility without manual tracking. + + + + Build automated alerts to notify customers about shipment status changes, delays, or container availability. + + + + Prioritize container pickups based on last free day information to avoid demurrage charges. + + + + Identify and resolve customs holds, freight issues, or delays that require intervention. + + + + Build real-time dashboards showing shipment status across your supply chain. + + + + Plan warehouse staffing and inventory based on accurate container arrival predictions. + + + +## Getting Started + +Ready to start integrating with Terminal49? Here's how to begin: + + + + [Sign up for Terminal49](https://app.terminal49.com/signup) and get your API key from the [developer portal](https://app.terminal49.com/developers/api-keys). + + + Follow our [quick start guide](/api-docs/getting-started/tracking-shipments-and-containers) to submit your first tracking request. + + + Configure [webhooks](/api-docs/in-depth-guides/webhooks) to receive real-time updates about your shipments. + + + Browse our [API reference](/api-docs/api-reference) to discover all available endpoints. + + + + + Our team is here to help you get the most out of Terminal49. Contact us at [support@terminal49.com](mailto:support@terminal49.com) with any questions about our API capabilities. + diff --git a/docs-improvement-research.md b/docs-improvement-research.md new file mode 100644 index 00000000..59637eab --- /dev/null +++ b/docs-improvement-research.md @@ -0,0 +1,59 @@ +Strategic Improvement Plan for Terminal49 API Documentation + +Enhance Endpoint Documentation with Comprehensive Examples + +Each API endpoint should have clear, multi-scenario examples to guide users. Currently, the documentation provides basic JSON payload samples (often truncated for brevity) , but it can be enriched with additional examples covering different states and outcomes. We will: + • Add multiple example responses for various states – For instance, a Shipment endpoint could show a container in transit, one that has arrived with holds, and one after pickup. This gives users insight into how fields like status, holds, or dates appear in different scenarios. Similarly, for Tracking Requests, include examples for a pending request, a succeeded tracking (with linked shipment created), and a failed attempt (with error information). + • Include request and response pairs – Show the request payload and the full JSON response for each example. This helps developers and non-developers see the input-output clearly. For example, demonstrating a tracking_requests POST and the resulting shipment data (including what a “pending” vs “active” tracking looks like) makes the process concrete. + • Ensure consistency and clarity – Use realistic data in examples (e.g. sample BOL numbers, SCACs, timestamps) that reflect real-world values. Avoid heavy jargon in field descriptions; instead, add brief comments or notes explaining what key fields mean in plain language (e.g. “last_free_day: Date by which container must be picked up to avoid storage fees”). This caters to logistics operators and management who may not be familiar with raw JSON details. + +Providing richer examples will make the API reference more self-explanatory. Users can quickly grasp how data changes with different conditions, reducing trial-and-error during integration. It also benefits non-developers by illustrating outcomes in an accessible way, rather than abstract descriptions. + +Expand Education on All API Capabilities + +Beyond the endpoint specs, the documentation should teach users about Terminal49’s full feature set and how to use it effectively. Currently, the docs touch on some use cases (e.g. highlighting data points like Last Free Day or fees and their use cases ), but we can broaden this educational content. Our plan: + • Comprehensive “Capabilities Overview” – Introduce a section that summarizes everything the API can do in layman’s terms. For example: “Terminal49 can track ocean shipments end-to-end, including vessel events, terminal events, rail movements, and customs holds. It also provides reference data (ports, shipping lines) and value-added data like standardized location names.” This overview should be written for a non-technical audience (heads of operations, management) to understand the possibilities without diving into endpoints. + • Deep-dive guides for specific features – Expand the existing guides to cover all important capabilities. For instance, create easy-to-follow explanations for things like Raw vs. Transport Events (what they are and when to use each), JSON API includes (using the include parameter to fetch related objects, building on the “Including Resources” guide), and Data normalization (how Terminal49 standardizes carrier data). Each guide should be scenario-driven and avoid unnecessary technical jargon. If terms like “JSONAPI” or “SCAC” are used, we’ll add a one-line explanation for clarity (e.g. “SCAC – a standardized carrier code ”). + • Visual aids and diagrams – Where helpful, include diagrams or flowcharts. For example, a diagram of the Tracking Request Lifecycle could illustrate the steps from request submission to various outcomes (succeeded, failed, awaiting manifest, etc.) and the creation of shipments/containers. The docs already reference a “Tracking Request Diagram” (which appears to be a placeholder) – we will create a clear diagram for it, making the event flow easier to digest  . Visual explanations will help IT leaders and operations managers quickly grasp processes without parsing text-heavy descriptions. + • Highlight lesser-known endpoints – Ensure that endpoints for ancillary data (like Shipping Lines, Ports, Terminals) are not just listed, but put into context. We might add notes like: “Use the Get Port by LOCODE endpoint to translate location codes into human-readable port names – useful for reporting.” This educates users that the API can serve as a single source of truth for reference data, not just tracking events. + +By broadening the educational material, every stakeholder – from a developer to a logistics manager – can learn what the API offers. The tone will remain educational and straightforward, focusing on “what you can do” with Terminal49 and “how to do it” in simple terms. This empowers users to take full advantage of all features, not just the basic tracking requests. + +Improve Onboarding and Emphasize Event-Driven Integration + +A smooth onboarding experience is crucial. We will create a clear getting-started path that guides users through initial setup and strongly encourages the preferred event-driven model using webhooks. The current documentation does mention that Terminal49 is an “event-based API” and that webhooks are more efficient than polling  , but we can make this guidance more prominent and actionable: + • Step-by-step onboarding guide – Combine and streamline the “Start Here” and “Quick Start” materials into a cohesive onboarding tutorial. This guide will walk a new user through obtaining API keys, creating a first tracking request, and setting up a webhook endpoint to receive updates. Instead of treating webhook setup as an optional or later step, we’ll integrate it early in the process. For example: after the user creates a tracking request (via a quickstart example), the next step in the guide is to register a webhook and see a sample notification. This reinforces best practices from the start. + • Emphasize webhooks (with explanations) – Clearly explain why an event-driven approach is recommended. We’ll add a short comparison: polling (periodically calling the API for updates) vs. webhooks (receiving push notifications). Explain that polling is simpler to implement initially but can miss real-time updates and put load on your systems, whereas webhooks deliver instant notifications and are more efficient . We will keep this non-judgmental and educational, e.g. “For testing or simple integrations you might poll the API, but in production we strongly encourage webhooks to ensure you don’t miss time-sensitive updates like terminal holds or ETA changes.” By outlining the trade-offs, IT leaders can make informed decisions and are more likely to invest in a webhook listener from the beginning. + • Webhook setup guidance – Provide code snippets and tools to lower the barrier to using webhooks. The docs already include a JavaScript snippet for receiving a webhook POST  ; we can expand on this with a minimal complete example (perhaps a simple express.js or Flask server snippet) that prints incoming events. Also, clarify webhook security best practices (e.g., validating payloads or using secret tokens) in simple terms. This gives developers a head start in implementing robust webhook consumers. + • Use of test events for onboarding – Incorporate Terminal49’s Test Numbers feature into onboarding. We will encourage users to try a test tracking number (with SCAC “TEST”) as their first tracking request, which triggers a predictable webhook event . For example, the guide can instruct: “Try tracking TEST-TR-SUCCEEDED with SCAC TEST – this will always generate a tracking_request.succeeded event to your webhook, so you can verify your endpoint is working.” By doing this, users get immediate feedback in their system (a fake shipment and a webhook call) without waiting for a real shipment update. This hands-on approach both teaches the event cycle and builds confidence that the integration is working as intended. + +Overall, the onboarding will be reworked to front-load the knowledge that Terminal49 excels as an event-driven system. By the end of the first tutorial, the user should have: an API key set up, a tracking request made, and a webhook that has received a sample notification. This clear path sets all types of users (developers and operations teams alike) on the right track from day one. + +Introduce Tutorials and Industry-Specific Integration Guides + +To cater to a broad audience and various use cases, we will add new documentation sections: Tutorials for common integration tasks, and Industry-Specific Guides for contextual best practices. These additions address the needs of both hands-on developers and decision-makers looking for tailored solutions. + • Step-by-step tutorials – These will be practical guides that walk through building something with the API. For example: “Building a Shipment Tracker in 30 Minutes” could guide a developer through retrieving a list of shipments and displaying status updates in a simple web app. Another tutorial might be “Sending Automatic Email Alerts for Delays”, showing how to combine webhooks and a notification service. The idea is to provide cookbook-style examples that are easy to follow. Such tutorials will use simple language and assume minimal prior knowledge, making them friendly for new developers or IT generalists. They’ll also implicitly teach API usage (e.g., how to paginate through shipments, how to filter events) in an applied manner. + • Language-specific quickstart examples – To further help developers, we can include brief guides or code samples for popular languages (Python, JavaScript, etc.). For instance, a Python script example for creating a tracking request and printing the result, or a Node.js example for setting up a webhook receiver. These can be separate pages or an appendix in the tutorials section. The tone remains educational, with clear comments, so even non-developers reading it can grasp what the code is doing. + • Industry-specific guides – Create documentation pages that speak directly to the use cases of different roles or sectors in logistics. For example: “Integrating Terminal49 for Freight Forwarders” can outline how a freight forwarder’s operations team might use the API to feed their Transportation Management System, emphasizing features like consolidating multiple carrier updates into one feed. “Terminal49 for Drayage and Trucking Companies” might focus on using Last Free Day and availability data to optimize dispatch and reduce fees. “Visibility for Retail Supply Chains” could show how an importer’s head of operations can get proactive alerts on delays. These guides will be written in mostly non-technical terms (with minimal code), focusing on workflow integration and ROI of the API data. We will include diagrams or flowcharts relevant to each use case, and small code/config snippets as needed (e.g., an example of a webhook payload for a container availability event in the drayage guide). + • Embed real-world examples and case studies – Where possible, include brief case-study style anecdotes in these guides (e.g., “A freight forwarder used Terminal49 to reduce manual track & trace work by 50%. Here’s how you can achieve that via the API’s consolidated tracking.”). This makes the documentation engaging for management-level readers and shows the practical impact of integration. + +By adding tutorials and specialized guides, we ensure that the documentation isn’t one-size-fits-all. Developers get hands-on guidance to shorten their learning curve, and logistics professionals see clear connections between the API features and their business needs. The tone in these sections will remain approachable and solution-focused, avoiding unnecessary technical depth unless required. + +Address Documentation Gaps and Continuous Improvements + +Before any radical restructuring, we will methodically address gaps in the current docs and make incremental improvements. The goal is to fix missing or unclear information and enhance clarity, ensuring completeness. Key actions include: + • Audit and fill missing details – We’ll perform a thorough audit of each documentation page (guides and reference) to identify omissions. For example, ensure every request parameter and response field is documented (or at least linked to an explanation) in the API reference. If any endpoints are undocumented or lack descriptions, we will add them. One area to check is error handling – document common error responses and what they mean (e.g., 422 Unprocessable Entity for duplicate tracking requests, as shown in an example ). We will also confirm if rate limits or quotas exist and document those for transparency. This audit-driven approach closes the small gaps that might confuse users. + • Improve clarity and reduce jargon – As we revise content, we will rewrite any overly technical or jargon-heavy sections in plain language. For instance, the docs reference being JSONAPI-compliant and the tradeoffs of that ; we can simplify this by directly stating how the data is structured and reassure users that libraries can help, without diving too deep into jargon. We will also add a glossary for unavoidable terms (BOL, SCAC, LFD, etc.) or tooltips in the documentation site for quick definitions. This ensures that even readers without deep industry or technical knowledge can follow along. + • Reorganize subtly for flow – We’ll review the navigation structure to see if users find information easily. The current layout (Getting Started, In-Depth Guides, Useful Info, then endpoint reference) is logical. We plan only light reorganization if needed – for example, we might group all “integration basics” guides (webhooks, retries, test numbers) under a clear section. Another improvement could be to introduce an FAQ/Troubleshooting page (collecting common questions like why a tracking request might fail, how to handle delayed updates, etc., many of which are answered across different pages). Rather than a drastic restructure, these tweaks will make the docs more user-friendly without disrupting the overall familiar layout. + • Leverage user feedback – We will make use of the “Suggest edits” and “Raise issue” links in the docs . Any recurring issues or suggestions raised by the community will be incorporated. For example, if users often ask for clarification on a particular webhook event, we’ll update that documentation section promptly. Establishing a feedback loop will keep the documentation living and responsive to stakeholder needs. + • Iterate before overhaul – Only after implementing the above improvements and gathering feedback will we evaluate if a more aggressive restructure is necessary. Our intention is to avoid major upheaval unless the current format proves truly insufficient. If needed, we might consider a restructure such as merging similar guides, or creating a separate high-level overview section for non-developers. However, the priority is to first enhance content within the existing framework. This way, we deliver immediate value (clarity and completeness) to users and minimize confusion from a sudden docs reorganization. + +By systematically closing gaps and iterating, the documentation will become more polished and complete. Small details like surfacing the Test Numbers page (which is currently hidden under “Useful Info”) at relevant points can greatly improve the user experience  . Each improvement will prioritize making the integration process easier and more foolproof for all parties. + +Conclusion and Next Steps + +This strategic plan focuses on making Terminal49’s API documentation clear, complete, and welcoming for everyone – from software developers to operations managers. By enhancing endpoint examples, expanding educational content, improving onboarding with an event-driven mindset, and adding tutorials and role-specific guides, we address both the “how-to” and the “why it matters.” Throughout these updates, we’ll maintain an educational, jargon-free tone so that readers don’t feel intimidated and can quickly find the information they need. + +The improvements are designed to be actionable in phases. First, we will enrich content (examples, guides, clarifications) which yields an immediate boost in usability. Next, we’ll gather feedback and make iterative adjustments. Should deeper structural changes be warranted, we’ll approach them once we’ve stabilized the content itself. This measured approach ensures continuity: existing users find the docs increasingly helpful rather than suddenly unfamiliar, and new users get a smooth introduction. + +By executing this plan, Terminal49 will provide a documentation experience on par with its modern API – one that prioritizes clarity, completeness, and ease of integration. Ultimately, better documentation will reduce support requests, accelerate integrations, and instill confidence in all stakeholders that they can successfully leverage Terminal49’s platform for their logistics operations.   \ No newline at end of file diff --git a/docs/api-docs/getting-started/tracking.mdx b/docs/api-docs/getting-started/tracking.mdx index c02f9dfc..1f2792a6 100644 --- a/docs/api-docs/getting-started/tracking.mdx +++ b/docs/api-docs/getting-started/tracking.mdx @@ -1,25 +1,24 @@ --- -title: Complete Tracking Guide -description: Learn about all methods of tracking shipments in Terminal49, tracking request lifecycle, and available API endpoints. -icon: "magnifying-glass-chart" +title: Tracking Guide +description: Learn about all methods of tracking shipments and containers in Terminal49, the tracking request lifecycle, and available API endpoints. +icon: "container-storage" --- - -# Complete Tracking Guide - + Tracking is a core function of Terminal49's platform. This guide covers all aspects of tracking shipments and containers, from initiating tracking through various methods to understanding the tracking request lifecycle and managing tracking data. + ## Tracking Methods -Terminal49 offers multiple ways to start tracking your shipments: +Terminal49 offers multiple ways to start tracking your shipments or containers: - + Use the Tracking Request API to programmatically submit container numbers, bills of lading, or booking numbers for tracking. - - Manually enter tracking numbers through Terminal49's web dashboard for immediate tracking. + + Manually enter tracking numbers or upload your spreadheet/CSV through Terminal49's web dashboard for immediate tracking. - + Email CSV files with multiple tracking references to quickly set up tracking for multiple shipments at once. @@ -40,39 +39,7 @@ This method is ideal for: [Detailed API Reference →](/api-docs/api-reference/tracking-requests/create-a-tracking-request) -### Dashboard Interface - -For manual tracking or lower volumes, Terminal49's web dashboard offers a user-friendly interface: - -1. Log in to your Terminal49 account -2. Navigate to the "Track" section -3. Enter your tracking number(s) and select the carrier -4. Click "Track" to begin monitoring - -This method is ideal for: -- Quick one-off tracking needs -- Small shipment volumes -- Testing or verification purposes -- Users who prefer a visual interface - -### Bulk CSV Upload - -For setting up tracking for multiple shipments at once: - -1. Prepare a CSV file with columns for tracking numbers, carrier codes, and optional reference numbers -2. Email the CSV file to imports@terminal49.com -3. Terminal49 will process the file and begin tracking all shipments - -This method is ideal for: -- Initial onboarding of many shipments -- Regular batch imports from other systems -- Scheduled tracking updates - - - Contact your Terminal49 account manager for the exact CSV format requirements and processing schedule. - - -## Trackable Reference Types +## Tracking Number Support Terminal49 supports tracking using three primary reference types: @@ -139,6 +106,39 @@ Terminal49 supports tracking using three primary reference types: House Bill of Lading (HBL) numbers issued by freight forwarders are not directly supported for tracking. You must use the Master Bill of Lading (MBL) from the ocean carrier. +### Dashboard Interface + +For manual tracking or lower volumes, Terminal49's web dashboard offers a user-friendly interface: + +1. Log in to your Terminal49 account +2. Navigate to the "Track" section +3. Enter your tracking number(s) and select the carrier +4. Click "Track" to begin monitoring + +This method is ideal for: +- Quick one-off tracking needs +- Small shipment volumes +- Testing or verification purposes +- Users who prefer a visual interface +### Bulk CSV Upload via Email + +For setting up tracking for multiple shipments at once: + +1. Prepare a CSV/spreadsheet file with your tracking details +2. Email the file to track@terminal49.com +3. Terminal49 will process the file and begin tracking all shipments + +For detailed instructions on CSV formatting options and requirements, see our [help documentation](https://help.terminal49.com/articles/4091681959-how-to-track-shipments-or-containers-via-email). + +This method is ideal for: +- Initial onboarding of many shipments +- Regular batch imports from other systems +- Scheduled tracking updates +- Teams that prefer working with spreadsheets + + + + ## Tracking Request API Details When using the API to create tracking requests, you can provide a wealth of additional information to enhance your tracking experience: diff --git a/docs/api-docs/improved/tracking-request-example.mdx b/docs/api-docs/improved/tracking-request-example.mdx deleted file mode 100644 index 48199b93..00000000 --- a/docs/api-docs/improved/tracking-request-example.mdx +++ /dev/null @@ -1,640 +0,0 @@ ---- -title: Create a Tracking Request -description: Submit a new tracking request to monitor shipments via bill of lading, booking number, or container number. -icon: "magnifying-glass-chart" ---- - -# Create a Tracking Request - -**Endpoint:** `POST /v2/tracking_requests` - -Creates a tracking request to begin monitoring a shipment or container. Terminal49 will attempt to locate the shipment or container using the provided reference number and carrier information. Once successfully located, tracking will commence, and you'll receive updates as the shipment progresses. - -## Endpoint Overview - -| Field | Value | -| ----- | ----- | -| **HTTP Method** | POST | -| **Endpoint** | `/v2/tracking_requests` | -| **Content-Type** | `application/vnd.api+json` | -| **Authentication** | API Key (required) | -| **Rate Limit** | [See rate limiting documentation](/api-docs/in-depth-guides/rate-limiting) | - -## Business Use Cases - -- **Initial Tracking Setup**: Start tracking a new container shipment when you receive a bill of lading or booking confirmation -- **Customer Onboarding**: Quickly set up tracking for a customer's shipments when they provide their shipping documents -- **Bulk Import**: Programmatically submit multiple tracking requests to monitor your entire inventory in transit -- **Proactive Monitoring**: Establish tracking early in the shipping cycle to receive timely updates - -## Request Parameters - -### Headers - -| Header | Value | Required | -| ------ | ----- | -------- | -| `Authorization` | `Token YOUR_API_KEY` | Yes | -| `Content-Type` | `application/vnd.api+json` | Yes | - -### Request Body - - - Resource type, must be `tracking_request` - - - - The type of reference number being provided: - - `container`: A container number (including carrier prefix) - - `bill_of_lading`: A Master Bill of Lading (MBL) number - - `booking`: A booking number provided by the carrier - - - - The identifier for the shipment or container. Format depends on the request_type: - - For containers: Typically 11 characters (e.g., MSCU1234567) - - For bill of lading numbers: Format varies by carrier but should match the format on shipping documents - - For booking numbers: Alphanumeric format provided by the carrier - - - - The Standard Carrier Alpha Code (SCAC) identifying the ocean carrier. While optional, providing this significantly improves tracking success rates. See the [Supported Carriers](/api-docs/useful-info/api-data-sources-availability) documentation for a list of supported SCAC codes. - - - - Optional array of reference numbers like purchase orders, customer references, or other identifiers associated with this shipment - - - - Optional array of tags to categorize shipments - - - - Optional ID of a party to associate with this tracking request as the customer - - - - Must be `party` if customer relationship is included - - -## Response - -Tracking requests are processed asynchronously. The initial response indicates the request has been accepted, but does not confirm successful tracking. To be notified of tracking status changes, configure a webhook for `tracking_request.succeeded` and `tracking_request.failed` events. - -### Response Status Codes - -| Status Code | Description | -| ----------- | ----------- | -| 201 | Successfully created tracking request | -| 401 | Unauthorized - Invalid API key | -| 422 | Validation error - Invalid request parameters | -| 429 | Rate limit exceeded | -| 500 | Server error | - -### Response Fields - - - Unique identifier for the tracking request. Store this to check status later. - - - - Resource type, always "tracking_request". - - - - The tracking reference number you provided. - - - - The tracking reference type you provided. - - - - The SCAC code of the carrier. - - - - Current status of the tracking request. Possible values: - - `pending`: Request is being processed - - `succeeded`: Container/shipment located and tracking established - - `failed`: Unable to track the shipment (see failed_reason) - - `awaiting_manifest`: Bill of lading is valid but not yet available in the carrier's system - - `tracking_stopped`: Tracking has been stopped for this request - - - - If status is "failed", this field provides the reason. Common values: - - `duplicate`: Shipment already exists in Terminal49 - - `not_found`: Shipping line couldn't find the tracking number - - `retries_exhausted`: Maximum retries reached without success - - `invalid_number`: Improperly formatted tracking number - - - See the [Tracking Request Lifecycle](/api-docs/in-depth-guides/tracking-request-lifecycle) guide for a complete list of failure reasons. - - - - - ISO-8601 timestamp of when the tracking request was created. - - - - ISO-8601 timestamp of when the tracking request was last updated. - - - - When the tracking request succeeds, this contains a reference to the created shipment. - - - - ID of the linked shipment that you can use to retrieve full shipment details. - - - - Always "shipment" for successful tracking requests. - - - - -## Example Scenarios - - - - ```json title="Request" - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU" - } - } - } - ``` - - ```json title="Response" - { - "data": { - "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T14:23:12Z", - "updated_at": "2023-05-15T14:23:12Z" - }, - "relationships": { - "tracked_object": { - "data": null - } - } - } - } - ``` - - **Notes:** - - The bill of lading is typically the most reliable identifier for tracking shipments. - - The `status` will initially be `pending` while Terminal49 processes your request. - - Once processing completes, you'll receive a webhook notification and the status will change to `succeeded` or `failed`. - - - - ```json title="Request" - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "container", - "request_number": "MSCU1234567", - "scac": "MSCU" - } - } - } - ``` - - ```json title="Response" - { - "data": { - "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", - "type": "tracking_request", - "attributes": { - "request_type": "container", - "request_number": "MSCU1234567", - "scac": "MSCU", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T15:45:22Z", - "updated_at": "2023-05-15T15:45:22Z" - }, - "relationships": { - "tracked_object": { - "data": null - } - } - } - } - ``` - - **Notes:** - - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. - - Container numbers should include the carrier prefix (e.g., MSCU for MSC containers). - - Container tracking can sometimes fail if the container is very new or not yet associated with a booking in the carrier's system. - - - - ```json title="Request" - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "ref_numbers": ["PO-12345", "INVOICE-789"], - "shipment_tags": ["priority", "electronics"] - }, - "relationships": { - "customer": { - "data": { - "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", - "type": "party" - } - } - } - } - } - ``` - - ```json title="Response" - { - "data": { - "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "ref_numbers": ["PO-12345", "INVOICE-789"], - "shipment_tags": ["priority", "electronics"], - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T16:12:34Z", - "updated_at": "2023-05-15T16:12:34Z" - }, - "relationships": { - "tracked_object": { - "data": null - }, - "customer": { - "data": { - "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", - "type": "party" - } - } - } - } - } - ``` - - **Notes:** - - Reference numbers and tags help you organize and search for shipments in your system. - - Customer association requires first [creating a party](/api-docs/api-reference/parties/create-a-party). - - These associations will be preserved when the shipment is created after a successful tracking request. - - - - ```json title="Request" - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "TEST-TR-SUCCEEDED", - "scac": "TEST" - } - } - } - ``` - - ```json title="Response" - { - "data": { - "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "TEST-TR-SUCCEEDED", - "scac": "TEST", - "status": "pending", - "failed_reason": null, - "created_at": "2023-05-15T16:30:45Z", - "updated_at": "2023-05-15T16:30:45Z" - }, - "relationships": { - "tracked_object": { - "data": null - } - } - } - } - ``` - - **Available Test Numbers:** - - `TEST-TR-SUCCEEDED` - Always succeeds and creates a test shipment - - `TEST-TR-FAILED` - Always fails with a "not_found" reason - - `TEST-TR-AWAITING-MANIFEST` - Simulates an awaiting manifest state - - `TEST-TR-DELAYED` - Simulates a delayed processing state before succeeding - - **Notes:** - - Using test numbers is perfect for development and testing your integration. - - Test numbers will trigger corresponding webhook events for complete testing. - - No actual tracking is performed with test numbers. - - - - ```json title="Request" - { - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "INVALID-FORMAT", - "scac": "UNKNOWN" - } - } - } - ``` - - ```json title="Response with validation error (Status: 422)" - { - "errors": [ - { - "status": "422", - "title": "Invalid SCAC", - "detail": "The SCAC 'UNKNOWN' is not supported.", - "source": { - "pointer": "/data/attributes/scac" - } - } - ] - } - ``` - - ```json title="Response with duplicate error (Status: 422)" - { - "errors": [ - { - "status": "422", - "source": { - "pointer": "/data/attributes/request_number" - }, - "title": "Unprocessable Entity", - "detail": "Request number 'MAEU9736478' with scac 'MAEU' already exists in a tracking_request with a pending or created status", - "code": "duplicate" - } - ] - } - ``` - - ```json title="Response with authentication error (Status: 401)" - { - "errors": [ - { - "status": "401", - "title": "Unauthorized", - "detail": "You need to provide a valid API key to access this resource." - } - ] - } - ``` - - **Common Errors:** - - Invalid or unsupported SCAC codes - - Improperly formatted request numbers - - Duplicate tracking requests (same number/SCAC combination) - - Invalid authentication credentials - - Rate limit exceeded - - - -## Webhook Events - -When creating a tracking request, you may receive the following webhook events: - - - - Sent when Terminal49 successfully locates your shipment and begins tracking it. The notification includes details about the created shipment and its containers. - - - - Sent when Terminal49 is unable to locate your shipment after all retry attempts. Includes the failed_reason to help troubleshoot. - - - - Sent when the bill of lading number is valid but the shipment data is not yet available in the carrier's system. Terminal49 will continue checking daily. - - - - Sent when tracking for this request has been manually or automatically stopped. - - - -## Code Samples - - - - ```bash - curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" \ - -d '{ - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU" - } - } - }' - ``` - - - ```javascript - const trackShipment = async () => { - try { - const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { - method: 'POST', - headers: { - 'Authorization': 'Token YOUR_API_KEY', - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: 'tracking_request', - attributes: { - request_type: 'bill_of_lading', - request_number: 'MAEU9736478', - scac: 'MAEU' - } - } - }) - }); - - const result = await response.json(); - - if (!response.ok) { - console.error('Error creating tracking request:', result.errors); - return; - } - - console.log('Tracking request created:', result.data.id); - - // Store the tracking request ID for future reference - const trackingRequestId = result.data.id; - - // You would typically configure a webhook to receive updates - // rather than polling for status changes - } catch (error) { - console.error('Error:', error); - } - }; - - trackShipment(); - ``` - - - ```python - import requests - import json - - api_key = 'YOUR_API_KEY' - url = 'https://api.terminal49.com/v2/tracking_requests' - - headers = { - 'Authorization': f'Token {api_key}', - 'Content-Type': 'application/vnd.api+json' - } - - payload = { - 'data': { - 'type': 'tracking_request', - 'attributes': { - 'request_type': 'bill_of_lading', - 'request_number': 'MAEU9736478', - 'scac': 'MAEU' - } - } - } - - try: - response = requests.post(url, headers=headers, data=json.dumps(payload)) - response.raise_for_status() # Raise exception for 4XX/5XX status codes - - result = response.json() - tracking_request_id = result['data']['id'] - - print(f'Tracking request created with ID: {tracking_request_id}') - print(f'Status: {result["data"]["attributes"]["status"]}') - - except requests.exceptions.HTTPError as err: - print(f'HTTP Error: {err}') - if response.status_code == 422: - errors = response.json().get('errors', []) - for error in errors: - print(f'Validation error: {error.get("detail")}') - except requests.exceptions.RequestException as err: - print(f'Request Error: {err}') - ``` - - - ```ruby - require 'net/http' - require 'uri' - require 'json' - - api_key = 'YOUR_API_KEY' - uri = URI.parse('https://api.terminal49.com/v2/tracking_requests') - - headers = { - 'Authorization' => "Token #{api_key}", - 'Content-Type' => 'application/vnd.api+json' - } - - payload = { - data: { - type: 'tracking_request', - attributes: { - request_type: 'bill_of_lading', - request_number: 'MAEU9736478', - scac: 'MAEU' - } - } - } - - begin - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - - request = Net::HTTP::Post.new(uri.request_uri, headers) - request.body = payload.to_json - - response = http.request(request) - - if response.code.to_i >= 200 && response.code.to_i < 300 - result = JSON.parse(response.body) - tracking_request_id = result['data']['id'] - - puts "Tracking request created with ID: #{tracking_request_id}" - puts "Status: #{result['data']['attributes']['status']}" - else - puts "HTTP Error: #{response.code}" - errors = JSON.parse(response.body)['errors'] rescue nil - if errors - errors.each do |error| - puts "Error: #{error['detail']}" - end - end - end - rescue => e - puts "Request failed: #{e.message}" - end - ``` - - - -## Related Resources - - - - Learn about the full lifecycle of a tracking request, including all possible statuses and failure reasons. - - - Learn how to set up webhooks to receive real-time notifications about your tracking requests. - - - Check the status of an existing tracking request. - - - Retrieve all tracking requests associated with your account. - - - -## Best Practices - -1. **Use webhooks instead of polling**: Set up webhook notifications for `tracking_request.succeeded` and `tracking_request.failed` events instead of repeatedly checking tracking request status. - -2. **Provide accurate carrier information**: Include the `scac` parameter whenever possible to improve tracking success rates. - -3. **Handle retries appropriately**: If a tracking request fails with certain errors (e.g., "not_found"), implement a retry strategy with increasing delays between attempts. - -4. **Store reference data**: Add meaningful reference numbers and tags to make it easier to find and organize shipments in your system. - -5. **Test with test numbers**: Use the test numbers (`TEST-TR-SUCCEEDED`, `TEST-TR-FAILED`) to validate your integration before using real tracking data. - -6. **Handle rate limits**: Implement proper rate limit handling by incorporating backoff strategies when you receive 429 responses. - -7. **Process webhooks idempotently**: Design your webhook handlers to be idempotent, as the same event may be delivered multiple times in rare circumstances. diff --git a/docs/api-docs/in-depth-guides/api-quick-start.mdx b/docs/api-docs/in-depth-guides/api-quick-start.mdx deleted file mode 100644 index 1190f36e..00000000 --- a/docs/api-docs/in-depth-guides/api-quick-start.mdx +++ /dev/null @@ -1,463 +0,0 @@ ---- -title: API Quick Start Guide -description: Get up and running with Terminal49's API in minutes - track containers, receive updates, and integrate with your systems. -icon: "rocket" ---- - -# API Quick Start Guide - -This guide will help you get started with Terminal49's API by walking through the most common workflow: tracking a shipment and receiving status updates. You'll learn both how to request data and how to receive real-time updates via webhooks. - -## Prerequisites - -Before you begin, you'll need: - -1. **A Terminal49 account** - [Sign up here](https://app.terminal49.com/register) if you don't have one -2. **An API key** - Find or create one in your [developer portal](https://app.terminal49.com/developers/api-keys) -3. **A tracking number** to monitor: - - Bill of Lading (BOL) number, Booking number, or Container number - - The [SCAC](https://en.wikipedia.org/wiki/Standard_Carrier_Alpha_Code) of the carrier (e.g., MAEU for Maersk) - - - Don't have a tracking number? Use our test data: `TEST-TR-SUCCEEDED` with SCAC `TEST` for immediate results. - - -## Step 1: Authentication - -All API requests require authentication using your API key. Include it in the Authorization header with the prefix `Token`: - -```bash -curl -X GET "https://api.terminal49.com/v2/shipments" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" -``` - - -```javascript -// Node.js with fetch -const headers = { - 'Authorization': `Token ${YOUR_API_KEY}`, - 'Content-Type': 'application/vnd.api+json' -}; - -const response = await fetch('https://api.terminal49.com/v2/shipments', { headers }); -``` - -```php -// PHP with cURL -$curl = curl_init(); -curl_setopt_array($curl, [ - CURLOPT_URL => "https://api.terminal49.com/v2/shipments", - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => [ - "Authorization: Token YOUR_API_KEY", - "Content-Type: application/vnd.api+json" - ], -]); -$response = curl_exec($curl); -``` - -```ruby -# Ruby with HTTParty -require 'httparty' - -headers = { - 'Authorization' => "Token YOUR_API_KEY", - 'Content-Type' => 'application/vnd.api+json' -} - -response = HTTParty.get("https://api.terminal49.com/v2/shipments", headers: headers) -``` - - -## Step 2: Create a Tracking Request - -The first step to track a shipment is to create a tracking request. This tells Terminal49 which shipment you want to track. - -**Workflow Example: Start tracking a container to get its ETA** - - -```bash -# Using cURL -curl -X POST "https://api.terminal49.com/v2/tracking_requests" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" \ - -d '{ - "data": { - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU" - } - } - }' -``` - -```javascript -// Using fetch (Node.js/Browser) -const response = await fetch('https://api.terminal49.com/v2/tracking_requests', { - method: 'POST', - headers: { - 'Authorization': `Token ${YOUR_API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: 'tracking_request', - attributes: { - request_type: 'bill_of_lading', - request_number: 'MAEU9736478', - scac: 'MAEU' - } - } - }) -}); - -const result = await response.json(); -console.log(result); -``` - -```php -// Using PHP -$curl = curl_init(); -$payload = json_encode([ - 'data' => [ - 'type' => 'tracking_request', - 'attributes' => [ - 'request_type' => 'bill_of_lading', - 'request_number' => 'MAEU9736478', - 'scac' => 'MAEU' - ] - ] -]); - -curl_setopt_array($curl, [ - CURLOPT_URL => "https://api.terminal49.com/v2/tracking_requests", - CURLOPT_RETURNTRANSFER => true, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HTTPHEADER => [ - "Authorization: Token YOUR_API_KEY", - "Content-Type: application/vnd.api+json" - ], -]); - -$response = curl_exec($curl); -$result = json_decode($response, true); -print_r($result); -``` - - -The response will include a tracking request ID and status: - -```json -{ - "data": { - "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "status": "pending", - "created_at": "2023-03-16T18:35:42Z", - "updated_at": "2023-03-16T18:35:42Z" - }, - "relationships": { - "tracked_object": { - "data": null - } - } - } -} -``` - - - The tracking request starts with status `pending`. Terminal49 will process it asynchronously, and it will change to `succeeded` or `failed`. - - -## Step 3: Check Tracking Request Status (Polling Method) - -To check if your tracking request has completed, you can poll the tracking request: - - -```bash -# Using cURL -curl -X GET "https://api.terminal49.com/v2/tracking_requests/b1f30140-c9cb-4354-b8e3-3d92a76289cf" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" -``` - -```javascript -// Using fetch -const trackingRequestId = 'b1f30140-c9cb-4354-b8e3-3d92a76289cf'; -const response = await fetch(`https://api.terminal49.com/v2/tracking_requests/${trackingRequestId}`, { - headers: { - 'Authorization': `Token ${YOUR_API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - } -}); - -const result = await response.json(); -console.log(`Tracking request status: ${result.data.attributes.status}`); -``` - - -When the request succeeds, it will include a relationship to the newly created shipment: - -```json -{ - "data": { - "id": "b1f30140-c9cb-4354-b8e3-3d92a76289cf", - "type": "tracking_request", - "attributes": { - "request_type": "bill_of_lading", - "request_number": "MAEU9736478", - "scac": "MAEU", - "status": "succeeded", - "created_at": "2023-03-16T18:35:42Z", - "updated_at": "2023-03-16T18:36:10Z" - }, - "relationships": { - "tracked_object": { - "data": { - "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", - "type": "shipment" - } - } - } - } -} -``` - -## Step 4: Get Shipment Details and ETA - -Once the tracking request has succeeded, you can retrieve the shipment and its estimated arrival time: - - -```bash -# Using cURL -curl -X GET "https://api.terminal49.com/v2/shipments/7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b?include=containers" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" -``` - -```javascript -// Using fetch -const shipmentId = '7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b'; -const response = await fetch(`https://api.terminal49.com/v2/shipments/${shipmentId}?include=containers`, { - headers: { - 'Authorization': `Token ${YOUR_API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - } -}); - -const result = await response.json(); -console.log(`ETA: ${result.data.attributes.estimated_port_arrival_date}`); -``` - - -The response includes shipment details with the ETA: - -```json -{ - "data": { - "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", - "type": "shipment", - "attributes": { - "bill_of_lading_number": "MAEU9736478", - "booking_number": "123456789", - "shipping_line_scac": "MAEU", - "shipping_line_name": "Maersk", - "estimated_port_arrival_date": "2023-04-15T14:00:00Z", - "port_of_lading_name": "Shanghai", - "port_of_lading_locode": "CNSHA", - "port_of_discharge_name": "Los Angeles", - "port_of_discharge_locode": "USLAX", - "vessel_name": "MAERSK SEVILLE", - "voyage": "243E" - // ...other attributes - }, - "relationships": { - "containers": { - "data": [ - { - "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", - "type": "container" - } - ] - } - } - }, - "included": [ - { - "id": "e5g4h8i2-4f8c-9b2a-3c6d8e9f0a1b", - "type": "container", - "attributes": { - "number": "MAEU1234567", - "size_type": "40HC", - "status": "In Transit", - "location": "At Sea", - "last_free_day": "2023-04-22T23:59:59Z" - // ...other attributes - } - } - ] -} -``` - -## Step 5: Set Up Webhooks for Real-Time Updates (Event-Driven Method) - -**The Recommended Approach**: Instead of constantly polling for updates, use webhooks to receive notifications when events occur. - -1. Set up a webhook endpoint on your server to receive POST requests -2. Register your webhook with Terminal49: - - -```bash -# Using cURL -curl -X POST "https://api.terminal49.com/v2/webhooks" \ - -H "Authorization: Token YOUR_API_KEY" \ - -H "Content-Type: application/vnd.api+json" \ - -d '{ - "data": { - "type": "webhook", - "attributes": { - "url": "https://your-server.com/webhook", - "active": true, - "events": ["*"] - } - } - }' -``` - -```javascript -// Using fetch -const response = await fetch('https://api.terminal49.com/v2/webhooks', { - method: 'POST', - headers: { - 'Authorization': `Token ${YOUR_API_KEY}`, - 'Content-Type': 'application/vnd.api+json' - }, - body: JSON.stringify({ - data: { - type: 'webhook', - attributes: { - url: 'https://your-server.com/webhook', - active: true, - events: ['*'] // Or specify individual events like 'shipment.estimated_arrival_change' - } - } - }) -}); - -const result = await response.json(); -console.log(`Webhook created with ID: ${result.data.id}`); -``` - - -3. Implement a webhook handler on your server: - - -```javascript -// Node.js with Express -const express = require('express'); -const app = express(); -app.use(express.json()); - -app.post('/webhook', (req, res) => { - const event = req.body; - - console.log(`Received event: ${event.data.id}`); - console.log(`Event type: ${event.type}`); - - // Handle different event types - if (event.type === 'shipment.estimated_arrival_change') { - const shipment = event.data; - console.log(`Shipment ${shipment.attributes.bill_of_lading_number} ETA updated to: ${shipment.attributes.estimated_port_arrival_date}`); - - // Update your database or notify stakeholders - // ... - } - - // Always respond with 200 OK to acknowledge receipt - res.status(200).send('Event received'); -}); - -app.listen(3000, () => { - console.log('Webhook server running on port 3000'); -}); -``` - -```php -// PHP webhook handler - -``` - - -### Example Webhook Event: ETA Change - -When a shipment's ETA changes, you'll receive a webhook event like this: - -```json -{ - "type": "shipment.estimated_arrival_change", - "data": { - "id": "7a8f8d5e-4c7d-4f8c-9b2a-3c6d8e9f0a1b", - "type": "shipment", - "attributes": { - "bill_of_lading_number": "MAEU9736478", - "estimated_port_arrival_date": "2023-04-17T16:30:00Z", - "previous_estimated_port_arrival_date": "2023-04-15T14:00:00Z", - // ...other attributes - } - } -} -``` - - - **Testing Webhooks**: Use services like [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) during development to easily receive and inspect webhook events. - - -## Next Steps - -Now that you've set up basic tracking and notifications, you can: - -1. **Explore More Workflows**: - - [Container Availability Monitoring](/api-docs/in-depth-guides/common-workflows#container-availability) - - [Terminal Hold Detection](/api-docs/in-depth-guides/common-workflows#terminal-holds) - - [Last Free Day Tracking](/api-docs/in-depth-guides/common-workflows#last-free-day) - -2. **Optimize Your Integration**: - - [Batch tracking multiple containers](/api-docs/in-depth-guides/json-api-guide#creating-resources) - - [Learn more about rate limits](/api-docs/in-depth-guides/rate-limiting) - - [Handle errors properly](/api-docs/in-depth-guides/error-handling) - -3. **Dive Deeper**: - - [Explore the full JSON:API implementation](/api-docs/in-depth-guides/json-api-guide) - - [Check out our Postman collection](https://app.getpostman.com/run-collection/4989da1ade6756b2f636) diff --git a/docs/api-docs/in-depth-guides/quickstart.mdx b/docs/api-docs/in-depth-guides/quickstart.mdx index 1db17be5..4b7e9606 100644 --- a/docs/api-docs/in-depth-guides/quickstart.mdx +++ b/docs/api-docs/in-depth-guides/quickstart.mdx @@ -4,8 +4,6 @@ description: Get up and running with Terminal49's API in minutes - track contain icon: "rocket" --- -# Quickstart - This guide will help you get started with Terminal49's API by walking through the most common workflow: tracking a shipment and receiving status updates. You'll learn both how to request data and how to receive real-time updates via webhooks. ## Prerequisites diff --git a/docs/api-docs/in-depth-guides/rate-limiting.mdx b/docs/api-docs/in-depth-guides/rate-limiting.mdx new file mode 100644 index 00000000..07162c6e --- /dev/null +++ b/docs/api-docs/in-depth-guides/rate-limiting.mdx @@ -0,0 +1,335 @@ +--- +title: Rate Limiting +description: Understanding Terminal49's API rate limits and best practices for efficient API usage +icon: "gauge-high" +--- + +# Rate Limiting + +This guide explains Terminal49's API rate limits and provides strategies for working efficiently within these limits. + +## Understanding Rate Limits + +Rate limiting is a technique used to control the amount of incoming and outgoing traffic to or from a network or service. Terminal49 implements rate limiting to ensure fair usage of the API across all users and to maintain overall system stability and performance. + +### Current Rate Limits + +Terminal49's API uses the following rate limiting structure: + +| Plan | Requests per minute | Requests per hour | Requests per day | +|------|---------------------|-------------------|------------------| +| Standard | 60 | 1,000 | 10,000 | +| Enterprise | 120 | 5,000 | 50,000 | + + + These limits may be adjusted based on your specific needs. If you're consistently hitting rate limits, please contact our support team to discuss options for increasing your quota. + + +## How Rate Limiting Works + +### Request Counting + +Each API request made with your API key counts toward your rate limit. The following requests all count as separate requests: + +- `GET /shipments` +- `GET /containers` +- `POST /tracking_requests` + +### Rate Limit Headers + +Terminal49 returns the following headers with each API response to help you monitor your rate limit usage: + +``` +X-RateLimit-Limit: 60 +X-RateLimit-Remaining: 58 +X-RateLimit-Reset: 1617192000 +``` + + + The maximum number of requests allowed in the current time window. + + + + The number of requests remaining in the current time window. + + + + The time at which the current rate limit window resets, in Unix time. + + +### Rate Limit Exceeded Response + +When you exceed the rate limit, the API will return a `429 Too Many Requests` status code with the following response body: + +```json +{ + "errors": [ + { + "status": "429", + "title": "Too Many Requests", + "detail": "Rate limit exceeded. Please wait and try again later." + } + ] +} +``` + +The response will also include headers indicating when you can retry your request. + +## Best Practices for Handling Rate Limits + +### Implement Retries with Exponential Backoff + +When you receive a 429 response, implement a retry mechanism with exponential backoff: + +```javascript +async function makeRequestWithRetry(endpoint, maxRetries = 3) { + let retries = 0; + + while (retries < maxRetries) { + try { + const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, { + headers: { + 'Authorization': `Token ${API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } + }); + + if (response.status === 429) { + // Parse the Retry-After header if available + const retryAfter = response.headers.get('Retry-After'); + const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, retries) * 1000; + + console.log(`Rate limit exceeded. Retrying after ${waitTime}ms...`); + await new Promise(resolve => setTimeout(resolve, waitTime)); + retries++; + continue; + } + + return response; + } catch (error) { + console.error('API request failed:', error); + retries++; + + if (retries >= maxRetries) { + throw error; + } + + // Exponential backoff + const waitTime = Math.pow(2, retries) * 1000; + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + } + + throw new Error('Maximum retry attempts reached'); +} +``` + +### Batch Requests When Possible + +Instead of making individual requests for each resource, use the `include` parameter to fetch related resources in a single request: + +``` +// Instead of: +GET /shipments/123 +GET /containers/456 +GET /containers/789 + +// Use: +GET /shipments/123?include=containers +``` + +This approach not only reduces the number of requests counting toward your rate limit but also improves overall performance. + +### Implement Request Queuing + +For applications that need to make many API requests, implement a request queue that respects the rate limits: + +```javascript +class RequestQueue { + constructor(requestsPerMinute = 60) { + this.queue = []; + this.processing = false; + this.interval = 60 * 1000 / requestsPerMinute; // Distribute requests evenly + } + + async add(endpoint, method = 'GET', data = null) { + return new Promise((resolve, reject) => { + this.queue.push({ endpoint, method, data, resolve, reject }); + if (!this.processing) { + this.processQueue(); + } + }); + } + + async processQueue() { + this.processing = true; + + while (this.queue.length > 0) { + const { endpoint, method, data, resolve, reject } = this.queue.shift(); + + try { + const result = await this.makeApiRequest(endpoint, method, data); + resolve(result); + } catch (error) { + reject(error); + } + + // Wait before processing the next request + await new Promise(resolve => setTimeout(resolve, this.interval)); + } + + this.processing = false; + } + + // Your API request implementation here + async makeApiRequest(endpoint, method, data) { + // ... + } +} + +// Usage: +const queue = new RequestQueue(60); // 60 requests per minute +const shipments = await queue.add('shipments'); +const containers = await queue.add('containers'); +``` + +### Cache Responses + +Implement caching for responses that don't change frequently to reduce the number of API calls: + +```javascript +const cache = new Map(); + +async function fetchWithCache(endpoint, cacheTime = 5 * 60 * 1000) { // 5 minutes + const cacheKey = endpoint; + const cachedData = cache.get(cacheKey); + + if (cachedData && cachedData.timestamp > Date.now() - cacheTime) { + console.log(`Using cached data for ${endpoint}`); + return cachedData.data; + } + + const response = await fetch(`https://api.terminal49.com/v2/${endpoint}`, { + headers: { + 'Authorization': `Token ${API_KEY}`, + 'Content-Type': 'application/vnd.api+json' + } + }); + + const data = await response.json(); + + cache.set(cacheKey, { + timestamp: Date.now(), + data + }); + + return data; +} +``` + +### Monitor Your Usage + +Implement monitoring to track your API usage and detect when you're approaching rate limits: + +```javascript +function monitorRateLimits(response) { + const limit = parseInt(response.headers.get('X-RateLimit-Limit')); + const remaining = parseInt(response.headers.get('X-RateLimit-Remaining')); + const resetTime = new Date(parseInt(response.headers.get('X-RateLimit-Reset')) * 1000); + + const usagePercentage = ((limit - remaining) / limit) * 100; + + console.log(`API Rate Limit: ${remaining}/${limit} remaining (${usagePercentage.toFixed(2)}%)`); + console.log(`Resets at: ${resetTime.toLocaleTimeString()}`); + + // Alert if usage is high + if (usagePercentage > 80) { + console.warn('API rate limit usage is high! Consider reducing request frequency.'); + } +} +``` + +## Enterprise Considerations + +### Webhooks as an Alternative + +For high-frequency updates to resources, consider using [webhooks](/api-docs/in-depth-guides/webhooks) instead of polling the API: + +```javascript +// Instead of polling every few minutes: +setInterval(async () => { + const shipments = await fetchWithCache('shipments?filter[status]=active'); + // Process shipments... +}, 5 * 60 * 1000); + +// Set up a webhook endpoint to receive updates in real-time +app.post('/webhook/shipment-updates', (req, res) => { + const shipmentEvent = req.body; + // Process shipment update in real-time + res.status(200).send('Event received'); +}); +``` + +### Request Rate Allocation + +If you're building an application with multiple components or users that share the same API key, consider implementing a token bucket algorithm to fairly distribute your rate limit quota: + +```javascript +class TokenBucket { + constructor(capacity, fillPerSecond) { + this.capacity = capacity; + this.tokens = capacity; + this.lastFill = Date.now(); + this.fillPerSecond = fillPerSecond; + } + + take(count = 1) { + this.refill(); + + if (this.tokens >= count) { + this.tokens -= count; + return true; + } + + return false; + } + + refill() { + const now = Date.now(); + const deltaSeconds = (now - this.lastFill) / 1000; + const newTokens = deltaSeconds * this.fillPerSecond; + + this.tokens = Math.min(this.capacity, this.tokens + newTokens); + this.lastFill = now; + } +} + +// Usage: +// For 60 requests per minute (1 per second) +const bucket = new TokenBucket(60, 1); + +async function makeRateLimitedRequest(endpoint) { + if (bucket.take()) { + return await fetch(`https://api.terminal49.com/v2/${endpoint}`); + } else { + // Wait and retry + await new Promise(resolve => setTimeout(resolve, 1000)); + return makeRateLimitedRequest(endpoint); + } +} +``` + +## Getting Help + +If you consistently hit rate limits despite implementing these best practices, you may need a higher quota for your use case. Contact Terminal49 support at [support@terminal49.com](mailto:support@terminal49.com) with: + +1. Your API key ID (not the actual key) +2. The endpoints that are hitting rate limits +3. Your typical request patterns and frequency +4. Business justification for needing a higher limit + +## Next Steps + +- Learn about [Error Handling](/api-docs/in-depth-guides/error-handling) for dealing with rate limit errors +- Explore [Webhooks](/api-docs/in-depth-guides/webhooks) as an alternative to frequent API polling +- Understand [Pagination](/api-docs/api-reference/pagination) to efficiently retrieve large datasets diff --git a/docs/api-docs/in-depth-guides/common-workflows.mdx b/docs/api-docs/in-depth-guides/use-cases.mdx similarity index 98% rename from docs/api-docs/in-depth-guides/common-workflows.mdx rename to docs/api-docs/in-depth-guides/use-cases.mdx index 45e6f7df..251bb5b4 100644 --- a/docs/api-docs/in-depth-guides/common-workflows.mdx +++ b/docs/api-docs/in-depth-guides/use-cases.mdx @@ -1,12 +1,12 @@ --- -title: Common Workflows -description: Learn how to implement the most common logistics workflows with Terminal49's API, including container tracking, ETA monitoring, and hold management. +title: Use Cases +description: Learn how to implement the most common logistics use cases with Terminal49's API, including container tracking, ETA monitoring, and hold management. icon: "arrows-split-up-and-left" --- -# Common Workflows +# Use Cases -This guide demonstrates how to implement the most common logistics workflows with Terminal49's API. For each workflow, we'll show two approaches: +This guide demonstrates the most common logistics use cases you can implement with Terminal49's API. For each use case, we'll show two approaches: 1. **Request-based approach**: Proactively querying the API 2. **Event-driven approach**: Receiving webhook notifications (recommended) diff --git a/docs/home.mdx b/docs/home.mdx deleted file mode 100644 index 186db702..00000000 --- a/docs/home.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Terminal49 Dev Documentation -og:title: Terminal49 Home | API Documentation -og:description: Learn about Terminal49's home features and API documentation for container and shipment tracking. ---- -We offer two fantastic ways to track your shipments from origin to destination. - - 1. [Terminal49 DataSync](/datasync/overview). Get tables full of fresh information delivered into your current data system. Easy to set up, and perfect for complementing your current data. - 2. [Terminal49 API](/api-docs/getting-started/start-here). Connect directly with the API, pull data for specific shipments and containers, and get updates via webhooks. - -If you already have a data store that feeds the rest of your system, DataSync is probably what you want. - -## What can I use Terminal49 data for? -Here are just a few of the data points we return and possible use-cases. - -| DATA | EXAMPLE USE CASE | -|----------------------------------------|--------------------------------------------------------------------------| -| Destination ETA | Surface ETA changes to your relevant teams as they're reported | -| Last Free Day and terminal status¹ | Track containers approaching LFD and prioritize dispatching | -| Fees and holds at destination terminal | Clear your cargo to keep you containers moving | -| Actual departure and arrival times | Report journey times by route to compare your ocean carriers performance | - -_1. At container ports in the US_ - -## How it works -All you need to provide are your BOL numbers and SCACs. Terminal 49 will lookup the shipment with the carrier and populate shipment details including containers. - -Once the shipment is set up, Terminal 49 periodically checks with the carrier and the destination terminal. - -If any of the details of your shipment or containers change (for example - if the ETA changes) we'll ensure you're always kept up to date. - - - If you're using DataSync, we'll update the data in your system - - If you're using the API, we'll post the shipment to the the webhook you provide - -👈🏽 Please click API Docs or Data Sync on the left to get started! \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json deleted file mode 100644 index 82f48da3..00000000 --- a/docs/mint.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "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/capabilities-overview", - "api-docs/getting-started/tracking-shipments-and-containers", - "api-docs/getting-started/list-shipments-and-containers", - "api-docs/getting-started/receive-status-updates" - ] - }, - { - "group": "In Depth Guides", - "pages": [ - "api-docs/in-depth-guides/quickstart", - "api-docs/in-depth-guides/json-api-guide", - "api-docs/in-depth-guides/polling-vs-webhooks", - "api-docs/in-depth-guides/webhooks", - "api-docs/in-depth-guides/including-resources", - "api-docs/in-depth-guides/adding-customer", - "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" - ] - }, - { - "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" - ] - }, - { - "group": " ", - "pages": ["datasync/home"] - }, - { - "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/transport-events", - "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", - "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"] - }, - { - "group": "Parties", - "pages": [ - "api-docs/api-reference/parties/list-parties", - "api-docs/api-reference/parties/create-a-party", - "api-docs/api-reference/parties/get-a-party", - "api-docs/api-reference/parties/edit-a-party" - ] - } - ], - "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" - } - ] -} diff --git a/docs/openapi.json.bak b/docs/openapi.json.bak new file mode 100644 index 00000000..6355a9ad --- /dev/null +++ b/docs/openapi.json.bak @@ -0,0 +1,15105 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Terminal49 API Reference", + "version": "0.2.0", + "contact": { + "name": "Terminal49 API support", + "url": "https://www.terminal49.com", + "email": "support@terminal49.com" + }, + "description": "The Terminal 49 API offers a convenient way to programmatically track your shipments from origin to destination.", + "x-label": "Beta", + "termsOfService": "https://www.terminal49.com/terms" + }, + "servers": [ + { + "url": "https://api.terminal49.com/v2", + "description": "Production" + } + ], + "paths": { + "/shipments": { + "get": { + "summary": "List shipments", + "tags": [ + "Shipments" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipment" + } + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/terminal" + } + ] + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": [ + { + "id": "62738624-7032-4a50-892e-c55826228c25", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T17:28:59Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2148468620", + "normalized_number": "2148468620", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNNBG", + "port_of_lading_name": "Ningbo", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "EVER FORWARD", + "pod_vessel_imo": "9850551", + "pod_voyage_number": "1119E", + "destination_locode": "USCLE", + "destination_name": "Cleveland", + "destination_timezone": "America/New_York", + "destination_ata_at": null, + "destination_eta_at": "2024-07-02T08:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-09T03:42:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-22T13:36:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T17:28:59Z", + "line_tracking_last_succeeded_at": "2024-06-26T17:28:59Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/62738624-7032-4a50-892e-c55826228c25" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "2a90c27b-c2ee-45e2-892f-5c695d74e2d0", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "3b1cc325-ffe9-400a-82ce-f5c4891af382", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "ce22669e-14b2-4501-b782-f0a360f07cd0", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "7aefc29e-0898-4825-8376-4f998b51d033", + "type": "container" + } + ] + } + } + }, + { + "id": "baaa725e-aa0e-4937-ac78-54d9e2e8621e", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T16:47:42Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "HDMUTAOM72244900", + "normalized_number": "TAOM72244900", + "shipping_line_scac": "HDMU", + "shipping_line_name": "Hyundai Merchant Marine", + "shipping_line_short_name": "Hyundai", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNQDG", + "port_of_lading_name": "Qingdao", + "port_of_discharge_locode": "USSAV", + "port_of_discharge_name": "Savannah", + "pod_vessel_name": "UMM SALAL", + "pod_vessel_imo": "9525857", + "pod_voyage_number": "0038E", + "destination_locode": "USROQ", + "destination_name": "Rossville", + "destination_timezone": "America/Chicago", + "destination_ata_at": null, + "destination_eta_at": "2024-06-30T07:05:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-05-07T03:01:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-20T21:27:00Z", + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T16:48:04Z", + "line_tracking_last_succeeded_at": "2024-06-26T16:48:04Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/baaa725e-aa0e-4937-ac78-54d9e2e8621e" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "6129528d-846e-4571-ae16-b5328a4285ab", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "87ca3f37-e4d1-46eb-9eb1-6b5ffafde95d", + "type": "port" + } + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "772cd872-9677-4c68-9b7a-4e9e843b00e2", + "type": "container" + }, + { + "id": "52efc544-0de1-452f-bcf8-0290a6ce5c11", + "type": "container" + }, + { + "id": "3107692e-61ad-4b4c-b3d4-78348b2e37ff", + "type": "container" + } + ] + } + } + }, + { + "id": "7721a48c-5e93-43c9-9f5f-5be10a87fdde", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T16:28:39Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2738424980", + "normalized_number": "2738424980", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "ITSPE", + "port_of_lading_name": "La Spezia", + "port_of_discharge_locode": "USSAV", + "port_of_discharge_name": "Savannah", + "pod_vessel_name": "OOCL GUANGZHOU", + "pod_vessel_imo": "9404869", + "pod_voyage_number": "162E", + "destination_locode": "USATL", + "destination_name": "Atlanta", + "destination_timezone": "America/New_York", + "destination_ata_at": null, + "destination_eta_at": "2024-06-27T06:42:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-05T08:03:00Z", + "pol_timezone": "Europe/Rome", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-23T15:34:00Z", + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T16:28:39Z", + "line_tracking_last_succeeded_at": "2024-06-26T16:28:39Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/7721a48c-5e93-43c9-9f5f-5be10a87fdde" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "b5656766-a56f-4b32-8e03-d240e7519604", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "6129528d-846e-4571-ae16-b5328a4285ab", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "7daf9ea3-3018-4d62-b88c-43803df9030c", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "022ef8fc-1e2a-4ad6-8eae-330d65eb1c8e", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "2a25fd3e-a18e-47cc-9cea-62771e82d0f2", + "type": "container" + }, + { + "id": "6cdc725d-2b31-40f9-86dd-76225390a488", + "type": "container" + }, + { + "id": "2f1e9a9d-4689-4f4d-84a8-64409b56521d", + "type": "container" + } + ] + } + } + }, + { + "id": "32b5ad78-43ba-42d9-bdc0-4cf12320e020", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:59:52Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2738277190", + "normalized_number": "2738277190", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNQDG", + "port_of_lading_name": "Qingdao", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "EVER FORWARD", + "pod_vessel_imo": "9850551", + "pod_voyage_number": "1119E", + "destination_locode": "USMEM", + "destination_name": "Memphis", + "destination_timezone": "America/Chicago", + "destination_ata_at": null, + "destination_eta_at": "2024-07-01T14:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-02T18:22:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-22T13:36:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T15:59:52Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:59:52Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/32b5ad78-43ba-42d9-bdc0-4cf12320e020" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "ba0b9f68-2025-40ca-ae12-1c2210cca333", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "d0ec0da1-8a3a-4b11-b1e3-5716bbc71dc3", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "60d2fd62-14e2-4ca2-a927-9633fc58fdff", + "type": "container" + }, + { + "id": "0ffe3e75-b3df-4d20-b9f4-97bbbcec404d", + "type": "container" + } + ] + } + } + }, + { + "id": "bd117d3b-8fa4-487c-9bab-25c15e227d1a", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:59:35Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2147973020", + "normalized_number": "2147973020", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "IDSUB", + "port_of_lading_name": "Surabaya", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "CMA CGM A.LINCOLN", + "pod_vessel_imo": "9780859", + "pod_voyage_number": "1TU70E1MA", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2024-05-14T02:37:00Z", + "pol_timezone": "Asia/Jakarta", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-23T21:58:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T15:59:35Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:59:35Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/bd117d3b-8fa4-487c-9bab-25c15e227d1a" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "4201ab42-c51f-48ac-b7a1-12146e02c6a2", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "815ff702-fdb5-4455-a28e-314c345d7481", + "type": "container" + } + ] + } + } + } + ], + "meta": { + "size": 5, + "total": 34044 + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments?page[size]=5", + "current": "https://api.terminal49.com/v2/shipments?page[number]=1&page[size]=5", + "next": "https://api.terminal49.com/v2/shipments?page[number]=2&page[size]=5", + "last": "https://api.terminal49.com/v2/shipments?page[number]=6809&page[size]=5" + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Errors": { + "value": { + "error": [ + { + "title": "Invalid arrival_date_from", + "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" + }, + { + "title": "Invalid arrival_date_to", + "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" + }, + { + "title": "Invalid port_of_lading", + "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" + }, + { + "title": "Invalid port_of_discharge", + "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" + } + ] + } + } + } + } + } + } + }, + "operationId": "get-shipments", + "description": "Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.\n\nThis 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. ", + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]", + "description": "\n" + }, + { + "schema": { + "type": "integer", + "default": 30 + }, + "in": "query", + "name": "page[size]", + "description": "\n" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "q", + "description": "\nSearch shipments by master bill of lading, reference number, or container number.", + "deprecated": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "number", + "description": "Search shipments by the original request tracking `request_number`" + } + ] + } + }, + "/shipments/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Shipment Id" + } + ], + "get": { + "summary": "Get a shipment", + "tags": [ + "Shipments" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/terminal" + } + ] + } + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:05:20Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "MEDUF5399896", + "normalized_number": "MEDUF5399896", + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "shipping_line_short_name": "MSC", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "FRLEH", + "port_of_lading_name": "Le Havre", + "port_of_discharge_locode": "USNYC", + "port_of_discharge_name": "New York / New Jersey", + "pod_vessel_name": "MSC ANISHA R.", + "pod_vessel_imo": "9227297", + "pod_voyage_number": "421A", + "destination_locode": "USIND", + "destination_name": "Indianapolis", + "destination_timezone": "US/Eastern", + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2024-06-11T22:00:00Z", + "pol_timezone": "Europe/Paris", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": null, + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T15:05:20Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:05:20Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "fe7fc831-8a81-4079-8938-b90015912e8b", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "e1e492f6-c8ba-45a9-ad1a-67a7e74547ce", + "type": "port" + } + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3?include=containers" + }, + "included": [ + { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container", + "attributes": { + "number": "CAIU7432986", + "seal_number": null, + "created_at": "2024-06-26T15:05:21Z", + "ref_numbers": [], + "pod_arrived_at": null, + "pod_discharged_at": "2024-06-22T04:00:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T17:51:12Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-07T04:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard - Y0709A", + "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "US/Eastern", + "empty_terminated_timezone": "US/Eastern", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "45b542cb-332b-4684-b915-42e3a0759823", + "type": "transport_event" + }, + { + "id": "174ed528-a1a9-4002-aef0-f2c9369199da", + "type": "transport_event" + }, + { + "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", + "type": "transport_event" + }, + { + "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", + "type": "transport_event" + }, + { + "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", + "type": "raw_event" + }, + { + "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", + "type": "raw_event" + }, + { + "id": "74810c04-6c8a-4194-8cff-52936584a965", + "type": "raw_event" + }, + { + "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", + "type": "raw_event" + }, + { + "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", + "type": "raw_event" + }, + { + "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", + "type": "raw_event" + } + ] + } + } + } + ] + } + } + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": { + "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "6140575020", + "ref_numbers": [ + "my-ref-49", + "PO#18412" + ], + "created_at": "2020-02-20T13:37:12Z", + "tags": [], + "port_of_lading_locode": "INVTZ", + "port_of_lading_name": "Visakhapatnam, IN", + "port_of_discharge_locode": "USNYC", + "port_of_discharge_name": "New York / New Jersey, NY", + "destination_locode": "USDET", + "destination_name": "Detroit, MI", + "shipping_line_scac": "HLCU", + "pod_vessel_name": "CMA CGM ALMAVIVA", + "pod_voyage_number": "0108", + "pol_etd_at": null, + "pol_atd_at": "2020-02-15T21:53Z", + "pol_timezone": "Asia/Calcutta", + "pod_eta_at": "2020-03-18T08:00Z", + "pod_ata_at": null, + "pod_timezone": "America/New_York", + "destination_eta_at": "2020-03-26T17:00Z", + "destination_ata_at": null, + "destination_timezone": "America/Detroit" + }, + "relationships": { + "containers": { + "data": [ + { + "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", + "type": "container" + } + ] + }, + "port_of_lading": { + "data": { + "id": "bde5465a-1160-4fde-a026-74df9c362f65", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port" + } + }, + "destination": { + "data": { + "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", + "type": "metro_area" + } + }, + "customer": { + "data": { + "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", + "type": "account" + } + } + } + }, + "included": [ + { + "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", + "type": "container", + "attributes": { + "number": "UACU4743531", + "equipment_type": "reefer", + "length": 40, + "height": "high_cube", + "weight_in_lbs": 35075, + "created_at": "2020-02-20T08:19:52Z", + "seal_number": null, + "pickup_lfd": null, + "availability_known": false, + "available_for_pickup": null, + "current_transportation_mode": "vessel", + "pod_discharged_at": null, + "pod_picked_up_at": null, + "destination_unloaded_at": null, + "destination_picked_up_at": null, + "empty_returned_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "most_recent_location": { + "data": { + "id": "dd179094-a1d4-4129-842d-b952e43df4b7", + "type": "port" + } + }, + "shipment": { + "data": { + "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", + "type": "shipment" + } + } + } + }, + { + "id": "bde5465a-1160-4fde-a026-74df9c362f65", + "type": "port", + "attributes": { + "name": "Visakhapatnam", + "code": "INVTZ", + "country_code": "IN", + "timezone": "Asia/Calcutta" + } + }, + { + "id": "dd179094-a1d4-4129-842d-b952e43df4b7", + "type": "port", + "attributes": { + "name": "Damietta", + "code": "EGDAM", + "country_code": "EG", + "time_zone": "Africa/Cairo" + } + }, + { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port", + "attributes": { + "name": "New York / New Jersey", + "code": "USNYC", + "country_code": "US", + "timezone": "America/New_York" + } + }, + { + "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", + "type": "metro_area", + "attributes": { + "name": "Detroit", + "code": "USDET", + "country_code": "US", + "state_abbr": "MI", + "timezone": "America/Detroit" + } + }, + { + "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", + "type": "account", + "attributes": { + "name": "A-Z Imports" + } + }, + { + "id": "252a5450-2893-4207-b5c4-81ce3152ce84", + "type": "vessel", + "attributes": { + "name": "CMA CGM ALMAVIVA", + "imo": "9450648", + "mmsi": "228339600", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-shipment-id", + "description": "Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + }, + "patch": { + "summary": "Edit a shipment", + "operationId": "patch-shipments-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + }, + "description": "Update a shipment", + "tags": [ + "Shipments" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_numbers": { + "type": "array", + "example": [ + "REFNUMBER10" + ], + "description": "Shipment ref numbers.", + "items": { + "type": "string" + } + }, + "shipment_tags": { + "type": "array", + "x-stoplight": { + "id": "02itol38fmg55" + }, + "uniqueItems": true, + "description": "Tags related to a shipment", + "items": { + "x-stoplight": { + "id": "64x90wvr9d6nd" + }, + "type": "string", + "example": "tag1, tag2" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/shipments/{id}/stop_tracking": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Stop tracking a shipment", + "operationId": "patch-shipments-id-stop-tracking", + "tags": [ + "Shipments" + ], + "description": "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.\n\nYou 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.", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + } + } + }, + "/shipments/{id}/resume_tracking": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Resume tracking a shipment", + "operationId": "patch-shipments-id-resume-tracking", + "tags": [ + "Shipments" + ], + "description": "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.", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + } + } + }, + "/tracking_requests": { + "post": { + "summary": "Create a tracking request", + "operationId": "post-track", + "responses": { + "201": { + "description": "Tracking Request Created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipping_line" + } + ] + } + } + } + }, + "examples": { + "Pending Tracking Request": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "tracking_request", + "attributes": { + "request_number": "MEDUFR030802", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-04-04T16:13:35-07:00", + "updated_at": "2020-04-04T17:13:35-07:00", + "status": "pending", + "failed_reason": null + }, + "relationships": { + "tracked_object": { + "data": null + }, + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "links": { + "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" + } + } + } + }, + "Example: MSC BL": { + "summary": "Track by Bill of Lading", + "value": { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "ref_numbers": [ + "PO12345", + "HBL12345", + "CUSREF1234" + ], + "shipment_tags": [ + "camembert" + ], + "scac": "MSCU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Booking Number": { + "summary": "Track by Booking Number with References, Tags and Customer", + "value": { + "data": { + "attributes": { + "request_type": "booking_number", + "request_number": "OOLU8324567", + "ref_numbers": [ + "PO-8765", + "INV-12345" + ], + "shipment_tags": [ + "priority", + "electronics" + ], + "scac": "OOLU" + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Container": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" + } + } + }, + "summary": "Track by Container Number" + }, + "Example: Test Number": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + }, + "summary": "Using Test Numbers for Development" + } + } + } + }, + "headers": {} + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac can't be blank", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'XXXX' is not recognized", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'UALC' is not supported. We do not currently integrate with Universal Africa Lines", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number can't be blank", + "code": "blank" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "request_type": { + "type": "string", + "example": "bill_of_lading", + "enum": [ + "bill_of_lading", + "booking_number", + "container" + ], + "description": " The type of document number to be supplied. Container number support is currently in BETA." + }, + "request_number": { + "type": "string", + "example": "MEDUFR030802" + }, + "scac": { + "type": "string", + "example": "MSCU", + "minLength": 4, + "maxLength": 4 + }, + "ref_numbers": { + "type": "array", + "description": "Optional list of reference numbers to be added to the shipment when tracking request completes", + "items": { + "type": "string" + } + }, + "shipment_tags": { + "type": "array", + "description": "Optional list of tags to be added to the shipment when tracking request completes", + "items": { + "type": "string" + } + } + }, + "required": [ + "request_type", + "request_number", + "scac" + ] + }, + "relationships": { + "type": "object", + "properties": { + "customer": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "party" + ] + } + } + } + } + } + } + }, + "type": { + "enum": [ + "tracking_request" + ], + "type": "string" + } + }, + "required": [ + "type" + ] + } + } + }, + "examples": { + "Example: MSC BL": { + "value": { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "ref_numbers": [ + "PO12345", + "HBL12345", + "CUSREF1234" + ], + "shipment_tags": [ + "camembert" + ], + "scac": "MSCU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Booking Number": { + "value": { + "data": { + "attributes": { + "request_type": "booking_number", + "request_number": "OOLU8324567", + "ref_numbers": [ + "PO-8765", + "INV-12345" + ], + "shipment_tags": [ + "priority", + "electronics" + ], + "scac": "OOLU" + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Container": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" + } + } + } + }, + "Example: Test Number": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + } + } + } + } + }, + "description": "Create a shipment tracking request" + }, + "security": [ + { + "authorization": [] + } + ], + "description": "To track an ocean shipment, you create a new tracking request. \nTwo attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. \n\nOnce 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. \n\nIf the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. \n\nA `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. ", + "tags": [ + "Tracking Requests" + ], + "parameters": [] + }, + "parameters": [], + "get": { + "summary": "List tracking requests", + "operationId": "get-tracking-requests", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking_request" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipping_line" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + } + }, + "description": "" + } + ] + } + } + } + }, + "examples": {} + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + } + } + } + } + }, + "description": "Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.", + "tags": [ + "Tracking Requests" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "q", + "description": "A search term to be applied against request_number and reference_numbers.", + "deprecated": true + }, + { + "schema": { + "type": "string", + "enum": [ + "created", + "pending", + "failed" + ], + "example": "created" + }, + "in": "query", + "name": "filter[status]", + "description": "filter by `status`" + }, + { + "schema": { + "type": "string", + "example": "MSCU" + }, + "in": "query", + "name": "filter[scac]", + "description": "filter by shipping line `scac`" + }, + { + "schema": { + "type": "string", + "example": "2020-04-28T22:59:15Z", + "format": "date-time" + }, + "in": "query", + "name": "filter[created_at][start]", + "description": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp" + }, + { + "schema": { + "type": "string", + "format": "date-time", + "example": "2020-04-28T22:59:15Z" + }, + "in": "query", + "description": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", + "name": "filter[created_at][end]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "filter[request_number]", + "description": "filter by `request_number`" + } + ] + } + }, + "/tracking_requests/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Tracking Request ID" + } + ], + "get": { + "summary": "Get a single tracking request", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/shipping_line" + } + ] + } + } + } + }, + "examples": { + "With Status Created": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "tracking_request", + "attributes": { + "request_number": "MEDUFR030802", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-04-04T16:13:35-07:00", + "updated_at": "2020-04-04T17:13:35-07:00", + "status": "created", + "failed_reason": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", + "type": "shipment" + } + } + }, + "links": { + "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" + } + }, + "included": [ + { + "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", + "type": "shipment", + "attributes": { + "created_at": "2020-04-04T16:13:37-07:00", + "bill_of_lading_number": "MEDUFR030802", + "ref_numbers": [], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "FRFOS", + "port_of_lading_name": "Fos-Sur-Mer", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Oakland", + "pod_vessel_name": "MSC ALGECIRAS", + "pod_vessel_imo": "9605243", + "pod_voyage_number": "920A", + "destination_locode": "USOAK", + "destination_name": "Oakland", + "destination_timezone": "America/Los_Angeles", + "destination_ata_at": "2019-06-21T18:46:00-07:00", + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2019-05-24T03:00:00-07:00", + "pol_timezone": "Europe/Paris", + "pod_eta_at": null, + "pod_ata_at": "2019-06-21T18:46:00-07:00", + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "6d8c6c29-72a6-49ad-87b7-fd045f202212", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "pod_terminal": { + "data": null + }, + "destination": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "containers": { + "data": [ + { + "id": "11c1fa10-52a5-48e2-82f4-5523756b3d0f", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83" + } + } + ] + } + }, + "Multiple containers": { + "value": { + "data": { + "id": "62c30bd4-d7fc-40dc-9fd6-fb39224301f5", + "type": "tracking_request", + "attributes": { + "request_number": "212157148", + "request_type": "bill_of_lading", + "scac": "MAEU", + "ref_numbers": [], + "shipment_tags": [], + "created_at": "2021-07-27T16:44:14Z", + "updated_at": "2021-07-27T17:44:14Z", + "status": "created", + "failed_reason": null, + "is_retrying": false, + "retry_count": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", + "type": "shipment" + } + }, + "customer": { + "data": null + } + }, + "links": { + "self": "/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5" + } + }, + "links": { + "self": "https://api.terminal49.com/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5?filter%5Bstatus%5D=created" + }, + "included": [ + { + "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", + "type": "shipment", + "attributes": { + "created_at": "2021-07-27T16:44:16Z", + "ref_numbers": null, + "tags": [], + "bill_of_lading_number": "212157148", + "shipping_line_scac": "MAEU", + "shipping_line_name": "Maersk", + "shipping_line_short_name": "Maersk", + "port_of_lading_locode": "MYTPP", + "port_of_lading_name": "Tanjung Pelepas", + "port_of_discharge_locode": null, + "port_of_discharge_name": null, + "pod_vessel_name": null, + "pod_vessel_imo": null, + "pod_voyage_number": null, + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": null, + "pol_timezone": "Asia/Kuala_Lumpur", + "pod_eta_at": "2021-09-15T15:00:00Z", + "pod_ata_at": null, + "pod_timezone": null, + "line_tracking_last_attempted_at": null, + "line_tracking_last_succeeded_at": "2021-07-27T16:44:16Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "6c387786-252c-476d-9f99-7d835b6b978b", + "type": "port" + } + }, + "port_of_discharge": { + "data": null + }, + "pod_terminal": { + "data": null + }, + "destination": { + "data": null + }, + "destination_terminal": { + "data": null + }, + "containers": { + "data": [ + { + "id": "965880c9-a37e-4ed7-a060-9c49c0f0c5ed", + "type": "container" + }, + { + "id": "ea1f8e08-fcdf-498d-9cb5-0c370b023eeb", + "type": "container" + }, + { + "id": "67f55105-8ea2-4137-9244-f9cc204f5766", + "type": "container" + }, + { + "id": "5ab5d058-772c-466c-bc73-0b8767ad5a79", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/dfc9f601-f6fe-412e-a71c-feabcc2dd4e3" + } + } + ] + } + } + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + } + } + } + } + }, + "operationId": "get-track-request-by-id", + "description": "Get the details and status of an existing tracking request. ", + "tags": [ + "Tracking Requests" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." + } + ], + "x-internal": false + }, + "patch": { + "summary": "Edit a tracking request", + "operationId": "patch-track-request-by-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + } + } + } + } + } + } + }, + "description": "Update a tracking request", + "tags": [ + "Tracking Requests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_number": { + "type": "string", + "example": "REFNUMBER11", + "description": "Tracking request ref number." + } + } + } + } + } + } + } + } + } + } + } + }, + "/webhooks/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get single webhook", + "tags": [ + "Webhooks" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + } + }, + "operationId": "get-webhooks-id", + "description": "Get the details of a single webhook" + }, + "patch": { + "summary": "Edit a webhook", + "operationId": "patch-webhooks-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + } + }, + "description": "Update a single webhook", + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", + "format": "uri", + "description": "The URL of the webhook endpoint." + }, + "events": { + "type": "array", + "description": "The list of events to enable for this endpoint.", + "uniqueItems": true, + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ] + } + }, + "active": { + "type": "boolean" + }, + "headers": { + "type": "array", + "description": "Optional custom headers to pass with each webhook invocation", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the header. (Please not this will be auto-capitalized) " + }, + "value": { + "type": "string", + "description": "The value to pass for the header\n" + } + } + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + } + } + } + }, + "required": [ + "data" + ] + }, + "examples": {} + } + } + } + }, + "delete": { + "summary": "Delete a webhook", + "operationId": "delete-webhooks-id", + "responses": { + "200": { + "description": "OK" + } + }, + "description": "Delete a webhook", + "tags": [ + "Webhooks" + ] + } + }, + "/webhooks": { + "get": { + "summary": "List webhooks", + "tags": [ + "Webhooks" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook" + } + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "links": { + "$ref": "#/components/schemas/links" + } + } + }, + "examples": { + "example-1": { + "value": { + "data": [ + { + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "type": "webhook", + "attributes": { + "url": "http://example.com", + "active": true, + "events": [ + "tracking_request.succeeded" + ], + "secret": "672bd7b58b54645934a830d8fa", + "headers": [ + { + "name": "x-secret-sauce", + "value": "sriracha" + } + ] + } + } + ], + "meta": { + "size": 0, + "total": 0 + }, + "links": { + "last": "http://example.com", + "next": "http://example.com", + "prev": "http://example.com", + "first": "http://example.com", + "self": "http://example.com" + } + } + } + } + } + } + } + }, + "operationId": "get-webhooks", + "description": "Get a list of all the webhooks", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + } + ] + }, + "post": { + "summary": "Create a webhook", + "operationId": "post-webhooks", + "tags": [ + "Webhooks" + ], + "description": "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. \n\nThis is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. ", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "required": [ + "url", + "active" + ], + "properties": { + "url": { + "type": "string", + "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", + "format": "uri", + "description": "The URL of the webhook endpoint." + }, + "events": { + "type": "array", + "uniqueItems": true, + "description": "The list of events to enable for this endpoint.", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "example": "tracking_request.succeeded" + } + }, + "active": { + "type": "boolean" + }, + "headers": { + "type": "array", + "description": "Optional custom headers to pass with each webhook invocation", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the header. (Please note this will be auto-capitalized) " + }, + "value": { + "type": "string", + "description": "The value to pass for the header\n" + } + } + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + } + } + } + }, + "required": [ + "data" + ] + }, + "examples": { + "Test Webhook (all events)": { + "value": { + "data": { + "attributes": { + "url": "https://webhook.site/", + "events": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "active": true + }, + "type": "webhook" + } + } + } + } + } + }, + "description": "" + }, + "responses": { + "201": { + "description": "Create a test webhook endpoint", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + }, + "examples": { + "Test Endpoint Created": { + "value": { + "data": { + "type": "webhook", + "id": "9809fb96-7754-488f-99df-29ca8d410d89", + "attributes": { + "url": "https://webhook.site/", + "active": true, + "events": [ + "tracking_request.succeeded" + ], + "secret": "C193J3QOXMFH", + "created_at": "2020-06-05T19:06:13Z" + } + } + } + } + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + }, + "parameters": [] + }, + "/webhook_notifications/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a single webhook notification", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook_notification" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + }, + { + "$ref": "#/components/schemas/container_updated_event" + } + ] + } + } + } + }, + "examples": { + "Tracking Request": { + "value": { + "data": { + "id": "a76187fc-5749-43f9-9053-cfaad9790a31", + "type": "webhook_notification", + "attributes": { + "id": "a76187fc-5749-43f9-9053-cfaad9790a31", + "event": "tracking_request.succeeded", + "delivery_status": "pending", + "created_at": "2020-09-11T21:25:34Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", + "type": "tracking_request" + } + }, + "webhook": { + "data": { + "id": "914b21ce-dd7d-4c49-8503-65aba488e9a9", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", + "type": "tracking_request", + "attributes": { + "request_number": "TE497ED1063E", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-09-11T21:25:34Z", + "updated_at": "2020-09-11T22:25:34Z", + "status": "created", + "failed_reason": null, + "is_retrying": false, + "retry_count": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + } + }, + "links": { + "self": "/v2/tracking_requests/bdeca506-9741-4ab1-a0a7-cfd1d908e923" + } + }, + { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment", + "attributes": { + "created_at": "2020-09-11T21:25:33Z", + "bill_of_lading_number": "TE497ED1063E", + "ref_numbers": [], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2020-08-29T21:25:33Z", + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-09-18T21:25:33Z", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "4384d6a5-5ccc-43b7-8d19-4a9525e74c08", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [ + { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/b5b10c0a-8d18-46da-b4c2-4e5fa790e7da" + } + }, + { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container", + "attributes": { + "number": "ARDU1824900", + "seal_number": "139F1451", + "created_at": "2020-09-11T21:25:34Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "standard", + "weight_in_lbs": 53507, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "availability_known": true, + "available_for_pickup": false, + "pod_arrived_at": null, + "pod_discharged_at": null, + "final_destination_full_out_at": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "shipment": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + }, + "pod_terminal": { + "data": { + "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "56078596-5293-4c84-9245-cca00a787265", + "type": "transport_event" + } + ] + } + } + }, + { + "id": "56078596-5293-4c84-9245-cca00a787265", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_departed", + "created_at": "2020-09-11T21:25:34Z", + "voyage_number": null, + "timestamp": "2020-08-29T21:25:33Z", + "location_locode": "MXZLO", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": { + "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + } + ] + } + }, + "Estimated Event": { + "value": { + "data": { + "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", + "type": "webhook_notification", + "attributes": { + "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", + "event": "shipment.estimated.arrival", + "delivery_status": "pending", + "created_at": "2020-09-11T21:25:34Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", + "type": "estimated_event" + } + }, + "webhook": { + "data": { + "id": "614eab61-ae3c-4d40-bbe9-41200a172691", + "type": "webhook" + } + } + } + }, + "included": [ + { + "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", + "type": "estimated_event", + "attributes": { + "created_at": "2020-04-06T19:02:46-07:00", + "estimated_timestamp": "2020-04-09T19:02:46-07:00", + "voyage_number": "A1C", + "event": "shipment.estimated.arrival", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "715ed64b-6195-49f6-9407-1383a8088bfd", + "type": "shipment" + } + }, + "port": { + "data": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "ebf68c6c-9d0d-4383-aa41-e097009dfb4c", + "type": "vessel" + } + } + } + }, + { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port", + "attributes": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "name": "Port of Oakland", + "code": "USOAK", + "state_abbr": "CA", + "city": "Oakland", + "country_code": "US", + "time_zone": "America/Los_Angeles" + } + }, + { + "id": "715ed64b-6195-49f6-9407-1383a8088bfd", + "type": "shipment", + "attributes": { + "created_at": "2020-04-06T19:02:46-07:00", + "bill_of_lading_number": "TE49DD6650B9", + "ref_numbers": [ + "REF-4A25EA" + ], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": null, + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-04-13T19:02:46-07:00", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "1378c720-efe9-4562-a2ad-562002eb4b1d", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "2508d879-4451-4d7f-ab23-92258b5df553", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [] + } + }, + "links": { + "self": "/v2/shipments/715ed64b-6195-49f6-9407-1383a8088bfd" + } + } + ] + } + }, + "Transport Event": { + "value": { + "data": { + "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", + "type": "webhook_notification", + "attributes": { + "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", + "event": "container.transport.vessel_arrived", + "delivery_status": "pending", + "created_at": "2020-07-28T23:12:53Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", + "type": "transport_event" + } + }, + "webhook": { + "data": { + "id": "534d498b-8332-439a-accb-129dfd144ceb", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2020-07-28T23:12:53Z", + "voyage_number": null, + "timestamp": "2020-07-28T23:12:53Z", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "1fc35241-4c8b-420d-803a-9e6661720a05", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "8c2f335a-b155-4021-87f0-9b040159a981", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "b381c692-8dad-4f04-873f-d9e567143335", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "26fede8d-2c6d-4bf5-98d6-5a86d30f17a9", + "type": "terminal" + } + } + } + }, + { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port", + "attributes": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "name": "Port of Oakland", + "code": "USOAK", + "state_abbr": "CA", + "city": "Oakland", + "country_code": "US", + "time_zone": "America/Los_Angeles" + } + }, + { + "id": "1fc35241-4c8b-420d-803a-9e6661720a05", + "type": "shipment", + "attributes": { + "created_at": "2020-07-28T23:12:53Z", + "bill_of_lading_number": "TE491846459E", + "ref_numbers": [ + null + ], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2020-07-15T23:12:53Z", + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-08-04T23:12:53Z", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "06564cb7-77d6-4e0e-8e4a-37756ca21bc9", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "06f5d3bb-f258-4f1b-8c2f-db78248f6e29", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [ + { + "id": "8c2f335a-b155-4021-87f0-9b040159a981", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/1fc35241-4c8b-420d-803a-9e6661720a05" + } + } + ] + } + }, + "Container Updated Event": { + "value": { + "data": { + "id": "416e293f-4423-47f7-abf3-1ae97054f41f", + "type": "webhook_notification", + "attributes": { + "id": "416e293f-4423-47f7-abf3-1ae97054f41f", + "event": "container.updated", + "delivery_status": "pending", + "created_at": "2020-06-04T22:03:09Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", + "type": "container_updated_event" + } + }, + "webhook": { + "data": { + "id": "cda37836-aa40-455e-8b43-5fd74930c7f6", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", + "type": "container_updated_event", + "attributes": { + "changeset": { + "available_for_pickup": [ + false, + true + ], + "pod_terminal_holds": [ + null, + [ + { + "name": "customs", + "status": "hold", + "description": "CUST EXAM" + } + ] + ] + }, + "timestamp": "2020-06-04T22:03:09Z", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "container": { + "data": { + "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", + "type": "container" + } + }, + "terminal": { + "data": { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "type": "terminal" + } + } + } + }, + { + "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", + "type": "container", + "attributes": { + "number": "GLDU1355602", + "seal_number": "431ac97412228532", + "created_at": "2020-05-04T22:03:09Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "standard", + "weight_in_lbs": 55634, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pickup_lfd": null, + "availability_known": true, + "available_for_pickup": null, + "pod_arrived_at": "2020-06-04T22:03:08Z", + "pod_discharged_at": "2020-06-04T22:03:08Z", + "final_destination_full_out_at": "2020-06-04T22:03:08Z", + "pod_full_out_at": null, + "empty_terminated_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "shipment": { + "data": null + } + } + }, + { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "type": "terminal", + "attributes": { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "nickname": "Denesik-Hintz", + "name": "Adams LLC Terminal", + "firms_code": "E005" + }, + "relationships": { + "port": { + "data": { + "id": "d8a92775-95f9-47be-a6d2-42542a32d5fc", + "type": "port" + } + } + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-webhook-notification-id", + "description": "\n", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "description": "Comma delimited list of relations to include.", + "name": "include" + } + ] + } + }, + "/webhook_notifications": { + "get": { + "summary": "List webhook notifications", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook_notification" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + } + ] + } + } + } + } + } + } + } + }, + "operationId": "get-webhook-notifications", + "description": "Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. ", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include." + } + ] + } + }, + "/webhook_notifications/examples": { + "get": { + "summary": "Get webhook notification payload examples", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook_notification" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + } + ] + } + } + } + } + } + } + } + }, + "operationId": "get-webhook-notifications-example", + "description": "Returns an example payload as it would be sent to a webhook endpoint for the provided `event` ", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "example": "container.transport.full_out" + }, + "in": "query", + "name": "event", + "description": "The webhook notification event name you wish to see an example of" + } + ] + }, + "parameters": [] + }, + "/webhooks/ips": { + "get": { + "summary": "List webhook IPs", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "webhook_notification_ips": { + "type": "array", + "items": { + "type": "string", + "format": "ipv4" + } + }, + "last_updated": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Example List of IPs": { + "value": { + "webhook_notification_ips": [ + "35.222.62.171", + "3.230.67.145", + "44.217.15.129" + ], + "last_updated": "2023-10-17T21:23:16Z" + } + } + } + } + } + } + }, + "operationId": "get-webhooks-ips", + "description": "Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.", + "tags": [ + "Webhooks" + ], + "parameters": [] + } + }, + "/containers": { + "get": { + "summary": "List containers", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/container" + } + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipment" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "Example List of containers": { + "value": { + "data": [ + { + "id": "be0b247b-c144-4163-8919-cf9178930736", + "type": "container", + "attributes": { + "number": "TCLU6718159", + "seal_number": null, + "created_at": "2024-06-26T15:05:18Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-21T14:12:00Z", + "pod_discharged_at": "2024-06-23T00:19:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 53502, + "pod_full_out_at": "2024-06-26T16:15:00Z", + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:56Z", + "fees_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Community", + "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:18Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": null, + "empty_terminated_timezone": "America/New_York", + "pod_rail_carrier_scac": null, + "ind_rail_carrier_scac": null, + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "cc8f8e43-d6a9-4edb-a8c0-d0ab03c113d3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "fb53b35c-6a0c-4e22-b196-b623e8ba7db5", + "type": "transport_event" + }, + { + "id": "179897a3-04f5-450b-86e1-db57970c0248", + "type": "transport_event" + }, + { + "id": "a119b8e6-10c5-4967-8364-885f7dbf8e50", + "type": "transport_event" + }, + { + "id": "3372d58c-df89-46a2-b064-b308a9dc7040", + "type": "transport_event" + }, + { + "id": "800a3e59-8231-4e42-a80f-73c97cfb1be9", + "type": "transport_event" + }, + { + "id": "d466676b-0073-4b0c-89aa-d42486e9ed4f", + "type": "transport_event" + }, + { + "id": "85665836-5915-4cb9-ab78-b8487598cd0d", + "type": "transport_event" + }, + { + "id": "94cca22a-520a-4d19-a551-0554aceb3794", + "type": "transport_event" + }, + { + "id": "032e812f-d5e4-48af-8d79-2c2b41a07032", + "type": "transport_event" + }, + { + "id": "1b7dd74d-eff6-4e06-9a15-234295ce6fd5", + "type": "transport_event" + }, + { + "id": "b9f07b7e-1653-4209-b375-4588653d5275", + "type": "transport_event" + }, + { + "id": "a35f3c1b-ad35-4347-b0f2-9e08f0d4ca64", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "e26fc659-f79d-4cc0-8efc-5ce5e8444891", + "type": "raw_event" + }, + { + "id": "8820af5e-cb77-41c5-a897-906f2c56eb1e", + "type": "raw_event" + }, + { + "id": "a628a2c2-dab6-4b04-b3ae-d7ec99098a89", + "type": "raw_event" + }, + { + "id": "6ca157f9-3b58-4db5-8155-fc7b41a62611", + "type": "raw_event" + }, + { + "id": "6fb06390-4b0a-4c1f-9703-ec89927df7f3", + "type": "raw_event" + }, + { + "id": "26fe408d-e091-412a-a2fb-23a4d778b6b9", + "type": "raw_event" + }, + { + "id": "16490dd8-d79f-468a-91b1-dbb30bb45c85", + "type": "raw_event" + }, + { + "id": "ff3db923-a644-4706-8057-1bd53c95fbd5", + "type": "raw_event" + }, + { + "id": "3fd27ffd-9618-477f-b1a9-cbc179defefe", + "type": "raw_event" + }, + { + "id": "f92efdd3-f79c-4a1c-97ce-9a47588b525c", + "type": "raw_event" + }, + { + "id": "3b2a88ef-df0b-4e61-99c1-d4a175910111", + "type": "raw_event" + }, + { + "id": "9b367e33-9e43-488f-8217-081698adf40d", + "type": "raw_event" + }, + { + "id": "ee0915df-e2f8-46b0-acf1-816871ca142d", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "container", + "attributes": { + "number": "TCLU2224327", + "seal_number": null, + "created_at": "2024-06-26T15:05:34Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-21T14:12:00Z", + "pod_discharged_at": "2024-06-23T17:21:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 20, + "equipment_height": "standard", + "weight_in_lbs": 44225, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:56Z", + "fees_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard", + "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:34Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "America/Chicago", + "empty_terminated_timezone": "America/Chicago", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-02T14:20:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "99f84294-3bda-4765-81b3-31765e6d2a24", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "e6fa9a01-511b-4f43-a7e1-d628315b84ef", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "4a5a04b7-8974-4c46-beaa-bf55004422c9", + "type": "transport_event" + }, + { + "id": "11c90391-aa9c-408b-b20e-daed8dd09586", + "type": "transport_event" + }, + { + "id": "d7f23c71-7084-4fbb-9fef-740f624182aa", + "type": "transport_event" + }, + { + "id": "c4c3537d-5c47-4623-afe0-edc0dd6f75c5", + "type": "transport_event" + }, + { + "id": "e4002e13-0a74-4bd4-9147-787bb21e2fda", + "type": "transport_event" + }, + { + "id": "57b5568f-d8a6-443d-b1c8-be5cd080e5ed", + "type": "transport_event" + }, + { + "id": "0771cff1-79bf-4eaa-9d9d-790cb433ce44", + "type": "transport_event" + }, + { + "id": "031021b9-7b39-41f7-bd45-26cc8cb799d2", + "type": "transport_event" + }, + { + "id": "9956448d-34d3-4c23-bcfd-19807eb4034f", + "type": "transport_event" + }, + { + "id": "f1caf6ea-3e92-4b68-bcea-556828301062", + "type": "transport_event" + }, + { + "id": "2dd558a8-fa07-454c-acf3-b53d072264af", + "type": "transport_event" + }, + { + "id": "83118511-d9ed-4b16-b323-5e685d9b266e", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "f256289f-219d-45f6-b727-56fb9c6bc433", + "type": "raw_event" + }, + { + "id": "c5581f60-ffb2-4ef0-a855-b70acd3b294f", + "type": "raw_event" + }, + { + "id": "3c6716f9-814c-4459-9bbb-753f010446b2", + "type": "raw_event" + }, + { + "id": "62c7b849-c99b-4e37-9861-105141cc0a4c", + "type": "raw_event" + }, + { + "id": "7db37d43-426a-4f84-82af-2957621ce466", + "type": "raw_event" + }, + { + "id": "d4342119-db56-46fc-8299-f573f5b52e73", + "type": "raw_event" + }, + { + "id": "ba159c3b-590c-43ff-bc04-b0354fd326f4", + "type": "raw_event" + }, + { + "id": "ec1be31a-17f1-4f6f-85ab-c74cb0dbe6cb", + "type": "raw_event" + }, + { + "id": "d12c4656-0d1f-4f30-a0fa-a2ee887741a8", + "type": "raw_event" + }, + { + "id": "146d56e2-b41d-4469-8202-0c7d7315e794", + "type": "raw_event" + }, + { + "id": "6b86b4d1-f85f-4c26-9fbc-3132a62f0fbc", + "type": "raw_event" + }, + { + "id": "bb2c9105-e421-4372-9265-e61b3fa54851", + "type": "raw_event" + }, + { + "id": "3a09c49f-c12e-49e9-b2de-b8dca2b3d608", + "type": "raw_event" + }, + { + "id": "54d91ebc-8f57-4764-b7c6-a3f4dc2459be", + "type": "raw_event" + }, + { + "id": "9e788c46-9431-41eb-baee-c8b14fb4f590", + "type": "raw_event" + }, + { + "id": "d3051802-8b6f-497d-ad16-fb35547c8662", + "type": "raw_event" + }, + { + "id": "af3090e7-c6d3-4d8e-88aa-9cd757102f9b", + "type": "raw_event" + }, + { + "id": "95daf204-d95d-4a89-bedf-358bedb8b3b8", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "8d1faeeb-3890-4fac-8659-cd13737b26f1", + "type": "container", + "attributes": { + "number": "CMAU0619052", + "seal_number": null, + "created_at": "2024-06-26T15:02:11Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-22T22:10:00Z", + "pod_discharged_at": "2024-06-23T20:38:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 20, + "equipment_height": "standard", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-06-27T07:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Grounded", + "pod_last_tracking_request_at": "2024-06-26T18:47:47Z", + "shipment_last_tracking_request_at": "2024-06-26T15:02:11Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "Asia/Shanghai", + "empty_terminated_timezone": "Asia/Shanghai", + "pod_rail_carrier_scac": null, + "ind_rail_carrier_scac": null, + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "f706cbea-3264-473d-8d26-af257f3bc1be", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "1bb3a814-edab-403f-8ef2-a6d0966df423", + "type": "transport_event" + }, + { + "id": "19f197bf-444c-40ee-8478-6f02abe715a9", + "type": "transport_event" + }, + { + "id": "4c9223bb-0218-4175-8a2e-7bb99c40642a", + "type": "transport_event" + }, + { + "id": "432da964-6e99-45e9-b4b1-00b7be858591", + "type": "transport_event" + }, + { + "id": "b462b4d1-1e02-4037-af7f-6c8fa981f268", + "type": "transport_event" + }, + { + "id": "e3ca4a25-692f-474a-aa71-48fb9840aef1", + "type": "transport_event" + }, + { + "id": "014b9d1f-f033-4a3e-89f9-c57569883436", + "type": "transport_event" + }, + { + "id": "2cec71c9-721a-4060-993f-0ffcf01151cd", + "type": "transport_event" + }, + { + "id": "24941515-1b5e-4fca-87eb-092e56ed156a", + "type": "transport_event" + }, + { + "id": "0fd06bc0-1fda-467c-ab67-90a30c6d62ab", + "type": "transport_event" + }, + { + "id": "0bce915e-b9ba-42a0-a484-136266fe8b9a", + "type": "transport_event" + }, + { + "id": "582006aa-6547-4712-b964-5637aad839b4", + "type": "transport_event" + }, + { + "id": "11458420-b354-4288-a275-7572d3c60e33", + "type": "transport_event" + }, + { + "id": "5c900fa1-11bf-4f96-89e7-b12f6a98edc4", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "999d7de7-eaed-4313-85df-772a6d24a85e", + "type": "raw_event" + }, + { + "id": "ac9c2780-240d-443f-87f4-95465fa5447b", + "type": "raw_event" + }, + { + "id": "a0ee3724-91c3-4b78-af32-2f16e8c2d600", + "type": "raw_event" + }, + { + "id": "79725b1f-19f7-4fc3-8c69-586e237c1719", + "type": "raw_event" + }, + { + "id": "4f07f257-ea4f-4e61-94ed-a01598899020", + "type": "raw_event" + }, + { + "id": "41612acb-b3d4-495f-9138-f34105851d21", + "type": "raw_event" + }, + { + "id": "84ffdd37-ec39-404a-9b68-d72d8fb96d48", + "type": "raw_event" + }, + { + "id": "7e9d8a6d-5339-4266-a6fb-22c28f41149f", + "type": "raw_event" + }, + { + "id": "2359b787-b218-42dc-b9a5-84b608aee671", + "type": "raw_event" + }, + { + "id": "ea33303e-0e48-4442-9886-0dfe38b726b5", + "type": "raw_event" + }, + { + "id": "3689d013-8525-418b-92ed-95ec684130b4", + "type": "raw_event" + }, + { + "id": "a64f7f7e-a6fa-4913-aba0-b28ba189d68b", + "type": "raw_event" + }, + { + "id": "c264a859-4fcb-4fab-95c0-29be99ec54a4", + "type": "raw_event" + }, + { + "id": "2aabf25c-9a3e-44bc-b6c8-ed7b1e3c3630", + "type": "raw_event" + }, + { + "id": "6ae70038-02d7-425a-99c1-8761c69a9033", + "type": "raw_event" + }, + { + "id": "c30021d8-93e9-4bfd-a978-e9e24de148c8", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "853f1794-9b94-4118-9970-4e28e549440d", + "type": "container", + "attributes": { + "number": "TGHU6578122", + "seal_number": null, + "created_at": "2024-06-26T15:08:30Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-23T21:58:00Z", + "pod_discharged_at": "2024-06-24T02:28:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 8898, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-06-27T07:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Wheeled", + "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", + "shipment_last_tracking_request_at": "2024-06-26T15:08:30Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "America/New_York", + "empty_terminated_timezone": "America/New_York", + "pod_rail_carrier_scac": "BNSF", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-07T08:00:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "f3cfe624-706e-4a0c-89d5-140980d986fd", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "7e4557b9-cc5a-4298-aaec-1a32e90202c9", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "94f687d0-dc6b-4342-8710-cb98bc98716e", + "type": "transport_event" + }, + { + "id": "a8d0a842-95fe-4c12-a80e-bd12e87a1421", + "type": "transport_event" + }, + { + "id": "c1f1a186-3737-41ca-ae2b-f79a17519991", + "type": "transport_event" + }, + { + "id": "7fcc5496-d402-4b1c-a6c5-8514e6433070", + "type": "transport_event" + }, + { + "id": "ab7cd84a-edc5-476d-a1c4-190011582314", + "type": "transport_event" + }, + { + "id": "68d91384-3eb3-4d21-ac7a-c1f688f649c2", + "type": "transport_event" + }, + { + "id": "5a702f46-4356-4570-bd2e-2b8adab5ba3e", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "a4b7e4b6-5227-4f34-9e91-9e75223798ae", + "type": "raw_event" + }, + { + "id": "4f3e3c7b-d1e6-4fde-b95e-ce9a8835445c", + "type": "raw_event" + }, + { + "id": "39e8570d-1a8f-4e10-8d2e-ac930abc5971", + "type": "raw_event" + }, + { + "id": "b8015a72-9741-4fbf-8adc-d30b87de6aa3", + "type": "raw_event" + }, + { + "id": "61512b1a-a94e-4cac-8bab-763588dbbddf", + "type": "raw_event" + }, + { + "id": "c0bf87f2-37c3-4895-90b0-9f97ac4b5c13", + "type": "raw_event" + }, + { + "id": "3856c7e7-f832-42ca-873b-096952599e29", + "type": "raw_event" + }, + { + "id": "485be998-0861-4d10-8a60-f66d27eb46a7", + "type": "raw_event" + }, + { + "id": "29cde194-bbd9-40c2-adba-5dcb1514f5fc", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "681d713d-bcd6-4303-b082-b9f893e7d1d9", + "type": "container", + "attributes": { + "number": "CSNU8439129", + "seal_number": null, + "created_at": "2024-06-26T15:02:32Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-23T21:58:00Z", + "pod_discharged_at": "2024-06-26T04:30:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 40488, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-01T07:00:00Z", + "pickup_appointment_at": "2024-06-28T15:00:00Z", + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Grounded", + "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", + "shipment_last_tracking_request_at": "2024-06-26T15:02:32Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "America/Chicago", + "empty_terminated_timezone": "America/Chicago", + "pod_rail_carrier_scac": "BNSF", + "ind_rail_carrier_scac": "BNSF", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-04T22:00:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "edd626cf-b0b5-4679-8a6c-80c8e9fe7698", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "572b372f-21c7-4403-8fb0-948377c74642", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "9d19125a-2944-442c-8132-bf0d83670e5c", + "type": "transport_event" + }, + { + "id": "b2ff0b47-3151-41e2-8c46-7f95cdbe4167", + "type": "transport_event" + }, + { + "id": "e52feaa3-7a50-4570-a2ea-bf06f955ce23", + "type": "transport_event" + }, + { + "id": "c56d95e5-774f-432c-b6f4-c53967f07292", + "type": "transport_event" + }, + { + "id": "9b1d8b48-e870-46fa-bc46-10f9b30c64d4", + "type": "transport_event" + }, + { + "id": "3e1d3571-6b24-4ad2-a071-4e70d59af521", + "type": "transport_event" + }, + { + "id": "60f5eefe-13d5-4f85-9b65-d13b4c67115a", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2af51127-0971-4741-9b97-ea1b338e3a7c", + "type": "raw_event" + }, + { + "id": "4472ded6-eb69-4f21-8666-9a4f2342dfeb", + "type": "raw_event" + }, + { + "id": "ce238754-d5fd-4dc8-9f55-2ac6efdfbb5e", + "type": "raw_event" + }, + { + "id": "5cd3b5ef-843f-4f60-87ce-5beaeea86f7b", + "type": "raw_event" + }, + { + "id": "55291737-98b6-403c-9424-1605e6e01007", + "type": "raw_event" + }, + { + "id": "cdc55ea8-a075-45b3-9570-ade6fb2f0d94", + "type": "raw_event" + }, + { + "id": "c0ab5406-4e10-4fdc-85a4-e17ce8d956f5", + "type": "raw_event" + }, + { + "id": "965f0172-1c20-4342-a44a-7be0e594ff76", + "type": "raw_event" + }, + { + "id": "db178011-c795-42a4-9537-b4e77ffb4f98", + "type": "raw_event" + } + ] + } + } + } + ], + "meta": { + "size": 5, + "total": 59229 + }, + "links": { + "self": "https://api.terminal49.com/v2/containers?page[size]=5", + "current": "https://api.terminal49.com/v2/containers?page[number]=1&page[size]=5", + "next": "https://api.terminal49.com/v2/containers?page[number]=2&page[size]=5", + "last": "https://api.terminal49.com/v2/containers?page[number]=11846&page[size]=5" + } + } + } + } + } + } + } + }, + "operationId": "get-containers", + "description": "Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.\n\nThis API will return all containers associated with the account.", + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer", + "default": 30 + }, + "in": "query", + "name": "page[size]", + "description": "" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "terminal_checked_before", + "description": "Number of seconds in which containers were refreshed" + } + ] + }, + "patch": { + "summary": "Edit a container", + "operationId": "patch-containers-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/container" + } + } + } + } + } + } + }, + "description": "Update a container", + "tags": [ + "Containers" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_numbers": { + "type": "array", + "items": { + "type": "string", + "example": "REF-12345" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/containers/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/container" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/terminal" + }, + { + "$ref": "#/components/schemas/transport_event" + } + ] + } + } + } + }, + "examples": { + "Example Container": { + "value": { + "data": { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container", + "attributes": { + "number": "CAIU7432986", + "seal_number": null, + "created_at": "2024-06-26T15:05:21Z", + "ref_numbers": [], + "pod_arrived_at": null, + "pod_discharged_at": "2024-06-22T04:00:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T17:51:12Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-07T04:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard - Y0709A", + "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "US/Eastern", + "empty_terminated_timezone": "US/Eastern", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "45b542cb-332b-4684-b915-42e3a0759823", + "type": "transport_event" + }, + { + "id": "174ed528-a1a9-4002-aef0-f2c9369199da", + "type": "transport_event" + }, + { + "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", + "type": "transport_event" + }, + { + "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", + "type": "transport_event" + }, + { + "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", + "type": "raw_event" + }, + { + "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", + "type": "raw_event" + }, + { + "id": "74810c04-6c8a-4194-8cff-52936584a965", + "type": "raw_event" + }, + { + "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", + "type": "raw_event" + }, + { + "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", + "type": "raw_event" + }, + { + "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", + "type": "raw_event" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/containers/55a700e4-7005-45a9-92fd-1ff38641dbd9" + } + } + } + } + } + } + } + }, + "operationId": "get-containers-id", + "description": "Retrieves the details of a container.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + } + }, + "/containers/{id}/raw_events": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container's raw events", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/raw_event" + } + } + } + }, + "examples": { + "Example Raw Events": { + "value": { + "data": [ + { + "id": "ca6b760f-13e9-4bf6-ab49-3cf2e40757fb", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-03T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-03", + "estimated_at": null, + "actual_at": null, + "event": "empty_out", + "index": 0, + "original_event": "Truck Gate out empty", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": "Oakland", + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "bcdfc796-0570-4c85-9336-d6c7d0da02d2", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-09T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-09", + "estimated_at": null, + "actual_at": null, + "event": "full_in", + "index": 1, + "original_event": "Truck Arrival in", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "a4ff01b0-b374-4123-ae65-3dc0c7ea41ea", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-14T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-14", + "estimated_at": null, + "actual_at": null, + "event": "vessel_loaded", + "index": 2, + "original_event": "Vessel Loaded", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "ca5862ef-6e27-4245-a281-0cec6bbe1fb7", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-15T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-15", + "estimated_at": null, + "actual_at": null, + "event": "vessel_departed", + "index": 3, + "original_event": "Vessel departed", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "f47a903e-e6d1-41c5-aec6-8401b2abf297", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-25T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-25", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_arrived", + "index": 4, + "original_event": "Vessel arrived", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "72a1a13b-a2e0-4ac0-851d-eec41e9e9087", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-25T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-25", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_discharged", + "index": 5, + "original_event": "Vessel Discharged", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "cd91f0cf-ee73-4c47-b99f-63245cb5bc96", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-07T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-07", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_loaded", + "index": 6, + "original_event": "Vessel Loaded", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "561dbb7e-c3ab-4e63-b09b-957878b1425f", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-07T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-07", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_departed", + "index": 7, + "original_event": "Vessel departed", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "551711a6-62ad-4205-8da2-00e0c0cbd2db", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-12T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-12", + "estimated_at": null, + "actual_at": null, + "event": "vessel_arrived", + "index": 8, + "original_event": "Vessel arrived", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "f4027470-75ca-4e2a-b4f0-47654a25ac48", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-13T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-13", + "estimated_at": null, + "actual_at": null, + "event": "vessel_discharged", + "index": 9, + "original_event": "Vessel Discharged", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "50f11e4f-411e-48e2-8141-64226500df9c", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-14T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-14", + "estimated_at": null, + "actual_at": null, + "event": "full_out", + "index": 10, + "original_event": "Truck Departure from", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "49aea23c-b8c5-4a97-b133-f7a9723fa1b4", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-15T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-15", + "estimated_at": null, + "actual_at": null, + "event": "empty_in", + "index": 11, + "original_event": "Truck Gate in empty", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + } + ], + "included": [ + { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel", + "attributes": { + "name": "MSC FAITH", + "imo": null, + "mmsi": "636019213", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + }, + { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel", + "attributes": { + "name": "SINGAPORE EXPRESS", + "imo": null, + "mmsi": "477300500", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-containers-id-raw_events", + "description": "#### Deprecation warning\nThe `raw_events` endpoint is provided as-is.\n\n For past events we recommend consuming `transport_events`.\n\n---\nGet 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. \n\nNormalized 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. \n\nFor 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.", + "deprecated": true + } + }, + "/containers/{id}/transport_events": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container's transport events", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/transport_event" + } + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/metro_area" + }, + { + "$ref": "#/components/schemas/terminal" + }, + { + "$ref": "#/components/schemas/rail_terminal" + }, + { + "$ref": "#/components/schemas/vessel" + } + ] + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "Example transport events": { + "value": { + "data": [ + { + "id": "efc3f3c1-cdc2-4a7d-a176-762ddec107b8", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_loaded", + "created_at": "2021-01-05T08:41:12Z", + "voyage_number": "15W10", + "timestamp": null, + "location_locode": "CLSAI", + "timezone": "America/Santiago" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "951058bd-2c3b-4bcc-94e1-9be2526b9687", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_departed", + "created_at": "2021-01-05T08:41:11Z", + "voyage_number": "15W10", + "timestamp": null, + "location_locode": "CLSAI", + "timezone": "America/Santiago" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "69af6795-56c2-4157-9a87-afd761cc85a0", + "type": "transport_event", + "attributes": { + "event": "container.transport.full_out", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": null, + "timestamp": "2020-04-14T00:00:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "68c3c29a-504a-4dbb-ad27-7194ef42d484", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_discharged", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": "15W10", + "timestamp": "2020-04-13T00:00:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", + "type": "terminal" + } + } + } + }, + { + "id": "03349405-a9be-4f3e-abde-28f2cb3922bd", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": "15W10", + "timestamp": "2020-04-13T01:24:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", + "type": "terminal" + } + } + } + }, + { + "id": "ba9f85b4-658d-4f23-9308-635964df8037", + "type": "transport_event", + "attributes": { + "event": "container.transport.empty_in", + "created_at": "2020-05-14T00:05:42Z", + "voyage_number": null, + "timestamp": "2020-04-15T00:00:00Z", + "location_locode": null, + "timezone": null + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": null + }, + "terminal": { + "data": null + } + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events", + "current": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events?page[number]=1" + } + } + } + } + } + } + } + }, + "operationId": "get-containers-id-transport_events", + "description": "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.\n\nThis does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. ", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + } + }, + "/shipping_lines": { + "get": { + "summary": "Shipping Lines", + "tags": [ + "Shipping Lines" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipping_line" + } + }, + "links": { + "$ref": "#/components/schemas/links" + } + } + } + } + } + } + }, + "operationId": "get-shipping_lines", + "description": "Return a list of shipping lines supported by Terminal49. \nN.B. There is no pagination for this endpoint." + } + }, + "/shipping_lines/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a single shipping line", + "tags": [ + "Shipping Lines" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipping_line" + } + } + } + } + } + } + }, + "operationId": "get-shipping_lines-id", + "description": "Return the details of a single shipping line." + } + }, + "/metro_areas/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a metro area using the un/locode or the id", + "tags": [ + "Metro Areas" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/metro_area" + } + } + } + } + } + } + }, + "operationId": "get-metro-area-id", + "description": "Return the details of a single metro area." + } + }, + "/ports/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a port using the locode or the id", + "tags": [ + "Ports" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/port" + } + } + } + } + } + } + }, + "operationId": "get-port-id", + "description": "Return the details of a single port." + } + }, + "/vessels/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a vessel using the id", + "tags": [ + "Vessels" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/vessel" + } + } + } + } + } + } + }, + "operationId": "get-vessels-id", + "description": "Returns a vessel by it's given identifier" + } + }, + "/vessels/{imo}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "imo", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a vessel using the imo", + "tags": [ + "Vessels" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/vessel" + } + } + } + } + } + } + }, + "operationId": "get-vessels-imo", + "description": "Returns a vessel by the given IMO number.", + "x-internal": true + } + }, + "/terminals/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a terminal using the id", + "tags": [ + "Terminals" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/terminal" + } + } + } + } + } + } + }, + "operationId": "get-terminal-id", + "description": "Return the details of a single terminal." + } + }, + "/parties": { + "get": { + "description": "Get a list of parties", + "operationId": "list-parties", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/party" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer", + "default": 25 + }, + "in": "query", + "name": "page[size]", + "description": "" + } + ], + "tags": [ + "Parties" + ] + }, + "post": { + "description": "Creates a new party", + "operationId": "post-party", + "responses": { + "201": { + "description": "Party Created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + }, + "examples": { + "New Party": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "party", + "attributes": { + "company_name": "COMPANY NAME" + } + }, + "links": { + "self": "/v2/parties/ba4cb904-827f-4038-8e31-1e92b3356218" + } + } + } + } + } + }, + "headers": {} + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/company_name" + }, + "title": "Unprocessable Entity", + "detail": "Company name can't be blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/customer" + }, + "title": "Unprocessable Entity", + "detail": "'XXXX' already exists in Account" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "company_name": { + "type": "string", + "example": "COMPANY NAME", + "description": "The name of the company" + } + } + } + } + } + } + } + } + } + }, + "tags": [ + "Parties" + ] + } + }, + "/parties/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + } + } + } + } + }, + "operationId": "get-parties-id", + "description": "Returns a party by it's given identifier", + "tags": [ + "Parties" + ] + }, + "patch": { + "description": "Updates a party", + "operationId": "edit-party", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/company_name" + }, + "title": "Unprocessable Entity", + "detail": "Company name can't be blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/customer" + }, + "title": "Unprocessable Entity", + "detail": "'XXXX' already exists in Account" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "company_name": { + "type": "string", + "example": "COMPANY NAME", + "description": "The name of the company" + } + } + } + } + } + } + } + } + } + }, + "tags": [ + "Parties" + ] + } + } + }, + "x-tagGroups": [ + { + "name": "End Points", + "tags": [ + "Shipments", + "Containers", + "Tracking Requests", + "Webhooks", + "Webhook Notifications", + "Metro Areas" + ] + } + ], + "components": { + "schemas": { + "shipment": { + "title": "Shipment model", + "type": "object", + "x-examples": {}, + "description": "", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "relationships": { + "type": "object", + "properties": { + "destination": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port", + "metro_area" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "port_of_lading": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "containers": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "container" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + } + }, + "port_of_discharge": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "pod_terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "terminal" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "destination_terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "terminal", + "rail_terminal" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "line_tracking_stopped_by_user": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "user" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + } + } + }, + "attributes": { + "type": "object", + "properties": { + "bill_of_lading_number": { + "type": "string" + }, + "normalized_number": { + "type": "string", + "description": "The normalized version of the shipment number used for querying the carrier" + }, + "ref_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "port_of_lading_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "port_of_lading_name": { + "type": "string", + "nullable": true + }, + "port_of_discharge_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "port_of_discharge_name": { + "type": "string", + "nullable": true + }, + "destination_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "destination_name": { + "type": "string", + "nullable": true + }, + "shipping_line_scac": { + "type": "string" + }, + "shipping_line_name": { + "type": "string" + }, + "shipping_line_short_name": { + "type": "string" + }, + "customer_name": { + "type": "string", + "nullable": true + }, + "pod_vessel_name": { + "type": "string", + "nullable": true + }, + "pod_vessel_imo": { + "type": "string", + "nullable": true + }, + "pod_voyage_number": { + "type": "string", + "nullable": true + }, + "pol_etd_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pol_atd_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_original_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_ata_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "destination_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "destination_ata_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pol_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "pod_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "destination_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "line_tracking_last_attempted_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 last tried to update the shipment status from the shipping line", + "nullable": true + }, + "line_tracking_last_succeeded_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 last successfully updated the shipment status from the shipping line", + "nullable": true + }, + "line_tracking_stopped_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 stopped checking at the shipping line", + "nullable": true + }, + "line_tracking_stopped_reason": { + "type": "string", + "enum": [ +{ + "openapi": "3.0.0", + "info": { + "title": "Terminal49 API Reference", + "version": "0.2.0", + "contact": { + "name": "Terminal49 API support", + "url": "https://www.terminal49.com", + "email": "support@terminal49.com" + }, + "description": "The Terminal 49 API offers a convenient way to programmatically track your shipments from origin to destination.", + "x-label": "Beta", + "termsOfService": "https://www.terminal49.com/terms" + }, + "servers": [ + { + "url": "https://api.terminal49.com/v2", + "description": "Production" + } + ], + "paths": { + "/shipments": { + "get": { + "summary": "List shipments", + "tags": [ + "Shipments" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipment" + } + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/terminal" + } + ] + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": [ + { + "id": "62738624-7032-4a50-892e-c55826228c25", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T17:28:59Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2148468620", + "normalized_number": "2148468620", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNNBG", + "port_of_lading_name": "Ningbo", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "EVER FORWARD", + "pod_vessel_imo": "9850551", + "pod_voyage_number": "1119E", + "destination_locode": "USCLE", + "destination_name": "Cleveland", + "destination_timezone": "America/New_York", + "destination_ata_at": null, + "destination_eta_at": "2024-07-02T08:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-09T03:42:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-22T13:36:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T17:28:59Z", + "line_tracking_last_succeeded_at": "2024-06-26T17:28:59Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/62738624-7032-4a50-892e-c55826228c25" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "2a90c27b-c2ee-45e2-892f-5c695d74e2d0", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "3b1cc325-ffe9-400a-82ce-f5c4891af382", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "ce22669e-14b2-4501-b782-f0a360f07cd0", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "7aefc29e-0898-4825-8376-4f998b51d033", + "type": "container" + } + ] + } + } + }, + { + "id": "baaa725e-aa0e-4937-ac78-54d9e2e8621e", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T16:47:42Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "HDMUTAOM72244900", + "normalized_number": "TAOM72244900", + "shipping_line_scac": "HDMU", + "shipping_line_name": "Hyundai Merchant Marine", + "shipping_line_short_name": "Hyundai", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNQDG", + "port_of_lading_name": "Qingdao", + "port_of_discharge_locode": "USSAV", + "port_of_discharge_name": "Savannah", + "pod_vessel_name": "UMM SALAL", + "pod_vessel_imo": "9525857", + "pod_voyage_number": "0038E", + "destination_locode": "USROQ", + "destination_name": "Rossville", + "destination_timezone": "America/Chicago", + "destination_ata_at": null, + "destination_eta_at": "2024-06-30T07:05:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-05-07T03:01:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-20T21:27:00Z", + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T16:48:04Z", + "line_tracking_last_succeeded_at": "2024-06-26T16:48:04Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/baaa725e-aa0e-4937-ac78-54d9e2e8621e" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "6129528d-846e-4571-ae16-b5328a4285ab", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "87ca3f37-e4d1-46eb-9eb1-6b5ffafde95d", + "type": "port" + } + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "772cd872-9677-4c68-9b7a-4e9e843b00e2", + "type": "container" + }, + { + "id": "52efc544-0de1-452f-bcf8-0290a6ce5c11", + "type": "container" + }, + { + "id": "3107692e-61ad-4b4c-b3d4-78348b2e37ff", + "type": "container" + } + ] + } + } + }, + { + "id": "7721a48c-5e93-43c9-9f5f-5be10a87fdde", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T16:28:39Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2738424980", + "normalized_number": "2738424980", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "ITSPE", + "port_of_lading_name": "La Spezia", + "port_of_discharge_locode": "USSAV", + "port_of_discharge_name": "Savannah", + "pod_vessel_name": "OOCL GUANGZHOU", + "pod_vessel_imo": "9404869", + "pod_voyage_number": "162E", + "destination_locode": "USATL", + "destination_name": "Atlanta", + "destination_timezone": "America/New_York", + "destination_ata_at": null, + "destination_eta_at": "2024-06-27T06:42:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-05T08:03:00Z", + "pol_timezone": "Europe/Rome", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-23T15:34:00Z", + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T16:28:39Z", + "line_tracking_last_succeeded_at": "2024-06-26T16:28:39Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/7721a48c-5e93-43c9-9f5f-5be10a87fdde" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "b5656766-a56f-4b32-8e03-d240e7519604", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "6129528d-846e-4571-ae16-b5328a4285ab", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "7daf9ea3-3018-4d62-b88c-43803df9030c", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "022ef8fc-1e2a-4ad6-8eae-330d65eb1c8e", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "2a25fd3e-a18e-47cc-9cea-62771e82d0f2", + "type": "container" + }, + { + "id": "6cdc725d-2b31-40f9-86dd-76225390a488", + "type": "container" + }, + { + "id": "2f1e9a9d-4689-4f4d-84a8-64409b56521d", + "type": "container" + } + ] + } + } + }, + { + "id": "32b5ad78-43ba-42d9-bdc0-4cf12320e020", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:59:52Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2738277190", + "normalized_number": "2738277190", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNQDG", + "port_of_lading_name": "Qingdao", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "EVER FORWARD", + "pod_vessel_imo": "9850551", + "pod_voyage_number": "1119E", + "destination_locode": "USMEM", + "destination_name": "Memphis", + "destination_timezone": "America/Chicago", + "destination_ata_at": null, + "destination_eta_at": "2024-07-01T14:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-02T18:22:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-22T13:36:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T15:59:52Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:59:52Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/32b5ad78-43ba-42d9-bdc0-4cf12320e020" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "0ccbe8af-c8d0-4abd-a842-3bfad1d82024", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "42c53b13-0d29-4fa6-8663-7343a56319f1", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "ba0b9f68-2025-40ca-ae12-1c2210cca333", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "d0ec0da1-8a3a-4b11-b1e3-5716bbc71dc3", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "60d2fd62-14e2-4ca2-a927-9633fc58fdff", + "type": "container" + }, + { + "id": "0ffe3e75-b3df-4d20-b9f4-97bbbcec404d", + "type": "container" + } + ] + } + } + }, + { + "id": "bd117d3b-8fa4-487c-9bab-25c15e227d1a", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:59:35Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2147973020", + "normalized_number": "2147973020", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "IDSUB", + "port_of_lading_name": "Surabaya", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "CMA CGM A.LINCOLN", + "pod_vessel_imo": "9780859", + "pod_voyage_number": "1TU70E1MA", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2024-05-14T02:37:00Z", + "pol_timezone": "Asia/Jakarta", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-23T21:58:00Z", + "pod_timezone": "America/Los_Angeles", + "line_tracking_last_attempted_at": "2024-06-26T15:59:35Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:59:35Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/bd117d3b-8fa4-487c-9bab-25c15e227d1a" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "4201ab42-c51f-48ac-b7a1-12146e02c6a2", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "47b27584-4ec9-4e2f-95e1-7a42928cc40c", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "815ff702-fdb5-4455-a28e-314c345d7481", + "type": "container" + } + ] + } + } + } + ], + "meta": { + "size": 5, + "total": 34044 + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments?page[size]=5", + "current": "https://api.terminal49.com/v2/shipments?page[number]=1&page[size]=5", + "next": "https://api.terminal49.com/v2/shipments?page[number]=2&page[size]=5", + "last": "https://api.terminal49.com/v2/shipments?page[number]=6809&page[size]=5" + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Errors": { + "value": { + "error": [ + { + "title": "Invalid arrival_date_from", + "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" + }, + { + "title": "Invalid arrival_date_to", + "detail": "filter['arrival_from'] must be in the format 'YYYY-MM-DD' and a valid date" + }, + { + "title": "Invalid port_of_lading", + "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" + }, + { + "title": "Invalid port_of_discharge", + "detail": "port_of_discharge must be an array of 5 character UN/LOCODEs" + } + ] + } + } + } + } + } + } + }, + "operationId": "get-shipments", + "description": "Returns a list of your shipments. The shipments are returned sorted by creation date, with the most recent shipments appearing first.\n\nThis 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. ", + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]", + "description": "\n" + }, + { + "schema": { + "type": "integer", + "default": 30 + }, + "in": "query", + "name": "page[size]", + "description": "\n" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "q", + "description": "\nSearch shipments by master bill of lading, reference number, or container number.", + "deprecated": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "number", + "description": "Search shipments by the original request tracking `request_number`" + } + ] + } + }, + "/shipments/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Shipment Id" + } + ], + "get": { + "summary": "Get a shipment", + "tags": [ + "Shipments" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/terminal" + } + ] + } + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T15:05:20Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "MEDUF5399896", + "normalized_number": "MEDUF5399896", + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "shipping_line_short_name": "MSC", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "FRLEH", + "port_of_lading_name": "Le Havre", + "port_of_discharge_locode": "USNYC", + "port_of_discharge_name": "New York / New Jersey", + "pod_vessel_name": "MSC ANISHA R.", + "pod_vessel_imo": "9227297", + "pod_voyage_number": "421A", + "destination_locode": "USIND", + "destination_name": "Indianapolis", + "destination_timezone": "US/Eastern", + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2024-06-11T22:00:00Z", + "pol_timezone": "Europe/Paris", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": null, + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2024-06-26T15:05:20Z", + "line_tracking_last_succeeded_at": "2024-06-26T15:05:20Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "fe7fc831-8a81-4079-8938-b90015912e8b", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "e1e492f6-c8ba-45a9-ad1a-67a7e74547ce", + "type": "port" + } + }, + "destination_terminal": { + "data": null + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments/02b1bd6f-407c-45bb-8645-06e7ee34e7e3?include=containers" + }, + "included": [ + { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container", + "attributes": { + "number": "CAIU7432986", + "seal_number": null, + "created_at": "2024-06-26T15:05:21Z", + "ref_numbers": [], + "pod_arrived_at": null, + "pod_discharged_at": "2024-06-22T04:00:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T17:51:12Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-07T04:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard - Y0709A", + "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "US/Eastern", + "empty_terminated_timezone": "US/Eastern", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "45b542cb-332b-4684-b915-42e3a0759823", + "type": "transport_event" + }, + { + "id": "174ed528-a1a9-4002-aef0-f2c9369199da", + "type": "transport_event" + }, + { + "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", + "type": "transport_event" + }, + { + "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", + "type": "transport_event" + }, + { + "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", + "type": "raw_event" + }, + { + "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", + "type": "raw_event" + }, + { + "id": "74810c04-6c8a-4194-8cff-52936584a965", + "type": "raw_event" + }, + { + "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", + "type": "raw_event" + }, + { + "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", + "type": "raw_event" + }, + { + "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", + "type": "raw_event" + } + ] + } + } + } + ] + } + } + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": { + "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "6140575020", + "ref_numbers": [ + "my-ref-49", + "PO#18412" + ], + "created_at": "2020-02-20T13:37:12Z", + "tags": [], + "port_of_lading_locode": "INVTZ", + "port_of_lading_name": "Visakhapatnam, IN", + "port_of_discharge_locode": "USNYC", + "port_of_discharge_name": "New York / New Jersey, NY", + "destination_locode": "USDET", + "destination_name": "Detroit, MI", + "shipping_line_scac": "HLCU", + "pod_vessel_name": "CMA CGM ALMAVIVA", + "pod_voyage_number": "0108", + "pol_etd_at": null, + "pol_atd_at": "2020-02-15T21:53Z", + "pol_timezone": "Asia/Calcutta", + "pod_eta_at": "2020-03-18T08:00Z", + "pod_ata_at": null, + "pod_timezone": "America/New_York", + "destination_eta_at": "2020-03-26T17:00Z", + "destination_ata_at": null, + "destination_timezone": "America/Detroit" + }, + "relationships": { + "containers": { + "data": [ + { + "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", + "type": "container" + } + ] + }, + "port_of_lading": { + "data": { + "id": "bde5465a-1160-4fde-a026-74df9c362f65", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port" + } + }, + "destination": { + "data": { + "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", + "type": "metro_area" + } + }, + "customer": { + "data": { + "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", + "type": "account" + } + } + } + }, + "included": [ + { + "id": "c99a81c0-ff69-4bdf-aa5f-8e33a787f5fa", + "type": "container", + "attributes": { + "number": "UACU4743531", + "equipment_type": "reefer", + "length": 40, + "height": "high_cube", + "weight_in_lbs": 35075, + "created_at": "2020-02-20T08:19:52Z", + "seal_number": null, + "pickup_lfd": null, + "availability_known": false, + "available_for_pickup": null, + "current_transportation_mode": "vessel", + "pod_discharged_at": null, + "pod_picked_up_at": null, + "destination_unloaded_at": null, + "destination_picked_up_at": null, + "empty_returned_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "most_recent_location": { + "data": { + "id": "dd179094-a1d4-4129-842d-b952e43df4b7", + "type": "port" + } + }, + "shipment": { + "data": { + "id": "512ae32c-a604-44f8-9c15-2ca9cd3d2ae8", + "type": "shipment" + } + } + } + }, + { + "id": "bde5465a-1160-4fde-a026-74df9c362f65", + "type": "port", + "attributes": { + "name": "Visakhapatnam", + "code": "INVTZ", + "country_code": "IN", + "timezone": "Asia/Calcutta" + } + }, + { + "id": "dd179094-a1d4-4129-842d-b952e43df4b7", + "type": "port", + "attributes": { + "name": "Damietta", + "code": "EGDAM", + "country_code": "EG", + "time_zone": "Africa/Cairo" + } + }, + { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "port", + "attributes": { + "name": "New York / New Jersey", + "code": "USNYC", + "country_code": "US", + "timezone": "America/New_York" + } + }, + { + "id": "c9ae2b6b-5088-4e07-ba09-2872121e4fa2", + "type": "metro_area", + "attributes": { + "name": "Detroit", + "code": "USDET", + "country_code": "US", + "state_abbr": "MI", + "timezone": "America/Detroit" + } + }, + { + "id": "ff76b51a-371e-45ec-86d1-9d03ccae566a", + "type": "account", + "attributes": { + "name": "A-Z Imports" + } + }, + { + "id": "252a5450-2893-4207-b5c4-81ce3152ce84", + "type": "vessel", + "attributes": { + "name": "CMA CGM ALMAVIVA", + "imo": "9450648", + "mmsi": "228339600", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-shipment-id", + "description": "Retrieves the details of an existing shipment. You need only supply the unique shipment `id` that was returned upon `tracking_request` creation.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + }, + "patch": { + "summary": "Edit a shipment", + "operationId": "patch-shipments-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + }, + "description": "Update a shipment", + "tags": [ + "Shipments" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_numbers": { + "type": "array", + "example": [ + "REFNUMBER10" + ], + "description": "Shipment ref numbers.", + "items": { + "type": "string" + } + }, + "shipment_tags": { + "type": "array", + "x-stoplight": { + "id": "02itol38fmg55" + }, + "uniqueItems": true, + "description": "Tags related to a shipment", + "items": { + "x-stoplight": { + "id": "64x90wvr9d6nd" + }, + "type": "string", + "example": "tag1, tag2" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/shipments/{id}/stop_tracking": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Stop tracking a shipment", + "operationId": "patch-shipments-id-stop-tracking", + "tags": [ + "Shipments" + ], + "description": "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.\n\nYou 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.", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + } + } + }, + "/shipments/{id}/resume_tracking": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Resume tracking a shipment", + "operationId": "patch-shipments-id-resume-tracking", + "tags": [ + "Shipments" + ], + "description": "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.", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipment" + } + } + } + } + } + } + } + } + }, + "/tracking_requests": { + "post": { + "summary": "Create a tracking request", + "operationId": "post-track", + "responses": { + "201": { + "description": "Tracking Request Created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipping_line" + } + ] + } + } + } + }, + "examples": { + "Pending Tracking Request": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "tracking_request", + "attributes": { + "request_number": "MEDUFR030802", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-04-04T16:13:35-07:00", + "updated_at": "2020-04-04T17:13:35-07:00", + "status": "pending", + "failed_reason": null + }, + "relationships": { + "tracked_object": { + "data": null + }, + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "links": { + "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" + } + } + } + }, + "Example: MSC BL": { + "value": { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "ref_numbers": [ + "PO12345", + "HBL12345", + "CUSREF1234" + ], + "shipment_tags": [ + "camembert" + ], + "scac": "MSCU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + }, + "summary": "Track by Bill of Lading" + }, + "Example: Booking Number": { + "value": { + "data": { + "attributes": { + "request_type": "booking_number", + "request_number": "OOLU8324567", + "ref_numbers": [ + "PO-8765", + "INV-12345" + ], + "shipment_tags": [ + "priority", + "electronics" + ], + "scac": "OOLU" + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + }, + "type": "tracking_request" + } + }, + "summary": "Track by Booking Number with References, Tags and Customer" + }, + "Example: Container": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" + } + } + }, + "summary": "Track by Container Number" + }, + "Example: Test Number": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + }, + "summary": "Using Test Numbers for Development" + } + } + } + }, + "headers": {} + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac can't be blank", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'XXXX' is not recognized", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/scac" + }, + "title": "Unprocessable Entity", + "detail": "Scac 'UALC' is not supported. We do not currently integrate with Universal Africa Lines", + "code": "blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/request_number" + }, + "title": "Unprocessable Entity", + "detail": "Request number can't be blank", + "code": "blank" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "request_type": { + "type": "string", + "example": "bill_of_lading", + "enum": [ + "bill_of_lading", + "booking_number", + "container" + ], + "description": " The type of document number to be supplied. Container number support is currently in BETA." + }, + "request_number": { + "type": "string", + "example": "MEDUFR030802" + }, + "scac": { + "type": "string", + "example": "MSCU", + "minLength": 4, + "maxLength": 4 + }, + "ref_numbers": { + "type": "array", + "description": "Optional list of reference numbers to be added to the shipment when tracking request completes", + "items": { + "type": "string" + } + }, + "shipment_tags": { + "type": "array", + "description": "Optional list of tags to be added to the shipment when tracking request completes", + "items": { + "type": "string" + } + } + }, + "required": [ + "request_type", + "request_number", + "scac" + ] + }, + "relationships": { + "type": "object", + "properties": { + "customer": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "party" + ] + } + } + } + } + } + } + }, + "type": { + "enum": [ + "tracking_request" + ], + "type": "string" + } + }, + "required": [ + "type" + ] + } + } + }, + "examples": { + "Example: MSC BL": { + "value": { + "data": { + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MEDUFR030802", + "ref_numbers": [ + "PO12345", + "HBL12345", + "CUSREF1234" + ], + "shipment_tags": [ + "camembert" + ], + "scac": "MSCU" + }, + "relationships": { + "customer": { + "data": { + "id": "f7cb530a-9e60-412c-a5bc-205a2f34ba54", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Booking Number": { + "value": { + "data": { + "attributes": { + "request_type": "booking_number", + "request_number": "OOLU8324567", + "ref_numbers": [ + "PO-8765", + "INV-12345" + ], + "shipment_tags": [ + "priority", + "electronics" + ], + "scac": "OOLU" + }, + "relationships": { + "customer": { + "data": { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "party" + } + } + }, + "type": "tracking_request" + } + } + }, + "Example: Container": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU" + } + } + } + }, + "Example: Test Number": { + "value": { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST" + } + } + } + } + } + } + }, + "description": "Create a shipment tracking request" + }, + "security": [ + { + "authorization": [] + } + ], + "description": "To track an ocean shipment, you create a new tracking request. \nTwo attributes are required to track a shipment. A `bill of lading/booking number` and a shipping line `SCAC`. \n\nOnce 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. \n\nIf the attempt to fetch fails then we will send a `tracking_request.failed` webhook notification to your `webhooks`. \n\nA `tracking_request.succeeded` or `tracking_request.failed` webhook notificaiton will only be sent if you have atleast one active webhook. ", + "tags": [ + "Tracking Requests" + ], + "parameters": [] + }, + "parameters": [], + "get": { + "summary": "List tracking requests", + "operationId": "get-tracking-requests", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tracking_request" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipping_line" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + } + }, + "description": "" + } + ] + } + } + } + }, + "examples": {} + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + } + } + } + } + }, + "description": "Returns a list of your tracking requests. The tracking requests are returned sorted by creation date, with the most recent tracking request appearing first.", + "tags": [ + "Tracking Requests" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "q", + "description": "A search term to be applied against request_number and reference_numbers.", + "deprecated": true + }, + { + "schema": { + "type": "string", + "enum": [ + "created", + "pending", + "failed" + ], + "example": "created" + }, + "in": "query", + "name": "filter[status]", + "description": "filter by `status`" + }, + { + "schema": { + "type": "string", + "example": "MSCU" + }, + "in": "query", + "name": "filter[scac]", + "description": "filter by shipping line `scac`" + }, + { + "schema": { + "type": "string", + "example": "2020-04-28T22:59:15Z", + "format": "date-time" + }, + "in": "query", + "name": "filter[created_at][start]", + "description": "filter by tracking_requests `created_at` after a certain ISO8601 timestamp" + }, + { + "schema": { + "type": "string", + "format": "date-time", + "example": "2020-04-28T22:59:15Z" + }, + "in": "query", + "description": "filter by tracking_requests `created_at` before a certain ISO8601 timestamp", + "name": "filter[created_at][end]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "filter[request_number]", + "description": "filter by `request_number`" + } + ] + } + }, + "/tracking_requests/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Tracking Request ID" + } + ], + "get": { + "summary": "Get a single tracking request", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/shipping_line" + } + ] + } + } + } + }, + "examples": { + "With Status Created": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "tracking_request", + "attributes": { + "request_number": "MEDUFR030802", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-04-04T16:13:35-07:00", + "updated_at": "2020-04-04T17:13:35-07:00", + "status": "created", + "failed_reason": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", + "type": "shipment" + } + } + }, + "links": { + "self": "/v2/tracking_requests/ba4cb904-827f-4038-8e31-1e92b3356218" + } + }, + "included": [ + { + "id": "eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83", + "type": "shipment", + "attributes": { + "created_at": "2020-04-04T16:13:37-07:00", + "bill_of_lading_number": "MEDUFR030802", + "ref_numbers": [], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "FRFOS", + "port_of_lading_name": "Fos-Sur-Mer", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Oakland", + "pod_vessel_name": "MSC ALGECIRAS", + "pod_vessel_imo": "9605243", + "pod_voyage_number": "920A", + "destination_locode": "USOAK", + "destination_name": "Oakland", + "destination_timezone": "America/Los_Angeles", + "destination_ata_at": "2019-06-21T18:46:00-07:00", + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2019-05-24T03:00:00-07:00", + "pol_timezone": "Europe/Paris", + "pod_eta_at": null, + "pod_ata_at": "2019-06-21T18:46:00-07:00", + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "6d8c6c29-72a6-49ad-87b7-fd045f202212", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "pod_terminal": { + "data": null + }, + "destination": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "containers": { + "data": [ + { + "id": "11c1fa10-52a5-48e2-82f4-5523756b3d0f", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/eb6f218a-0b4a-47f9-8ef9-759aa5e0ea83" + } + } + ] + } + }, + "Multiple containers": { + "value": { + "data": { + "id": "62c30bd4-d7fc-40dc-9fd6-fb39224301f5", + "type": "tracking_request", + "attributes": { + "request_number": "212157148", + "request_type": "bill_of_lading", + "scac": "MAEU", + "ref_numbers": [], + "shipment_tags": [], + "created_at": "2021-07-27T16:44:14Z", + "updated_at": "2021-07-27T17:44:14Z", + "status": "created", + "failed_reason": null, + "is_retrying": false, + "retry_count": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", + "type": "shipment" + } + }, + "customer": { + "data": null + } + }, + "links": { + "self": "/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5" + } + }, + "links": { + "self": "https://api.terminal49.com/v2/tracking_requests/62c30bd4-d7fc-40dc-9fd6-fb39224301f5?filter%5Bstatus%5D=created" + }, + "included": [ + { + "id": "dfc9f601-f6fe-412e-a71c-feabcc2dd4e3", + "type": "shipment", + "attributes": { + "created_at": "2021-07-27T16:44:16Z", + "ref_numbers": null, + "tags": [], + "bill_of_lading_number": "212157148", + "shipping_line_scac": "MAEU", + "shipping_line_name": "Maersk", + "shipping_line_short_name": "Maersk", + "port_of_lading_locode": "MYTPP", + "port_of_lading_name": "Tanjung Pelepas", + "port_of_discharge_locode": null, + "port_of_discharge_name": null, + "pod_vessel_name": null, + "pod_vessel_imo": null, + "pod_voyage_number": null, + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": null, + "pol_timezone": "Asia/Kuala_Lumpur", + "pod_eta_at": "2021-09-15T15:00:00Z", + "pod_ata_at": null, + "pod_timezone": null, + "line_tracking_last_attempted_at": null, + "line_tracking_last_succeeded_at": "2021-07-27T16:44:16Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "6c387786-252c-476d-9f99-7d835b6b978b", + "type": "port" + } + }, + "port_of_discharge": { + "data": null + }, + "pod_terminal": { + "data": null + }, + "destination": { + "data": null + }, + "destination_terminal": { + "data": null + }, + "containers": { + "data": [ + { + "id": "965880c9-a37e-4ed7-a060-9c49c0f0c5ed", + "type": "container" + }, + { + "id": "ea1f8e08-fcdf-498d-9cb5-0c370b023eeb", + "type": "container" + }, + { + "id": "67f55105-8ea2-4137-9244-f9cc204f5766", + "type": "container" + }, + { + "id": "5ab5d058-772c-466c-bc73-0b8767ad5a79", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/dfc9f601-f6fe-412e-a71c-feabcc2dd4e3" + } + } + ] + } + } + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": {} + } + } + } + }, + "operationId": "get-track-request-by-id", + "description": "Get the details and status of an existing tracking request. ", + "tags": [ + "Tracking Requests" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include. 'tracked_object' is included by default." + } + ], + "x-internal": false + }, + "patch": { + "summary": "Edit a tracking request", + "operationId": "patch-track-request-by-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/tracking_request" + } + } + } + } + } + } + }, + "description": "Update a tracking request", + "tags": [ + "Tracking Requests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_number": { + "type": "string", + "example": "REFNUMBER11", + "description": "Tracking request ref number." + } + } + } + } + } + } + } + } + } + } + } + }, + "/webhooks/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get single webhook", + "tags": [ + "Webhooks" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + } + }, + "operationId": "get-webhooks-id", + "description": "Get the details of a single webhook" + }, + "patch": { + "summary": "Edit a webhook", + "operationId": "patch-webhooks-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + } + }, + "description": "Update a single webhook", + "tags": [ + "Webhooks" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", + "format": "uri", + "description": "The URL of the webhook endpoint." + }, + "events": { + "type": "array", + "description": "The list of events to enable for this endpoint.", + "uniqueItems": true, + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ] + } + }, + "active": { + "type": "boolean" + }, + "headers": { + "type": "array", + "description": "Optional custom headers to pass with each webhook invocation", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the header. (Please not this will be auto-capitalized) " + }, + "value": { + "type": "string", + "description": "The value to pass for the header\n" + } + } + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + } + } + } + }, + "required": [ + "data" + ] + }, + "examples": {} + } + } + } + }, + "delete": { + "summary": "Delete a webhook", + "operationId": "delete-webhooks-id", + "responses": { + "200": { + "description": "OK" + } + }, + "description": "Delete a webhook", + "tags": [ + "Webhooks" + ] + } + }, + "/webhooks": { + "get": { + "summary": "List webhooks", + "tags": [ + "Webhooks" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook" + } + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "links": { + "$ref": "#/components/schemas/links" + } + } + }, + "examples": { + "example-1": { + "value": { + "data": [ + { + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "type": "webhook", + "attributes": { + "url": "http://example.com", + "active": true, + "events": [ + "tracking_request.succeeded" + ], + "secret": "672bd7b58b54645934a830d8fa", + "headers": [ + { + "name": "x-secret-sauce", + "value": "sriracha" + } + ] + } + } + ], + "meta": { + "size": 0, + "total": 0 + }, + "links": { + "last": "http://example.com", + "next": "http://example.com", + "prev": "http://example.com", + "first": "http://example.com", + "self": "http://example.com" + } + } + } + } + } + } + } + }, + "operationId": "get-webhooks", + "description": "Get a list of all the webhooks", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + } + ] + }, + "post": { + "summary": "Create a webhook", + "operationId": "post-webhooks", + "tags": [ + "Webhooks" + ], + "description": "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. \n\nThis is the recommended way tracking shipments and containers via the API. You should use this instead of polling our the API periodically. ", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "required": [ + "url", + "active" + ], + "properties": { + "url": { + "type": "string", + "example": "https://webhook.site/#!/39084fbb-d887-42e8-be08-b9183ad02362", + "format": "uri", + "description": "The URL of the webhook endpoint." + }, + "events": { + "type": "array", + "uniqueItems": true, + "description": "The list of events to enable for this endpoint.", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "example": "tracking_request.succeeded" + } + }, + "active": { + "type": "boolean" + }, + "headers": { + "type": "array", + "description": "Optional custom headers to pass with each webhook invocation", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the header. (Please note this will be auto-capitalized) " + }, + "value": { + "type": "string", + "description": "The value to pass for the header\n" + } + } + } + } + } + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + } + } + } + }, + "required": [ + "data" + ] + }, + "examples": { + "Test Webhook (all events)": { + "value": { + "data": { + "attributes": { + "url": "https://webhook.site/", + "events": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "active": true + }, + "type": "webhook" + } + } + } + } + } + }, + "description": "" + }, + "responses": { + "201": { + "description": "Create a test webhook endpoint", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook" + } + } + }, + "examples": { + "Test Endpoint Created": { + "value": { + "data": { + "type": "webhook", + "id": "9809fb96-7754-488f-99df-29ca8d410d89", + "attributes": { + "url": "https://webhook.site/", + "active": true, + "events": [ + "tracking_request.succeeded" + ], + "secret": "C193J3QOXMFH", + "created_at": "2020-06-05T19:06:13Z" + } + } + } + } + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + } + }, + "parameters": [] + }, + "/webhook_notifications/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a single webhook notification", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/webhook_notification" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + }, + { + "$ref": "#/components/schemas/container_updated_event" + } + ] + } + } + } + }, + "examples": { + "Tracking Request": { + "value": { + "data": { + "id": "a76187fc-5749-43f9-9053-cfaad9790a31", + "type": "webhook_notification", + "attributes": { + "id": "a76187fc-5749-43f9-9053-cfaad9790a31", + "event": "tracking_request.succeeded", + "delivery_status": "pending", + "created_at": "2020-09-11T21:25:34Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", + "type": "tracking_request" + } + }, + "webhook": { + "data": { + "id": "914b21ce-dd7d-4c49-8503-65aba488e9a9", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "bdeca506-9741-4ab1-a0a7-cfd1d908e923", + "type": "tracking_request", + "attributes": { + "request_number": "TE497ED1063E", + "request_type": "bill_of_lading", + "scac": "MSCU", + "ref_numbers": [], + "created_at": "2020-09-11T21:25:34Z", + "updated_at": "2020-09-11T22:25:34Z", + "status": "created", + "failed_reason": null, + "is_retrying": false, + "retry_count": null + }, + "relationships": { + "tracked_object": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + } + }, + "links": { + "self": "/v2/tracking_requests/bdeca506-9741-4ab1-a0a7-cfd1d908e923" + } + }, + { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment", + "attributes": { + "created_at": "2020-09-11T21:25:33Z", + "bill_of_lading_number": "TE497ED1063E", + "ref_numbers": [], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2020-08-29T21:25:33Z", + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-09-18T21:25:33Z", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "4384d6a5-5ccc-43b7-8d19-4a9525e74c08", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [ + { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/b5b10c0a-8d18-46da-b4c2-4e5fa790e7da" + } + }, + { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container", + "attributes": { + "number": "ARDU1824900", + "seal_number": "139F1451", + "created_at": "2020-09-11T21:25:34Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "standard", + "weight_in_lbs": 53507, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "availability_known": true, + "available_for_pickup": false, + "pod_arrived_at": null, + "pod_discharged_at": null, + "final_destination_full_out_at": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "shipment": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + }, + "pod_terminal": { + "data": { + "id": "17891bc8-52da-40bf-8ff0-0247ec05faf1", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "56078596-5293-4c84-9245-cca00a787265", + "type": "transport_event" + } + ] + } + } + }, + { + "id": "56078596-5293-4c84-9245-cca00a787265", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_departed", + "created_at": "2020-09-11T21:25:34Z", + "voyage_number": null, + "timestamp": "2020-08-29T21:25:33Z", + "location_locode": "MXZLO", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "b5b10c0a-8d18-46da-b4c2-4e5fa790e7da", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "b2fc728c-e2f5-4a99-8899-eb7b34ef22d7", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": { + "id": "2a765fdd-c479-4345-b71d-c4ef839952e2", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + } + ] + } + }, + "Estimated Event": { + "value": { + "data": { + "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", + "type": "webhook_notification", + "attributes": { + "id": "d7e04138-b59d-4c41-9d2d-251d95bedd6e", + "event": "shipment.estimated.arrival", + "delivery_status": "pending", + "created_at": "2020-09-11T21:25:34Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", + "type": "estimated_event" + } + }, + "webhook": { + "data": { + "id": "614eab61-ae3c-4d40-bbe9-41200a172691", + "type": "webhook" + } + } + } + }, + "included": [ + { + "id": "b68bc6cb-2c37-43f6-889b-86a16b2b6fe6", + "type": "estimated_event", + "attributes": { + "created_at": "2020-04-06T19:02:46-07:00", + "estimated_timestamp": "2020-04-09T19:02:46-07:00", + "voyage_number": "A1C", + "event": "shipment.estimated.arrival", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "715ed64b-6195-49f6-9407-1383a8088bfd", + "type": "shipment" + } + }, + "port": { + "data": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "ebf68c6c-9d0d-4383-aa41-e097009dfb4c", + "type": "vessel" + } + } + } + }, + { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port", + "attributes": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "name": "Port of Oakland", + "code": "USOAK", + "state_abbr": "CA", + "city": "Oakland", + "country_code": "US", + "time_zone": "America/Los_Angeles" + } + }, + { + "id": "715ed64b-6195-49f6-9407-1383a8088bfd", + "type": "shipment", + "attributes": { + "created_at": "2020-04-06T19:02:46-07:00", + "bill_of_lading_number": "TE49DD6650B9", + "ref_numbers": [ + "REF-4A25EA" + ], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": null, + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-04-13T19:02:46-07:00", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "1378c720-efe9-4562-a2ad-562002eb4b1d", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "ed4001a5-ad9d-43c3-883c-79354f422510", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "2508d879-4451-4d7f-ab23-92258b5df553", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [] + } + }, + "links": { + "self": "/v2/shipments/715ed64b-6195-49f6-9407-1383a8088bfd" + } + } + ] + } + }, + "Transport Event": { + "value": { + "data": { + "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", + "type": "webhook_notification", + "attributes": { + "id": "abec839a-48fe-4540-93d7-d3ea3d67bdbf", + "event": "container.transport.vessel_arrived", + "delivery_status": "pending", + "created_at": "2020-07-28T23:12:53Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", + "type": "transport_event" + } + }, + "webhook": { + "data": { + "id": "534d498b-8332-439a-accb-129dfd144ceb", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "a6ecb8ab-98d6-4cab-8487-ce9dd7be082b", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2020-07-28T23:12:53Z", + "voyage_number": null, + "timestamp": "2020-07-28T23:12:53Z", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "1fc35241-4c8b-420d-803a-9e6661720a05", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "8c2f335a-b155-4021-87f0-9b040159a981", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "b381c692-8dad-4f04-873f-d9e567143335", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "26fede8d-2c6d-4bf5-98d6-5a86d30f17a9", + "type": "terminal" + } + } + } + }, + { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port", + "attributes": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "name": "Port of Oakland", + "code": "USOAK", + "state_abbr": "CA", + "city": "Oakland", + "country_code": "US", + "time_zone": "America/Los_Angeles" + } + }, + { + "id": "1fc35241-4c8b-420d-803a-9e6661720a05", + "type": "shipment", + "attributes": { + "created_at": "2020-07-28T23:12:53Z", + "bill_of_lading_number": "TE491846459E", + "ref_numbers": [ + null + ], + "shipping_line_scac": "MSCU", + "shipping_line_name": "Mediterranean Shipping Company", + "port_of_lading_locode": "MXZLO", + "port_of_lading_name": "Manzanillo", + "port_of_discharge_locode": "USOAK", + "port_of_discharge_name": "Port of Oakland", + "pod_vessel_name": "MSC CHANNE", + "pod_vessel_imo": "9710438", + "pod_voyage_number": "098N", + "destination_locode": null, + "destination_name": null, + "destination_timezone": null, + "destination_ata_at": null, + "destination_eta_at": null, + "pol_etd_at": null, + "pol_atd_at": "2020-07-15T23:12:53Z", + "pol_timezone": "America/Mexico_City", + "pod_eta_at": "2020-08-04T23:12:53Z", + "pod_ata_at": null, + "pod_timezone": "America/Los_Angeles" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "06564cb7-77d6-4e0e-8e4a-37756ca21bc9", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "f5a8a49f-d8b2-4d2a-8a43-0e4ff0ce7995", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "06f5d3bb-f258-4f1b-8c2f-db78248f6e29", + "type": "terminal" + } + }, + "destination": { + "data": null + }, + "containers": { + "data": [ + { + "id": "8c2f335a-b155-4021-87f0-9b040159a981", + "type": "container" + } + ] + } + }, + "links": { + "self": "/v2/shipments/1fc35241-4c8b-420d-803a-9e6661720a05" + } + } + ] + } + }, + "Container Updated Event": { + "value": { + "data": { + "id": "416e293f-4423-47f7-abf3-1ae97054f41f", + "type": "webhook_notification", + "attributes": { + "id": "416e293f-4423-47f7-abf3-1ae97054f41f", + "event": "container.updated", + "delivery_status": "pending", + "created_at": "2020-06-04T22:03:09Z" + }, + "relationships": { + "reference_object": { + "data": { + "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", + "type": "container_updated_event" + } + }, + "webhook": { + "data": { + "id": "cda37836-aa40-455e-8b43-5fd74930c7f6", + "type": "webhook" + } + }, + "webhook_notification_logs": { + "data": [] + } + } + }, + "included": [ + { + "id": "fc48cb10-b7a8-47a4-a12f-89bce7434978", + "type": "container_updated_event", + "attributes": { + "changeset": { + "available_for_pickup": [ + false, + true + ], + "pod_terminal_holds": [ + null, + [ + { + "name": "customs", + "status": "hold", + "description": "CUST EXAM" + } + ] + ] + }, + "timestamp": "2020-06-04T22:03:09Z", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "container": { + "data": { + "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", + "type": "container" + } + }, + "terminal": { + "data": { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "type": "terminal" + } + } + } + }, + { + "id": "1445af31-991c-4d52-a183-6c3ea97cd6e8", + "type": "container", + "attributes": { + "number": "GLDU1355602", + "seal_number": "431ac97412228532", + "created_at": "2020-05-04T22:03:09Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "standard", + "weight_in_lbs": 55634, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pickup_lfd": null, + "availability_known": true, + "available_for_pickup": null, + "pod_arrived_at": "2020-06-04T22:03:08Z", + "pod_discharged_at": "2020-06-04T22:03:08Z", + "final_destination_full_out_at": "2020-06-04T22:03:08Z", + "pod_full_out_at": null, + "empty_terminated_at": null, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null + }, + "relationships": { + "shipment": { + "data": null + } + } + }, + { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "type": "terminal", + "attributes": { + "id": "07db0258-1911-4acf-8e70-0cfe4b100f80", + "nickname": "Denesik-Hintz", + "name": "Adams LLC Terminal", + "firms_code": "E005" + }, + "relationships": { + "port": { + "data": { + "id": "d8a92775-95f9-47be-a6d2-42542a32d5fc", + "type": "port" + } + } + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-webhook-notification-id", + "description": "\n", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "description": "Comma delimited list of relations to include.", + "name": "include" + } + ] + } + }, + "/webhook_notifications": { + "get": { + "summary": "List webhook notifications", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook_notification" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + } + ] + } + } + } + } + } + } + } + }, + "operationId": "get-webhook-notifications", + "description": "Return the list of webhook notifications. This can be useful for reconciling your data if your endpoint has been down. ", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page[size]" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include." + } + ] + } + }, + "/webhook_notifications/examples": { + "get": { + "summary": "Get webhook notification payload examples", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook_notification" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/webhook" + }, + { + "$ref": "#/components/schemas/tracking_request" + }, + { + "$ref": "#/components/schemas/transport_event" + }, + { + "$ref": "#/components/schemas/estimated_event" + } + ] + } + } + } + } + } + } + } + }, + "operationId": "get-webhook-notifications-example", + "description": "Returns an example payload as it would be sent to a webhook endpoint for the provided `event` ", + "tags": [ + "Webhook Notifications" + ], + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ], + "example": "container.transport.full_out" + }, + "in": "query", + "name": "event", + "description": "The webhook notification event name you wish to see an example of" + } + ] + }, + "parameters": [] + }, + "/webhooks/ips": { + "get": { + "summary": "List webhook IPs", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "webhook_notification_ips": { + "type": "array", + "items": { + "type": "string", + "format": "ipv4" + } + }, + "last_updated": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Example List of IPs": { + "value": { + "webhook_notification_ips": [ + "35.222.62.171", + "3.230.67.145", + "44.217.15.129" + ], + "last_updated": "2023-10-17T21:23:16Z" + } + } + } + } + } + } + }, + "operationId": "get-webhooks-ips", + "description": "Return the list of IPs used for sending webhook notifications. This can be useful for whitelisting the IPs on the firewall.", + "tags": [ + "Webhooks" + ], + "parameters": [] + } + }, + "/containers": { + "get": { + "summary": "List containers", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/container" + } + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipment" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "Example List of containers": { + "value": { + "data": [ + { + "id": "be0b247b-c144-4163-8919-cf9178930736", + "type": "container", + "attributes": { + "number": "TCLU6718159", + "seal_number": null, + "created_at": "2024-06-26T15:05:18Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-21T14:12:00Z", + "pod_discharged_at": "2024-06-23T00:19:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 53502, + "pod_full_out_at": "2024-06-26T16:15:00Z", + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:56Z", + "fees_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Community", + "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:18Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": null, + "empty_terminated_timezone": "America/New_York", + "pod_rail_carrier_scac": null, + "ind_rail_carrier_scac": null, + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "cc8f8e43-d6a9-4edb-a8c0-d0ab03c113d3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "fb53b35c-6a0c-4e22-b196-b623e8ba7db5", + "type": "transport_event" + }, + { + "id": "179897a3-04f5-450b-86e1-db57970c0248", + "type": "transport_event" + }, + { + "id": "a119b8e6-10c5-4967-8364-885f7dbf8e50", + "type": "transport_event" + }, + { + "id": "3372d58c-df89-46a2-b064-b308a9dc7040", + "type": "transport_event" + }, + { + "id": "800a3e59-8231-4e42-a80f-73c97cfb1be9", + "type": "transport_event" + }, + { + "id": "d466676b-0073-4b0c-89aa-d42486e9ed4f", + "type": "transport_event" + }, + { + "id": "85665836-5915-4cb9-ab78-b8487598cd0d", + "type": "transport_event" + }, + { + "id": "94cca22a-520a-4d19-a551-0554aceb3794", + "type": "transport_event" + }, + { + "id": "032e812f-d5e4-48af-8d79-2c2b41a07032", + "type": "transport_event" + }, + { + "id": "1b7dd74d-eff6-4e06-9a15-234295ce6fd5", + "type": "transport_event" + }, + { + "id": "b9f07b7e-1653-4209-b375-4588653d5275", + "type": "transport_event" + }, + { + "id": "a35f3c1b-ad35-4347-b0f2-9e08f0d4ca64", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "e26fc659-f79d-4cc0-8efc-5ce5e8444891", + "type": "raw_event" + }, + { + "id": "8820af5e-cb77-41c5-a897-906f2c56eb1e", + "type": "raw_event" + }, + { + "id": "a628a2c2-dab6-4b04-b3ae-d7ec99098a89", + "type": "raw_event" + }, + { + "id": "6ca157f9-3b58-4db5-8155-fc7b41a62611", + "type": "raw_event" + }, + { + "id": "6fb06390-4b0a-4c1f-9703-ec89927df7f3", + "type": "raw_event" + }, + { + "id": "26fe408d-e091-412a-a2fb-23a4d778b6b9", + "type": "raw_event" + }, + { + "id": "16490dd8-d79f-468a-91b1-dbb30bb45c85", + "type": "raw_event" + }, + { + "id": "ff3db923-a644-4706-8057-1bd53c95fbd5", + "type": "raw_event" + }, + { + "id": "3fd27ffd-9618-477f-b1a9-cbc179defefe", + "type": "raw_event" + }, + { + "id": "f92efdd3-f79c-4a1c-97ce-9a47588b525c", + "type": "raw_event" + }, + { + "id": "3b2a88ef-df0b-4e61-99c1-d4a175910111", + "type": "raw_event" + }, + { + "id": "9b367e33-9e43-488f-8217-081698adf40d", + "type": "raw_event" + }, + { + "id": "ee0915df-e2f8-46b0-acf1-816871ca142d", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "a9e52f3d-2fa9-467c-8deb-09e90dac2f0b", + "type": "container", + "attributes": { + "number": "TCLU2224327", + "seal_number": null, + "created_at": "2024-06-26T15:05:34Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-21T14:12:00Z", + "pod_discharged_at": "2024-06-23T17:21:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 20, + "equipment_height": "standard", + "weight_in_lbs": 44225, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:56Z", + "fees_at_pod_terminal": [], + "pickup_lfd": null, + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard", + "pod_last_tracking_request_at": "2024-06-26T18:47:56Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:34Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "America/Chicago", + "empty_terminated_timezone": "America/Chicago", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-02T14:20:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "99f84294-3bda-4765-81b3-31765e6d2a24", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "e6fa9a01-511b-4f43-a7e1-d628315b84ef", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "a243bdf8-0da3-4056-a6a7-05fe8ab43999", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "4a5a04b7-8974-4c46-beaa-bf55004422c9", + "type": "transport_event" + }, + { + "id": "11c90391-aa9c-408b-b20e-daed8dd09586", + "type": "transport_event" + }, + { + "id": "d7f23c71-7084-4fbb-9fef-740f624182aa", + "type": "transport_event" + }, + { + "id": "c4c3537d-5c47-4623-afe0-edc0dd6f75c5", + "type": "transport_event" + }, + { + "id": "e4002e13-0a74-4bd4-9147-787bb21e2fda", + "type": "transport_event" + }, + { + "id": "57b5568f-d8a6-443d-b1c8-be5cd080e5ed", + "type": "transport_event" + }, + { + "id": "0771cff1-79bf-4eaa-9d9d-790cb433ce44", + "type": "transport_event" + }, + { + "id": "031021b9-7b39-41f7-bd45-26cc8cb799d2", + "type": "transport_event" + }, + { + "id": "9956448d-34d3-4c23-bcfd-19807eb4034f", + "type": "transport_event" + }, + { + "id": "f1caf6ea-3e92-4b68-bcea-556828301062", + "type": "transport_event" + }, + { + "id": "2dd558a8-fa07-454c-acf3-b53d072264af", + "type": "transport_event" + }, + { + "id": "83118511-d9ed-4b16-b323-5e685d9b266e", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "f256289f-219d-45f6-b727-56fb9c6bc433", + "type": "raw_event" + }, + { + "id": "c5581f60-ffb2-4ef0-a855-b70acd3b294f", + "type": "raw_event" + }, + { + "id": "3c6716f9-814c-4459-9bbb-753f010446b2", + "type": "raw_event" + }, + { + "id": "62c7b849-c99b-4e37-9861-105141cc0a4c", + "type": "raw_event" + }, + { + "id": "7db37d43-426a-4f84-82af-2957621ce466", + "type": "raw_event" + }, + { + "id": "d4342119-db56-46fc-8299-f573f5b52e73", + "type": "raw_event" + }, + { + "id": "ba159c3b-590c-43ff-bc04-b0354fd326f4", + "type": "raw_event" + }, + { + "id": "ec1be31a-17f1-4f6f-85ab-c74cb0dbe6cb", + "type": "raw_event" + }, + { + "id": "d12c4656-0d1f-4f30-a0fa-a2ee887741a8", + "type": "raw_event" + }, + { + "id": "146d56e2-b41d-4469-8202-0c7d7315e794", + "type": "raw_event" + }, + { + "id": "6b86b4d1-f85f-4c26-9fbc-3132a62f0fbc", + "type": "raw_event" + }, + { + "id": "bb2c9105-e421-4372-9265-e61b3fa54851", + "type": "raw_event" + }, + { + "id": "3a09c49f-c12e-49e9-b2de-b8dca2b3d608", + "type": "raw_event" + }, + { + "id": "54d91ebc-8f57-4764-b7c6-a3f4dc2459be", + "type": "raw_event" + }, + { + "id": "9e788c46-9431-41eb-baee-c8b14fb4f590", + "type": "raw_event" + }, + { + "id": "d3051802-8b6f-497d-ad16-fb35547c8662", + "type": "raw_event" + }, + { + "id": "af3090e7-c6d3-4d8e-88aa-9cd757102f9b", + "type": "raw_event" + }, + { + "id": "95daf204-d95d-4a89-bedf-358bedb8b3b8", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "8d1faeeb-3890-4fac-8659-cd13737b26f1", + "type": "container", + "attributes": { + "number": "CMAU0619052", + "seal_number": null, + "created_at": "2024-06-26T15:02:11Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-22T22:10:00Z", + "pod_discharged_at": "2024-06-23T20:38:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 20, + "equipment_height": "standard", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-06-27T07:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Grounded", + "pod_last_tracking_request_at": "2024-06-26T18:47:47Z", + "shipment_last_tracking_request_at": "2024-06-26T15:02:11Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "Asia/Shanghai", + "empty_terminated_timezone": "Asia/Shanghai", + "pod_rail_carrier_scac": null, + "ind_rail_carrier_scac": null, + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "f706cbea-3264-473d-8d26-af257f3bc1be", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "1bb3a814-edab-403f-8ef2-a6d0966df423", + "type": "transport_event" + }, + { + "id": "19f197bf-444c-40ee-8478-6f02abe715a9", + "type": "transport_event" + }, + { + "id": "4c9223bb-0218-4175-8a2e-7bb99c40642a", + "type": "transport_event" + }, + { + "id": "432da964-6e99-45e9-b4b1-00b7be858591", + "type": "transport_event" + }, + { + "id": "b462b4d1-1e02-4037-af7f-6c8fa981f268", + "type": "transport_event" + }, + { + "id": "e3ca4a25-692f-474a-aa71-48fb9840aef1", + "type": "transport_event" + }, + { + "id": "014b9d1f-f033-4a3e-89f9-c57569883436", + "type": "transport_event" + }, + { + "id": "2cec71c9-721a-4060-993f-0ffcf01151cd", + "type": "transport_event" + }, + { + "id": "24941515-1b5e-4fca-87eb-092e56ed156a", + "type": "transport_event" + }, + { + "id": "0fd06bc0-1fda-467c-ab67-90a30c6d62ab", + "type": "transport_event" + }, + { + "id": "0bce915e-b9ba-42a0-a484-136266fe8b9a", + "type": "transport_event" + }, + { + "id": "582006aa-6547-4712-b964-5637aad839b4", + "type": "transport_event" + }, + { + "id": "11458420-b354-4288-a275-7572d3c60e33", + "type": "transport_event" + }, + { + "id": "5c900fa1-11bf-4f96-89e7-b12f6a98edc4", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "999d7de7-eaed-4313-85df-772a6d24a85e", + "type": "raw_event" + }, + { + "id": "ac9c2780-240d-443f-87f4-95465fa5447b", + "type": "raw_event" + }, + { + "id": "a0ee3724-91c3-4b78-af32-2f16e8c2d600", + "type": "raw_event" + }, + { + "id": "79725b1f-19f7-4fc3-8c69-586e237c1719", + "type": "raw_event" + }, + { + "id": "4f07f257-ea4f-4e61-94ed-a01598899020", + "type": "raw_event" + }, + { + "id": "41612acb-b3d4-495f-9138-f34105851d21", + "type": "raw_event" + }, + { + "id": "84ffdd37-ec39-404a-9b68-d72d8fb96d48", + "type": "raw_event" + }, + { + "id": "7e9d8a6d-5339-4266-a6fb-22c28f41149f", + "type": "raw_event" + }, + { + "id": "2359b787-b218-42dc-b9a5-84b608aee671", + "type": "raw_event" + }, + { + "id": "ea33303e-0e48-4442-9886-0dfe38b726b5", + "type": "raw_event" + }, + { + "id": "3689d013-8525-418b-92ed-95ec684130b4", + "type": "raw_event" + }, + { + "id": "a64f7f7e-a6fa-4913-aba0-b28ba189d68b", + "type": "raw_event" + }, + { + "id": "c264a859-4fcb-4fab-95c0-29be99ec54a4", + "type": "raw_event" + }, + { + "id": "2aabf25c-9a3e-44bc-b6c8-ed7b1e3c3630", + "type": "raw_event" + }, + { + "id": "6ae70038-02d7-425a-99c1-8761c69a9033", + "type": "raw_event" + }, + { + "id": "c30021d8-93e9-4bfd-a978-e9e24de148c8", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "853f1794-9b94-4118-9970-4e28e549440d", + "type": "container", + "attributes": { + "number": "TGHU6578122", + "seal_number": null, + "created_at": "2024-06-26T15:08:30Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-23T21:58:00Z", + "pod_discharged_at": "2024-06-24T02:28:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 8898, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-06-27T07:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Wheeled", + "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", + "shipment_last_tracking_request_at": "2024-06-26T15:08:30Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "America/New_York", + "empty_terminated_timezone": "America/New_York", + "pod_rail_carrier_scac": "BNSF", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-07T08:00:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "f3cfe624-706e-4a0c-89d5-140980d986fd", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "7e4557b9-cc5a-4298-aaec-1a32e90202c9", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "94f687d0-dc6b-4342-8710-cb98bc98716e", + "type": "transport_event" + }, + { + "id": "a8d0a842-95fe-4c12-a80e-bd12e87a1421", + "type": "transport_event" + }, + { + "id": "c1f1a186-3737-41ca-ae2b-f79a17519991", + "type": "transport_event" + }, + { + "id": "7fcc5496-d402-4b1c-a6c5-8514e6433070", + "type": "transport_event" + }, + { + "id": "ab7cd84a-edc5-476d-a1c4-190011582314", + "type": "transport_event" + }, + { + "id": "68d91384-3eb3-4d21-ac7a-c1f688f649c2", + "type": "transport_event" + }, + { + "id": "5a702f46-4356-4570-bd2e-2b8adab5ba3e", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "a4b7e4b6-5227-4f34-9e91-9e75223798ae", + "type": "raw_event" + }, + { + "id": "4f3e3c7b-d1e6-4fde-b95e-ce9a8835445c", + "type": "raw_event" + }, + { + "id": "39e8570d-1a8f-4e10-8d2e-ac930abc5971", + "type": "raw_event" + }, + { + "id": "b8015a72-9741-4fbf-8adc-d30b87de6aa3", + "type": "raw_event" + }, + { + "id": "61512b1a-a94e-4cac-8bab-763588dbbddf", + "type": "raw_event" + }, + { + "id": "c0bf87f2-37c3-4895-90b0-9f97ac4b5c13", + "type": "raw_event" + }, + { + "id": "3856c7e7-f832-42ca-873b-096952599e29", + "type": "raw_event" + }, + { + "id": "485be998-0861-4d10-8a60-f66d27eb46a7", + "type": "raw_event" + }, + { + "id": "29cde194-bbd9-40c2-adba-5dcb1514f5fc", + "type": "raw_event" + } + ] + } + } + }, + { + "id": "681d713d-bcd6-4303-b082-b9f893e7d1d9", + "type": "container", + "attributes": { + "number": "CSNU8439129", + "seal_number": null, + "created_at": "2024-06-26T15:02:32Z", + "ref_numbers": [], + "pod_arrived_at": "2024-06-23T21:58:00Z", + "pod_discharged_at": "2024-06-26T04:30:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 40488, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T18:47:47Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-01T07:00:00Z", + "pickup_appointment_at": "2024-06-28T15:00:00Z", + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Grounded", + "pod_last_tracking_request_at": "2024-06-26T18:47:46Z", + "shipment_last_tracking_request_at": "2024-06-26T15:02:32Z", + "availability_known": true, + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": "America/Chicago", + "empty_terminated_timezone": "America/Chicago", + "pod_rail_carrier_scac": "BNSF", + "ind_rail_carrier_scac": "BNSF", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": "2024-07-04T22:00:00Z", + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "edd626cf-b0b5-4679-8a6c-80c8e9fe7698", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "572b372f-21c7-4403-8fb0-948377c74642", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "eaa2580c-5f5b-4198-85e4-821145d62098", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "9d19125a-2944-442c-8132-bf0d83670e5c", + "type": "transport_event" + }, + { + "id": "b2ff0b47-3151-41e2-8c46-7f95cdbe4167", + "type": "transport_event" + }, + { + "id": "e52feaa3-7a50-4570-a2ea-bf06f955ce23", + "type": "transport_event" + }, + { + "id": "c56d95e5-774f-432c-b6f4-c53967f07292", + "type": "transport_event" + }, + { + "id": "9b1d8b48-e870-46fa-bc46-10f9b30c64d4", + "type": "transport_event" + }, + { + "id": "3e1d3571-6b24-4ad2-a071-4e70d59af521", + "type": "transport_event" + }, + { + "id": "60f5eefe-13d5-4f85-9b65-d13b4c67115a", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2af51127-0971-4741-9b97-ea1b338e3a7c", + "type": "raw_event" + }, + { + "id": "4472ded6-eb69-4f21-8666-9a4f2342dfeb", + "type": "raw_event" + }, + { + "id": "ce238754-d5fd-4dc8-9f55-2ac6efdfbb5e", + "type": "raw_event" + }, + { + "id": "5cd3b5ef-843f-4f60-87ce-5beaeea86f7b", + "type": "raw_event" + }, + { + "id": "55291737-98b6-403c-9424-1605e6e01007", + "type": "raw_event" + }, + { + "id": "cdc55ea8-a075-45b3-9570-ade6fb2f0d94", + "type": "raw_event" + }, + { + "id": "c0ab5406-4e10-4fdc-85a4-e17ce8d956f5", + "type": "raw_event" + }, + { + "id": "965f0172-1c20-4342-a44a-7be0e594ff76", + "type": "raw_event" + }, + { + "id": "db178011-c795-42a4-9537-b4e77ffb4f98", + "type": "raw_event" + } + ] + } + } + } + ], + "meta": { + "size": 5, + "total": 59229 + }, + "links": { + "self": "https://api.terminal49.com/v2/containers?page[size]=5", + "current": "https://api.terminal49.com/v2/containers?page[number]=1&page[size]=5", + "next": "https://api.terminal49.com/v2/containers?page[number]=2&page[size]=5", + "last": "https://api.terminal49.com/v2/containers?page[number]=11846&page[size]=5" + } + } + } + } + } + } + } + }, + "operationId": "get-containers", + "description": "Returns a list of container. The containers are returned sorted by creation date, with the most recently refreshed containers appearing first.\n\nThis API will return all containers associated with the account.", + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer", + "default": 30 + }, + "in": "query", + "name": "page[size]", + "description": "" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "terminal_checked_before", + "description": "Number of seconds in which containers were refreshed" + } + ] + }, + "patch": { + "summary": "Edit a container", + "operationId": "patch-containers-id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/container" + } + } + } + } + } + } + }, + "description": "Update a container", + "tags": [ + "Containers" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "ref_numbers": { + "type": "array", + "items": { + "type": "string", + "example": "REF-12345" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/containers/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/container" + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/terminal" + }, + { + "$ref": "#/components/schemas/transport_event" + } + ] + } + } + } + }, + "examples": { + "Example Container": { + "value": { + "data": { + "id": "55a700e4-7005-45a9-92fd-1ff38641dbd9", + "type": "container", + "attributes": { + "number": "CAIU7432986", + "seal_number": null, + "created_at": "2024-06-26T15:05:21Z", + "ref_numbers": [], + "pod_arrived_at": null, + "pod_discharged_at": "2024-06-22T04:00:00Z", + "final_destination_full_out_at": null, + "holds_at_pod_terminal": [], + "available_for_pickup": true, + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": null, + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2024-06-26T17:51:12Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2024-07-07T04:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": "Yard - Y0709A", + "pod_last_tracking_request_at": "2024-06-26T17:51:12Z", + "shipment_last_tracking_request_at": "2024-06-26T15:05:20Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "US/Eastern", + "empty_terminated_timezone": "US/Eastern", + "pod_rail_carrier_scac": "CSXT", + "ind_rail_carrier_scac": "CSXT", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": null, + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "02b1bd6f-407c-45bb-8645-06e7ee34e7e3", + "type": "shipment" + } + }, + "pickup_facility": { + "data": null + }, + "pod_terminal": { + "data": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "45b542cb-332b-4684-b915-42e3a0759823", + "type": "transport_event" + }, + { + "id": "174ed528-a1a9-4002-aef0-f2c9369199da", + "type": "transport_event" + }, + { + "id": "7a2f30a6-a756-4c14-9477-fbfc1c7fe2f8", + "type": "transport_event" + }, + { + "id": "e7365004-175a-46e8-96cd-dbed0f3daf21", + "type": "transport_event" + }, + { + "id": "7c567bf3-7f01-4a3d-a176-eaa1f7165585", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "2956f71c-bfb9-4e49-b9e2-1b4d53c74cac", + "type": "raw_event" + }, + { + "id": "391e0eda-65b5-4fc3-a53d-25ecd9570259", + "type": "raw_event" + }, + { + "id": "74810c04-6c8a-4194-8cff-52936584a965", + "type": "raw_event" + }, + { + "id": "4b1500e2-b23b-4896-87bd-c38b1d16f385", + "type": "raw_event" + }, + { + "id": "8b9a7d88-720a-4304-8c1e-a3336e39f481", + "type": "raw_event" + }, + { + "id": "bf1f59c5-5dd8-4013-87f9-d7056bc87114", + "type": "raw_event" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/containers/55a700e4-7005-45a9-92fd-1ff38641dbd9" + } + } + } + } + } + } + } + }, + "operationId": "get-containers-id", + "description": "Retrieves the details of a container.", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + } + }, + "/containers/{id}/raw_events": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container's raw events", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/raw_event" + } + } + } + }, + "examples": { + "Example Raw Events": { + "value": { + "data": [ + { + "id": "ca6b760f-13e9-4bf6-ab49-3cf2e40757fb", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-03T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-03", + "estimated_at": null, + "actual_at": null, + "event": "empty_out", + "index": 0, + "original_event": "Truck Gate out empty", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": "Oakland", + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "bcdfc796-0570-4c85-9336-d6c7d0da02d2", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-09T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-09", + "estimated_at": null, + "actual_at": null, + "event": "full_in", + "index": 1, + "original_event": "Truck Arrival in", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "a4ff01b0-b374-4123-ae65-3dc0c7ea41ea", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-14T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-14", + "estimated_at": null, + "actual_at": null, + "event": "vessel_loaded", + "index": 2, + "original_event": "Vessel Loaded", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "ca5862ef-6e27-4245-a281-0cec6bbe1fb7", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-15T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-15", + "estimated_at": null, + "actual_at": null, + "event": "vessel_departed", + "index": 3, + "original_event": "Vessel departed", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "f47a903e-e6d1-41c5-aec6-8401b2abf297", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-25T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-25", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_arrived", + "index": 4, + "original_event": "Vessel arrived", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "72a1a13b-a2e0-4ac0-851d-eec41e9e9087", + "type": "raw_event", + "attributes": { + "timestamp": "2020-03-25T00:00:00Z", + "estimated": false, + "actual_on": "2020-03-25", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_discharged", + "index": 5, + "original_event": "Vessel Discharged", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "FA009R", + "location_name": null, + "location_locode": null, + "vessel_name": "MSC FAITH", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel" + } + } + } + }, + { + "id": "cd91f0cf-ee73-4c47-b99f-63245cb5bc96", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-07T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-07", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_loaded", + "index": 6, + "original_event": "Vessel Loaded", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "561dbb7e-c3ab-4e63-b09b-957878b1425f", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-07T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-07", + "estimated_at": null, + "actual_at": null, + "event": "transshipment_departed", + "index": 7, + "original_event": "Vessel departed", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "551711a6-62ad-4205-8da2-00e0c0cbd2db", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-12T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-12", + "estimated_at": null, + "actual_at": null, + "event": "vessel_arrived", + "index": 8, + "original_event": "Vessel arrived", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "f4027470-75ca-4e2a-b4f0-47654a25ac48", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-13T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-13", + "estimated_at": null, + "actual_at": null, + "event": "vessel_discharged", + "index": 9, + "original_event": "Vessel Discharged", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": "15W10", + "location_name": null, + "location_locode": null, + "vessel_name": "SINGAPORE EXPRESS", + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + } + } + }, + { + "id": "50f11e4f-411e-48e2-8141-64226500df9c", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-14T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-14", + "estimated_at": null, + "actual_at": null, + "event": "full_out", + "index": 10, + "original_event": "Truck Departure from", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + }, + { + "id": "49aea23c-b8c5-4a97-b133-f7a9723fa1b4", + "type": "raw_event", + "attributes": { + "timestamp": "2020-04-15T00:00:00Z", + "estimated": false, + "actual_on": "2020-04-15", + "estimated_at": null, + "actual_at": null, + "event": "empty_in", + "index": 11, + "original_event": "Truck Gate in empty", + "created_at": "2020-04-18T00:18:27Z", + "voyage_number": null, + "location_name": null, + "location_locode": null, + "vessel_name": null, + "vessel_imo": null, + "timezone": null + }, + "relationships": { + "location": { + "data": null + }, + "vessel": { + "data": null + } + } + } + ], + "included": [ + { + "id": "4b473d0e-7073-4171-8b5b-15e71e9e13cc", + "type": "vessel", + "attributes": { + "name": "MSC FAITH", + "imo": null, + "mmsi": "636019213", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + }, + { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel", + "attributes": { + "name": "SINGAPORE EXPRESS", + "imo": null, + "mmsi": "477300500", + "latitude": 70.22625823437389, + "longitude": 45.06279126658865, + "nautical_speed_knots": 100, + "navigational_heading_degrees": 1, + "position_timestamp": "2023-06-05T19:46:18Z" + } + } + ] + } + } + } + } + } + } + }, + "operationId": "get-containers-id-raw_events", + "description": "#### Deprecation warning\nThe `raw_events` endpoint is provided as-is.\n\n For past events we recommend consuming `transport_events`.\n\n---\nGet 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. \n\nNormalized 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. \n\nFor 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.", + "deprecated": true + } + }, + "/containers/{id}/transport_events": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a container's transport events", + "tags": [ + "Containers" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/transport_event" + } + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/shipment" + }, + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/metro_area" + }, + { + "$ref": "#/components/schemas/terminal" + }, + { + "$ref": "#/components/schemas/rail_terminal" + }, + { + "$ref": "#/components/schemas/vessel" + } + ] + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "Example transport events": { + "value": { + "data": [ + { + "id": "efc3f3c1-cdc2-4a7d-a176-762ddec107b8", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_loaded", + "created_at": "2021-01-05T08:41:12Z", + "voyage_number": "15W10", + "timestamp": null, + "location_locode": "CLSAI", + "timezone": "America/Santiago" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "951058bd-2c3b-4bcc-94e1-9be2526b9687", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_departed", + "created_at": "2021-01-05T08:41:11Z", + "voyage_number": "15W10", + "timestamp": null, + "location_locode": "CLSAI", + "timezone": "America/Santiago" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "0ad2cf2b-e694-4ccc-9cd2-40af0d1fa1b5", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "69af6795-56c2-4157-9a87-afd761cc85a0", + "type": "transport_event", + "attributes": { + "event": "container.transport.full_out", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": null, + "timestamp": "2020-04-14T00:00:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "68c3c29a-504a-4dbb-ad27-7194ef42d484", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_discharged", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": "15W10", + "timestamp": "2020-04-13T00:00:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", + "type": "terminal" + } + } + } + }, + { + "id": "03349405-a9be-4f3e-abde-28f2cb3922bd", + "type": "transport_event", + "attributes": { + "event": "container.transport.vessel_arrived", + "created_at": "2020-05-14T00:05:41Z", + "voyage_number": "15W10", + "timestamp": "2020-04-13T01:24:00Z", + "location_locode": "USOAK", + "timezone": "America/Los_Angeles" + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": { + "id": "345c05ab-4217-4ffe-a1a4-6c03b9ad2b36", + "type": "vessel" + } + }, + "location": { + "data": { + "id": "42d1ba3a-f4b8-431d-a6fe-49fd748a59e7", + "type": "port" + } + }, + "terminal": { + "data": { + "id": "3e550f0e-ac2a-48fb-b242-5be45ecf2c78", + "type": "terminal" + } + } + } + }, + { + "id": "ba9f85b4-658d-4f23-9308-635964df8037", + "type": "transport_event", + "attributes": { + "event": "container.transport.empty_in", + "created_at": "2020-05-14T00:05:42Z", + "voyage_number": null, + "timestamp": "2020-04-15T00:00:00Z", + "location_locode": null, + "timezone": null + }, + "relationships": { + "shipment": { + "data": { + "id": "06264731-503e-498e-bc76-f90b87b31562", + "type": "shipment" + } + }, + "container": { + "data": { + "id": "eeafd337-72b5-4e5c-87cb-9ef83fa99cf4", + "type": "container" + } + }, + "vessel": { + "data": null + }, + "location": { + "data": null + }, + "terminal": { + "data": null + } + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events", + "current": "https://api.terminal49.com/v2/containers/eeafd337-72b5-4e5c-87cb-9ef83fa99cf4/transport_events?page[number]=1" + } + } + } + } + } + } + } + }, + "operationId": "get-containers-id-transport_events", + "description": "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.\n\nThis does not provide any estimated future events. See `container/:id/raw_events` endpoint for that. ", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include", + "description": "Comma delimited list of relations to include" + } + ] + } + }, + "/shipping_lines": { + "get": { + "summary": "Shipping Lines", + "tags": [ + "Shipping Lines" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipping_line" + } + }, + "links": { + "$ref": "#/components/schemas/links" + } + } + } + } + } + } + }, + "operationId": "get-shipping_lines", + "description": "Return a list of shipping lines supported by Terminal49. \nN.B. There is no pagination for this endpoint." + } + }, + "/shipping_lines/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a single shipping line", + "tags": [ + "Shipping Lines" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/shipping_line" + } + } + } + } + } + } + }, + "operationId": "get-shipping_lines-id", + "description": "Return the details of a single shipping line." + } + }, + "/metro_areas/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a metro area using the un/locode or the id", + "tags": [ + "Metro Areas" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/metro_area" + } + } + } + } + } + } + }, + "operationId": "get-metro-area-id", + "description": "Return the details of a single metro area." + } + }, + "/ports/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a port using the locode or the id", + "tags": [ + "Ports" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/port" + } + } + } + } + } + } + }, + "operationId": "get-port-id", + "description": "Return the details of a single port." + } + }, + "/vessels/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a vessel using the id", + "tags": [ + "Vessels" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/vessel" + } + } + } + } + } + } + }, + "operationId": "get-vessels-id", + "description": "Returns a vessel by it's given identifier" + } + }, + "/vessels/{imo}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "imo", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a vessel using the imo", + "tags": [ + "Vessels" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/vessel" + } + } + } + } + } + } + }, + "operationId": "get-vessels-imo", + "description": "Returns a vessel by the given IMO number.", + "x-internal": true + } + }, + "/terminals/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Get a terminal using the id", + "tags": [ + "Terminals" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/terminal" + } + } + } + } + } + } + }, + "operationId": "get-terminal-id", + "description": "Return the details of a single terminal." + } + }, + "/parties": { + "get": { + "description": "Get a list of parties", + "operationId": "list-parties", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/party" + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "integer", + "default": 1 + }, + "in": "query", + "name": "page[number]" + }, + { + "schema": { + "type": "integer", + "default": 25 + }, + "in": "query", + "name": "page[size]", + "description": "" + } + ], + "tags": [ + "Parties" + ] + }, + "post": { + "description": "Creates a new party", + "operationId": "post-party", + "responses": { + "201": { + "description": "Party Created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + }, + "examples": { + "New Party": { + "value": { + "data": { + "id": "ba4cb904-827f-4038-8e31-1e92b3356218", + "type": "party", + "attributes": { + "company_name": "COMPANY NAME" + } + }, + "links": { + "self": "/v2/parties/ba4cb904-827f-4038-8e31-1e92b3356218" + } + } + } + } + } + }, + "headers": {} + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/company_name" + }, + "title": "Unprocessable Entity", + "detail": "Company name can't be blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/customer" + }, + "title": "Unprocessable Entity", + "detail": "'XXXX' already exists in Account" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "company_name": { + "type": "string", + "example": "COMPANY NAME", + "description": "The name of the company" + } + } + } + } + } + } + } + } + } + }, + "tags": [ + "Parties" + ] + } + }, + "/parties/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + } + } + } + } + }, + "operationId": "get-parties-id", + "description": "Returns a party by it's given identifier", + "tags": [ + "Parties" + ] + }, + "patch": { + "description": "Updates a party", + "operationId": "edit-party", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/party" + }, + "links": { + "$ref": "#/components/schemas/link-self" + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "examples": { + "Error Examples": { + "value": { + "errors": [ + { + "status": "422", + "source": { + "pointer": "/data/attributes/company_name" + }, + "title": "Unprocessable Entity", + "detail": "Company name can't be blank" + }, + { + "status": "422", + "source": { + "pointer": "/data/attributes/customer" + }, + "title": "Unprocessable Entity", + "detail": "'XXXX' already exists in Account" + } + ] + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "object", + "required": [ + "attributes" + ], + "properties": { + "attributes": { + "type": "object", + "properties": { + "company_name": { + "type": "string", + "example": "COMPANY NAME", + "description": "The name of the company" + } + } + } + } + } + } + } + } + } + }, + "tags": [ + "Parties" + ] + } + } + }, + "x-tagGroups": [ + { + "name": "End Points", + "tags": [ + "Shipments", + "Containers", + "Tracking Requests", + "Webhooks", + "Webhook Notifications", + "Metro Areas" + ] + } + ], + "components": { + "schemas": { + "shipment": { + "title": "Shipment model", + "type": "object", + "x-examples": {}, + "description": "", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "relationships": { + "type": "object", + "properties": { + "destination": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port", + "metro_area" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "port_of_lading": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "containers": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "container" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + } + }, + "port_of_discharge": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "type": { + "type": "string", + "enum": [ + "port" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "pod_terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "terminal" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "destination_terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "terminal", + "rail_terminal" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + }, + "line_tracking_stopped_by_user": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "user" + ] + }, + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "id" + ] + } + } + } + } + }, + "attributes": { + "type": "object", + "properties": { + "bill_of_lading_number": { + "type": "string" + }, + "normalized_number": { + "type": "string", + "description": "The normalized version of the shipment number used for querying the carrier" + }, + "ref_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "port_of_lading_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "port_of_lading_name": { + "type": "string", + "nullable": true + }, + "port_of_discharge_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "port_of_discharge_name": { + "type": "string", + "nullable": true + }, + "destination_locode": { + "type": "string", + "description": "UN/LOCODE", + "nullable": true + }, + "destination_name": { + "type": "string", + "nullable": true + }, + "shipping_line_scac": { + "type": "string" + }, + "shipping_line_name": { + "type": "string" + }, + "shipping_line_short_name": { + "type": "string" + }, + "customer_name": { + "type": "string", + "nullable": true + }, + "pod_vessel_name": { + "type": "string", + "nullable": true + }, + "pod_vessel_imo": { + "type": "string", + "nullable": true + }, + "pod_voyage_number": { + "type": "string", + "nullable": true + }, + "pol_etd_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pol_atd_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_original_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_ata_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "destination_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "destination_ata_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pol_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "pod_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "destination_timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "line_tracking_last_attempted_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 last tried to update the shipment status from the shipping line", + "nullable": true + }, + "line_tracking_last_succeeded_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 last successfully updated the shipment status from the shipping line", + "nullable": true + }, + "line_tracking_stopped_at": { + "type": "string", + "format": "date-time", + "description": "When Terminal49 stopped checking at the shipping line", + "nullable": true + }, + "line_tracking_stopped_reason": { + "type": "string", + "enum": [ + "all_containers_terminated", + "past_arrival_window", + "no_updates_at_line", + "cancelled_by_user", + "booking_cancelled", + null + ], + "description": "The reason Terminal49 stopped checking", + "nullable": true + } + }, + "required": [ + "bill_of_lading_number" + ] + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "self" + ] + } + }, + "required": [ + "id", + "type", + "attributes", + "relationships", + "links" + ] + }, + "meta": { + "title": "meta", + "type": "object", + "properties": { + "size": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "link-self": { + "title": "link", + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + }, + "links": { + "title": "links", + "type": "object", + "properties": { + "last": { + "type": "string", + "format": "uri" + }, + "next": { + "type": "string", + "format": "uri" + }, + "prev": { + "type": "string", + "format": "uri" + }, + "first": { + "type": "string", + "format": "uri" + }, + "self": { + "type": "string", + "format": "uri" + } + } + }, + "container": { + "title": "Container model", + "type": "object", + "x-examples": { + "Example Container": { + "id": "ff77a822-23a7-4ccd-95ca-g534c071baaf3", + "type": "container", + "attributes": { + "number": "KOCU4959010", + "ref_numbers": [ + "REF-1", + "REF-2" + ], + "seal_number": "210084213", + "created_at": "2021-10-18T09:52:33Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "weight_in_lbs": 20210, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pickup_lfd": "2022-01-21T08:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": "APMZ418805", + "location_at_pod_terminal": "Delivered 02/11/2022 14:18", + "availability_known": true, + "available_for_pickup": false, + "pod_arrived_at": "2022-01-03T10:30:00Z", + "pod_discharged_at": "2022-01-08T09:15:00Z", + "final_destination_full_out_at": null, + "pod_full_out_at": "2022-02-11T22:18:00Z", + "empty_terminated_at": null, + "terminal_checked_at": "2022-02-11T22:45:32Z", + "pod_rail_carrier_scac": "UPRR", + "ind_rail_carrier_scac": "CSXT", + "pod_timezone": "America/Los_Angeles", + "final_destination_timezone": null, + "empty_terminated_timezone": null, + "pod_last_tracking_request_at": "2022-02-11T22:40:00Z", + "shipment_last_tracking_request_at": "2022-02-11T22:40:00Z", + "pod_rail_loaded_at": "2022-02-11T22:18:00Z", + "pod_rail_departed_at": "2022-02-11T23:30:00Z", + "ind_eta_at": null, + "ind_ata_at": "2022-02-15T01:12:00Z", + "ind_rail_unloaded_at": "2022-02-15T07:54:00Z", + "ind_facility_lfd_on": null + }, + "relationships": { + "shipment": { + "data": { + "id": "x92acf88-c263-43ddf-b005-aca2a32d47f1", + "type": "shipment" + } + }, + "pod_terminal": { + "data": { + "id": "x551cac7-aff5-40a6-9c63-49facf19cc3df", + "type": "terminal" + } + }, + "pickup_facility": { + "data": { + "id": "d7d8d314-b02b-4caa-b04f-d3d4726f4107", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "xecfe2d1-c498-4022-a9f8-ec56722e1215", + "type": "transport_event" + }, + { + "id": "2900a9b8-d9e2-4696-abd86-4a767b885d23", + "type": "transport_event" + }, + { + "id": "5ad0dce1-x78e4-464d-af5f-a36190428a2c", + "type": "transport_event" + }, + { + "id": "876575d5-5ede-40d6-a093-c3a4cfcxaa1c7", + "type": "transport_event" + }, + { + "id": "dc2a9d8f-75e6-43xa5-a04e-58458495f08c", + "type": "transport_event" + }, + { + "id": "50xd2ea1-01ac-473d-8a08-3b5d77d2b793", + "type": "transport_event" + }, + { + "id": "9d1f55xe3-6758-4be7-872a-30451ddd957e", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "38084a1d-a2eb-434e-81ac3-606c89a61c4b", + "type": "raw_event" + }, + { + "id": "53680df3-93d5-4385-86c5-a33ee41b4c1f", + "type": "raw_event" + }, + { + "id": "7d9cdf70-51e8-4b75-a8229-f5d691495ab6", + "type": "raw_event" + }, + { + "id": "e62d41ac-8738-42e8-b582-35ef28ae88e2", + "type": "raw_event" + }, + { + "id": "1209172b-acd8-4ce0-8821-dbc4934208b3", + "type": "raw_event" + }, + { + "id": "4265ea5f-2b9a-436f-98fa-803d8ed49acb2", + "type": "raw_event" + }, + { + "id": "c3cb2eb7-6c0a-4db8-8742-517b97b175d5", + "type": "raw_event" + }, + { + "id": "b1959f36-a218-4b6e-863a9-2e0b4ad5159c", + "type": "raw_event" + } + ] + } + } + } + }, + "description": "Represents the equipment during a specific journey.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "container" + ] + }, + "attributes": { + "type": "object", + "properties": { + "number": { + "type": "string" + }, + "ref_numbers": { + "type": "array", + "items": { + "type": "string" + } + }, + "equipment_type": { + "type": "string", + "enum": [ + "dry", + "reefer", + "open top", + "flat rack", + "bulk", + "tank", + null + ], + "nullable": true + }, + "equipment_length": { + "type": "integer", + "enum": [ + null, + 10, + 20, + 40, + 45 + ], + "nullable": true + }, + "equipment_height": { + "type": "string", + "enum": [ + "standard", + "high_cube", + null + ], + "nullable": true + }, + "weight_in_lbs": { + "type": "number", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "seal_number": { + "type": "string", + "nullable": true + }, + "pickup_lfd": { + "type": "string", + "format": "date-time", + "description": "The last free day for pickup before demmurage accrues. Corresponding timezone is pod_timezone.", + "nullable": true + }, + "pickup_appointment_at": { + "type": "string", + "format": "date-time", + "description": "When available the pickup appointment time at the terminal is returned.", + "nullable": true + }, + "availability_known": { + "type": "boolean", + "description": "Whether Terminal 49 is receiving availability status from the terminal." + }, + "available_for_pickup": { + "type": "boolean", + "description": "If availability_known is true, then whether container is available to be picked up at terminal.", + "nullable": true + }, + "pod_arrived_at": { + "type": "string", + "format": "date-time", + "description": "Time the vessel arrived at the POD", + "nullable": true + }, + "pod_discharged_at": { + "type": "string", + "format": "date-time", + "description": "Discharge time at the port of discharge", + "nullable": true + }, + "pod_full_out_at": { + "type": "string", + "format": "date-time", + "description": "Full Out time at port of discharge. Null for inland moves.", + "nullable": true + }, + "terminal_checked_at": { + "type": "string", + "format": "date-time", + "description": "When the terminal was last checked.", + "nullable": true + }, + "pod_full_out_chassis_number": { + "type": "string", + "description": "The chassis number used when container was picked up at POD (if available)", + "nullable": true + }, + "location_at_pod_terminal": { + "type": "string", + "description": "Location at port of discharge terminal", + "nullable": true + }, + "final_destination_full_out_at": { + "type": "string", + "format": "date-time", + "description": "Pickup time at final destination for inland moves.", + "nullable": true + }, + "empty_terminated_at": { + "type": "string", + "format": "date-time", + "description": "Time empty container was returned.", + "nullable": true + }, + "holds_at_pod_terminal": { + "type": "array", + "items": { + "$ref": "#/components/schemas/terminal_hold" + } + }, + "fees_at_pod_terminal": { + "type": "array", + "items": { + "$ref": "#/components/schemas/terminal_fee" + } + }, + "pod_timezone": { + "type": "string", + "description": "IANA tz. Applies to attributes pod_arrived_at, pod_discharged_at, pickup_appointment_at, pod_full_out_at.", + "nullable": true + }, + "final_destination_timezone": { + "type": "string", + "description": "IANA tz. Applies to attribute final_destination_full_out_at.", + "nullable": true + }, + "empty_terminated_timezone": { + "type": "string", + "description": "IANA tz. Applies to attribute empty_terminated_at.", + "nullable": true + }, + "pod_rail_carrier_scac": { + "type": "string", + "description": "The SCAC of the rail carrier for the pickup leg of the container's journey.(BETA)", + "nullable": true + }, + "ind_rail_carrier_scac": { + "type": "string", + "description": "The SCAC of the rail carrier for the delivery leg of the container's journey.(BETA)", + "nullable": true + }, + "pod_last_tracking_request_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "shipment_last_tracking_request_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_rail_loaded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "pod_rail_departed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ind_eta_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ind_ata_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ind_rail_unloaded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ind_facility_lfd_on": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "relationships": { + "type": "object", + "properties": { + "shipment": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + } + } + } + } + }, + "pickup_facility": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "terminal" + ] + } + } + } + } + }, + "pod_terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "terminal" + ] + } + } + } + } + }, + "transport_events": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "transport_event" + ] + } + } + } + } + } + }, + "raw_events": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "raw_event" + ] + } + } + } + } + } + } + } + } + }, + "required": [ + "id", + "type", + "attributes" + ] + }, + "port": { + "title": "Port model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "attributes": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string", + "description": "UN/LOCODE" + }, + "state_abbr": { + "type": "string", + "x-stoplight": { + "id": "jixah1a0q3exs" + }, + "nullable": true + }, + "city": { + "type": "string", + "x-stoplight": { + "id": "657ij4boc7kyv" + }, + "nullable": true + }, + "country_code": { + "type": "string", + "description": "2 digit country code" + }, + "time_zone": { + "type": "string", + "description": "IANA tz" + }, + "latitude": { + "type": "number", + "x-stoplight": { + "id": "480os7a90z6kk" + }, + "nullable": true + }, + "longitude": { + "type": "number", + "x-stoplight": { + "id": "nfdetqgx5p1yv" + }, + "nullable": true + } + } + }, + "type": { + "type": "string", + "enum": [ + "port" + ] + } + }, + "required": [ + "id", + "type" + ] + }, + "shipping_line": { + "title": "Shipping line model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "attributes": { + "type": "object", + "required": [ + "scac", + "name", + "alternative_scacs", + "short_name", + "bill_of_lading_tracking_support", + "booking_number_tracking_support", + "container_number_tracking_support" + ], + "properties": { + "scac": { + "type": "string", + "minLength": 4, + "maxLength": 4 + }, + "name": { + "type": "string" + }, + "alternative_scacs": { + "type": "array", + "x-stoplight": { + "id": "jwf70hnip0xwb" + }, + "description": "Additional SCACs which will be accepted in tracking requests", + "items": { + "x-stoplight": { + "id": "nrqnwg5y2u0ni" + }, + "type": "string", + "minLength": 4, + "maxLength": 4 + } + }, + "short_name": { + "type": "string" + }, + "bill_of_lading_tracking_support": { + "type": "boolean" + }, + "booking_number_tracking_support": { + "type": "boolean" + }, + "container_number_tracking_support": { + "type": "boolean" + } + } + }, + "type": { + "type": "string", + "enum": [ + "shipping_line" + ] + } + }, + "required": [ + "id", + "attributes", + "type" + ] + }, + "account": { + "title": "Account model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "container" + ] + }, + "attributes": { + "type": "object", + "required": [ + "company_name" + ], + "properties": { + "company_name": { + "type": "string" + } + } + } + }, + "required": [ + "id", + "type", + "attributes" + ], + "x-examples": {} + }, + "error": { + "title": "Error model", + "type": "object", + "properties": { + "detail": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "source": { + "type": "object", + "nullable": true, + "properties": { + "pointer": { + "type": "string", + "nullable": true + }, + "parameter": { + "type": "string", + "nullable": true + } + } + }, + "code": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + }, + "meta": { + "type": "object", + "nullable": true, + "properties": { + "tracking_request_id": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + } + }, + "required": [ + "title" + ] + }, + "metro_area": { + "title": "Metro area model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "attributes": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string", + "description": "UN/LOCODE" + }, + "state_abbr": { + "type": "string", + "x-stoplight": { + "id": "j9yuwej2ym7yq" + }, + "nullable": true + }, + "country_code": { + "type": "string", + "x-stoplight": { + "id": "hfupdk750wcrj" + } + }, + "time_zone": { + "type": "string", + "description": "IANA tz", + "x-stoplight": { + "id": "izvtty345nfsz" + } + }, + "latitude": { + "type": "number", + "x-stoplight": { + "id": "9l62t4cwsp53w" + }, + "nullable": true + }, + "longitude": { + "type": "number", + "x-stoplight": { + "id": "3tzibc0li8xvg" + }, + "nullable": true + } + } + }, + "type": { + "type": "string", + "enum": [ + "metro_area" + ] + }, + "": { + "type": "string", + "x-stoplight": { + "id": "kwcjunrtu3r5o" + } + } + }, + "required": [ + "id", + "type" + ] + }, + "terminal": { + "title": "Terminal model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "relationships": { + "type": "object", + "required": [ + "port" + ], + "properties": { + "port": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "port" + ] + } + } + } + } + } + } + }, + "attributes": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "firms_code": { + "type": "string", + "description": "CBP FIRMS Code or CBS Sublocation Code" + }, + "smdg_code": { + "type": "string", + "description": "SMDG Code" + }, + "bic_facility_code": { + "type": "string", + "description": "BIC Facility Code" + }, + "street": { + "type": "string", + "description": "Street part of the address" + }, + "city": { + "type": "string", + "description": "City part of the address" + }, + "state": { + "type": "string", + "description": "State part of the address" + }, + "state_abbr": { + "type": "string", + "description": "State abbreviation for the state" + }, + "zip": { + "type": "string", + "description": "ZIP code part of the address" + }, + "country": { + "type": "string", + "description": "Country part of the address" + } + } + }, + "type": { + "type": "string", + "enum": [ + "terminal" + ] + } + }, + "required": [ + "attributes", + "relationships" + ] + }, + "rail_terminal": { + "title": "Rail Terminal model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "relationships": { + "type": "object", + "properties": { + "port": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "port" + ] + } + } + } + } + }, + "metro_area": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "metro_area" + ] + } + } + } + } + } + } + }, + "attributes": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "firms_code": { + "type": "string", + "description": "CBP FIRMS Code or CBS Sublocation Code" + } + } + }, + "type": { + "type": "string", + "enum": [ + "rail_terminal" + ] + } + }, + "required": [ + "attributes" + ] + }, + "tracking_request": { + "title": "Tracking Request", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "tracking_request" + ] + }, + "attributes": { + "type": "object", + "properties": { + "request_number": { + "type": "string", + "example": "ONEYSH9AME650500" + }, + "ref_numbers": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "string", + "enum": [ + "pending", + "awaiting_manifest", + "created", + "failed", + "tracking_stopped" + ] + }, + "failed_reason": { + "type": "string", + "enum": [ + "booking_cancelled", + "duplicate", + "expired", + "internal_processing_error", + "invalid_number", + "not_found", + "retries_exhausted", + "shipping_line_unreachable", + "unrecognized_response", + "data_unavailable", + null + ], + "description": "If the tracking request has failed, or is currently failing, the last reason we were unable to complete the request", + "nullable": true + }, + "request_type": { + "type": "string", + "enum": [ + "bill_of_lading", + "booking_number", + "container" + ], + "example": "bill_of_lading" + }, + "scac": { + "type": "string", + "example": "ONEY", + "minLength": 4, + "maxLength": 4 + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "is_retrying": { + "type": "boolean" + }, + "retry_count": { + "type": "integer", + "description": "How many times T49 has attempted to get the shipment from the shipping line", + "nullable": true + } + }, + "required": [ + "request_number", + "status", + "request_type", + "scac", + "created_at" + ] + }, + "relationships": { + "type": "object", + "properties": { + "tracked_object": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + } + } + } + } + }, + "customer": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "party" + ] + } + } + } + } + } + } + } + }, + "required": [ + "id", + "type" + ] + }, + "webhook": { + "title": "webhook", + "type": "object", + "x-examples": {}, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "webhook" + ] + }, + "attributes": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "https end point" + }, + "active": { + "type": "boolean", + "default": true, + "description": "Whether the webhook will be delivered when events are triggered" + }, + "events": { + "type": "array", + "description": "The list of events to enabled for this endpoint", + "uniqueItems": true, + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ] + } + }, + "secret": { + "type": "string", + "description": "A random token that will sign all delivered webhooks" + }, + "headers": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + }, + "required": [ + "url", + "active", + "events", + "secret" + ] + } + }, + "required": [ + "id", + "type" + ], + "description": "" + }, + "vessel": { + "title": "vessel", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "vessel" + ] + }, + "attributes": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the ship or vessel", + "example": "Ever Given" + }, + "imo": { + "type": "string", + "description": "International Maritime Organization (IMO) number", + "nullable": true, + "example": "9811000" + }, + "mmsi": { + "type": "string", + "description": "Maritime Mobile Service Identity (MMSI)", + "nullable": true, + "example": "353136000" + }, + "latitude": { + "type": "number", + "description": "The current latitude position of the vessel", + "nullable": true, + "example": 25.29845 + }, + "longitude": { + "type": "number", + "description": "The current longitude position of the vessel", + "nullable": true, + "example": 121.217 + }, + "nautical_speed_knots": { + "type": "number", + "description": "The current speed of the ship in knots (nautical miles per hour)", + "nullable": true, + "example": 90 + }, + "navigational_heading_degrees": { + "type": "number", + "description": "The current heading of the ship in degrees, where 0 is North, 90 is East, 180 is South, and 270 is West", + "nullable": true, + "example": 194 + }, + "position_timestamp": { + "type": "string", + "description": "The timestamp of when the ship's position was last recorded, in ISO 8601 date and time format", + "nullable": true, + "example": "2023-07-28T14:01:37Z" + } + } + } + } + }, + "transport_event": { + "title": "Transport Event Model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "transport_event" + ] + }, + "attributes": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ] + }, + "voyage_number": { + "type": "string", + "nullable": true + }, + "timestamp": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "location_locode": { + "type": "string", + "description": "UNLOCODE of the event location", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "data_source": { + "type": "string", + "enum": [ + "shipping_line", + "terminal", + "ais" + ], + "example": "shipping_line", + "description": "The original source of the event data" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "shipment": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + } + } + } + } + }, + "location": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "port", + "metro_area" + ] + } + } + } + } + }, + "vessel": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "enum": [ + "vessel" + ] + } + } + } + } + }, + "terminal": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "terminal", + "rail_terminal" + ] + } + } + } + } + }, + "container": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "container" + ] + } + } + } + } + } + } + } + }, + "required": [ + "id", + "type" + ] + }, + "estimated_event": { + "title": "Estimated Event Model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "estimated_event" + ] + }, + "attributes": { + "type": "object", + "required": [ + "created_at", + "estimated_timestamp", + "event" + ], + "properties": { + "created_at": { + "type": "string", + "description": "When the estimated event was created", + "format": "date-time" + }, + "estimated_timestamp": { + "type": "string", + "format": "date-time" + }, + "event": { + "type": "string", + "enum": [ + "shipment.estimated.arrival" + ] + }, + "location_locode": { + "type": "string", + "description": "UNLOCODE of the event location", + "nullable": true + }, + "timezone": { + "type": "string", + "description": "IANA tz", + "nullable": true + }, + "voyage_number": { + "type": "string", + "nullable": true + }, + "data_source": { + "type": "string", + "enum": [ + "shipping_line", + "terminal" + ], + "description": "The original source of the event data" + } + } + }, + "relationships": { + "type": "object", + "required": [ + "shipment" + ], + "properties": { + "shipment": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + } + } + } + } + }, + "port": { + "type": "object", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "port" + ] + } + } + } + } + }, + "vessel": { + "type": "object", + "description": "\n", + "properties": { + "data": { + "type": "object", + "nullable": true, + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "vessel" + ] + } + } + } + } + } + } + } + }, + "required": [ + "id", + "type", + "attributes", + "relationships" + ] + }, + "webhook_notification": { + "title": "webhook_notification", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "webhook_notification" + ] + }, + "attributes": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "container.transport.vessel_arrived", + "container.transport.vessel_discharged", + "container.transport.vessel_loaded", + "container.transport.vessel_departed", + "container.transport.rail_departed", + "container.transport.rail_arrived", + "container.transport.rail_loaded", + "container.transport.rail_unloaded", + "container.transport.transshipment_arrived", + "container.transport.transshipment_discharged", + "container.transport.transshipment_loaded", + "container.transport.transshipment_departed", + "container.transport.feeder_arrived", + "container.transport.feeder_discharged", + "container.transport.feeder_loaded", + "container.transport.feeder_departed", + "container.transport.empty_out", + "container.transport.full_in", + "container.transport.full_out", + "container.transport.empty_in", + "container.transport.vessel_berthed", + "shipment.estimated.arrival", + "tracking_request.succeeded", + "tracking_request.failed", + "tracking_request.awaiting_manifest", + "tracking_request.tracking_stopped", + "container.created", + "container.updated", + "container.pod_terminal_changed", + "container.transport.arrived_at_inland_destination", + "container.transport.estimated.arrived_at_inland_destination", + "container.pickup_lfd.changed" + ] + }, + "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" + ] + } + } + }, + "terminal_hold": { + "title": "terminal_hold", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "hold" + ] + }, + "description": { + "type": "string", + "description": "Text description from the terminal (if any)", + "nullable": true + } + }, + "required": [ + "name", + "status" + ] + }, + "terminal_fee": { + "title": "terminal_fee", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "demurrage", + "exam", + "extended_dwell_time", + "other", + "total" + ] + }, + "amount": { + "type": "number", + "description": "The fee amount in local currency" + }, + "currency_code": { + "type": "string", + "description": "The ISO 4217 currency code of the fee is charged in. E.g. USD", + "example": "USD" + } + }, + "required": [ + "type", + "amount" + ] + }, + "container_updated_event": { + "title": "container_updated_event", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "properties": { + "changeset": { + "type": "object", + "description": "A hash of all the changed attributes with the values being an array of the before and after. E.g. \n`{\"pickup_lfd\": [null, \"2020-05-20\"]}`\n\nThe current attributes that can be alerted on are:\n- `available_for_pickup`\n- `pickup_lfd`\n- `fees_at_pod_terminal`\n- `holds_at_pod_terminal`\n- `pickup_appointment_at`\n- `pod_terminal`" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "" + }, + "timezone": { + "type": "string", + "description": "IANA tz " + }, + "data_source": { + "type": "string", + "enum": [ + "terminal" + ], + "example": "terminal" + } + }, + "required": [ + "changeset", + "timestamp" + ] + }, + "relationships": { + "type": "object", + "required": [ + "container", + "terminal" + ], + "properties": { + "container": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "container" + ] + } + } + } + } + }, + "terminal": { + "type": "object", + "description": "", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "terminal" + ] + } + } + } + } + } + } + } + }, + "required": [ + "relationships" + ] + }, + "raw_event": { + "title": "Raw Event Model", + "type": "object", + "description": "Raw Events represent the milestones from the shipping line for a given container.\n\n### About raw_event datetimes\n\nThe events may include estimated future events. The event is a future event if an `estimated_` timestamp is not null. \n\nThe datetime properties `timestamp` and `estimated`. \n\nWhen the `time_zone` property is present the datetimes are UTC timestamps, which can be converted to the local time by parsing the provided `time_zone`.\n\nWhen the `time_zone` property is absent, the datetimes represent local times which serialized as UTC timestamps for consistency. ", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "description": "", + "enum": [ + "raw_event" + ] + }, + "attributes": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + "empty_out", + "full_in", + "positioned_in", + "positioned_out", + "vessel_loaded", + "vessel_departed", + "transshipment_arrived", + "transshipment_discharged", + "transshipment_loaded", + "transshipment_departed", + "feeder_arrived", + "feeder_discharged", + "feeder_loaded", + "feeder_departed", + "rail_loaded", + "rail_departed", + "rail_arrived", + "rail_unloaded", + "vessel_arrived", + "vessel_discharged", + "arrived_at_destination", + "delivered", + "full_out", + "empty_in", + "vgm_received", + "carrier_release", + "customs_release" + ], + "description": "Normalized string representing the event", + "nullable": true + }, + "original_event": { + "type": "string", + "description": "The event name as returned by the carrier" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "The datetime the event either transpired or will occur in UTC" + }, + "estimated": { + "type": "boolean", + "description": "True if the timestamp is estimated, false otherwise" + }, + "actual_on": { + "type": "string", + "format": "date", + "description": "Deprecated: The date of the event at the event location when no time information is available. ", + "nullable": true + }, + "estimated_on": { + "type": "string", + "format": "date", + "description": "Deprecated: The estimated date of the event at the event location when no time information is available. ", + "nullable": true + }, + "actual_at": { + "type": "string", + "format": "date-time", + "description": "Deprecated: The datetime the event transpired in UTC", + "nullable": true + }, + "estimated_at": { + "type": "string", + "format": "date-time", + "description": "Deprecated: The estimated datetime the event will occur in UTC", + "nullable": true + }, + "timezone": { + "type": "string", + "description": "IANA tz where the event occured", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the raw_event was created in UTC" + }, + "location_name": { + "type": "string", + "description": "The city or facility name of the event location" + }, + "location_locode": { + "type": "string", + "description": "UNLOCODE of the event location", + "nullable": true + }, + "vessel_name": { + "type": "string", + "description": "The name of the vessel where applicable", + "nullable": true + }, + "vessel_imo": { + "type": "string", + "description": "The IMO of the vessel where applicable", + "nullable": true + }, + "index": { + "type": "integer", + "description": "The order of the event. This may be helpful when only dates (i.e. actual_on) are available." + }, + "voyage_number": { + "type": "string", + "nullable": true + } + } + }, + "relationships": { + "type": "object", + "properties": { + "location": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "port", + "metro_area" + ] + } + } + } + } + }, + "vessel": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "vessel" + ] + } + } + } + } + } + } + } + } + }, + "container_pod_terminal_changed_event": { + "title": "Container Pod Terminal Changed Event", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "properties": { + "data_source": { + "type": "string", + "enum": [ + "shipping_line", + "terminal", + "pierpass" + ], + "example": "shipping_line", + "description": "Where the information about the terminal change came from" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the terminal change was recorded" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "container": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "container" + ] + } + } + }, + "shipment": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "shipment" + ] + } + } + }, + "terminal": { + "type": "object", + "description": "The terminal the container has changed to. If this container is still on the vessel this represents an advisory. If it was previously at the terminal this represents an off-dock move.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "terminal" + ] + } + } + } + } + } + }, + "description": "" + }, + "party": { + "title": "Party model", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "attributes": { + "type": "object", + "required": [ + "company_name" + ], + "properties": { + "company_name": { + "type": "string", + "description": "Company name" + } + } + }, + "type": { + "type": "string", + "enum": [ + "party" + ] + } + }, + "required": [ + "attributes" + ] + } + }, + "securitySchemes": { + "authorization": { + "name": "Authorization", + "type": "apiKey", + "in": "header", + "description": "`Token YOUR_API_TOKEN`\n\nThe APIs require authentication to be done using header-based API Key and Secret Authentication. \n\nAPI key and secret are sent va the `Authorization` request header.\n\nYou send your API key and secret in the following way:\n\n`Authorization: Token YOUR_API_KEY`" + } + } + }, + "tags": [ + { + "name": "Containers" + }, + { + "name": "Shipments" + }, + { + "name": "Locations" + }, + { + "name": "Events" + }, + { + "name": "Tracking Requests" + }, + { + "name": "Webhooks" + }, + { + "name": "Webhook Notifications" + }, + { + "name": "Ports" + }, + { + "name": "Metro Areas" + }, + { + "name": "Terminals" + } + ], + "security": [ + { + "authorization": [] + } + ] +} \ No newline at end of file diff --git a/endpoint-documentation-template.md b/endpoint-documentation-template.md new file mode 100644 index 00000000..a2fbd5a5 --- /dev/null +++ b/endpoint-documentation-template.md @@ -0,0 +1,102 @@ +# Endpoint Documentation Template + +## Overview +This template provides a standardized format for documenting Terminal49 API endpoints with comprehensive examples, clear explanations, and business context. + +--- + +```yaml +--- +title: [Endpoint Name - e.g., "Create a Tracking Request"] +description: [Brief description of what this endpoint does] +openapi: [method] [path] # e.g., post /tracking_requests +--- +``` + +## Endpoint Purpose +[1-2 sentences explaining what this endpoint is used for and when you would use it in your integration] + +## Business Use Cases +[Brief explanation of common business scenarios where this endpoint is valuable] +- **Use Case 1**: [e.g., "Initiate tracking for a new container shipment"] +- **Use Case 2**: [e.g., "Set up automated monitoring for a customer's bill of lading"] + +## Request Format + +### Required Parameters +[List and explain required parameters] + +### Optional Parameters +[List and explain optional parameters] + +### Example Requests + +#### Example 1: [Basic/Common Scenario] +```json +// Request +{ + [Request body with comments explaining key fields] +} + +// Response +{ + [Success response with comments explaining important fields] +} +``` + +#### Example 2: [Alternative Scenario] +```json +// Request +{ + [Request body for alternative scenario] +} + +// Response +{ + [Response showing different outcome] +} +``` + +#### Example 3: [Error Scenario] +```json +// Request +{ + [Request body that would cause an error] +} + +// Response +{ + [Error response with explanation] +} +``` + +## Response Fields +| Field | Type | Description | +|-------|------|-------------| +| `field1` | string | [Explanation with business context] | +| `field2` | integer | [Explanation with business context] | + +## Status Codes +| Code | Meaning | +|------|---------| +| 200 | Success - [what this means] | +| 400 | Bad Request - [common causes] | +| 401 | Unauthorized - [authentication issue] | +| 422 | Unprocessable Entity - [validation errors] | + +## Related Webhooks +If you've registered for webhooks, you may receive the following events related to this endpoint: +- `event.name`: [Description of when this event is triggered] +- `event.name2`: [Description of when this event is triggered] + +## Related Guides +- [Link to related guide] +- [Link to tutorial that uses this endpoint] + +## Troubleshooting +Common issues and their solutions: +- **Issue**: [Common error or confusion] + **Solution**: [How to resolve it] + +- **Issue**: [Another common issue] + **Solution**: [How to resolve it] diff --git a/fix_json.py b/fix_json.py new file mode 100644 index 00000000..fdcd9f2e --- /dev/null +++ b/fix_json.py @@ -0,0 +1,44 @@ +import json +import sys + +try: + with open('docs/openapi.json', 'r') as f: + content = f.read() + + # Try to load the JSON to check if it's valid + try: + data = json.loads(content) + print("JSON is valid!") + except json.JSONDecodeError as e: + print(f"JSON error: {e}") + + # Examine the problematic area + error_pos = e.pos + start = max(0, error_pos - 20) + end = min(len(content), error_pos + 20) + problematic_content = content[start:end] + + print(f"Problematic content around position {error_pos}:") + print(repr(problematic_content)) + + # Fix the content by ensuring proper JSON format (remove any trailing characters) + # Find the last valid closing brace + last_brace_pos = content.rstrip().rfind('}') + if last_brace_pos > 0: + fixed_content = content[:last_brace_pos+1] + + # Verify the fixed content + try: + json.loads(fixed_content) + print("Fixed JSON is valid!") + + # Save the fixed content back to the file + with open('docs/openapi.json', 'w') as f: + f.write(fixed_content) + print("File has been fixed and saved.") + except json.JSONDecodeError as e2: + print(f"Could not fix JSON: {e2}") + else: + print("Could not find closing brace to fix JSON.") +except Exception as e: + print(f"Error: {e}") diff --git a/fix_json_thorough.py b/fix_json_thorough.py new file mode 100644 index 00000000..90d5f3c8 --- /dev/null +++ b/fix_json_thorough.py @@ -0,0 +1,46 @@ +import json +import re + +# Attempt to clean and parse the JSON file +try: + with open('docs/openapi.json', 'r') as f: + content = f.read() + + # Look for the main JSON structure + # Find where the root object ends (the last closing brace that matches the first opening brace) + open_braces = 0 + clean_end_pos = None + + for i, char in enumerate(content): + if char == '{': + open_braces += 1 + elif char == '}': + open_braces -= 1 + if open_braces == 0: + clean_end_pos = i + 1 + break + + if clean_end_pos is not None: + # Extract just the clean JSON part + clean_content = content[:clean_end_pos] + + # Make a backup + with open('docs/openapi.json.backup', 'w') as f: + f.write(content) + + # Validate the cleaned content + try: + parsed = json.loads(clean_content) + print("Successfully cleaned and parsed the JSON!") + + # Write the clean, formatted JSON back to the file + with open('docs/openapi.json', 'w') as f: + json.dump(parsed, f, indent=2) + + print("Fixed JSON has been written back to the file.") + except json.JSONDecodeError as e: + print(f"Still issues with the JSON: {e}") + else: + print("Could not find the end of the root JSON object.") +except Exception as e: + print(f"Error: {e}") diff --git a/fix_openapi.py b/fix_openapi.py new file mode 100644 index 00000000..5d141b03 --- /dev/null +++ b/fix_openapi.py @@ -0,0 +1,35 @@ +import json +import os + +# Open the file and read line by line to understand the issue +with open('docs/openapi.json', 'r') as f: + lines = f.readlines() + +# Find where the main JSON object ends +end_line = None +for i, line in enumerate(lines): + if line.strip() == '}' and i > len(lines) - 5: # Look for closing brace near the end + end_line = i + break + +if end_line is not None: + # Keep only up to the end of the JSON object + fixed_lines = lines[:end_line+1] + fixed_content = ''.join(fixed_lines) + + # Validate the fixed content + try: + json.loads(fixed_content) + print("Fixed JSON is valid!") + + # Create a backup + os.rename('docs/openapi.json', 'docs/openapi.json.bak') + + # Save the fixed content + with open('docs/openapi.json', 'w') as f: + f.write(fixed_content) + print("File has been fixed and saved. Original backed up to openapi.json.bak.") + except json.JSONDecodeError as e: + print(f"Could not fix JSON: {e}") +else: + print("Could not identify where to fix the JSON.") diff --git a/json-api-guide.mdx b/json-api-guide.mdx new file mode 100644 index 00000000..80bbefa8 --- /dev/null +++ b/json-api-guide.mdx @@ -0,0 +1,255 @@ +--- +title: Understanding JSON:API +description: Learn the basics of JSON:API and how it's implemented in Terminal49's API. +--- + +# JSON:API Guide + +Terminal49's API follows the [JSON:API specification](https://jsonapi.org/), a standard for building APIs in JSON. This guide will help you understand the key concepts and how to work with our API effectively. + +## What is JSON:API? + +JSON:API is a specification for how a client should request resources to be fetched or modified, and how a server should respond. It's designed to minimize both the number of requests and the amount of data transmitted between clients and servers. + + + The Terminal49 API conforms to the JSON:API v1.0 specification. This standardization makes our API more predictable and easier to work with once you understand the patterns. + + +## JSON:API Structure + +### Basic Resource Objects + +All Terminal49 API responses follow this general structure: + +```json +{ + "data": { + "id": "123", + "type": "shipment", + "attributes": { + "bill_of_lading_number": "MAEU9736478", + "created_at": "2023-05-15T14:23:12Z" + // Other shipment attributes + }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" }, + { "id": "789", "type": "container" } + ] + } + } + } +} +``` + +The main components are: + + + The primary resource or collection of resources. + + + + A unique identifier for the resource. + + + + The resource type (e.g., "shipment", "container", "tracking_request"). + + + + The resource's properties (e.g., numbers, dates, status values). + + + + References to related resources (e.g., a shipment's containers). + + +### Collections of Resources + +When requesting multiple resources, the response structure is: + +```json +{ + "data": [ + { + "id": "123", + "type": "shipment", + "attributes": { ... } + }, + { + "id": "124", + "type": "shipment", + "attributes": { ... } + } + ], + "meta": { + "total_count": 45 + }, + "links": { + "self": "https://api.terminal49.com/v2/shipments?page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/shipments?page[number]=2&page[size]=2" + } +} +``` + + + Contains non-standard meta-information, like counts or pagination details. + + + + Contains links for pagination or related operations. + + +## Working with JSON:API in Terminal49 + +### Creating Resources + +When creating a resource (like a tracking request), your request should follow this format: + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU" + } + } + } + + + +Notice that: +- The `type` field is required and must match the expected resource type +- All properties go inside the `attributes` object +- You don't specify an `id` for new resources (the server assigns it) + +### Including Related Resources + +One powerful feature of JSON:API is the ability to include related resources in a single request using the `include` parameter: + +``` +GET /shipments/123?include=containers,containers.transport_events +``` + +This returns the shipment with its containers and each container's transport events in one response: + +```json +{ + "data": { + "id": "123", + "type": "shipment", + "attributes": { ... }, + "relationships": { + "containers": { + "data": [ + { "id": "456", "type": "container" } + ] + } + } + }, + "included": [ + { + "id": "456", + "type": "container", + "attributes": { ... }, + "relationships": { + "transport_events": { + "data": [ + { "id": "789", "type": "transport_event" } + ] + } + } + }, + { + "id": "789", + "type": "transport_event", + "attributes": { ... } + } + ] +} +``` + + + Contains all the related resources referenced in the main resource's relationships. + + +### Error Handling + +Error responses also follow a standard format: + +```json +{ + "errors": [ + { + "status": "422", + "title": "Invalid attribute", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } + } + ] +} +``` + +Each error object contains: + + + The HTTP status code relevant to the error. + + + + A short, human-readable summary of the problem. + + + + A human-readable explanation of the specific error. + + + + An object containing references to the source of the error. The `pointer` field typically contains a JSON Pointer to the specific field that caused the error. + + +## JSON:API Libraries + +Since Terminal49 uses standard JSON:API, you can leverage client libraries for your preferred programming language to make working with our API easier: + + + + JSON:API libraries for JavaScript + + + jsonapi-rb for Ruby applications + + + JSON:API libraries for Python + + + +Using a library helps handle the resource structure, relationship management, and error parsing automatically. + +## Tips for Working with Terminal49's JSON:API + +- Always include the correct `Content-Type` header in your requests: + ``` + Content-Type: application/vnd.api+json + ``` + +- Use the `include` parameter to reduce the number of API calls when you need related resources + +- When creating or updating resources, remember that all properties must be inside the `attributes` object + +- For filtering collections, use the filter query parameter with the attribute name: + ``` + GET /containers?filter[status]=arrived + ``` + +- Pagination is handled via `page[number]` and `page[size]` query parameters + + + For a deeper understanding of JSON:API, check the [official specification](https://jsonapi.org/format/) or our [In-Depth Guides](/api-docs/in-depth-guides/including-resources) on working with Terminal49's API. + diff --git a/mintlify-endpoint-documentation-template.mdx b/mintlify-endpoint-documentation-template.mdx new file mode 100644 index 00000000..d897b532 --- /dev/null +++ b/mintlify-endpoint-documentation-template.mdx @@ -0,0 +1,151 @@ +--- +title: [Endpoint Name - e.g., "Create a Tracking Request"] +description: [Brief description of what this endpoint does] +og:title: [Endpoint Name] | Terminal49 API Documentation +og:description: [SEO-friendly description about this endpoint] +openapi: [method] [path] # e.g., post /tracking_requests +--- + +## Endpoint Purpose +[1-2 sentences explaining what this endpoint is used for and when you would use it in your integration] + +## Business Use Cases +[Brief explanation of common business scenarios where this endpoint is valuable] +- **Use Case 1**: [e.g., "Initiate tracking for a new container shipment"] +- **Use Case 2**: [e.g., "Set up automated monitoring for a customer's bill of lading"] + +## Example Scenarios + + + + + + { + // Request body with comments + } + + + { + // Success response with comments + } + + + + **Notes:** + - [Important information about this scenario] + - [Explanation of key response fields] + + + + + + { + // Request body for alternative scenario + } + + + { + // Response showing different outcome + } + + + + **Notes:** + - [Information specific to this scenario] + - [Business context for when you'd use this approach] + + + + + + { + // Request that would trigger an error + } + + + { + // Error response + } + + + + **Notes:** + - [Explanation of what caused the error] + - [How to resolve this issue] + + + +## Key Response Fields + + + [Explanation with business context] + + + + [Explanation with business context] + + + + An object containing related information. + + + + [Explanation of nested field] + + + + [Explanation of nested array field] + + + + +## Related Webhook Events + +When using this endpoint, you may receive the following webhook events: + + + [Description of when this event is triggered and what it contains] + + + + [Description of when this event is triggered and what it contains] + + +## Related Guides & Tutorials + + + + [Brief description of related guide] + + + [Brief description of related tutorial] + + + +## Troubleshooting + + + + **Problem**: [Description of common problem] + + **Solution**: [Detailed steps to resolve] + + ```json + // Example of correct usage + ``` + + + + **Problem**: [Description of another common problem] + + **Solution**: [Detailed steps to resolve] + + + + + [Important information or tips about using this endpoint effectively] + + + + [Critical warnings about potential pitfalls or limitations] + diff --git a/sample-create-tracking-request.mdx b/sample-create-tracking-request.mdx new file mode 100644 index 00000000..0b668eda --- /dev/null +++ b/sample-create-tracking-request.mdx @@ -0,0 +1,308 @@ +--- +title: Create a Tracking Request +description: Submit a new tracking request to monitor ocean shipments via bill of lading, booking number, or container number. +og:title: Create a Tracking Request | Terminal49 API Documentation +og:description: Create tracking requests via Terminal49's API for accurate shipment tracking and real-time notifications. +openapi: post /tracking_requests +--- + +## Endpoint Purpose +Submitting a tracking request tells Terminal49 to start monitoring a specific shipment for you. This is typically the first step in the tracking workflow. + +## Business Use Cases +- **Initial Tracking Setup**: Start tracking a new container shipment when you receive a bill of lading or booking confirmation. +- **Customer Onboarding**: Quickly set up tracking for a customer's shipments when they provide their shipping documents. +- **Bulk Import**: Programmatically submit multiple tracking requests to monitor your entire inventory in transit. + +## Example Scenarios + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", // Type of tracking number + "request_number": "MAEU9736478", // Bill of lading number from the carrier + "scac": "MAEU" // Shipping line SCAC code (Maersk in this case) + } + } + } + + + { + "data": { + "id": "b9c6a3e0-411e-4378-b93c-aacea42a673e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "MAEU9736478", + "scac": "MAEU", + "status": "pending", // Request is being processed + "failed_reason": null, + "created_at": "2023-05-15T14:23:12Z", + "updated_at": "2023-05-15T14:23:12Z" + }, + "relationships": { + "shipment": { + "data": null // Will be populated when tracking succeeds + } + } + } + } + + + + **Notes:** + - The bill of lading is typically the most reliable identifier for tracking shipments. + - The `status` will initially be `pending` while Terminal49 processes your request. + - Once processing completes, you'll receive a webhook notification and the status will change to `succeeded` or `failed`. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "container", // Tracking by container number + "request_number": "MSCU1234567", // Container number + "scac": "MSCU" // Shipping line SCAC code (MSC in this case) + } + } + } + + + { + "data": { + "id": "f7c9b5d2-311e-4378-a91c-bbcea42a785f", + "type": "tracking_request", + "attributes": { + "request_type": "container", + "request_number": "MSCU1234567", + "scac": "MSCU", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T15:45:22Z", + "updated_at": "2023-05-15T15:45:22Z" + }, + "relationships": { + "shipment": { + "data": null + } + } + } + } + + + + **Notes:** + - Container number tracking support varies between carriers. Refer to the Carrier Data Matrix to verify support. + - Container numbers should include the carrier prefix (e.g., MSCU for MSC containers). + - Container tracking can sometimes fail if the container is very new or not yet associated with a booking in the carrier's system. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", // Test number that will always succeed + "scac": "TEST" // Special SCAC for test requests + } + } + } + + + { + "data": { + "id": "e8d7c6b5-211d-4378-a81b-aacfa42a895e", + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "TEST-TR-SUCCEEDED", + "scac": "TEST", + "status": "pending", + "failed_reason": null, + "created_at": "2023-05-15T16:30:45Z", + "updated_at": "2023-05-15T16:30:45Z" + }, + "relationships": { + "shipment": { + "data": null + } + } + } + } + + + + **Notes:** + - Using test numbers is perfect for development and testing your integration. + - `TEST-TR-SUCCEEDED` will always trigger a successful tracking response. + - `TEST-TR-FAILED` will always trigger a failed tracking response. + - These test numbers will trigger corresponding webhook events for complete testing. + + + + + + { + "data": { + "type": "tracking_request", + "attributes": { + "request_type": "bill_of_lading", + "request_number": "INVALID-FORMAT", // Invalid bill of lading format + "scac": "UNKNOWN" // Non-existent SCAC code + } + } + } + + + { + "errors": [ + { + "status": "422", + "title": "Invalid SCAC", + "detail": "The SCAC 'UNKNOWN' is not supported.", + "source": { + "pointer": "/data/attributes/scac" + } + } + ] + } + + + + **Notes:** + - This example shows a validation error for an invalid SCAC code. + - Common errors include invalid SCAC codes, improperly formatted request numbers, or duplicate tracking requests. + - For a complete list of supported SCACs, refer to the Carrier Data Matrix. + + + +## Key Response Fields + + + Unique identifier for the tracking request. Store this to check status later. + + + + Current status of the tracking request. Possible values: + - `pending`: Request is being processed + - `succeeded`: Container/shipment located and tracking established + - `failed`: Unable to track the shipment (see failed_reason) + - `awaiting_manifest`: Bill of lading is valid but not yet available in the carrier's system + - `tracking_stopped`: Tracking has been stopped for this request + + + + If status is "failed", this field provides the reason. Common values: + - `duplicate`: Shipment already exists in Terminal49 + - `not_found`: Shipping line couldn't find the tracking number + - `retries_exhausted`: Maximum retries reached without success + - `invalid_number`: Improperly formatted tracking number + + + See the [Tracking Request Lifecycle](/api-docs/in-depth-guides/tracking-request-lifecycle) guide for a complete list of failure reasons. + + + + + When the tracking request succeeds, this contains a reference to the created shipment. + + + + ID of the linked shipment that you can use to retrieve full shipment details. + + + + Always "shipment" for successful tracking requests. + + + + +## Related Webhook Events + +When creating a tracking request, you may receive the following webhook events: + + + Sent when Terminal49 successfully locates your shipment and begins tracking it. The notification includes details about the created shipment and its containers. + + + + Sent when Terminal49 is unable to locate your shipment after all retry attempts. Includes the failed_reason to help troubleshoot. + + + + Sent when the bill of lading number is valid but the shipment data is not yet available in the carrier's system. Terminal49 will continue checking daily. + + +## Related Guides & Tutorials + + + + Learn about the full lifecycle of a tracking request, including all possible statuses and failure reasons. + + + Learn how to set up webhooks to receive real-time notifications about your tracking requests. + + + Complete list of test tracking numbers you can use during development. + + + Learn how to efficiently submit and manage multiple tracking requests. + + + +## Troubleshooting + + + + **Problem**: Your tracking request has been in "pending" status for an extended period. + + **Solution**: + 1. Terminal49 retries tracking requests up to 14 times over 24 hours. + 2. Check that the SCAC and request number are correct. + 3. For bill of lading numbers, note that they may remain in "awaiting_manifest" status for up to 7 days. + 4. If the issue persists beyond 24 hours, contact support@terminal49.com. + + + + **Problem**: Your tracking request failed with reason "not_found". + + **Solution**: + 1. Verify the tracking number format is correct (no spaces, proper prefixes). + 2. Confirm you're using the correct SCAC code for the carrier. + 3. For newly issued bills of lading, try again in 24-48 hours as it may take time to appear in the carrier's system. + 4. If using a container number, check if the carrier supports container tracking in the Carrier Data Matrix. + + + + **Problem**: Your tracking request failed with reason "duplicate". + + **Solution**: + 1. The shipment is already being tracked in Terminal49. + 2. Use `GET /shipments` with a filter to find the existing shipment: + + ```bash + GET /shipments?filter[bill_of_lading_number]=MAEU9736478 + ``` + + 3. You can access the existing shipment data instead of creating a new tracking request. + + + + + For the fastest tracking setup, use the master bill of lading (MBL) number from the carrier. House bills of lading (HBL) are not supported. + + + + Container number tracking is not supported by all carriers. Always check the Carrier Data Matrix before implementing container tracking. + diff --git a/sample.json b/sample.json new file mode 100644 index 00000000..7919c324 --- /dev/null +++ b/sample.json @@ -0,0 +1,100 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Terminal49 API Reference", + "version": "0.2.0", + "contact": { + "name": "Terminal49 API support", + "url": "https://www.terminal49.com", + "email": "support@terminal49.com" + }, + "description": "The Terminal 49 API offers a convenient way to programmatically track your shipments from origin to destination.", + "x-label": "Beta", + "termsOfService": "https://www.terminal49.com/terms" + }, + "servers": [ + { + "url": "https://api.terminal49.com/v2", + "description": "Production" + } + ], + "paths": { + "/shipments": { + "get": { + "summary": "List shipments", + "tags": [ + "Shipments" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/shipment" + } + }, + "included": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/container" + }, + { + "$ref": "#/components/schemas/port" + }, + { + "$ref": "#/components/schemas/terminal" + } + ] + } + }, + "links": { + "$ref": "#/components/schemas/links" + }, + "meta": { + "$ref": "#/components/schemas/meta" + } + } + }, + "examples": { + "En-route to NY with inland move": { + "value": { + "data": [ + { + "id": "62738624-7032-4a50-892e-c55826228c25", + "type": "shipment", + "attributes": { + "created_at": "2024-06-26T17:28:59Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "OOLU2148468620", + "normalized_number": "2148468620", + "shipping_line_scac": "OOLU", + "shipping_line_name": "Orient Overseas Container Line", + "shipping_line_short_name": "OOCL", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "CNNBG", + "port_of_lading_name": "Ningbo", + "port_of_discharge_locode": "USLAX", + "port_of_discharge_name": "Los Angeles", + "pod_vessel_name": "EVER FORWARD", + "pod_vessel_imo": "9850551", + "pod_voyage_number": "1119E", + "destination_locode": "USCLE", + "destination_name": "Cleveland", + "destination_timezone": "America/New_York", + "destination_ata_at": null, + "destination_eta_at": "2024-07-02T08:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2024-06-09T03:42:00Z", + "pol_timezone": "Asia/Shanghai", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pod_ata_at": "2024-06-22T13:36:00Z",