diff --git a/Terminal49-API.postman_collection.json b/Terminal49-API.postman_collection.json index 37b75937..9769cd6b 100644 --- a/Terminal49-API.postman_collection.json +++ b/Terminal49-API.postman_collection.json @@ -5,7 +5,7 @@ "description": "", "item": [ { - "id": "8feb153e-64c2-4a74-b0b1-8d1d1c7d3b81", + "id": "b3e5f4fa-9195-4ff7-9ce4-dda5a416b6ae", "name": "List containers", "request": { "name": "List containers", @@ -63,7 +63,7 @@ }, "response": [ { - "id": "fac50e6c-f46f-4a71-99fb-43b9d28e0822", + "id": "83c09cd5-2038-460d-9bd4-fac0f1603a17", "name": "OK", "originalRequest": { "url": { @@ -129,7 +129,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"tank\",\n \"equipment_length\": 10,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"picked_up\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": null,\n \"equipment_length\": 10,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"loaded\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_full_out_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"reefer\",\n \"equipment_length\": 20,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"on_rail\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 40,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"extended_dwell_time\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"new\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -140,7 +140,7 @@ } }, { - "id": "26471916-4eb0-4829-bc74-e2af1e4b9be2", + "id": "60568b42-0b50-4aa4-836f-104a725e98cb", "name": "Edit a container", "request": { "name": "Edit a container", @@ -183,7 +183,7 @@ }, "response": [ { - "id": "9b9f4294-8de8-4196-bdc9-60aaf70a313f", + "id": "9b061404-a23e-4cdc-aadd-2ac8e3e46c7b", "name": "OK", "originalRequest": { "url": { @@ -234,7 +234,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"flat rack\",\n \"equipment_length\": 40,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"grounded\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"bulk\",\n \"equipment_length\": 45,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"loaded\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -245,7 +245,7 @@ } }, { - "id": "c78584ac-af18-4cc0-bd60-210afaca6096", + "id": "87002577-d76f-43cd-9d12-7b35194b78cd", "name": "Get a container", "request": { "name": "Get a container", @@ -297,7 +297,7 @@ }, "response": [ { - "id": "87d23034-dd18-484e-807a-6210c5a73f43", + "id": "18858865-7dcf-49b2-9f64-5dcc1a215180", "name": "OK", "originalRequest": { "url": { @@ -357,7 +357,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"flat rack\",\n \"equipment_length\": 45,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"picked_up\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"booking_cancelled\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ]\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 45,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"extended_dwell_time\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"grounded\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_arrival_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -368,7 +368,7 @@ } }, { - "id": "9e55b5f8-e4b2-4ef1-bf3b-cd2e7734f6f1", + "id": "3b3cd540-80ea-4910-a25c-1fb740ed729c", "name": "Get a container's raw events", "request": { "name": "Get a container's raw events", @@ -411,7 +411,7 @@ }, "response": [ { - "id": "48663e88-9f0e-470f-b0f1-a03ebb5ef94f", + "id": "76131007-7302-4f18-90fb-3177b6dcc4fe", "name": "OK", "originalRequest": { "url": { @@ -462,7 +462,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"delivered\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"vessel_discharged\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n }\n ]\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"positioned_out\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\",\n \"attributes\": {\n \"event\": \"carrier_release\",\n \"original_event\": \"\",\n \"timestamp\": \"\",\n \"estimated\": \"\",\n \"actual_on\": \"\",\n \"estimated_on\": \"\",\n \"actual_at\": \"\",\n \"estimated_at\": \"\",\n \"timezone\": \"\",\n \"created_at\": \"\",\n \"location_name\": \"\",\n \"location_locode\": \"\",\n \"vessel_name\": \"\",\n \"vessel_imo\": \"\",\n \"index\": \"\",\n \"voyage_number\": \"\"\n },\n \"relationships\": {\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"vessel\"\n }\n }\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -473,7 +473,7 @@ } }, { - "id": "e23a04e9-803a-4501-8c1a-f0886dd73d37", + "id": "eefa98b2-2f92-4907-85e4-13b97047c5eb", "name": "Get a container's transport events", "request": { "name": "Get a container's transport events", @@ -526,7 +526,7 @@ }, "response": [ { - "id": "cb72ba81-93c2-4949-81a6-4f39bf971f5e", + "id": "8ead2e63-1368-40a6-8d36-437fdd2c5024", "name": "OK", "originalRequest": { "url": { @@ -587,7 +587,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.transport.transshipment_departed\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"ais\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"rail_terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.transport.vessel_loaded\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"terminal\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_full_out_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.transport.vessel_berthed\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"terminal\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"metro_area\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"rail_terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\",\n \"attributes\": {\n \"event\": \"container.pickup_lfd.changed\",\n \"voyage_number\": \"\",\n \"timestamp\": \"\",\n \"timezone\": \"\",\n \"location_locode\": \"\",\n \"created_at\": \"\",\n \"data_source\": \"shipping_line\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"location\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"port\"\n }\n },\n \"vessel\": {\n \"data\": {\n \"id\": \"\",\n \"name\": \"vessel\"\n }\n },\n \"terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"container\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container\"\n }\n }\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_full_out_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -598,7 +598,7 @@ } }, { - "id": "be4e9853-afd0-43ef-99cf-223dd8659782", + "id": "96588ce3-2dff-4409-9897-325195e4c210", "name": "Get container map GeoJSON", "request": { "name": "Get container map GeoJSON", @@ -641,7 +641,7 @@ }, "response": [ { - "id": "f6b9fe71-f4f8-4eaf-aa74-d0225a5ab72c", + "id": "62441421-5b91-460b-ac31-f4bd7d22d440", "name": "OK", "originalRequest": { "url": { @@ -697,7 +697,7 @@ "_postman_previewlanguage": "json" }, { - "id": "35334064-6395-44cc-9eca-00e75565b454", + "id": "91811203-7bb6-4f25-97a0-9cb50946f403", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -759,7 +759,7 @@ } }, { - "id": "078fb90b-ce8f-4119-a7eb-4a3300dcfde2", + "id": "a8b64f37-cd7f-406c-8d8c-3616a4ab4161", "name": "Refresh container", "request": { "name": "Refresh container", @@ -802,7 +802,7 @@ }, "response": [ { - "id": "a012fa83-7e0d-4de4-815b-1956f8939c57", + "id": "5ce11d6f-ee0a-415a-8950-e764c27a5f9c", "name": "OK", "originalRequest": { "url": { @@ -858,7 +858,7 @@ "_postman_previewlanguage": "json" }, { - "id": "b6de4bbd-eda3-4fe0-89d3-0fb793d95a51", + "id": "c42486cb-e517-42f4-99ac-cac2069b913f", "name": "Forbidden - This API endpoint is not enabled for your account. Please contact support@terminal49.com", "originalRequest": { "url": { @@ -914,7 +914,7 @@ "_postman_previewlanguage": "json" }, { - "id": "6ca86128-048c-4621-9a5d-20197e8d4d6a", + "id": "c42eee17-d5d4-4761-a13d-1dd6c92ab8bd", "name": "Too Many Requests - You've hit the refresh limit. Please try again in a minute.", "originalRequest": { "url": { @@ -985,7 +985,7 @@ } }, { - "id": "07d5a71c-ade3-4c04-9839-d6b68dc5076a", + "id": "c828f265-d93c-48e4-a8a9-a8f438e82473", "name": "List container custom fields", "request": { "name": "List container custom fields", @@ -1025,7 +1025,7 @@ }, "response": [ { - "id": "41ea7f04-7be0-4c72-af5d-821bcda35d98", + "id": "004b7780-cbe1-4845-94f1-e3decbc821f3", "name": "OK", "originalRequest": { "url": { @@ -1076,7 +1076,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1087,7 +1087,7 @@ } }, { - "id": "7b23b34a-51f4-472d-bff2-436c4a4012fe", + "id": "782ff955-d861-4736-909f-d9dcf02b046f", "name": "Create a container custom field", "request": { "name": "Create a container custom field", @@ -1140,7 +1140,7 @@ }, "response": [ { - "id": "3a182bfe-ca6a-4210-ae00-13f0d0410ecb", + "id": "67f2e677-c1c9-4fcb-bfa9-6a96ce88ad98", "name": "Created", "originalRequest": { "url": { @@ -1204,7 +1204,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1215,7 +1215,7 @@ } }, { - "id": "1fe1f275-9c93-4ba6-810a-f659bbb024f7", + "id": "bddd9015-712b-4842-8b3b-bb9f2a8be3be", "name": "Update a container custom field", "request": { "name": "Update a container custom field", @@ -1279,7 +1279,7 @@ }, "response": [ { - "id": "6387c432-aa2b-473b-ab5b-0e0d6706f85e", + "id": "2b8f6ef8-dff3-4dfe-90ca-576ce1dcaf88", "name": "OK", "originalRequest": { "url": { @@ -1354,7 +1354,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1365,7 +1365,7 @@ } }, { - "id": "a65f63ac-119b-47b2-ba1c-b6a9ec631c87", + "id": "d8613d18-680b-4a9c-b382-cfb876279c57", "name": "Delete a container custom field", "request": { "name": "Delete a container custom field", @@ -1410,7 +1410,7 @@ }, "response": [ { - "id": "932e36d7-8e0f-48ec-89b5-531de87ab9f1", + "id": "dae86683-b4e0-4855-b3c4-260dcf804a79", "name": "No Content", "originalRequest": { "url": { @@ -1479,7 +1479,7 @@ "description": "", "item": [ { - "id": "28c4cee2-b25e-4424-9710-347385d6a651", + "id": "91df0fa7-f7de-48ad-a972-3aa59e6ea99f", "name": "List custom field definitions", "request": { "name": "List custom field definitions", @@ -1552,7 +1552,7 @@ }, "response": [ { - "id": "4ea0b553-981b-401c-bfab-cc6999a3e2f5", + "id": "23f77d9a-0a2f-4f7a-b71a-0be9f0dd5642", "name": "OK", "originalRequest": { "url": { @@ -1636,7 +1636,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"date\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 4054,\n \"key_1\": \"string\"\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Cargo\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"datetime\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": \"string\",\n \"key_1\": false\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"enum\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 258\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Cargo\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"enum\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 2408.2075859737097,\n \"key_1\": \"string\",\n \"key_2\": \"string\",\n \"key_3\": 3850.5712469206755\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1647,7 +1647,7 @@ } }, { - "id": "d16bc79f-9580-4052-b32a-ac2884300f0f", + "id": "15d1a8c5-0c36-43d1-8ad8-930de728b378", "name": "Create a custom field definition", "request": { "name": "Create a custom field definition", @@ -1675,7 +1675,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"enum_multi\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 3785,\n \"key_1\": 380,\n \"key_2\": 7300.366545327756\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"datetime\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 621.1285794527143,\n \"key_1\": false\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1687,7 +1687,7 @@ }, "response": [ { - "id": "eaf2b46e-cd83-4a4b-b721-b1320394d74c", + "id": "fc29726b-ca46-4813-8a50-980cbe303e50", "name": "Created", "originalRequest": { "url": { @@ -1721,7 +1721,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"enum_multi\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 3785,\n \"key_1\": 380,\n \"key_2\": 7300.366545327756\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"datetime\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 621.1285794527143,\n \"key_1\": false\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1738,7 +1738,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"boolean\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": \"string\",\n \"key_1\": \"string\"\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Cargo\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"short_text\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 6507,\n \"key_1\": 5816.59237687245\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1749,7 +1749,7 @@ } }, { - "id": "804a2971-8fc7-4176-99db-4818a5afd0f9", + "id": "a467068c-3ef5-4c90-841f-b4607a7fca7e", "name": "Get a custom field definition", "request": { "name": "Get a custom field definition", @@ -1788,7 +1788,7 @@ }, "response": [ { - "id": "6b51954a-4ea9-4637-9712-d42cb1c91b3e", + "id": "5c283ea4-d73d-4431-b11b-5b0b8bc70a21", "name": "OK", "originalRequest": { "url": { @@ -1838,7 +1838,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"boolean\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": \"string\",\n \"key_1\": \"string\"\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Cargo\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"short_text\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 6507,\n \"key_1\": 5816.59237687245\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1849,7 +1849,7 @@ } }, { - "id": "5e91a316-1cc4-4040-9065-abb51410bd84", + "id": "fb2ed60c-d4ba-4f1e-8c3a-e8ca4a890c42", "name": "Update a custom field definition", "request": { "name": "Update a custom field definition", @@ -1889,7 +1889,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"display_name\": \"\",\n \"description\": \"\",\n \"validation\": {\n \"key_0\": false,\n \"key_1\": 1517,\n \"key_2\": 6646.7294108373\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"display_name\": \"\",\n \"description\": \"\",\n \"validation\": {\n \"key_0\": 188.04679581558847,\n \"key_1\": \"string\",\n \"key_2\": 6818.762735740147\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1901,7 +1901,7 @@ }, "response": [ { - "id": "caa7e066-c2ff-468c-81e3-1fcc67be8263", + "id": "baa2f41e-a26d-4972-8967-055234fe7881", "name": "OK", "originalRequest": { "url": { @@ -1947,7 +1947,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"display_name\": \"\",\n \"description\": \"\",\n \"validation\": {\n \"key_0\": false,\n \"key_1\": 1517,\n \"key_2\": 6646.7294108373\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"display_name\": \"\",\n \"description\": \"\",\n \"validation\": {\n \"key_0\": 188.04679581558847,\n \"key_1\": \"string\",\n \"key_2\": 6818.762735740147\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -1964,7 +1964,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Shipment\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"boolean\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": \"string\",\n \"key_1\": \"string\"\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field_definition\",\n \"attributes\": {\n \"entity_type\": \"Cargo\",\n \"api_slug\": \"\",\n \"display_name\": \"\",\n \"data_type\": \"short_text\",\n \"description\": \"\",\n \"reference_type\": \"\",\n \"validation\": {\n \"key_0\": 6507,\n \"key_1\": 5816.59237687245\n },\n \"default_format\": \"\",\n \"default_value\": \"\"\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -1975,7 +1975,7 @@ } }, { - "id": "2e7975fd-d55b-4a85-b5a2-354992dd9060", + "id": "929b0f59-45ac-424b-bd96-faf3ead23ff6", "name": "Delete a custom field definition", "request": { "name": "Delete a custom field definition", @@ -2008,7 +2008,7 @@ }, "response": [ { - "id": "4e4a4ae0-e80e-4396-bf7a-f697deec640b", + "id": "bacf8402-e061-473a-89c2-e660cb543ba3", "name": "OK", "originalRequest": { "url": { @@ -2065,7 +2065,7 @@ "description": "", "item": [ { - "id": "88ffe44d-96f3-4266-b257-3d55bc90af78", + "id": "6723fc22-e4e1-41c1-8962-54739f3e733b", "name": "List custom field options", "request": { "name": "List custom field options", @@ -2105,7 +2105,7 @@ }, "response": [ { - "id": "1006d7cd-ec3c-4936-b0f7-f402c56d8751", + "id": "aa58526e-12fa-4db4-813e-7fd33dd5f14b", "name": "OK", "originalRequest": { "url": { @@ -2167,7 +2167,7 @@ } }, { - "id": "5a5682b2-c334-48ac-be28-c9a7e759832f", + "id": "5492cd86-b763-43a2-9842-d3095f8247e8", "name": "Create a custom field option", "request": { "name": "Create a custom field option", @@ -2220,7 +2220,7 @@ }, "response": [ { - "id": "2010d586-2eec-4266-9404-59fb6b6ca295", + "id": "27a5f9a7-101b-49e9-812d-66d42f62276a", "name": "Created", "originalRequest": { "url": { @@ -2295,7 +2295,7 @@ } }, { - "id": "12a6b670-6a37-43c9-a2d9-2e3adae2c45a", + "id": "bb9fd201-602a-4bb9-8611-30ccbcf12b26", "name": "Get a custom field option", "request": { "name": "Get a custom field option", @@ -2346,7 +2346,7 @@ }, "response": [ { - "id": "96f9e7be-59c2-409b-8a56-03136bc3428d", + "id": "38f1e3e1-2de1-4c48-b9e2-52f8ca02091e", "name": "OK", "originalRequest": { "url": { @@ -2419,7 +2419,7 @@ } }, { - "id": "a94d0769-14c9-40ae-869b-6208f776161f", + "id": "c6235bbb-015b-4996-b11e-f80149975489", "name": "Update a custom field option", "request": { "name": "Update a custom field option", @@ -2483,7 +2483,7 @@ }, "response": [ { - "id": "bcd7f861-08e2-4a23-b3f7-d0024a75682f", + "id": "99a64e53-e823-485c-8715-fd60a7df5120", "name": "OK", "originalRequest": { "url": { @@ -2569,7 +2569,7 @@ } }, { - "id": "7eddffe0-39bd-4f70-b162-03296051e28f", + "id": "11418832-08d8-4e9d-843f-32c8b85fd6e2", "name": "Delete a custom field option", "request": { "name": "Delete a custom field option", @@ -2614,7 +2614,7 @@ }, "response": [ { - "id": "54028f4a-7a39-4df5-ae82-dd30d7566136", + "id": "dabbf850-93db-4717-8cc6-d0b4c65b6bf1", "name": "OK", "originalRequest": { "url": { @@ -2683,7 +2683,7 @@ "description": "", "item": [ { - "id": "3e76b772-66b9-4cf9-8359-de15829c0b09", + "id": "4ae0e044-a52f-4709-97e8-422f63bd667c", "name": "List custom fields", "request": { "name": "List custom fields", @@ -2756,7 +2756,7 @@ }, "response": [ { - "id": "3f1fe3da-d268-4dba-a3d8-c74806a33fde", + "id": "f30ca9b3-404b-4505-a39a-cc2bdf7fc678", "name": "OK", "originalRequest": { "url": { @@ -2840,7 +2840,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -2851,7 +2851,7 @@ } }, { - "id": "14c010d3-71e6-439f-ab90-a32f7e252efc", + "id": "a2828ffa-c7b1-4f21-91d9-921779c9455a", "name": "Create a custom field", "request": { "name": "Create a custom field", @@ -2879,7 +2879,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -2891,7 +2891,7 @@ }, "response": [ { - "id": "5c126294-7f59-4e5d-b85d-985852a051a9", + "id": "1a1337bd-5c6c-4802-a3d7-d63206d9577a", "name": "Created", "originalRequest": { "url": { @@ -2925,7 +2925,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -2942,7 +2942,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -2953,7 +2953,7 @@ } }, { - "id": "4a801d95-4c0a-4874-9c0e-c809215c5ee6", + "id": "73f7eeea-3a22-4ef3-8fff-adc88329452d", "name": "Get a custom field", "request": { "name": "Get a custom field", @@ -2992,7 +2992,7 @@ }, "response": [ { - "id": "9d536e5b-e86e-4fe9-b08a-252e32105546", + "id": "626475d6-f469-4c5d-ba0d-491acdd77f11", "name": "OK", "originalRequest": { "url": { @@ -3042,7 +3042,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -3053,7 +3053,7 @@ } }, { - "id": "63218863-4b37-4d34-8104-bc0ce1ab759d", + "id": "ee87ab3b-e04d-4bea-9967-08d087337d9d", "name": "Update a custom field", "request": { "name": "Update a custom field", @@ -3105,7 +3105,7 @@ }, "response": [ { - "id": "346cd47b-0384-418d-978c-b8fc762deb0a", + "id": "cb9f395f-fa70-4d44-8f61-40bc7c5fef39", "name": "OK", "originalRequest": { "url": { @@ -3168,7 +3168,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -3179,7 +3179,7 @@ } }, { - "id": "8e7819f2-192e-47a2-b266-88a8edcc1b63", + "id": "1ca0c939-96ff-4a8d-9b0c-66798e1139cc", "name": "Delete a custom field", "request": { "name": "Delete a custom field", @@ -3212,7 +3212,7 @@ }, "response": [ { - "id": "5e57358e-bb11-4b87-8a37-3a1484d5f0d0", + "id": "7c5d2d88-657b-4ab2-8e2c-fff5c7a515cd", "name": "OK", "originalRequest": { "url": { @@ -3269,7 +3269,7 @@ "description": "", "item": [ { - "id": "e9b0abaa-3443-4e10-be14-a822d9566447", + "id": "949e44ec-a0ff-4c82-b0c2-89fc22be06a8", "name": "List shipments", "request": { "name": "List shipments", @@ -3354,7 +3354,7 @@ }, "response": [ { - "id": "5fb4222e-ca6f-4805-86c8-70f1107b9f40", + "id": "4c9c89ea-2c40-457b-a0d9-6d62e22b4b20", "name": "OK", "originalRequest": { "url": { @@ -3447,12 +3447,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": null\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"flat rack\",\n \"equipment_length\": 45,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"delivered\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": null,\n \"equipment_length\": 10,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"picked_up\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": null\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n ],\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": null,\n \"equipment_length\": 45,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"dropped\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"dry\",\n \"equipment_length\": 45,\n \"equipment_height\": \"high_cube\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"empty_returned\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "9d50a09c-8016-4931-b105-e3dfd5628f27", + "id": "66b677a0-eceb-404d-8874-9fc614f44e7c", "name": "Unprocessable Entity", "originalRequest": { "url": { @@ -3556,7 +3556,7 @@ } }, { - "id": "81cf89f7-c6a3-435f-8d34-308d285a5937", + "id": "3292d673-ad24-47a3-b057-3f0553f24b7f", "name": "Get a shipment", "request": { "name": "Get a shipment", @@ -3608,7 +3608,7 @@ }, "response": [ { - "id": "503963a0-0348-44be-abb4-e2d17101746c", + "id": "e25aa9a5-6faa-48c4-b9e6-287af83c27bf", "name": "OK", "originalRequest": { "url": { @@ -3668,12 +3668,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"cancelled_by_user\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"rail_terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"open top\",\n \"equipment_length\": 45,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"other\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"extended_dwell_time\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"on_ship\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"tank\",\n \"equipment_length\": 20,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"demurrage\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"total\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"delivered\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ]\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"past_full_out_window\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": \"tank\",\n \"equipment_length\": 10,\n \"equipment_height\": null,\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"extended_dwell_time\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"awaiting_inland_transfer\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"equipment_type\": null,\n \"equipment_length\": 20,\n \"equipment_height\": \"standard\",\n \"weight_in_lbs\": \"\",\n \"created_at\": \"\",\n \"seal_number\": \"\",\n \"pickup_lfd\": \"\",\n \"pickup_appointment_at\": \"\",\n \"availability_known\": \"\",\n \"available_for_pickup\": \"\",\n \"pod_arrived_at\": \"\",\n \"pod_discharged_at\": \"\",\n \"pod_full_out_at\": \"\",\n \"terminal_checked_at\": \"\",\n \"pod_full_out_chassis_number\": \"\",\n \"location_at_pod_terminal\": \"\",\n \"final_destination_full_out_at\": \"\",\n \"empty_terminated_at\": \"\",\n \"holds_at_pod_terminal\": [\n {\n \"name\": \"\",\n \"status\": \"hold\",\n \"description\": \"\"\n },\n {\n \"name\": \"\",\n \"status\": \"pending\",\n \"description\": \"\"\n }\n ],\n \"fees_at_pod_terminal\": [\n {\n \"type\": \"exam\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n },\n {\n \"type\": \"extended_dwell_time\",\n \"amount\": \"\",\n \"currency_code\": \"\"\n }\n ],\n \"pod_timezone\": \"\",\n \"final_destination_timezone\": \"\",\n \"empty_terminated_timezone\": \"\",\n \"pod_rail_carrier_scac\": \"\",\n \"ind_rail_carrier_scac\": \"\",\n \"pod_last_tracking_request_at\": \"\",\n \"shipment_last_tracking_request_at\": \"\",\n \"pod_rail_loaded_at\": \"\",\n \"pod_rail_departed_at\": \"\",\n \"ind_eta_at\": \"\",\n \"ind_ata_at\": \"\",\n \"ind_rail_unloaded_at\": \"\",\n \"ind_facility_lfd_on\": \"\",\n \"import_deadlines\": {\n \"pickup_lfd_terminal\": \"\",\n \"pickup_lfd_rail\": \"\",\n \"pickup_lfd_line\": \"\"\n },\n \"current_status\": \"grounded\"\n },\n \"relationships\": {\n \"shipment\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"pickup_facility\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"terminal\"\n }\n },\n \"transport_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"transport_event\"\n }\n ]\n },\n \"raw_events\": {\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n },\n {\n \"id\": \"\",\n \"type\": \"raw_event\"\n }\n ]\n }\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "3163b717-8d3f-4635-a6cf-37762e1f1f10", + "id": "331e1714-845d-446e-a836-a35397b5675e", "name": "Not Found", "originalRequest": { "url": { @@ -3744,7 +3744,7 @@ } }, { - "id": "8e3255f8-cd99-45f3-a706-0f7ef9000025", + "id": "db292a89-430d-40c6-8a41-5e088c77ed52", "name": "Edit a shipment", "request": { "name": "Edit a shipment", @@ -3799,7 +3799,7 @@ }, "response": [ { - "id": "c0b8a34f-efe7-40ec-b919-a0ec8f11d8b6", + "id": "28e897cb-9294-4f53-a140-f547e74ad6f5", "name": "OK", "originalRequest": { "url": { @@ -3862,7 +3862,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -3873,7 +3873,7 @@ } }, { - "id": "ad085c80-df86-420a-add6-518114d2c506", + "id": "9749c24f-6667-4c2f-8f58-ec496a060ee0", "name": "Stop tracking a shipment", "request": { "name": "Stop tracking a shipment", @@ -3916,7 +3916,7 @@ }, "response": [ { - "id": "83d1e1fb-8464-419e-b896-a8c1a7044a3a", + "id": "68bbfbce-8d24-4b40-b2c5-ffa53b657749", "name": "OK", "originalRequest": { "url": { @@ -3967,7 +3967,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -3978,7 +3978,7 @@ } }, { - "id": "3f2e56c7-2c20-4fdd-b369-de7d258d8846", + "id": "a030a7a7-6e37-4ee8-9257-8231c06eba97", "name": "Resume tracking a shipment", "request": { "name": "Resume tracking a shipment", @@ -4021,7 +4021,7 @@ }, "response": [ { - "id": "b5b88ff9-2a89-476f-a066-2a9ce025d319", + "id": "7b3be2ae-48ec-4aec-8239-2afa6ee3e43c", "name": "OK", "originalRequest": { "url": { @@ -4072,7 +4072,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"all_containers_terminated\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\",\n \"attributes\": {\n \"bill_of_lading_number\": \"\",\n \"normalized_number\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"created_at\": \"\",\n \"tags\": [\n \"\",\n \"\"\n ],\n \"port_of_lading_locode\": \"\",\n \"port_of_lading_name\": \"\",\n \"port_of_discharge_locode\": \"\",\n \"port_of_discharge_name\": \"\",\n \"destination_locode\": \"\",\n \"destination_name\": \"\",\n \"shipping_line_scac\": \"\",\n \"shipping_line_name\": \"\",\n \"shipping_line_short_name\": \"\",\n \"customer_name\": \"\",\n \"pod_vessel_name\": \"\",\n \"pod_vessel_imo\": \"\",\n \"pod_voyage_number\": \"\",\n \"pol_etd_at\": \"\",\n \"pol_atd_at\": \"\",\n \"pod_eta_at\": \"\",\n \"pod_original_eta_at\": \"\",\n \"pod_ata_at\": \"\",\n \"destination_eta_at\": \"\",\n \"destination_ata_at\": \"\",\n \"pol_timezone\": \"\",\n \"pod_timezone\": \"\",\n \"destination_timezone\": \"\",\n \"line_tracking_last_attempted_at\": \"\",\n \"line_tracking_last_succeeded_at\": \"\",\n \"line_tracking_stopped_at\": \"\",\n \"line_tracking_stopped_reason\": \"no_updates_at_line\"\n },\n \"relationships\": {\n \"destination\": {\n \"data\": {\n \"type\": \"metro_area\",\n \"id\": \"\"\n }\n },\n \"port_of_lading\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"containers\": {\n \"data\": [\n {\n \"type\": \"container\",\n \"id\": \"\"\n },\n {\n \"type\": \"container\",\n \"id\": \"\"\n }\n ]\n },\n \"port_of_discharge\": {\n \"data\": {\n \"type\": \"port\",\n \"id\": \"\"\n }\n },\n \"pod_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"destination_terminal\": {\n \"data\": {\n \"type\": \"terminal\",\n \"id\": \"\"\n }\n },\n \"line_tracking_stopped_by_user\": {\n \"data\": {\n \"type\": \"user\",\n \"id\": \"\"\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -4083,7 +4083,7 @@ } }, { - "id": "e0590ec5-adda-4b04-bc7a-94077579b35e", + "id": "ffde224b-8540-4a7a-bc62-ffedaf55c483", "name": "List shipment custom fields", "request": { "name": "List shipment custom fields", @@ -4123,7 +4123,7 @@ }, "response": [ { - "id": "d54afbe8-c48a-42f8-8471-1879794883c6", + "id": "3cd1ab3f-f59e-4cc2-a73b-1485c58f20f6", "name": "OK", "originalRequest": { "url": { @@ -4174,7 +4174,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -4185,7 +4185,7 @@ } }, { - "id": "5a3797fd-e91c-4867-9888-6342c31e955e", + "id": "29790f54-9609-4ca0-b1d7-77ad329227da", "name": "Create a shipment custom field", "request": { "name": "Create a shipment custom field", @@ -4238,7 +4238,7 @@ }, "response": [ { - "id": "abe4b1e7-7e52-4166-97f4-8e66833eb599", + "id": "eff42e0e-52a2-4b6f-ad95-66ca7510d3a9", "name": "Created", "originalRequest": { "url": { @@ -4302,7 +4302,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -4313,7 +4313,7 @@ } }, { - "id": "80865299-1944-4948-92b9-7e9daa325904", + "id": "f4d824f6-fd6c-45dc-ac40-f3e84bbe6515", "name": "Update a shipment custom field", "request": { "name": "Update a shipment custom field", @@ -4377,7 +4377,7 @@ }, "response": [ { - "id": "361ef2a3-0bfe-4bbe-98e7-5d2452d27933", + "id": "f654352a-b195-488d-82ee-142ee240bc2d", "name": "OK", "originalRequest": { "url": { @@ -4452,7 +4452,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"shipment\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"custom_field\",\n \"attributes\": {\n \"api_slug\": \"\",\n \"value\": \"\",\n \"display_value\": \"\"\n },\n \"relationships\": {\n \"entity\": {\n \"data\": {\n \"type\": \"container\",\n \"id\": \"\"\n }\n },\n \"definition\": {\n \"data\": {\n \"type\": \"custom_field_definition\",\n \"id\": \"\"\n }\n }\n }\n },\n \"links\": {\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -4463,7 +4463,7 @@ } }, { - "id": "78944bc6-babe-4d3e-9a32-7843b6ed5898", + "id": "de85c4cc-fbe5-450f-973f-bf2c2e159998", "name": "Delete a shipment custom field", "request": { "name": "Delete a shipment custom field", @@ -4508,7 +4508,7 @@ }, "response": [ { - "id": "4f5926a8-3e8e-4a45-ac01-0779521436c4", + "id": "c2bd89eb-b773-4d88-b09d-bead8d4fbfbf", "name": "No Content", "originalRequest": { "url": { @@ -4587,7 +4587,7 @@ "description": "", "item": [ { - "id": "8e55f125-94d7-44a8-850a-d0f5d83f9e27", + "id": "892f4271-19ea-46fa-813a-dc86cff87c0e", "name": "Create a tracking request", "request": { "name": "Create a tracking request", @@ -4618,7 +4618,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"booking_number\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"bill_of_lading\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -4646,7 +4646,7 @@ }, "response": [ { - "id": "c664f7d8-1401-4d7a-addd-98965a238cff", + "id": "8e25ca31-6dc5-4d67-aff6-94ac465357ed", "name": "Tracking Request Created", "originalRequest": { "url": { @@ -4680,7 +4680,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"booking_number\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"bill_of_lading\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -4697,12 +4697,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"failed\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": null,\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"failed\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"expired\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "93f31620-fc8f-4dc6-aedb-9f5287f4e551", + "id": "6fc9b7cc-2c27-42cb-bbd6-6557f6b68de4", "name": "Unprocessable Entity", "originalRequest": { "url": { @@ -4736,7 +4736,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"booking_number\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"bill_of_lading\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -4758,7 +4758,7 @@ "_postman_previewlanguage": "json" }, { - "id": "86383013-daf3-4f19-8287-20bf12a0ade3", + "id": "bfad860a-9d5c-4bab-9e57-a5de2008f6f4", "name": "Too Many Requests - You've hit the create tracking requests limit. Please try again in a minute.", "originalRequest": { "url": { @@ -4792,7 +4792,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"booking_number\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", + "raw": "{\n \"data\": {\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_type\": \"bill_of_lading\",\n \"request_number\": \"\",\n \"scac\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"shipment_tags\": [\n \"\",\n \"\"\n ]\n },\n \"relationships\": {\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -4829,7 +4829,7 @@ } }, { - "id": "e7ac5e33-ace4-4339-b0c7-be02cba0d9e3", + "id": "49907f04-051c-4555-987c-30508579b768", "name": "List tracking requests", "request": { "name": "List tracking requests", @@ -4870,7 +4870,7 @@ "type": "text/plain" }, "key": "filter[status]", - "value": "failed" + "value": "created" }, { "disabled": false, @@ -4959,7 +4959,7 @@ }, "response": [ { - "id": "746a078d-ebbb-4044-afad-46bfc50378cb", + "id": "74a91800-ecb6-4a4f-b472-6ca3a06b7901", "name": "OK", "originalRequest": { "url": { @@ -4995,7 +4995,7 @@ "type": "text/plain" }, "key": "filter[status]", - "value": "failed" + "value": "created" }, { "disabled": false, @@ -5097,12 +5097,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"pending\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"data_unavailable\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"failed\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"internal_processing_error\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"awaiting_manifest\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"booking_cancelled\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"awaiting_manifest\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"retries_exhausted\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "2056ec1c-1173-4a5f-a05c-3514371fa2ca", + "id": "cd469fe0-d624-4c2f-8186-9ba13ddd80ef", "name": "Not Found", "originalRequest": { "url": { @@ -5138,7 +5138,7 @@ "type": "text/plain" }, "key": "filter[status]", - "value": "failed" + "value": "created" }, { "disabled": false, @@ -5251,7 +5251,7 @@ } }, { - "id": "c8988774-38b3-4838-a252-0e6d1cdf00f3", + "id": "3e5d7515-e30e-49d5-98d2-abb52c689014", "name": "Infer Tracking Number", "request": { "name": "Infer Tracking Number", @@ -5311,7 +5311,7 @@ }, "response": [ { - "id": "4ca1fad7-4f76-4b00-b48b-89aa8fe86761", + "id": "889bd5ce-1bc8-4104-8f47-3d0aa3b91a28", "name": "Successfully inferred number type and shipping line", "originalRequest": { "url": { @@ -5363,12 +5363,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"\",\n \"attributes\": {\n \"number_type\": \"bill_of_lading\",\n \"validation\": {\n \"is_valid\": \"\",\n \"type\": \"container\",\n \"check_digit_passed\": \"\",\n \"parsed_number\": \"\",\n \"reason\": \"\"\n },\n \"shipping_line\": {\n \"decision\": \"auto_select\",\n \"selected\": {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n },\n \"candidates\": [\n {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n },\n {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n }\n ]\n }\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"\",\n \"attributes\": {\n \"number_type\": \"container\",\n \"validation\": {\n \"is_valid\": \"\",\n \"type\": \"shipment\",\n \"check_digit_passed\": \"\",\n \"parsed_number\": \"\",\n \"reason\": \"\"\n },\n \"shipping_line\": {\n \"decision\": \"no_prediction\",\n \"selected\": {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n },\n \"candidates\": [\n {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n },\n {\n \"scac\": \"\",\n \"name\": \"\",\n \"confidence\": \"\"\n }\n ]\n }\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "880a0c07-2622-4b0e-9b69-f4e6aba5c60c", + "id": "ee8b20ca-7300-438d-98f5-b25c9cb88705", "name": "Unprocessable Entity - Invalid tracking number format", "originalRequest": { "url": { @@ -5425,7 +5425,7 @@ "_postman_previewlanguage": "json" }, { - "id": "6f17f080-7bfe-4d25-854c-9f17d8ed70e8", + "id": "e8033482-0f48-4dbb-a5ec-76abfa990767", "name": "Too Many Requests - Rate limit exceeded", "originalRequest": { "url": { @@ -5497,7 +5497,7 @@ } }, { - "id": "cf6fcf28-476a-424a-9ed4-449c77c7ae0e", + "id": "5d5ad233-9e78-4896-87a7-0a18966dfa9b", "name": "Get a single tracking request", "request": { "name": "Get a single tracking request", @@ -5549,7 +5549,7 @@ }, "response": [ { - "id": "5b1aad3c-7503-4b03-81d2-09c56d43f5af", + "id": "7497d078-a3a4-4f0b-8eda-6a94e9fd322c", "name": "OK", "originalRequest": { "url": { @@ -5609,12 +5609,12 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"failed\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": null,\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"failed\",\n \"request_type\": \"booking_number\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"expired\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n },\n {\n \"id\": \"\",\n \"type\": \"container\",\n \"attributes\": {\n \"company_name\": \"\"\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" }, { - "id": "c27d3c55-d2e8-4277-8a54-76f0eeb8f779", + "id": "681756a7-a382-4394-a58f-3adecb3d5564", "name": "Not Found", "originalRequest": { "url": { @@ -5685,7 +5685,7 @@ } }, { - "id": "57d97e8e-3aaa-4601-91f4-9548e9bb04db", + "id": "76d3a4d7-720a-4960-82ed-78c14d30f55c", "name": "Edit a tracking request", "request": { "name": "Edit a tracking request", @@ -5728,7 +5728,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 9105.29061030703\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 5304\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -5740,7 +5740,7 @@ }, "response": [ { - "id": "2408ca2b-6956-4b27-a68c-b2dc103458fb", + "id": "2f70f3cd-e2ed-47f7-b0a5-db19d6785eb2", "name": "OK", "originalRequest": { "url": { @@ -5786,7 +5786,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 9105.29061030703\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"ref_number\": \"\"\n },\n \"type\": 5304\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -5803,7 +5803,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"awaiting_manifest\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"shipping_line_unreachable\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\",\n \"attributes\": {\n \"request_number\": \"\",\n \"status\": \"awaiting_manifest\",\n \"request_type\": \"bill_of_lading\",\n \"scac\": \"\",\n \"created_at\": \"\",\n \"ref_numbers\": [\n \"\",\n \"\"\n ],\n \"tags\": [\n \"\",\n \"\"\n ],\n \"failed_reason\": \"internal_processing_error\",\n \"updated_at\": \"\",\n \"is_retrying\": \"\",\n \"retry_count\": \"\"\n },\n \"relationships\": {\n \"tracked_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"shipment\"\n }\n },\n \"customer\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"party\"\n }\n }\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -5820,7 +5820,7 @@ "description": "", "item": [ { - "id": "bf3ba81c-8879-471b-b2d2-9486451f7626", + "id": "f67dfe79-fc1b-4c08-8d1b-e28f5d7000e8", "name": "Get single webhook", "request": { "name": "Get single webhook", @@ -5862,7 +5862,7 @@ }, "response": [ { - "id": "2f318a7b-c068-483d-b3de-d1ef5964073e", + "id": "ea6f073e-c788-4907-869c-e8b6975b6682", "name": "OK", "originalRequest": { "url": { @@ -5912,7 +5912,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.available\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_discharged\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -5923,7 +5923,7 @@ } }, { - "id": "dca67f32-d7de-4914-97bb-78806f59ea85", + "id": "103dc31a-a51f-4aa7-99e9-81be3d370cbe", "name": "Edit a webhook", "request": { "name": "Edit a webhook", @@ -5966,7 +5966,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.vessel_berthed\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.estimated.arrived_at_inland_destination\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -5978,7 +5978,7 @@ }, "response": [ { - "id": "1ec5e990-e512-4ad6-9b60-9afa676f9e4d", + "id": "daaa4a77-29a8-4f21-8150-847050af59d5", "name": "OK", "originalRequest": { "url": { @@ -6024,7 +6024,7 @@ "method": "PATCH", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.vessel_berthed\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"events\": [\n \"container.transport.estimated.arrived_at_inland_destination\"\n ],\n \"active\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -6041,7 +6041,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.available\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_discharged\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6052,7 +6052,7 @@ } }, { - "id": "b4315a25-2266-4106-924d-a07013335619", + "id": "3f0386fe-053f-4674-9879-58d4aab7b622", "name": "Delete a webhook", "request": { "name": "Delete a webhook", @@ -6088,7 +6088,7 @@ }, "response": [ { - "id": "80add439-5fdd-4179-8655-0fa43624fda6", + "id": "c1ca6b9b-2584-4287-9745-ffadb37eeb93", "name": "OK", "originalRequest": { "url": { @@ -6139,7 +6139,7 @@ } }, { - "id": "92f1e0a2-fe5d-476e-895f-a35d0c956098", + "id": "b27f005b-4d1b-4331-98f8-521e0432b3d8", "name": "List webhooks", "request": { "name": "List webhooks", @@ -6188,7 +6188,7 @@ }, "response": [ { - "id": "219ac272-6a0b-43ea-a41a-5759739ec517", + "id": "88279782-591a-4103-871f-bf021377e945", "name": "OK", "originalRequest": { "url": { @@ -6245,7 +6245,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.transshipment_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.pod_terminal_changed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ],\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n }\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_loaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.transshipment_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ],\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6256,7 +6256,7 @@ } }, { - "id": "6eb29744-b165-4fb8-b46a-6f07c9e63fc0", + "id": "b1f6f80e-2feb-4341-9c8c-9bcb6217bfaf", "name": "Create a webhook", "request": { "name": "Create a webhook", @@ -6287,7 +6287,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.vessel_berthed\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -6299,7 +6299,7 @@ }, "response": [ { - "id": "e375efea-8a95-4646-816c-ab55fe1ab7a0", + "id": "24e7a469-a183-46af-adf1-fd071a230beb", "name": "Create a test webhook endpoint", "originalRequest": { "url": { @@ -6333,7 +6333,7 @@ "method": "POST", "body": { "mode": "raw", - "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.vessel_berthed\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", + "raw": "{\n \"data\": {\n \"attributes\": {\n \"url\": \"\",\n \"active\": \"\",\n \"events\": [\n \"container.transport.vessel_loaded\"\n ],\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n },\n \"type\": \"webhook\"\n }\n}", "options": { "raw": { "headerFamily": "json", @@ -6350,7 +6350,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.available\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_discharged\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6361,7 +6361,7 @@ } }, { - "id": "0f242d26-e69f-4846-bf55-855dabe58e25", + "id": "04664a1c-2b90-41fb-a5ec-af5c6a212a37", "name": "List webhook IPs", "request": { "name": "List webhook IPs", @@ -6392,7 +6392,7 @@ }, "response": [ { - "id": "b8b686cf-9e66-4d13-bc9d-162bf4d5c323", + "id": "80031252-8f53-4e25-9774-fc78a6e0173b", "name": "OK", "originalRequest": { "url": { @@ -6448,7 +6448,7 @@ "description": "", "item": [ { - "id": "d4156664-68a3-427d-b24e-17cd672f2f75", + "id": "5b529618-59f7-4a23-8ede-bac681885346", "name": "Get a single webhook notification", "request": { "name": "Get a single webhook notification", @@ -6500,7 +6500,7 @@ }, "response": [ { - "id": "c5c8616d-e002-415f-a78e-2a9a28af2f27", + "id": "819574e1-b518-4e6e-a83e-f4b4f22124e0", "name": "200", "originalRequest": { "url": { @@ -6560,7 +6560,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"tracking_request.succeeded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"tracking_request\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_unloaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.transshipment_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", + "body": "{\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.pod_terminal_changed\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container_updated_event\"\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.vessel_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_departed\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6571,7 +6571,7 @@ } }, { - "id": "7a14e9e4-89e8-47f3-ad76-c7621e25544c", + "id": "de5a7b4e-84d0-48dd-a42b-5b6e809c4acc", "name": "List webhook notifications", "request": { "name": "List webhook notifications", @@ -6629,7 +6629,7 @@ }, "response": [ { - "id": "7c989450-9f4e-40a8-bc9d-db0f2d41088d", + "id": "572cf95c-1f3b-4062-9bfc-d10a82d39c0d", "name": "OK", "originalRequest": { "url": { @@ -6695,7 +6695,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.full_out\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"estimated_event\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.vessel_loaded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"estimated_event\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_unloaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_arrived\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.transshipment_arrived\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container_updated_event\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.rail_unloaded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container_updated_event\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_discharged\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"shipment.estimated.arrival\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6706,7 +6706,7 @@ } }, { - "id": "5e590b08-0661-498f-b522-e67c8728145b", + "id": "205f267d-4e4a-4006-ab6e-f8dd12ea8ea5", "name": "Get webhook notification payload examples", "request": { "name": "Get webhook notification payload examples", @@ -6730,7 +6730,7 @@ "type": "text/plain" }, "key": "event", - "value": "container.transport.transshipment_discharged" + "value": "container.created" } ], "variable": [] @@ -6747,7 +6747,7 @@ }, "response": [ { - "id": "d337ded5-0bed-48b5-9759-3d988ce009f1", + "id": "5980944b-a9a4-4644-8ce3-25f7c7881b98", "name": "OK", "originalRequest": { "url": { @@ -6766,7 +6766,7 @@ "type": "text/plain" }, "key": "event", - "value": "container.transport.transshipment_discharged" + "value": "container.created" } ], "variable": [] @@ -6796,7 +6796,7 @@ "value": "application/json" } ], - "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.full_out\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"estimated_event\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.vessel_loaded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"estimated_event\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.rail_unloaded\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_arrived\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", + "body": "{\n \"data\": [\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.transshipment_arrived\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container_updated_event\"\n }\n }\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook_notification\",\n \"attributes\": {\n \"event\": \"container.transport.rail_unloaded\",\n \"delivery_status\": \"pending\",\n \"created_at\": \"\"\n },\n \"relationships\": {\n \"webhook\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"webhook\"\n }\n },\n \"reference_object\": {\n \"data\": {\n \"id\": \"\",\n \"type\": \"container_updated_event\"\n }\n }\n }\n }\n ],\n \"links\": {\n \"last\": \"\",\n \"next\": \"\",\n \"prev\": \"\",\n \"first\": \"\",\n \"self\": \"\"\n },\n \"meta\": {\n \"size\": \"\",\n \"total\": \"\"\n },\n \"included\": [\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"container.transport.feeder_discharged\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n },\n {\n \"id\": \"\",\n \"type\": \"webhook\",\n \"attributes\": {\n \"url\": \"\",\n \"active\": true,\n \"events\": [\n \"shipment.estimated.arrival\"\n ],\n \"secret\": \"\",\n \"headers\": [\n {\n \"name\": \"\",\n \"value\": \"\"\n },\n {\n \"name\": \"\",\n \"value\": \"\"\n }\n ]\n }\n }\n ]\n}", "cookie": [], "_postman_previewlanguage": "json" } @@ -6813,7 +6813,7 @@ "description": "", "item": [ { - "id": "da5bb141-d2de-4301-a91b-7dcd87759b0b", + "id": "3c327665-f114-429e-b676-86e51e2d83c9", "name": "Get a port using the locode or the id", "request": { "name": "Get a port using the locode or the id", @@ -6855,7 +6855,7 @@ }, "response": [ { - "id": "46cafbb5-20ac-4de6-9bf8-43c4101d7df7", + "id": "989b9604-6384-4dd8-ad18-547c79be6ba2", "name": "OK", "originalRequest": { "url": { @@ -6922,7 +6922,7 @@ "description": "", "item": [ { - "id": "84a1b107-19ed-4afa-99cd-932b79f9ef25", + "id": "970d1b6c-f6e2-4132-a553-f3ba6d2a0435", "name": "Get a metro area using the un/locode or the id", "request": { "name": "Get a metro area using the un/locode or the id", @@ -6964,7 +6964,7 @@ }, "response": [ { - "id": "75bfcbb8-be1d-4ae0-ad94-c09610044d83", + "id": "f5a496aa-7697-4801-bd41-8d8a486f3344", "name": "OK", "originalRequest": { "url": { @@ -7031,7 +7031,7 @@ "description": "", "item": [ { - "id": "392e29d7-43a9-46d5-87f2-74a84c766c53", + "id": "444ef4f5-d11d-4438-933a-ec330ec4aa0c", "name": "Get a terminal using the id", "request": { "name": "Get a terminal using the id", @@ -7073,7 +7073,7 @@ }, "response": [ { - "id": "8fca2763-c2c2-456e-8fcb-004de4fdf76c", + "id": "0b34175c-925e-428f-8ba4-370df73346e8", "name": "OK", "originalRequest": { "url": { @@ -7140,7 +7140,7 @@ "description": "", "item": [ { - "id": "bfc94a46-d1b0-4a70-a341-c9bdd9424b2c", + "id": "f981a96b-235b-471b-a493-a7f650ef8f06", "name": "Get container map GeoJSON", "request": { "name": "Get container map GeoJSON", @@ -7183,7 +7183,7 @@ }, "response": [ { - "id": "336224f9-088d-4e3d-8df3-15da620392c5", + "id": "b7e06822-5f46-4b77-90f7-a2762218ac3c", "name": "OK", "originalRequest": { "url": { @@ -7239,7 +7239,7 @@ "_postman_previewlanguage": "json" }, { - "id": "ff3bffca-9e6e-4544-bf15-3d792f33c6f9", + "id": "9a17dce3-e42c-4012-8531-dfcad5c83361", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -7301,7 +7301,7 @@ } }, { - "id": "4f5a19d4-0cfd-42b0-9d68-ce4b8561a644", + "id": "a2a41047-1537-4c7b-851b-96700c8015ae", "name": "Get vessel future positions", "request": { "name": "Get vessel future positions", @@ -7363,7 +7363,7 @@ }, "response": [ { - "id": "179713c9-42c9-4fb2-a5f9-27912d426201", + "id": "f3fa7120-e057-4084-8df4-5a8aadff3179", "name": "OK", "originalRequest": { "url": { @@ -7438,7 +7438,7 @@ "_postman_previewlanguage": "json" }, { - "id": "2014d0d0-9637-47ef-ad51-a8a0b7f119c9", + "id": "fc09b49d-34dc-4b12-97d9-ff3c66f0f015", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -7519,7 +7519,7 @@ } }, { - "id": "aefc0b77-095d-4533-a998-43cb281a7264", + "id": "67d02059-946a-47c3-b272-812e31358f64", "name": "Get vessel future positions from coordinates", "request": { "name": "Get vessel future positions from coordinates", @@ -7599,7 +7599,7 @@ }, "response": [ { - "id": "5b4394fd-ea17-41d6-8aa3-e5e2573d8c19", + "id": "206425bb-74b1-49c4-8400-0262f778a23e", "name": "OK", "originalRequest": { "url": { @@ -7692,7 +7692,7 @@ "_postman_previewlanguage": "json" }, { - "id": "adf7805e-ad4c-4f67-a6b3-7d9d102a2833", + "id": "ecf16961-143d-41e5-9366-5a63672bf34e", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -7797,7 +7797,7 @@ "description": "", "item": [ { - "id": "0ab21109-7b42-4f75-a815-32f06c2c7ee0", + "id": "e52728c5-06e3-4575-8276-702dec095f0b", "name": "Shipping Lines", "request": { "name": "Shipping Lines", @@ -7827,7 +7827,7 @@ }, "response": [ { - "id": "5194f356-4e95-4cf1-b42d-d665f03fb776", + "id": "dd5f8a8b-c1c9-4061-96c3-c030c1f3824b", "name": "OK", "originalRequest": { "url": { @@ -7876,7 +7876,7 @@ } }, { - "id": "68540428-1fe9-4791-83ed-6f5105502415", + "id": "e3087563-2af6-4c57-b307-4856ac9b4b14", "name": "Get a single shipping line", "request": { "name": "Get a single shipping line", @@ -7918,7 +7918,7 @@ }, "response": [ { - "id": "3e453e2b-35ab-4c19-957b-52d374aea5d1", + "id": "8a40a76a-304c-4a9f-ac3a-387d3e8b6840", "name": "OK", "originalRequest": { "url": { @@ -7985,7 +7985,7 @@ "description": "", "item": [ { - "id": "75420fd3-7cce-444c-8d72-54e2d9cd89dd", + "id": "5789b503-3587-4449-9093-3a0ccb5ef54d", "name": "Get a vessel using the id", "request": { "name": "Get a vessel using the id", @@ -8046,7 +8046,7 @@ }, "response": [ { - "id": "4bcb7389-1ad6-4116-9b9e-55443e35374a", + "id": "dd2cbab8-2846-4ed0-ac4b-c292ad2e9d79", "name": "OK", "originalRequest": { "url": { @@ -8120,7 +8120,7 @@ "_postman_previewlanguage": "json" }, { - "id": "1ec25e60-6e47-43f1-a47d-24b312b5e20a", + "id": "44d2b4e6-4277-4c28-b89e-3866903d9c04", "name": "Forbidden - Feature not enabled", "originalRequest": { "url": { @@ -8200,7 +8200,7 @@ } }, { - "id": "d50c9ba5-3e12-49ad-8e85-3d7d6cc368a1", + "id": "84adebb8-8601-44d3-babb-d9db5d55264e", "name": "Get a vessel using the imo", "request": { "name": "Get a vessel using the imo", @@ -8261,7 +8261,7 @@ }, "response": [ { - "id": "04fa5300-853f-44e9-9a3c-b31b96403b51", + "id": "a56e1121-209c-49a1-992d-1c30ecddd5ef", "name": "OK", "originalRequest": { "url": { @@ -8335,7 +8335,7 @@ "_postman_previewlanguage": "json" }, { - "id": "f634ecf6-2dfb-4f8b-a722-c1f8b32a3b5f", + "id": "70bfbea5-8f9f-4149-85d2-b8ea787123d4", "name": "Forbidden - Feature not enabled", "originalRequest": { "url": { @@ -8415,7 +8415,7 @@ } }, { - "id": "9b102c28-50cc-4e6f-9638-5f2821c4f005", + "id": "a982e069-2cac-4d37-8940-d9d3e6ac7d1e", "name": "Get vessel future positions", "request": { "name": "Get vessel future positions", @@ -8477,7 +8477,7 @@ }, "response": [ { - "id": "fac087a3-64a5-4bb7-b17d-799189abbcdf", + "id": "84efccff-ef79-40c3-9dea-e0e136d2d630", "name": "OK", "originalRequest": { "url": { @@ -8552,7 +8552,7 @@ "_postman_previewlanguage": "json" }, { - "id": "25d01e8a-29e8-457a-bb93-8fe8b6087fd7", + "id": "0bd3530b-84fe-4db4-a102-989689c1871c", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -8633,7 +8633,7 @@ } }, { - "id": "d3cc8bd0-d0b8-4b89-8116-b2da81fdf98f", + "id": "21e504e1-ab56-4828-8104-165d421cde27", "name": "Get vessel future positions from coordinates", "request": { "name": "Get vessel future positions from coordinates", @@ -8713,7 +8713,7 @@ }, "response": [ { - "id": "a2e91d75-7bc8-49ed-b0fd-75598fd6af4d", + "id": "62ef209d-f2cc-4434-84f7-654bf2dfef98", "name": "OK", "originalRequest": { "url": { @@ -8806,7 +8806,7 @@ "_postman_previewlanguage": "json" }, { - "id": "9c43a842-5382-40c5-a0dc-7e0ce60ee2d3", + "id": "3e7cf02e-7bd7-4645-ae43-39bdb10ffd0e", "name": "Forbidden - Routing data feature is not enabled for this account", "originalRequest": { "url": { @@ -8911,7 +8911,7 @@ "description": "", "item": [ { - "id": "1de81e72-7d23-432b-888a-6a0272ed6ffb", + "id": "624fec35-2d6f-41ed-93f4-7c7ce89d2a00", "name": "list-parties", "request": { "name": "list-parties", @@ -8960,7 +8960,7 @@ }, "response": [ { - "id": "bc5ed6b4-e845-461a-aa64-629213085c5a", + "id": "5d77b298-c2df-4724-9ae1-900947a91777", "name": "OK", "originalRequest": { "url": { @@ -9028,7 +9028,7 @@ } }, { - "id": "e556e8e2-8915-42b6-8afd-5db270d70f42", + "id": "7cc95293-2fe9-4777-87d0-d35da9e08200", "name": "post-party", "request": { "name": "post-party", @@ -9071,7 +9071,7 @@ }, "response": [ { - "id": "7f54da28-65d7-4c86-b1d4-b0e57f6d2948", + "id": "1bee50fc-22a3-4b09-96f9-7daeec85b6f5", "name": "Party Created", "originalRequest": { "url": { @@ -9127,7 +9127,7 @@ "_postman_previewlanguage": "json" }, { - "id": "44192731-c0b3-4c10-85ea-1e872db6af6f", + "id": "a84461a7-27db-4469-b04c-43fdb88ad538", "name": "Unprocessable Entity", "originalRequest": { "url": { @@ -9189,7 +9189,7 @@ } }, { - "id": "dc385bec-5ae0-4e5b-acc4-9315dce401cc", + "id": "c4ff5bb7-19fe-4fa5-8649-023e99506572", "name": "get-parties-id", "request": { "name": "get-parties-id", @@ -9231,7 +9231,7 @@ }, "response": [ { - "id": "3cec2980-42d9-46ce-b0f7-776d746bda60", + "id": "fc615124-b098-4d84-9e1c-d9d92162cba4", "name": "OK", "originalRequest": { "url": { @@ -9292,7 +9292,7 @@ } }, { - "id": "13e592ac-10e3-4691-97d3-25b78cabfa5d", + "id": "1f8491fc-bffe-4736-8c84-95842d3c5297", "name": "edit-party", "request": { "name": "edit-party", @@ -9347,7 +9347,7 @@ }, "response": [ { - "id": "0dfa5b38-9ae6-4c35-891b-0727f9bf3021", + "id": "bfce5477-160c-47b4-b5cc-59792a0259e5", "name": "OK", "originalRequest": { "url": { @@ -9415,7 +9415,7 @@ "_postman_previewlanguage": "json" }, { - "id": "e22a1371-8a9a-4d4d-a905-4dcf5f02efd5", + "id": "dcc7a89c-bfb2-49eb-bd0d-12fdb5b598ca", "name": "Unprocessable Entity", "originalRequest": { "url": { @@ -9519,7 +9519,7 @@ } ], "info": { - "_postman_id": "fb0e2a93-584e-49c2-b87d-bb4c44bb13fa", + "_postman_id": "c1fcfbc5-589c-44b4-a9e1-c99c42d13c6f", "name": "Terminal49 API Reference", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "description": { diff --git a/sdks/typescript-sdk-cli/CLI_GA_PLAN.md b/sdks/typescript-sdk-cli/CLI_GA_PLAN.md new file mode 100644 index 00000000..8f215448 --- /dev/null +++ b/sdks/typescript-sdk-cli/CLI_GA_PLAN.md @@ -0,0 +1,158 @@ +# Terminal49 CLI GA Plan: Full SDK Parity + Dual Auth (API Token + MCP OAuth) + +## Summary + +Build `@terminal49/cli` as a production-grade CLI for humans and agents with: + +1. Full command parity with the TypeScript SDK. +2. Dual auth modes: API token and OAuth using the same auth flow as hosted MCP. +3. Enforced coverage gate of 90/90/85 (lines/functions/statements at 90, branches at 85). +4. Deterministic machine output plus high-quality human UX. + +## Current State Evaluation + +1. CLI package exists but is scaffold-only; core modules and all command files are TODO stubs. +2. CLI has zero tests; `npm test` exits with "No test files found." +3. CLI build/type-check currently fail because workspace dependencies are not installed/resolved and lock metadata does not include the new CLI workspace. +4. SDK auth is token-centric and does not cleanly model OAuth bearer tokens for REST-first execution. +5. MCP package has working tests and auth guardrails, but OAuth is not implemented in this repo's server path today. +6. Docs currently state "OAuth not required" while linking to missing OAuth docs, so auth documentation is inconsistent. +7. CI has SDK and MCP jobs but no CLI job. + +## Public API / Interface Changes + +1. Extend `@terminal49/sdk` config to support explicit auth scheme without breaking `apiToken` callers. +2. Add CLI auth command group and auth-related global options. +3. Define and version the machine-readable command discovery schema returned by `t49 commands --json`. +4. Add CLI config schema versioning to allow safe upgrades. + +## Decisions Locked + +1. OAuth model: Shared OAuth flow aligned with MCP auth flow. +2. Execution backend in OAuth mode: REST SDK first. +3. Coverage gate: 90/90/85. +4. Token storage default: plain config file. +5. V1 scope: full SDK parity. + +## Implementation Plan + +### Phase 0: Workspace and Packaging Baseline + +1. Wire CLI workspace dependency installation into root lockfile and CI install path. +2. Add missing CLI `README.md` (required by current `files` list in package manifest). +3. Ensure root-level `npm ci` yields resolvable `commander`, `chalk`, and `cli-table3` for CLI workspace. +4. Add workspace scripts for CLI build/test/lint consistency. + +### Phase 1: SDK Auth Extension for OAuth Compatibility + +1. Update SDK auth construction in `sdks/typescript-sdk/src/client.ts` to accept explicit auth scheme (`Token` or `Bearer`) while preserving existing `apiToken` behavior. +2. Add/adjust exported config types in SDK index/types for backward-compatible migration. +3. Add SDK tests for: + 1. Legacy token path still emits `Authorization: Token ...`. + 2. Explicit bearer path emits `Authorization: Bearer ...`. + 3. Prefixed token passthrough and invalid config behavior. + +### Phase 2: CLI Auth Subsystem (API Token + OAuth) + +1. Implement config persistence in `sdks/typescript-sdk-cli/src/config.ts` with schema version and file mode hardening (`0600`). +2. Add auth resolver in `sdks/typescript-sdk-cli/src/client-factory.ts` with deterministic precedence: + 1. Explicit CLI flags. + 2. Environment variables. + 3. Stored OAuth session (with refresh). + 4. Stored API token. +3. Implement OAuth login/logout/status commands in `sdks/typescript-sdk-cli/src/commands/config.ts` or dedicated `src/commands/auth.ts` and register from `src/index.ts`. +4. OAuth flow implementation: + 1. Use MCP SDK OAuth client primitives (PKCE auth code flow). + 2. Discover metadata from MCP server URL. + 3. Store tokens and token metadata in CLI config. + 4. Refresh access token on expiry before REST calls. + 5. On scope mismatch/401 in OAuth mode, emit deterministic auth error with remediation. +5. Add non-interactive behavior: + 1. `--json` mode never prompts. + 2. Missing OAuth session in non-interactive context exits with usage/auth error and next-step hint. + +### Phase 3: Command Surface (Full SDK Parity) + +1. Implement and register all planned commands: + 1. `shipments`: `get`, `list`, `update`, `stop-tracking`, `resume-tracking`. + 2. `containers`: `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail`. + 3. `tracking-requests`: `list`, `get`, `create`, `update`, `infer`. + 4. `track`. + 5. `shipping-lines list`. + 6. `search`. + 7. `config` and `commands`. +2. Implement argument validation/mapping with strict, predictable usage errors. +3. Keep command layer thin; push API semantics to SDK. + +### Phase 4: Output, Error Model, and Agent UX + +1. Implement JSON/table dispatch in `sdks/typescript-sdk-cli/src/output/formatter.ts` with TTY-aware defaults. +2. Implement JSON envelope in `sdks/typescript-sdk-cli/src/output/json.ts` for both success and errors. +3. Implement field projection and compact output in `sdks/typescript-sdk-cli/src/output/fields.ts`. +4. Implement table rendering by resource in `sdks/typescript-sdk-cli/src/output/table.ts`. +5. Implement stable error mapping and exit codes in `sdks/typescript-sdk-cli/src/errors.ts`, including OAuth-specific failures. + +### Phase 5: High-Coverage Test Suite + +1. Unit tests for auth/config/output/error utilities with exhaustive branches. +2. Unit tests for each command module validating: + 1. flag parsing. + 2. argument validation. + 3. SDK call mapping. + 4. output envelope shape. + 5. error translation. +3. Integration tests using `execa` for real process behavior: + 1. exit codes. + 2. stdout/stderr boundaries. + 3. JSON determinism. + 4. TTY/non-TTY mode. + 5. OAuth missing-session and token-refresh behavior. +4. Contract tests for `t49 commands --json` schema stability. +5. Optional live smoke tests behind env guard for token and OAuth modes. + +### Phase 6: CI, Docs, and Release Readiness + +1. Add dedicated CLI job to `.github/workflows/ci.yml` with build, lint, tests, and coverage threshold enforcement. +2. Ensure coverage fails CI below 90/90/85. +3. Add user docs for: + 1. API token auth. + 2. OAuth login flow. + 3. agent usage patterns. + 4. non-interactive mode. +4. Fix/remove broken OAuth doc links until corresponding docs are added. + +## Test Cases and Scenarios (Must-Have) + +1. Auth precedence matrix across flags/env/config/oauth-session. +2. OAuth login success, callback timeout, denied consent, invalid state, refresh success, refresh failure. +3. SDK auth header emission for token and bearer paths. +4. Every command happy path plus representative API failures (401/403/404/422/429/5xx/network). +5. Output compatibility: + 1. JSON envelope schema. + 2. `--compact`. + 3. `--fields` nested projection. + 4. table output for TTY. +6. Agent determinism: + 1. no ANSI in JSON mode. + 2. no prompts in non-interactive runs. + 3. stable error codes/messages. +7. Pagination and polling edge cases where implemented. + +## Acceptance Criteria + +1. `npm run build --workspace @terminal49/cli` passes on clean checkout. +2. `npm run test:coverage --workspace @terminal49/cli` passes with 90/90/85 minimums. +3. All planned commands are implemented and registered. +4. Both auth modes work end-to-end: + 1. API token mode. + 2. OAuth mode with token refresh and REST execution. +5. CI includes CLI checks and blocks merges on failures. +6. Command discovery JSON is versioned and contract-tested. + +## Assumptions and Defaults + +1. OAuth piggyback means reusing the same OAuth provider/discovery model as hosted MCP, not scraping Claude/Cursor local caches. +2. OAuth tokens issued by that flow are valid for REST SDK calls with `Bearer` auth; if not, CLI will fail with explicit scope remediation. +3. Plain config-file token storage is accepted for V1 despite weaker security posture. +4. Full SDK parity is required for GA, not a reduced MVP command set. +5. Node runtime target remains compatible with current repo toolchain and workspace CI matrix. diff --git a/sdks/typescript-sdk-cli/PLAN-v2.md b/sdks/typescript-sdk-cli/PLAN-v2.md new file mode 100644 index 00000000..6b178c1e --- /dev/null +++ b/sdks/typescript-sdk-cli/PLAN-v2.md @@ -0,0 +1,196 @@ +# Terminal49 CLI — Implementation Plan + +## Context + +The CLI at `sdks/typescript-sdk-cli/` has been scaffolded (19 stub files, all empty TODOs) with a detailed PLAN.md spec. The TypeScript SDK at `sdks/typescript-sdk/src/client.ts` is mature, covering ~20 of 39 API endpoints. The MCP server at `packages/mcp/` uses token auth only today; OAuth is planned but unbuilt. The goal is a high-quality CLI with full API coverage, dual auth (token + OAuth), usable by both humans and LLM agents. + +## Key Architectural Decisions + +### 1. SDK-First: Extend the SDK, not the CLI +The CLI stays a thin presentation layer. Missing endpoint methods get added to `Terminal49Client` in the SDK, following existing `execute()` patterns. This keeps business logic in one place and makes new endpoints available to both CLI and MCP server. + +### 2. Auth Strategy: Token-Only (OAuth Deferred) +Token auth only for now. OAuth (device code flow) deferred until Terminal49 ships an authorization server. + +Token resolution order: +1. `--token ` flag +2. `T49_API_TOKEN` env var +3. `~/.config/terminal49/config.json` → `token` +4. Error with actionable message + +### 3. Dependency: Use workspace SDK reference +Change `"@terminal49/sdk": "^0.1.0"` → `"@terminal49/sdk": "workspace:*"` (or `file:../typescript-sdk`) so CLI uses the local SDK with new methods during development. + +--- + +## Phase 1: Foundation (Infrastructure + Core Commands) + +### 1a. Infrastructure modules + +| File | What | +|------|------| +| `src/util/tty.ts` | TTY detection, `NO_COLOR`/`FORCE_COLOR` support | +| `src/config.ts` | Read/write `~/.config/terminal49/config.json` (XDG-compliant, plain `node:fs`, atomic writes) | +| `src/client-factory.ts` | Token resolution chain → creates `Terminal49Client` | +| `src/errors.ts` | SDK error → CLI error mapping, exit codes 0-9, `withErrorHandling()` wrapper | +| `src/output/fields.ts` | `--fields` projection with dot-notation | +| `src/output/json.ts` | JSON envelope `{ok, command, data, pagination?, meta?}`, `--compact` | +| `src/output/table.ts` | Table renderer via `cli-table3`, resource-specific column defs | +| `src/output/formatter.ts` | Dispatcher: selects json/table from flags+TTY, applies fields projection | + +### 1b. Core commands (use existing SDK methods) + +| File | Commands | SDK Methods | +|------|----------|-------------| +| `src/commands/config.ts` | `set`, `get`, `list`, `path` | None (pure config) | +| `src/commands/containers.ts` | `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail` | `containers.*`, `getDemurrage`, `getRailMilestones` | +| `src/commands/shipments.ts` | `get`, `list`, `update`, `stop-tracking`, `resume-tracking` | `shipments.*` | +| `src/index.ts` | Register commands into Commander program | — | + +### 1c. Tests +Unit tests for all modules + integration tests (spawn CLI binary, assert stdout/stderr/exit code). Target: ~80 tests. + +--- + +## Phase 2: Remaining Existing-SDK Commands + +| File | Commands | SDK Methods | +|------|----------|-------------| +| `src/commands/tracking-requests.ts` | `list`, `get`, `create`, `update`, `infer` | `trackingRequests.*` | +| `src/commands/track.ts` | ` [--scac] [--type]` | `trackingRequests.createFromInfer` | +| `src/commands/shipping-lines.ts` | `list [--search]` | `shippingLines.list` | +| `src/commands/search.ts` | `` | `client.search()` | +| `src/commands/commands.ts` | `[--json]` — machine-readable command discovery | Introspects Commander tree | + +Tests: ~40 more tests. + +--- + +## Phase 3: SDK Extension + New CLI Commands + +### 3a. Extend SDK (`sdks/typescript-sdk/src/client.ts`) + +Add new resource namespaces following existing patterns: + +| Namespace | Methods | API Endpoints | +|-----------|---------|---------------| +| `webhooks` | `list`, `get`, `create`, `update`, `delete`, `getIps` | `/webhooks`, `/webhooks/{id}`, `/webhooks/ips` | +| `webhookNotifications` | `list`, `get`, `getExamples` | `/webhook_notifications`, `/webhook_notifications/{id}`, `/webhook_notifications/examples` | +| `vessels` | `get`, `getByImo`, `futurePositions`, `futurePositionsWithCoords` | `/vessels/{id}`, `/vessels/{imo}`, `/vessels/{id}/future_positions*` | +| `ports` | `get` | `/ports/{id}` | +| `terminals` | `get` | `/terminals/{id}` | +| `parties` | `list`, `get` | `/parties`, `/parties/{id}` | +| `metroAreas` | `get` | `/metro_areas/{id}` | +| `customFieldDefinitions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions*` | +| `customFieldOptions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions/{id}/options*` | +| `customFields` | `list`, `get`, `create`, `update`, `delete` | `/custom_fields*` | + +Also add: `containers.mapGeojson(id)` and entity-scoped custom fields on `shipments`/`containers`. + +### 3b. New CLI command files + +| File | Commands | +|------|----------| +| `src/commands/webhooks.ts` | `list`, `get`, `create`, `update`, `delete`, `ips` | +| `src/commands/webhook-notifications.ts` | `list`, `get`, `examples` | +| `src/commands/vessels.ts` | `get`, `get-by-imo`, `future-positions`, `future-positions-coords` | +| `src/commands/ports.ts` | `get ` | +| `src/commands/terminals.ts` | `get ` | +| `src/commands/parties.ts` | `list`, `get` | +| `src/commands/metro-areas.ts` | `get ` | +| `src/commands/custom-fields.ts` | `list`, `get`, `create`, `update`, `delete` | +| `src/commands/custom-field-definitions.ts` | `list`, `get`, `create`, `update`, `delete` | +| `src/commands/custom-field-options.ts` | `list `, `get`, `create`, `update`, `delete` | + +Update `containers.ts` and `shipments.ts` with `map`, `custom-fields`, `set-custom-field` subcommands. + +Tests: ~80 more tests. + +--- + +## Phase 4: Agent & Scale Features + +| Feature | File | +|---------|------| +| `--all` pagination with NDJSON streaming | `src/util/pagination.ts` | +| `--poll` support with `--interval` / `--until` | `src/util/polling.ts` | +| Shell completions (bash/zsh/fish) | `src/commands/completions.ts` | + +--- + +## Phase 5: Distribution & Polish + +| Feature | File | +|---------|------| +| README with examples for humans + LLM agents | `README.md` | +| CI/CD (GitHub Actions: test, lint, build, publish to npm) | `.github/workflows/test-cli.yml` | +| npx support, Docker image | — | +| Final coverage audit (≥90% lines, ≥85% branches) | — | + +--- + +## Full Command Surface (39 endpoints + composed commands) + +``` +t49 shipments list|get|update|stop-tracking|resume-tracking|custom-fields|set-custom-field +t49 containers list|get|events|route|raw-events|refresh|demurrage|rail|map|custom-fields|set-custom-field +t49 tracking-requests list|get|create|update|infer +t49 track +t49 shipping-lines list +t49 webhooks list|get|create|update|delete|ips +t49 webhook-notifications list|get|examples +t49 vessels get|get-by-imo|future-positions|future-positions-coords +t49 ports get +t49 terminals get +t49 parties list|get +t49 metro-areas get +t49 custom-fields list|get|create|update|delete +t49 custom-field-definitions list|get|create|update|delete +t49 custom-field-options list|get|create|update|delete +t49 search +t49 config set|get|list|path +t49 commands [--json] +``` + +--- + +## Command Pattern (all commands follow this) + +```typescript +export function registerXxxCommand(program: Command): void { + const cmd = program.command('xxx').description('...'); + cmd.command('get ') + .option('--include ') + .action(withErrorHandling('xxx.get', async (id, opts, cmd) => { + const globalOpts = cmd.optsWithGlobals(); + const client = createClient(globalOpts); + const formatter = createFormatter(globalOpts); + const data = await client.xxx.get(id, { format: globalOpts.format }); + formatter.output('xxx.get', data); + })); +} +``` + +--- + +## Verification Plan + +1. **Unit tests**: `npm test` — ~200+ tests covering all modules +2. **Integration tests**: `npm run test:integration` — spawn CLI binary with mocked HTTP (msw) +3. **E2E smoke**: `npm run test:e2e` — live API with `T49_API_TOKEN` +4. **Manual verification**: Run each command group against live API +5. **Coverage**: `npm run test:coverage` — enforce ≥90% lines, ≥85% branches +6. **Lint**: `npm run lint` — biome check + +--- + +## Critical Files + +| File | Why | +|------|-----| +| `sdks/typescript-sdk/src/client.ts` | SDK client — must be extended for ~19 missing endpoints | +| `sdks/typescript-sdk-cli/src/errors.ts` | Every command depends on `withErrorHandling()` | +| `sdks/typescript-sdk-cli/src/output/formatter.ts` | Every command depends on the output dispatcher | +| `sdks/typescript-sdk-cli/src/client-factory.ts` | Every command needs an authenticated SDK client | +| `sdks/typescript-sdk-cli/src/index.ts` | All commands registered here | +| `sdks/typescript-sdk-cli/package.json` | SDK dependency must be changed to workspace reference | diff --git a/sdks/typescript-sdk-cli/PLAN-v3-merged.md b/sdks/typescript-sdk-cli/PLAN-v3-merged.md new file mode 100644 index 00000000..f8473831 --- /dev/null +++ b/sdks/typescript-sdk-cli/PLAN-v3-merged.md @@ -0,0 +1,256 @@ +# Terminal49 CLI — Merged Implementation Plan + +## Context + +Two plans exist: **PLAN-v2** (my prior plan) and **CLI_GA_PLAN** (other agent). Both target the same goal — production-grade CLI for humans and agents — but differ on scope, auth strategy, and phase ordering. This document merges the best of both after detailed comparison. + +--- + +## Plan Comparison Summary + +| Dimension | PLAN-v2 | CLI_GA_PLAN | Merged | +|-----------|---------|-------------|--------| +| **API coverage** | All 39 endpoints (17 command groups) | ~20 endpoints (7 command groups, SDK parity only) | All 39 endpoints | +| **Auth** | Token-only (per user decision) | Dual: token + OAuth PKCE | Token-only now, SDK prepped for Bearer | +| **SDK extension** | Explicit plan to add 10 new namespaces | Not addressed — CLI only wraps existing SDK | Extend SDK | +| **Phase ordering** | Infra → commands → SDK extension → agent features | Workspace → SDK auth → OAuth → commands → output → tests | Workspace fix → infra+output → commands → SDK extension → agent features | +| **Output/errors before commands?** | Yes (Phase 1a) | No (Phase 4, after commands) | Yes — commands depend on formatters | +| **Tests alongside code?** | Yes (co-located per phase) | No (separate Phase 5) | Yes | +| **Workspace/CI fix** | Mentioned dependency change only | Explicit Phase 0 with lockfile + CI | Phase 0 included | +| **Config hardening** | XDG + atomic writes | Schema versioning + `0600` permissions | All of the above | +| **`commands --json` contract** | Listed as a command | Schema versioning + contract testing | Contract tested | + +### What CLI_GA_PLAN gets right that PLAN-v2 missed + +1. **Phase 0: Workspace baseline** — root `package.json` has `"workspaces": ["packages/*", "sdks/*"]` so `sdks/typescript-sdk-cli` IS included, but CI (`ci.yml`) has no CLI job and lockfile may not resolve CLI deps. Must fix before anything else. +2. **SDK `Token`/`Bearer` scheme** — `buildFetch()` in `sdks/typescript-sdk/src/client.ts:643` hardcodes `Token ${this.apiToken}`. Even without OAuth, supporting explicit `Bearer` prepares for future auth modes and is a small, backward-compatible change. +3. **Config file `0600` permissions** — tokens stored in plaintext need restricted file perms. +4. **Config schema versioning** — `{ "version": 1, ... }` allows safe future migrations. +5. **Non-interactive enforcement** — `--json` mode should never prompt; missing auth in non-interactive context exits with hint. +6. **Current state evaluation** — build/type-check currently fail, zero tests, no CI job. + +### What PLAN-v2 gets right that CLI_GA_PLAN missed + +1. **Full 39-endpoint coverage** — CLI_GA_PLAN only covers ~20 endpoints (existing SDK methods). Missing: webhooks, webhook-notifications, vessels, ports, terminals, parties, metro-areas, custom-fields (3 groups), containers.mapGeojson, entity-scoped custom fields. +2. **SDK extension strategy** — 10 new namespaces with exact method signatures, following existing `execute()` patterns. +3. **Correct dependency ordering** — output/errors BEFORE commands (commands call `formatter.output()` and `withErrorHandling()`). +4. **Concrete command pattern** — code template showing every command follows `registerXxxCommand()` → `withErrorHandling()` → `createClient()` → `createFormatter()` → `formatter.output()`. +5. **Agent features** — `--all` (NDJSON pagination), `--poll`, shell completions. +6. **Composed commands** — `demurrage`, `rail`, `map` (not just raw API wrappers). + +### Where CLI_GA_PLAN is wrong + +1. **Phase ordering**: Output/errors in Phase 4, AFTER commands in Phase 3. Commands can't be implemented without the formatter and error handler. +2. **Tests as separate phase**: Phase 5 is "High-Coverage Test Suite." Tests should accompany implementation, not come after. Otherwise you build everything, then discover bugs. +3. **OAuth scope**: User explicitly chose "Token-only for now." GA plan treats OAuth as in-scope for V1, which contradicts the user's direction. +4. **Coverage claim**: Says "Full SDK parity" but the SDK only covers ~20 of 39 endpoints. True full coverage requires extending the SDK. + +--- + +## Merged Implementation Plan + +### Phase 0: Workspace & CI Baseline + +Fixes build/install before any code is written. + +| Task | File | +|------|------| +| Ensure `npm ci` at root resolves CLI workspace deps | `package-lock.json` (regenerate) | +| Add CLI job to CI workflow | `.github/workflows/ci.yml` | +| Add placeholder `README.md` (required by `files` in package.json) | `sdks/typescript-sdk-cli/README.md` | +| Change SDK dependency to workspace reference | `sdks/typescript-sdk-cli/package.json`: `"@terminal49/sdk": "workspace:*"` | +| Verify `npm run build --workspace @terminal49/cli` passes | — | + +### Phase 1: Foundation (Infrastructure + Core Commands + Tests) + +Infrastructure first, then the two most-used command groups, with tests alongside. + +**1a. SDK auth prep** (small, backward-compatible) + +Add optional `authScheme` to `Terminal49ClientConfig` in `sdks/typescript-sdk/src/client.ts`: +```typescript +export interface Terminal49ClientConfig { + apiToken: string; + authScheme?: 'Token' | 'Bearer'; // NEW — defaults to 'Token' for backward compat + // ...existing fields +} +``` +Update `buildFetch()` (line 643) to use `this.authScheme ?? 'Token'`. Existing callers are unaffected. + +**1b. CLI infrastructure modules** + +| File | What | +|------|------| +| `src/util/tty.ts` | TTY detection, `NO_COLOR`/`FORCE_COLOR` | +| `src/config.ts` | XDG-compliant config with schema versioning `{"version":1,...}`, `0600` file perms, atomic writes | +| `src/client-factory.ts` | Token resolution: flag → env → config → error. Creates `Terminal49Client`. Non-interactive enforcement. | +| `src/errors.ts` | SDK error → CLI error mapping, exit codes 0-9, `withErrorHandling()` wrapper | +| `src/output/fields.ts` | `--fields` projection with dot-notation | +| `src/output/json.ts` | JSON envelope `{ok, command, data, pagination?, meta?}`, `--compact`, no ANSI in JSON mode | +| `src/output/table.ts` | Table renderer via `cli-table3`, resource-specific column defs | +| `src/output/formatter.ts` | Dispatcher: selects json/table from flags+TTY, applies fields projection | + +**1c. Core commands** + +| File | Commands | SDK Methods | +|------|----------|-------------| +| `src/commands/config.ts` | `set`, `get`, `list`, `path` | None (pure config) | +| `src/commands/containers.ts` | `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail` | `containers.*`, `getDemurrage`, `getRailMilestones` | +| `src/commands/shipments.ts` | `get`, `list`, `update`, `stop-tracking`, `resume-tracking` | `shipments.*` | +| `src/index.ts` | Register commands into Commander program | — | + +**1d. Phase 1 tests**: ~80 unit + integration tests. + +### Phase 2: Remaining Existing-SDK Commands + Tests + +| File | Commands | SDK Methods | +|------|----------|-------------| +| `src/commands/tracking-requests.ts` | `list`, `get`, `create`, `update`, `infer` | `trackingRequests.*` | +| `src/commands/track.ts` | ` [--scac] [--type]` | `trackingRequests.createFromInfer` | +| `src/commands/shipping-lines.ts` | `list [--search]` | `shippingLines.list` | +| `src/commands/search.ts` | `` | `client.search()` | +| `src/commands/commands.ts` | `[--json]` — versioned schema, contract-tested | Introspects Commander tree | + +Tests: ~40 more tests, including contract test for `t49 commands --json` schema. + +### Phase 3: SDK Extension + New CLI Commands + Tests + +**3a. Extend SDK** (`sdks/typescript-sdk/src/client.ts`) + +| Namespace | Methods | API Endpoints | +|-----------|---------|---------------| +| `webhooks` | `list`, `get`, `create`, `update`, `delete`, `getIps` | `/webhooks*`, `/webhooks/ips` | +| `webhookNotifications` | `list`, `get`, `getExamples` | `/webhook_notifications*` | +| `vessels` | `get`, `getByImo`, `futurePositions`, `futurePositionsWithCoords` | `/vessels/{id}*` | +| `ports` | `get` | `/ports/{id}` | +| `terminals` | `get` | `/terminals/{id}` | +| `parties` | `list`, `get` | `/parties*` | +| `metroAreas` | `get` | `/metro_areas/{id}` | +| `customFieldDefinitions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions*` | +| `customFieldOptions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions/{id}/options*` | +| `customFields` | `list`, `get`, `create`, `update`, `delete` | `/custom_fields*` | + +Also: `containers.mapGeojson(id)`, entity-scoped custom fields on `shipments`/`containers`. + +**3b. New CLI command files** + +| File | Commands | +|------|----------| +| `src/commands/webhooks.ts` | `list`, `get`, `create`, `update`, `delete`, `ips` | +| `src/commands/webhook-notifications.ts` | `list`, `get`, `examples` | +| `src/commands/vessels.ts` | `get`, `get-by-imo`, `future-positions`, `future-positions-coords` | +| `src/commands/ports.ts` | `get ` | +| `src/commands/terminals.ts` | `get ` | +| `src/commands/parties.ts` | `list`, `get` | +| `src/commands/metro-areas.ts` | `get ` | +| `src/commands/custom-fields.ts` | `list`, `get`, `create`, `update`, `delete` | +| `src/commands/custom-field-definitions.ts` | `list`, `get`, `create`, `update`, `delete` | +| `src/commands/custom-field-options.ts` | `list `, `get`, `create`, `update`, `delete` | + +Update `containers.ts` with `map`, `custom-fields`, `set-custom-field`. Update `shipments.ts` similarly. + +Tests: ~80 more (SDK unit tests + CLI unit + integration). + +### Phase 4: Agent & Scale Features + +| Feature | File | +|---------|------| +| `--all` pagination with NDJSON streaming | `src/util/pagination.ts` | +| `--poll` support with `--interval` / `--until` | `src/util/polling.ts` | +| Shell completions (bash/zsh/fish) | `src/commands/completions.ts` | + +### Phase 5: Distribution & Polish + +| Feature | Details | +|---------|---------| +| README | Usage examples for humans + LLM agents | +| CI enforcement | Coverage fails below 90/90/85 (lines/functions at 90, branches at 85) | +| npx support | Verify `npx @terminal49/cli containers list` works | +| Docker image | Optional: `ghcr.io/terminal49/cli` | + +--- + +## Full Command Surface (39 endpoints + composed commands) + +``` +t49 shipments list|get|update|stop-tracking|resume-tracking|custom-fields|set-custom-field +t49 containers list|get|events|route|raw-events|refresh|demurrage|rail|map|custom-fields|set-custom-field +t49 tracking-requests list|get|create|update|infer +t49 track +t49 shipping-lines list +t49 webhooks list|get|create|update|delete|ips +t49 webhook-notifications list|get|examples +t49 vessels get|get-by-imo|future-positions|future-positions-coords +t49 ports get +t49 terminals get +t49 parties list|get +t49 metro-areas get +t49 custom-fields list|get|create|update|delete +t49 custom-field-definitions list|get|create|update|delete +t49 custom-field-options list|get|create|update|delete +t49 search +t49 config set|get|list|path +t49 commands [--json] +``` + +--- + +## Command Pattern (all commands follow this) + +```typescript +export function registerXxxCommand(program: Command): void { + const cmd = program.command('xxx').description('...'); + cmd.command('get ') + .option('--include ') + .action(withErrorHandling('xxx.get', async (id, opts, cmd) => { + const globalOpts = cmd.optsWithGlobals(); + const client = createClient(globalOpts); + const formatter = createFormatter(globalOpts); + const data = await client.xxx.get(id, { format: globalOpts.format }); + formatter.output('xxx.get', data); + })); +} +``` + +--- + +## Auth Strategy + +Token-only for now (per user decision). OAuth deferred until Terminal49 ships an authorization server. + +**Token resolution order:** +1. `--token ` flag +2. `T49_API_TOKEN` env var +3. `~/.config/terminal49/config.json` → `token` +4. Error with actionable message + next-step hint + +**SDK prep**: Add optional `authScheme: 'Token' | 'Bearer'` to `Terminal49ClientConfig` (defaults to `'Token'`, backward-compatible). This is a small change that future-proofs the SDK for OAuth without implementing it. + +**Non-interactive enforcement**: `--json` mode never prompts. Missing auth in non-interactive context exits with code 3 and hint. + +--- + +## Verification Plan + +1. **Unit tests**: `npm test` — ~200+ tests covering all modules +2. **Integration tests**: `npm run test:integration` — spawn CLI binary with mocked HTTP (msw) +3. **E2E smoke**: `npm run test:e2e` — live API with `T49_API_TOKEN` +4. **Contract test**: `t49 commands --json` output matches versioned schema +5. **Coverage**: `npm run test:coverage` — enforce 90/90/85 +6. **Lint**: `npm run lint` — biome check +7. **CI**: CLI job in `.github/workflows/ci.yml` blocks merge on failure + +--- + +## Critical Files + +| File | Why | +|------|-----| +| `sdks/typescript-sdk/src/client.ts` | SDK client — add `authScheme` + 10 new namespaces for ~19 missing endpoints | +| `sdks/typescript-sdk-cli/src/errors.ts` | Every command depends on `withErrorHandling()` | +| `sdks/typescript-sdk-cli/src/output/formatter.ts` | Every command depends on the output dispatcher | +| `sdks/typescript-sdk-cli/src/client-factory.ts` | Every command needs an authenticated SDK client | +| `sdks/typescript-sdk-cli/src/index.ts` | All commands registered here | +| `sdks/typescript-sdk-cli/package.json` | SDK dependency → `workspace:*` | +| `.github/workflows/ci.yml` | Add CLI job | +| `package-lock.json` | Must be regenerated to include CLI workspace | diff --git a/sdks/typescript-sdk-cli/PLAN.md b/sdks/typescript-sdk-cli/PLAN.md new file mode 100644 index 00000000..d58bfecb --- /dev/null +++ b/sdks/typescript-sdk-cli/PLAN.md @@ -0,0 +1,1074 @@ +# Terminal49 CLI — Implementation Plan + +> CLI built on top of `@terminal49/sdk` for LLM agents, chat interfaces, and human developers. + +--- + +## Table of Contents + +1. [Design Principles](#1-design-principles) +2. [Architecture](#2-architecture) +3. [Command Surface](#3-command-surface) +4. [Output Modes](#4-output-modes) +5. [Authentication & Configuration](#5-authentication--configuration) +6. [LLM Agent & Chat Interface Design](#6-llm-agent--chat-interface-design) +7. [Error Handling](#7-error-handling) +8. [Project Structure](#8-project-structure) +9. [Technology Choices](#9-technology-choices) +10. [Implementation Phases](#10-implementation-phases) +11. [Test Plan & Coverage](#11-test-plan--coverage) + +--- + +## 1. Design Principles + +Sourced from the [Node.js CLI Apps Best Practices](https://github.com/lirantal/nodejs-cli-apps-best-practices) and adapted for dual-consumer (LLM + human) use. + +### 1.1 Command-Line Experience + +| Principle | Rationale | +|---|---| +| **Respect POSIX args** | `--flag value`, `-f`, `--no-color` — predictable for scripts and LLMs | +| **Zero-config startup** | `T49_API_TOKEN` env var + sensible defaults — no wizard needed | +| **Stateful config** | `t49 config set token ` persists to `~/.config/terminal49/config.json` | +| **Enable composability** | Pipe-friendly: `t49 containers list --json \| jq '.items[].number'` | +| **Actionable errors** | Every error has a code, human message, and a JSON detail block | +| **Graceful shutdown** | Handle SIGINT/SIGTERM — cancel in-flight requests, print partial results | + +### 1.2 Dual-Consumer Design (LLM Agent + Human) + +| Consumer | Need | CLI Feature | +|---|---|---| +| LLM agent | Structured, parseable output | `--json` flag (default when stdout is not a TTY) | +| LLM agent | Deterministic, scriptable | Stable exit codes, no interactive prompts unless `--interactive` | +| LLM agent | Discoverability | `--help` with structured metadata, `t49 commands` listing | +| Human | Readable output | Table/colored output when TTY detected | +| Human | Progressive disclosure | Short help by default, `--help --verbose` for full docs | +| Chat interface | Streaming at scale | `--stream` for long-running list/poll operations | + +### 1.3 POSIX Compliance + +- Exit `0` on success, `1` on general error, `2` on usage error +- Map SDK errors to specific exit codes (see [Section 7](#7-error-handling)) +- `stdout` for data, `stderr` for diagnostics/progress +- Support `--` to end option parsing +- Respect `NO_COLOR`, `FORCE_COLOR`, `TERM` + +--- + +## 2. Architecture + +``` +┌──────────────────────────────────────────┐ +│ CLI Entry (bin/t49) │ +│ - arg parsing (Commander.js) │ +│ - config loading │ +│ - output formatting │ +└────────────────┬─────────────────────────┘ + │ +┌────────────────▼─────────────────────────┐ +│ Command Layer │ +│ src/commands/{resource}.ts │ +│ - validates CLI-specific args │ +│ - maps flags → SDK method params │ +│ - calls SDK, formats output │ +└────────────────┬─────────────────────────┘ + │ +┌────────────────▼─────────────────────────┐ +│ @terminal49/sdk │ +│ Terminal49Client │ +│ - typed HTTP, retry, error hierarchy │ +└──────────────────────────────────────────┘ +``` + +The CLI is a **thin presentation layer** over the SDK. Business logic stays in `@terminal49/sdk`. + +--- + +## 3. Command Surface + +All commands follow `t49 [args] [flags]`. + +### 3.1 Shipments + +``` +t49 shipments get # Get a shipment by ID +t49 shipments list # List shipments + --status # Filter by status + --port # Filter by port of discharge LOCODE + --carrier # Filter by shipping line SCAC + --updated-after # Filter by updated_at + --no-containers # Exclude container includes + --page --page-size # Pagination +t49 shipments update # Update shipment attributes + --attrs '{"customer_name":"Acme"}' # JSON attributes +t49 shipments stop-tracking # Stop tracking +t49 shipments resume-tracking # Resume tracking +``` + +### 3.2 Containers + +``` +t49 containers get # Get a container by ID + --include shipment,pod_terminal # Customize includes +t49 containers list # List containers + --status + --port + --carrier + --updated-after + --page --page-size +t49 containers events # Transport events +t49 containers route # Vessel routing +t49 containers raw-events # Raw events +t49 containers refresh # Force refresh from carrier +t49 containers demurrage # Demurrage info +t49 containers rail # Rail milestones +``` + +### 3.3 Tracking Requests + +``` +t49 track # Smart track — auto-detect carrier + type + --scac # Override carrier + --type container|bill_of_lading|booking_number + --ref --ref # Reference numbers + --tag # Shipment tags +t49 tracking-requests list # List all tracking requests + --page --page-size +t49 tracking-requests get # Get a tracking request +t49 tracking-requests create # Create manually + --type + --number + --scac +t49 tracking-requests update # Update + --attrs '{...}' +t49 tracking-requests infer # Infer number type + carrier +``` + +### 3.4 Shipping Lines + +``` +t49 shipping-lines list # List all shipping lines + --search # Filter by name/SCAC +``` + +### 3.5 Search + +``` +t49 search # Global search +``` + +### 3.6 Config + +``` +t49 config set token # Store API token +t49 config set base-url # Override base URL +t49 config get # Read a config value +t49 config list # Show all config +t49 config path # Print config file path +``` + +### 3.7 Meta + +``` +t49 --version # Print version +t49 --help # Top-level help +t49 commands # List all commands (for LLM discovery) + --json # Machine-readable command listing +t49 completions # Generate shell completions + --shell bash|zsh|fish +``` + +--- + +## 4. Output Modes + +### 4.1 Auto-Detection + +``` +if (process.stdout.isTTY) → table/human output +else → JSON output (for piping / LLM consumption) +``` + +### 4.2 Flags + +| Flag | Effect | +|---|---| +| `--json` | Force JSON output (one document per stdout) | +| `--table` | Force table output (human-readable) | +| `--raw` | Return raw JSON:API document (no deserialization) | +| `--format mapped` | Return deserialized plain objects (default) | +| `--format both` | Return `{raw, mapped}` | +| `--compact` | Minimal JSON (no whitespace) — optimized for LLM token usage | +| `--fields id,number,status` | Select specific fields (reduce output for agents) | +| `--no-color` | Disable color (or respect `NO_COLOR` env) | +| `--quiet` / `-q` | Suppress non-data output | +| `--verbose` / `-v` | Verbose diagnostics to stderr | + +### 4.3 LLM-Optimized JSON Envelope + +When `--json` is active, every response follows a predictable envelope: + +```json +{ + "ok": true, + "command": "containers.get", + "data": { ... }, + "pagination": { "page": 1, "pageSize": 25, "hasNext": true }, + "meta": { "requestId": "...", "duration_ms": 340 } +} +``` + +Error responses: + +```json +{ + "ok": false, + "command": "containers.get", + "error": { + "code": "NOT_FOUND", + "message": "Container abc123 not found", + "status": 404, + "details": { ... } + } +} +``` + +This predictable structure lets LLM agents parse results without brittle string matching. + +--- + +## 5. Authentication & Configuration + +### 5.1 Token Resolution Order + +1. `--token ` flag (highest priority) +2. `T49_API_TOKEN` environment variable +3. `~/.config/terminal49/config.json` → `token` key +4. Error with actionable message + +### 5.2 Config File + +Location: `$XDG_CONFIG_HOME/terminal49/config.json` or `~/.config/terminal49/config.json` + +```json +{ + "token": "your-api-token", + "baseUrl": "https://api.terminal49.com/v2", + "defaultFormat": "mapped", + "defaultOutput": "json", + "maxRetries": 2, + "color": true +} +``` + +### 5.3 Environment Variables + +| Variable | Maps to | +|---|---| +| `T49_API_TOKEN` | API token | +| `T49_BASE_URL` | API base URL | +| `T49_FORMAT` | Default response format (`raw`/`mapped`/`both`) | +| `T49_OUTPUT` | Default output mode (`json`/`table`) | +| `NO_COLOR` | Disable color | + +--- + +## 6. LLM Agent & Chat Interface Design + +This is the key differentiator — the CLI is designed to be a **tool** that LLM agents call. + +### 6.1 Machine-Readable Command Discovery + +```bash +$ t49 commands --json +{ + "commands": [ + { + "name": "containers.get", + "syntax": "t49 containers get [--include ]", + "args": [{"name": "id", "required": true, "type": "string"}], + "flags": [{"name": "--include", "type": "string", "default": "shipment,pod_terminal"}], + "description": "Retrieve a single container by ID with optional related resources" + }, + ... + ] +} +``` + +An LLM agent can call `t49 commands --json` once, then construct any subsequent call with full type information. + +### 6.2 Non-Interactive by Default + +- No prompts. All required args must be provided as flags. +- Exit with code `2` and a clear error message on missing required args. +- `--interactive` flag explicitly opts in to prompts (human use only). + +### 6.3 Token-Efficient Output + +The `--compact` flag removes all whitespace from JSON and the `--fields` flag selects only needed fields. Example for an agent that only needs container numbers and statuses: + +```bash +t49 containers list --json --compact --fields number,status +``` + +### 6.4 Structured Error Codes + +Every error maps to a stable string code that an LLM can switch on: + +| Code | HTTP | Meaning | +|---|---|---| +| `AUTH_MISSING` | 401 | No token provided | +| `AUTH_INVALID` | 401 | Token rejected | +| `FORBIDDEN` | 403 | Insufficient permissions | +| `FEATURE_NOT_ENABLED` | 403 | Feature not on plan | +| `NOT_FOUND` | 404 | Resource doesn't exist | +| `VALIDATION_ERROR` | 400/422 | Invalid input | +| `RATE_LIMITED` | 429 | Too many requests | +| `UPSTREAM_ERROR` | 5xx | API server error | +| `NETWORK_ERROR` | — | Connection/timeout failure | +| `USAGE_ERROR` | — | Invalid CLI usage | + +### 6.5 Pagination for Scale + +List commands support automatic page iteration for chat interfaces processing large datasets: + +```bash +# Single page (default) +t49 containers list --page 1 --page-size 100 + +# All pages (streams NDJSON lines — one JSON object per line) +t49 containers list --all --json +``` + +The `--all` flag iterates through all pages, emitting one JSON line per item (NDJSON format), suitable for streaming into LLM context windows or processing pipelines. + +### 6.6 Polling & Webhooks + +```bash +# Poll a container until status changes (for agent workflows) +t49 containers get --poll --interval 60 --until "status=arrived" +``` + +--- + +## 7. Error Handling + +### 7.1 Exit Codes + +| Code | Meaning | +|---|---| +| `0` | Success | +| `1` | General / API error | +| `2` | Usage / argument error | +| `3` | Authentication error | +| `4` | Authorization / feature error | +| `5` | Not found | +| `6` | Validation error | +| `7` | Rate limited | +| `8` | Upstream / server error | +| `9` | Network / connection error | + +### 7.2 Error Output + +Errors always go to `stderr` (human mode) or as the JSON envelope (JSON mode). + +Human mode: +``` +Error [NOT_FOUND]: Container abc123 not found (HTTP 404) + Hint: Verify the container ID with `t49 containers list` +``` + +JSON mode: +```json +{"ok":false,"error":{"code":"NOT_FOUND","message":"Container abc123 not found","status":404}} +``` + +### 7.3 SDK Error → CLI Error Mapping + +```typescript +function mapSdkError(err: Terminal49Error): CliError { + if (err instanceof AuthenticationError) return { code: 'AUTH_INVALID', exit: 3 }; + if (err instanceof FeatureNotEnabledError) return { code: 'FEATURE_NOT_ENABLED', exit: 4 }; + if (err instanceof AuthorizationError) return { code: 'FORBIDDEN', exit: 4 }; + if (err instanceof NotFoundError) return { code: 'NOT_FOUND', exit: 5 }; + if (err instanceof ValidationError) return { code: 'VALIDATION_ERROR', exit: 6 }; + if (err instanceof RateLimitError) return { code: 'RATE_LIMITED', exit: 7 }; + if (err instanceof UpstreamError) return { code: 'UPSTREAM_ERROR', exit: 8 }; + return { code: 'UNKNOWN', exit: 1 }; +} +``` + +--- + +## 8. Project Structure + +``` +sdks/typescript-sdk-cli/ +├── bin/ +│ └── t49.ts # Entry point with shebang +├── src/ +│ ├── index.ts # Program setup, global flags +│ ├── client-factory.ts # Creates SDK client from config/flags +│ ├── config.ts # Config file read/write (~/.config/terminal49/) +│ ├── output/ +│ │ ├── formatter.ts # Output dispatcher (json/table/raw) +│ │ ├── json.ts # JSON envelope formatter +│ │ ├── table.ts # Table renderer (for TTY) +│ │ └── fields.ts # --fields projection +│ ├── errors.ts # CLI error mapping, exit codes +│ ├── commands/ +│ │ ├── shipments.ts # t49 shipments * +│ │ ├── containers.ts # t49 containers * +│ │ ├── tracking-requests.ts # t49 tracking-requests * +│ │ ├── track.ts # t49 track (smart shortcut) +│ │ ├── shipping-lines.ts # t49 shipping-lines * +│ │ ├── search.ts # t49 search +│ │ ├── config.ts # t49 config * +│ │ └── commands.ts # t49 commands (discovery) +│ └── util/ +│ ├── pagination.ts # --all page iteration +│ ├── polling.ts # --poll implementation +│ └── tty.ts # TTY detection, color support +├── test/ +│ ├── unit/ +│ │ ├── commands/ +│ │ │ ├── shipments.test.ts +│ │ │ ├── containers.test.ts +│ │ │ ├── tracking-requests.test.ts +│ │ │ ├── track.test.ts +│ │ │ ├── shipping-lines.test.ts +│ │ │ ├── search.test.ts +│ │ │ ├── config.test.ts +│ │ │ └── commands.test.ts +│ │ ├── output/ +│ │ │ ├── formatter.test.ts +│ │ │ ├── json.test.ts +│ │ │ ├── table.test.ts +│ │ │ └── fields.test.ts +│ │ ├── errors.test.ts +│ │ ├── config.test.ts +│ │ └── client-factory.test.ts +│ ├── integration/ +│ │ ├── cli-exec.test.ts # Spawn CLI process, assert stdout/stderr/exit +│ │ ├── shipments.integration.test.ts +│ │ ├── containers.integration.test.ts +│ │ └── tracking-requests.integration.test.ts +│ ├── e2e/ +│ │ └── smoke.test.ts # Live API tests (requires T49_API_TOKEN) +│ └── fixtures/ +│ ├── mock-responses/ # Reuse SDK fixtures +│ └── expected-output/ # Snapshot of expected CLI output +├── package.json +├── tsconfig.json +├── vitest.config.ts +├── biome.json +└── README.md +``` + +--- + +## 9. Technology Choices + +| Concern | Choice | Rationale | +|---|---|---| +| **Arg parsing** | [Commander.js](https://github.com/tj/commander.js) | De facto standard (25M+ weekly downloads), git-style subcommands, auto-help, TypeScript types | +| **Table output** | [cli-table3](https://github.com/cli-table/cli-table3) | Lightweight, no deps, customizable columns | +| **Colors** | [chalk](https://github.com/chalk/chalk) | Respects `NO_COLOR`, wide adoption | +| **Config** | [conf](https://github.com/sindresorhus/conf) or custom JSON | XDG-compliant, simple key-value | +| **Testing** | Vitest | Matches existing SDK, fast, built-in coverage | +| **Coverage** | v8 via `@vitest/coverage-v8` | Matches SDK config, accurate for Node.js | +| **Linting** | Biome | Matches SDK config | +| **Build** | `tsc` | Matches SDK, simple | +| **Package** | `@terminal49/cli` | Scoped to org, installable via `npx @terminal49/cli` | + +### 9.1 Dependencies (Production) + +```json +{ + "@terminal49/sdk": "workspace:*", + "commander": "^13.0.0", + "chalk": "^5.4.0", + "cli-table3": "^0.6.5", + "conf": "^13.0.0" +} +``` + +### 9.2 Dependencies (Dev) + +```json +{ + "typescript": "^5.6.3", + "vitest": "^4.0.13", + "@vitest/coverage-v8": "^4.0.18", + "@biomejs/biome": "^1.9.4", + "@types/node": "^20.19.0" +} +``` + +--- + +## 10. Implementation Phases + +### Phase 1 — Foundation (scaffold + core commands) + +1. **Project scaffolding** — `package.json`, `tsconfig.json`, `biome.json`, `vitest.config.ts` +2. **Entry point** — `bin/t49.ts` with Commander program setup, global flags (`--json`, `--token`, `--verbose`, `--quiet`, `--no-color`) +3. **Client factory** — Resolves token from flag → env → config file, creates `Terminal49Client` +4. **Config module** — Read/write `~/.config/terminal49/config.json` +5. **Output module** — JSON envelope formatter, basic table formatter, TTY detection +6. **Error module** — SDK error → CLI error mapping, exit codes +7. **Commands: `config`** — `set`, `get`, `list`, `path` +8. **Commands: `containers`** — `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail` +9. **Commands: `shipments`** — `get`, `list`, `update`, `stop-tracking`, `resume-tracking` +10. **Tests for Phase 1** — Unit tests for all modules, integration tests for CLI execution + +### Phase 2 — Full Command Coverage + +11. **Commands: `tracking-requests`** — `list`, `get`, `create`, `update`, `infer` +12. **Commands: `track`** — Smart auto-detect shortcut +13. **Commands: `shipping-lines`** — `list` +14. **Commands: `search`** — Global search +15. **Commands: `commands`** — Machine-readable command discovery +16. **`--fields` projection** — Select specific output fields +17. **Tests for Phase 2** — Unit + integration for new commands + +### Phase 3 — Agent & Scale Features + +18. **`--all` pagination** — Auto-iterate pages, NDJSON streaming +19. **`--compact` output** — Token-efficient JSON for LLMs +20. **`--poll` support** — Polling with `--interval` and `--until` +21. **Shell completions** — `t49 completions --shell bash|zsh|fish` +22. **`--interactive` mode** — Inquirer.js prompts for human use +23. **Tests for Phase 3** — Unit + integration for streaming, polling, completions + +### Phase 4 — Polish & Distribution + +24. **README & docs** — Usage guide, examples for LLM agents, examples for humans +25. **CI/CD pipeline** — GitHub Actions: test, lint, build, publish to npm +26. **Docker image** — `ghcr.io/terminal49/cli` for environments without Node.js +27. **npx support** — `npx @terminal49/cli containers list` +28. **Final coverage audit** — Ensure ≥90% line coverage, 100% on error paths + +--- + +## 11. Test Plan & Coverage + +### 11.1 Testing Strategy + +``` + ┌───────────────┐ + │ E2E / Smoke │ Live API (CI optional, manual) + │ 5 tests │ + ├───────────────┤ + │ Integration │ Spawn CLI process, mock API + │ ~30 tests │ + ├───────────────┤ + │ Unit │ Functions in isolation + │ ~120 tests │ + └───────────────┘ +``` + +### 11.2 Unit Tests — Detailed Breakdown + +#### 11.2.1 Config Module (`test/unit/config.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Reads existing config file | Config file parsing | +| 2 | Returns defaults when no config file exists | Graceful missing file | +| 3 | Writes token to config file | Persistence | +| 4 | Writes arbitrary key to config file | Key-value storage | +| 5 | Reads XDG_CONFIG_HOME override | XDG compliance | +| 6 | Handles corrupted config file gracefully | Error resilience | +| 7 | Config path returns correct location | Path resolution | + +**Coverage target: 100%** + +#### 11.2.2 Client Factory (`test/unit/client-factory.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Uses `--token` flag when provided | Flag priority | +| 2 | Falls back to `T49_API_TOKEN` env var | Env var resolution | +| 3 | Falls back to config file token | Config resolution | +| 4 | Throws `AUTH_MISSING` when no token found | Error handling | +| 5 | Passes `--base-url` override to SDK | URL customization | +| 6 | Uses default base URL when none provided | Default behavior | +| 7 | Passes `maxRetries` from config | Config propagation | +| 8 | Sets `defaultFormat` from flag/env/config | Format resolution | + +**Coverage target: 100%** + +#### 11.2.3 Error Mapping (`test/unit/errors.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Maps `AuthenticationError` → exit 3, code `AUTH_INVALID` | Error mapping | +| 2 | Maps `AuthorizationError` → exit 4, code `FORBIDDEN` | Error mapping | +| 3 | Maps `FeatureNotEnabledError` → exit 4, code `FEATURE_NOT_ENABLED` | Error mapping | +| 4 | Maps `NotFoundError` → exit 5, code `NOT_FOUND` | Error mapping | +| 5 | Maps `ValidationError` → exit 6, code `VALIDATION_ERROR` | Error mapping | +| 6 | Maps `RateLimitError` → exit 7, code `RATE_LIMITED` | Error mapping | +| 7 | Maps `UpstreamError` → exit 8, code `UPSTREAM_ERROR` | Error mapping | +| 8 | Maps generic `Terminal49Error` → exit 1, code `UNKNOWN` | Fallback | +| 9 | Maps non-SDK error → exit 1, code `UNKNOWN` | Unexpected errors | +| 10 | Maps network/fetch error → exit 9, code `NETWORK_ERROR` | Network failures | +| 11 | Formats human-readable error with hint | Human output | +| 12 | Formats JSON error envelope | JSON output | + +**Coverage target: 100%** + +#### 11.2.4 Output — JSON Formatter (`test/unit/output/json.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Wraps success data in `{ok: true}` envelope | Envelope structure | +| 2 | Wraps error in `{ok: false}` envelope | Error envelope | +| 3 | Includes command name in envelope | Command tracking | +| 4 | Includes pagination when present | Pagination metadata | +| 5 | Includes meta/duration when present | Performance metadata | +| 6 | `--compact` removes whitespace | Token efficiency | +| 7 | Pretty-prints by default when not compact | Readability | + +**Coverage target: 100%** + +#### 11.2.5 Output — Table Formatter (`test/unit/output/table.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Renders container list as table with correct columns | Column mapping | +| 2 | Renders shipment list as table | Resource-specific columns | +| 3 | Renders tracking request list as table | Resource-specific columns | +| 4 | Handles empty list gracefully | Edge case | +| 5 | Truncates long values in columns | Display limits | +| 6 | Respects `NO_COLOR` | Color compliance | + +**Coverage target: 100%** + +#### 11.2.6 Output — Fields Projection (`test/unit/output/fields.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Projects single field from object | Basic projection | +| 2 | Projects multiple fields | Multi-field | +| 3 | Projects nested field with dot notation | Nested access | +| 4 | Returns undefined for missing field | Missing field handling | +| 5 | Projects fields from array items | Array projection | + +**Coverage target: 100%** + +#### 11.2.7 Output — Formatter Dispatcher (`test/unit/output/formatter.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Selects JSON when `--json` flag set | Explicit JSON | +| 2 | Selects table when `--table` flag set | Explicit table | +| 3 | Auto-selects JSON when stdout is not TTY | TTY detection | +| 4 | Auto-selects table when stdout is TTY | TTY detection | +| 5 | `--quiet` suppresses non-data output | Quiet mode | +| 6 | `--verbose` adds diagnostics to stderr | Verbose mode | +| 7 | Applies `--fields` projection before output | Field filtering | +| 8 | `--raw` outputs raw JSON:API document | Raw mode | + +**Coverage target: 100%** + +#### 11.2.8 Commands — Containers (`test/unit/commands/containers.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `get` calls `client.containers.get(id)` with correct args | SDK delegation | +| 2 | `get --include` passes custom includes | Include override | +| 3 | `list` calls `client.containers.list()` with no filters | Default list | +| 4 | `list --status` passes status filter | Filter mapping | +| 5 | `list --port` passes port filter | Filter mapping | +| 6 | `list --carrier` passes carrier filter | Filter mapping | +| 7 | `list --updated-after` passes date filter | Filter mapping | +| 8 | `list --page --page-size` passes pagination | Pagination mapping | +| 9 | `events` calls `client.containers.events(id)` | SDK delegation | +| 10 | `route` calls `client.containers.route(id)` | SDK delegation | +| 11 | `raw-events` calls `client.containers.rawEvents(id)` | SDK delegation | +| 12 | `refresh` calls `client.containers.refresh(id)` | SDK delegation | +| 13 | `demurrage` calls `client.getDemurrage(id)` | SDK delegation | +| 14 | `rail` calls `client.getRailMilestones(id)` | SDK delegation | +| 15 | Missing `` on `get` exits with code 2 | Usage validation | + +**Coverage target: 95%+** + +#### 11.2.9 Commands — Shipments (`test/unit/commands/shipments.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `get` calls `client.shipments.get(id, true)` | SDK delegation | +| 2 | `get --no-containers` calls with `false` | Flag negation | +| 3 | `list` with all filter combinations | Filter mapping | +| 4 | `list --page --page-size` | Pagination | +| 5 | `update --attrs '{...}'` calls `client.shipments.update` | Update delegation | +| 6 | `update` with invalid JSON attrs exits 2 | Input validation | +| 7 | `stop-tracking` calls `client.shipments.stopTracking` | SDK delegation | +| 8 | `resume-tracking` calls `client.shipments.resumeTracking` | SDK delegation | +| 9 | Missing `` exits 2 | Usage validation | + +**Coverage target: 95%+** + +#### 11.2.10 Commands — Tracking Requests (`test/unit/commands/tracking-requests.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `list` calls `client.trackingRequests.list()` | SDK delegation | +| 2 | `list --page --page-size` | Pagination | +| 3 | `get ` calls `client.trackingRequests.get(id)` | SDK delegation | +| 4 | `create --type --number --scac` | Creation args | +| 5 | `create` missing `--type` exits 2 | Validation | +| 6 | `create` missing `--number` exits 2 | Validation | +| 7 | `update --attrs '{...}'` | Update delegation | +| 8 | `infer ` calls `client.trackingRequests.inferNumber` | SDK delegation | + +**Coverage target: 95%+** + +#### 11.2.11 Commands — Track (Smart) (`test/unit/commands/track.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `track ` calls `createFromInfer` | Auto-detect flow | +| 2 | `track --scac` overrides carrier | SCAC override | +| 3 | `track --type` overrides type | Type override | +| 4 | `track --ref A --ref B` passes refs | Multi-value flag | +| 5 | `track` with no number exits 2 | Usage validation | +| 6 | Handles `ValidationError` from infer gracefully | Error propagation | + +**Coverage target: 95%+** + +#### 11.2.12 Commands — Shipping Lines (`test/unit/commands/shipping-lines.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `list` calls `client.shippingLines.list()` | SDK delegation | +| 2 | `list --search term` passes search param | Search filter | + +**Coverage target: 100%** + +#### 11.2.13 Commands — Search (`test/unit/commands/search.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `search ` calls `client.search(query)` | SDK delegation | +| 2 | Missing `` exits 2 | Usage validation | + +**Coverage target: 100%** + +#### 11.2.14 Commands — Config (`test/unit/commands/config.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `config set token ` writes token | Token persistence | +| 2 | `config get token` reads token | Token retrieval | +| 3 | `config list` shows all config | Full listing | +| 4 | `config path` prints file path | Path display | +| 5 | `config set base-url ` writes URL | URL persistence | +| 6 | `config set` with unknown key warns | Unknown key handling | + +**Coverage target: 100%** + +#### 11.2.15 Commands — Discovery (`test/unit/commands/commands.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `commands` lists all available commands | Completeness | +| 2 | `commands --json` returns structured JSON | Machine-readable | +| 3 | Each command has name, syntax, args, flags, description | Schema validation | + +**Coverage target: 100%** + +#### 11.2.16 Util — Pagination (`test/unit/util/pagination.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Iterates all pages when `--all` specified | Multi-page iteration | +| 2 | Stops when `links.next` is null | Termination | +| 3 | Emits NDJSON (one JSON per line) | Stream format | +| 4 | Handles empty first page | Edge case | + +**Coverage target: 100%** + +#### 11.2.17 Util — TTY (`test/unit/util/tty.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | Detects TTY when `process.stdout.isTTY` is true | TTY detection | +| 2 | Detects non-TTY when piped | Pipe detection | +| 3 | Respects `NO_COLOR` env var | Color compliance | +| 4 | Respects `FORCE_COLOR` env var | Color override | + +**Coverage target: 100%** + +### 11.3 Integration Tests — Detailed Breakdown + +Integration tests spawn the actual CLI binary and assert on stdout, stderr, and exit code. + +#### 11.3.1 CLI Process Execution (`test/integration/cli-exec.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `t49 --version` prints version and exits 0 | Basic execution | +| 2 | `t49 --help` prints help text and exits 0 | Help output | +| 3 | `t49 unknown-command` exits 2 with error | Unknown command | +| 4 | `t49 containers get` (no id) exits 2 | Missing required arg | +| 5 | `t49 containers list --json` (no token) exits 3 | Auth error | +| 6 | `t49 config path` prints a file path | Config path | + +**Coverage target: 100%** + +#### 11.3.2 Commands with Mocked API (`test/integration/containers.integration.test.ts`) + +Uses a local HTTP mock server (e.g. `msw` or `nock`) to simulate API responses. + +| # | Test Case | Validates | +|---|---|---| +| 1 | `containers list --json` returns JSON envelope with items | Full flow | +| 2 | `containers get --json` returns single container | Get flow | +| 3 | `containers list --table` renders a table | Table rendering | +| 4 | `containers events --json` returns events | Events flow | +| 5 | `containers list --json --fields number,status` projects fields | Field projection | +| 6 | `containers list --json --compact` outputs minified JSON | Compact mode | +| 7 | API returns 404 → exit 5 with `NOT_FOUND` error | Error mapping | +| 8 | API returns 429 → exit 7 with `RATE_LIMITED` error | Rate limit | +| 9 | API returns 500 → exit 8 with `UPSTREAM_ERROR` error | Server error | + +**Coverage target: 90%+** + +#### 11.3.3 Shipments Integration (`test/integration/shipments.integration.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `shipments list --json` with filters | Filter passthrough | +| 2 | `shipments get --json` | Get flow | +| 3 | `shipments update --attrs '...' --json` | Update flow | +| 4 | `shipments stop-tracking ` | Stop tracking flow | +| 5 | `shipments resume-tracking ` | Resume tracking flow | + +**Coverage target: 90%+** + +#### 11.3.4 Tracking Requests Integration (`test/integration/tracking-requests.integration.test.ts`) + +| # | Test Case | Validates | +|---|---|---| +| 1 | `tracking-requests create --type container --number X --scac Y` | Create flow | +| 2 | `tracking-requests infer --json` | Infer flow | +| 3 | `track --json` end-to-end | Smart track flow | +| 4 | `tracking-requests list --page 1 --page-size 5` | Paginated list | + +**Coverage target: 90%+** + +### 11.4 E2E / Smoke Tests (`test/e2e/smoke.test.ts`) + +Runs against the live Terminal49 API. Requires `T49_API_TOKEN`. Gated in CI behind a flag. + +| # | Test Case | Validates | +|---|---|---| +| 1 | `t49 containers list --json --page-size 1` returns valid JSON | Live API | +| 2 | `t49 shipments list --json --page-size 1` returns valid JSON | Live API | +| 3 | `t49 shipping-lines list --json` returns list | Live API | +| 4 | `t49 search "test" --json` returns results | Live API | +| 5 | `t49 tracking-requests list --json --page-size 1` returns valid JSON | Live API | + +**Coverage: Not tracked (live API)** + +### 11.5 Coverage Targets & Tracking + +| Layer | Target | Measured By | +|---|---|---| +| **Unit tests** | ≥ 90% lines, ≥ 85% branches | `vitest --coverage` | +| **Integration tests** | ≥ 80% lines | `vitest --coverage` with integration suite | +| **Combined** | ≥ 90% lines, ≥ 85% branches | Merged coverage report | +| **Error paths** | 100% | Manual audit of error mapping module | +| **Command registration** | 100% | Every SDK method has a corresponding CLI command | + +### 11.6 Coverage Configuration + +```typescript +// vitest.config.ts +export default defineConfig({ + test: { + globals: true, + environment: 'node', + exclude: ['dist/**', 'test/e2e/**'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html', 'lcov'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.test.ts', 'bin/**'], + thresholds: { + lines: 90, + branches: 85, + functions: 90, + statements: 90, + }, + }, + }, +}); +``` + +### 11.7 Coverage Tracking in CI + +```yaml +# .github/workflows/test-cli.yml +- name: Run tests with coverage + run: npx vitest run --coverage +- name: Check coverage thresholds + run: npx vitest run --coverage --reporter=json + # Fails CI if below thresholds defined in vitest.config.ts +- name: Upload coverage to Codecov (optional) + uses: codecov/codecov-action@v4 + with: + file: coverage/lcov.info + flags: cli +``` + +### 11.8 Test Execution Commands + +```bash +# All unit tests +npm test + +# Unit tests with coverage +npm run test:coverage + +# Integration tests only +npm run test:integration + +# E2E smoke tests (requires T49_API_TOKEN) +npm run test:e2e + +# Watch mode during development +npm run test:watch + +# Single command test +npx vitest run test/unit/commands/containers.test.ts +``` + +### 11.9 Test Dependencies + +```json +{ + "devDependencies": { + "vitest": "^4.0.13", + "@vitest/coverage-v8": "^4.0.18", + "msw": "^2.7.0", + "execa": "^9.5.0" + } +} +``` + +- **msw** (Mock Service Worker): Intercepts HTTP requests at the network level for integration tests. No changes to SDK code needed. +- **execa**: Spawns CLI process for integration tests, captures stdout/stderr/exitCode. + +### 11.10 Total Test Count Summary + +| Category | Test Count | +|---|---| +| Unit — Config | 7 | +| Unit — Client Factory | 8 | +| Unit — Errors | 12 | +| Unit — Output (JSON) | 7 | +| Unit — Output (Table) | 6 | +| Unit — Output (Fields) | 5 | +| Unit — Output (Formatter) | 8 | +| Unit — Cmd: Containers | 15 | +| Unit — Cmd: Shipments | 9 | +| Unit — Cmd: Tracking Requests | 8 | +| Unit — Cmd: Track | 6 | +| Unit — Cmd: Shipping Lines | 2 | +| Unit — Cmd: Search | 2 | +| Unit — Cmd: Config | 6 | +| Unit — Cmd: Commands | 3 | +| Unit — Util: Pagination | 4 | +| Unit — Util: TTY | 4 | +| **Unit Total** | **~112** | +| Integration — CLI Exec | 6 | +| Integration — Containers | 9 | +| Integration — Shipments | 5 | +| Integration — Tracking Requests | 4 | +| **Integration Total** | **~24** | +| E2E — Smoke | 5 | +| **Grand Total** | **~141** | + +--- + +## Appendix A: CLI Best Practices Checklist + +Based on [Node.js CLI Apps Best Practices](https://github.com/lirantal/nodejs-cli-apps-best-practices): + +- [x] Respect POSIX args conventions +- [x] Provide `--help` and `--version` +- [x] Use `stdout` for data, `stderr` for messages +- [x] Provide exit codes per POSIX conventions +- [x] Respect `NO_COLOR` and `FORCE_COLOR` +- [x] Graceful shutdown on SIGINT/SIGTERM +- [x] Zero-config with env vars +- [x] Stateful config for API tokens +- [x] Actionable error messages with hints +- [x] Shell completions (bash, zsh, fish) +- [x] Machine-readable output (`--json`) +- [x] Non-interactive by default (critical for LLM agents) +- [x] Composable with pipes +- [x] Installable via npx / npm global + +## Appendix B: LLM Agent Integration Examples + +### Example: OpenAI Function Calling Tool Definition + +```json +{ + "type": "function", + "function": { + "name": "terminal49_cli", + "description": "Query Terminal49 container tracking API via CLI", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Full t49 command, e.g. 'containers list --status active --json --compact'" + } + }, + "required": ["command"] + } + } +} +``` + +### Example: Claude MCP Tool + +```json +{ + "name": "t49", + "description": "Terminal49 container tracking CLI. Use 't49 commands --json' to discover all available commands.", + "input_schema": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": {"type": "string"}, + "description": "CLI arguments, e.g. ['containers', 'list', '--status', 'active', '--json']" + } + } + } +} +``` + +### Example: Agent Workflow + +```bash +# Step 1: Agent discovers available commands +t49 commands --json + +# Step 2: Agent tracks a container +t49 track MSCU1234567 --json --compact + +# Step 3: Agent checks container status +t49 containers get abc-123 --json --compact --fields number,status,location + +# Step 4: Agent lists all active shipments +t49 shipments list --status active --json --compact --fields id,billOfLading,shippingLineScac +``` diff --git a/sdks/typescript-sdk-cli/README.md b/sdks/typescript-sdk-cli/README.md new file mode 100644 index 00000000..064e6016 --- /dev/null +++ b/sdks/typescript-sdk-cli/README.md @@ -0,0 +1,57 @@ +# Terminal49 CLI + +`@terminal49/cli` is a command-line interface for Terminal49 APIs, designed for humans and automation agents. + +## Installation + +Use npm: + +```bash +npm install -g @terminal49/cli +``` + +Or run from source: + +```bash +cd sdks/typescript-sdk-cli +npm run dev -- t49 --help +``` + +## Authentication + +Use any one of: + +- `--token ` +- `T49_API_TOKEN` environment variable +- `t49 config set token ` + +## Commands + +- `t49 shipments get|list|update|stop-tracking|resume-tracking` +- `t49 containers get|list|events|route|raw-events|refresh|demurrage|rail` +- `t49 tracking-requests list|get|create|update|infer|create-from-infer` +- `t49 track ` +- `t49 shipping-lines list` +- `t49 search ` +- `t49 config path|get|set|list|clear|auth-status` +- `t49 commands` + +`--json` returns stable JSON envelopes. + +## Live Fixture Smoke Coverage + +To validate output formatting against real read-only API payloads: + +```bash +cd sdks/typescript-sdk-cli +export T49_API_TOKEN=TokenOrBearerValue +pnpm fixtures:capture:live +pnpm test +``` + +This captures JSON + table fixtures under: + +- `test/fixtures/api/live/` +- `test/fixtures/table/live/` + +Error fixtures are also captured for known read-only failure modes (for example 400/500 envelopes) so output behavior remains test-covered. diff --git a/sdks/typescript-sdk-cli/bin/t49.ts b/sdks/typescript-sdk-cli/bin/t49.ts new file mode 100644 index 00000000..9604c91b --- /dev/null +++ b/sdks/typescript-sdk-cli/bin/t49.ts @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +/** + * Terminal49 CLI entry point. + * + * This file bootstraps the Commander program defined in src/index.ts, + * loads global configuration, and executes the matched command. + */ + +import { createProgram } from '../src/index.js'; + +const program = createProgram(); +await program.parseAsync(process.argv); diff --git a/sdks/typescript-sdk-cli/biome.json b/sdks/typescript-sdk-cli/biome.json new file mode 100644 index 00000000..ebd3e55e --- /dev/null +++ b/sdks/typescript-sdk-cli/biome.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/sdks/typescript-sdk-cli/package.json b/sdks/typescript-sdk-cli/package.json new file mode 100644 index 00000000..e8369176 --- /dev/null +++ b/sdks/typescript-sdk-cli/package.json @@ -0,0 +1,57 @@ +{ + "name": "@terminal49/cli", + "version": "0.1.0", + "description": "Terminal49 container tracking CLI — for LLM agents, chat interfaces, and humans", + "type": "module", + "bin": { + "t49": "./dist/bin/t49.js" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "type-check": "tsc --noEmit", + "dev": "tsx bin/t49.ts", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:integration": "vitest run test/integration", + "test:e2e": "vitest run test/e2e", + "fixtures:capture:live": "bash scripts/capture-live-fixtures.sh", + "lint": "biome check src/ test/ bin/", + "lint:fix": "biome check --write src/ test/ bin/", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "README.md" + ], + "keywords": [ + "terminal49", + "container-tracking", + "shipping", + "cli", + "llm", + "agent" + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "dependencies": { + "@terminal49/sdk": "workspace:*", + "commander": "^13.0.0", + "chalk": "^5.4.0", + "cli-table3": "^0.6.5" + }, + "devDependencies": { + "typescript": "^5.6.3", + "vitest": "^4.0.13", + "@vitest/coverage-v8": "^4.0.18", + "@biomejs/biome": "^1.9.4", + "@types/node": "^20.19.0", + "msw": "^2.7.0", + "execa": "^9.5.0", + "tsx": "^4.19.0" + } +} diff --git a/sdks/typescript-sdk-cli/scripts/capture-live-fixtures.sh b/sdks/typescript-sdk-cli/scripts/capture-live-fixtures.sh new file mode 100755 index 00000000..00d7c344 --- /dev/null +++ b/sdks/typescript-sdk-cli/scripts/capture-live-fixtures.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -z "${T49_API_TOKEN:-}" ]]; then + echo "T49_API_TOKEN is required" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +API_FIXTURES="$CLI_DIR/test/fixtures/api/live" +TABLE_FIXTURES="$CLI_DIR/test/fixtures/table/live" + +mkdir -p "$API_FIXTURES" "$TABLE_FIXTURES" + +run_json() { + local out="$1" + shift + pnpm --dir "$CLI_DIR" --silent dev "$@" --json > "$API_FIXTURES/$out" +} + +run_table() { + local out="$1" + shift + pnpm --dir "$CLI_DIR" --silent dev "$@" --table > "$TABLE_FIXTURES/$out" +} + +read_id() { + local file="$1" + local expr="$2" + node -e "const fs=require('fs'); const j=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); const get=(obj,path)=>path.split('.').reduce((v,p)=>v&&v[p],obj); const id=get(j, process.argv[2]) || ''; process.stdout.write(String(id));" "$file" "$expr" +} + +run_json "shipments.list.json" shipments list --page-size 2 +run_json "containers.list.json" containers list --page-size 2 +run_json "tracking-requests.list.json" tracking-requests list --page-size 2 +run_json "shipping-lines.list.json" shipping-lines list --search HLCU +run_json "search.container-number.json" search HLCUIT1251213429 + +shipment_id="$(read_id "$API_FIXTURES/shipments.list.json" "data.items.0.id")" +container_id="$(read_id "$API_FIXTURES/containers.list.json" "data.items.0.id")" +tracking_id="$(read_id "$API_FIXTURES/tracking-requests.list.json" "data.items.0.id")" +shipment_bol="$(read_id "$API_FIXTURES/shipments.list.json" "data.items.0.billOfLading")" +container_number="$(read_id "$API_FIXTURES/containers.list.json" "data.items.0.number")" +tracking_number="$(read_id "$API_FIXTURES/tracking-requests.list.json" "data.items.0.requestNumber")" +terminal_id="$(read_id "$API_FIXTURES/containers.list.json" "data.items.0.terminals.podTerminal.id")" +vessel_imo="$(read_id "$API_FIXTURES/shipments.list.json" "data.items.0.vesselAtPod.imo")" +port_code="$(read_id "$API_FIXTURES/shipments.list.json" "data.items.0.ports.portOfLading.code")" + +run_json "shipments.get.json" shipments get "$shipment_id" +run_json "containers.get.json" containers get "$container_id" +run_json "tracking-requests.get.json" tracking-requests get "$tracking_id" +run_json "containers.events.json" containers events "$container_id" +run_json "containers.raw-events.json" containers raw-events "$container_id" +run_json "containers.demurrage.json" containers demurrage "$container_id" +run_json "containers.rail.json" containers rail "$container_id" +run_json "search.shipment-bol.json" search "$shipment_bol" +run_json "search.container-list-number.json" search "$container_number" +run_json "search.tracking-number.json" search "$tracking_number" + +run_json "webhooks.list.json" webhooks list --page-size 2 +run_json "webhooks.ips.json" webhooks ips +run_json "webhook-notifications.list.json" webhook-notifications list --page-size 2 +run_json "parties.list.json" parties list --page-size 2 +run_json "custom-field-definitions.list.json" custom-field-definitions list --page-size 2 + +if [[ -n "$terminal_id" ]]; then + run_json "terminals.get.json" terminals get "$terminal_id" +fi +if [[ -n "$vessel_imo" ]]; then + run_json "vessels.get-by-imo.json" vessels get-by-imo "$vessel_imo" +fi +if [[ -n "$port_code" ]]; then + run_json "ports.get.json" ports get "$port_code" +fi + +set +e +pnpm --dir "$CLI_DIR" --silent dev containers route "$container_id" --json > /tmp/t49.route.out 2> "$API_FIXTURES/containers.route.error.json" +pnpm --dir "$CLI_DIR" --silent dev containers map "$container_id" --json > /tmp/t49.map.out 2> "$API_FIXTURES/containers.map.error.json" +pnpm --dir "$CLI_DIR" --silent dev custom-fields list --page-size 2 --json > /tmp/t49.custom-fields.out 2> "$API_FIXTURES/custom-fields.list.error.json" +pnpm --dir "$CLI_DIR" --silent dev webhook-notifications examples --json > /tmp/t49.webhook-examples.out 2> "$API_FIXTURES/webhook-notifications.examples.error.json" +set -e + +run_table "shipments.list.txt" shipments list --page-size 2 +run_table "containers.list.txt" containers list --page-size 2 +run_table "tracking-requests.list.txt" tracking-requests list --page-size 2 +run_table "shipping-lines.list.txt" shipping-lines list --search HLCU +run_table "search.container-number.txt" search HLCUIT1251213429 +run_table "custom-field-definitions.list.txt" custom-field-definitions list --page-size 2 +run_table "parties.list.txt" parties list --page-size 2 +if [[ -n "$terminal_id" ]]; then + run_table "terminals.get.txt" terminals get "$terminal_id" +fi + +echo "Live fixtures captured in $API_FIXTURES and $TABLE_FIXTURES" diff --git a/sdks/typescript-sdk-cli/src/client-factory.ts b/sdks/typescript-sdk-cli/src/client-factory.ts new file mode 100644 index 00000000..5eaeb9b9 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/client-factory.ts @@ -0,0 +1,56 @@ +/** + * Creates a Terminal49Client instance from CLI flags, environment + * variables, and config file — in that priority order. + * + * Token resolution: + * 1. --token flag + * 2. T49_API_TOKEN env var + * 3. ~/.config/terminal49/config.json + * 4. Throw AUTH_MISSING error + */ + +import { + AuthenticationError, + Terminal49Client, + type ResponseFormat, +} from '@terminal49/sdk'; +import { loadConfig } from './config.js'; + +export interface CliGlobalOptions { + token?: string; + baseUrl?: string; + format?: 'raw' | 'mapped' | 'both'; + maxRetries?: number; + authScheme?: 'Token' | 'Bearer'; +} + +export async function createClient( + opts: CliGlobalOptions = {}, +): Promise { + const cfg = await loadConfig(); + const token = opts.token?.trim() || process.env.T49_API_TOKEN || cfg.token; + if (!token || token.trim() === '') { + throw new AuthenticationError('Missing authentication token. Set --token, T49_API_TOKEN, or config token.'); + } + const resolveAuthScheme = ( + value: string | undefined, + ): 'Token' | 'Bearer' | undefined => { + if (!value) return undefined; + const normalized = value.trim().toLowerCase(); + return normalized === 'bearer' ? 'Bearer' : 'Token'; + }; + const authScheme = resolveAuthScheme(opts.authScheme) + || resolveAuthScheme(process.env.T49_AUTH_SCHEME) + || resolveAuthScheme(cfg.authScheme) + || (token.match(/^(Token|Bearer)\s+/i)?.[0]?.trim().toLowerCase() === 'bearer' + ? 'Bearer' + : undefined); + + return new Terminal49Client({ + apiToken: token, + apiBaseUrl: opts.baseUrl?.trim() || cfg.baseUrl, + maxRetries: opts.maxRetries ?? cfg.maxRetries, + defaultFormat: (opts.format as ResponseFormat) ?? cfg.defaultFormat ?? 'raw', + authScheme, + }); +} diff --git a/sdks/typescript-sdk-cli/src/commands/commands.ts b/sdks/typescript-sdk-cli/src/commands/commands.ts new file mode 100644 index 00000000..41918dca --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/commands.ts @@ -0,0 +1,62 @@ +/** + * t49 commands + * + * Lists all available commands with their syntax, args, flags, + * and descriptions. Designed for LLM agent discovery: + * + * t49 commands --json + * + * Returns structured metadata that an agent can use to construct + * any subsequent CLI call with full type information. + */ + +import { Command } from 'commander'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +interface CommandMetadata { + name: string; + description: string; + usage: string; + options: { + flags: string[]; + description: string; + }[]; + subcommands: CommandMetadata[]; +} + +function collectCommands(program: Command): CommandMetadata[] { + return program.commands.map((command) => ({ + name: command.name(), + description: command.description(), + usage: command.usage(), + options: command.options.map((option) => ({ + flags: [option.short, option.long].filter(Boolean) as string[], + description: option.description, + })), + subcommands: command.commands.length > 0 ? collectCommands(command) : [], + })); +} + +export function registerCommandsCommand(program: Command): void { + program + .command('commands') + .description('List available commands and supported flags') + .option('--json', 'Force JSON output') + .action( + withErrorHandling( + 'commands', + async (_options: { json?: boolean }, command: Command) => { + const opts = command.optsWithGlobals(); + const formatter = createFormatter({ + json: Boolean(opts.json), + compact: opts.compact, + }); + formatter.output('commands', { + command: 'commands', + items: collectCommands(program), + }); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/config.ts b/sdks/typescript-sdk-cli/src/commands/config.ts new file mode 100644 index 00000000..0d27597f --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/config.ts @@ -0,0 +1,157 @@ +/** + * t49 config + * + * Subcommands: set, get, list, path + * + * Manages persistent configuration in ~/.config/terminal49/config.json + */ + +import { Command } from 'commander'; +import { createFormatter } from '../output/formatter.js'; +import { createClient } from '../client-factory.js'; +import { loadConfig, writeConfig, getConfigPath, resetConfig } from '../config.js'; +import { withErrorHandling } from '../errors.js'; + +type ConfigOutput = Record; + +export function registerConfigCommand(program: Command): void { + const cmd = program.command('config').description('View and manage CLI config'); + + cmd + .command('path') + .description('Print path to config file') + .action(() => { + process.stdout.write(`${getConfigPath()}\n`); + }); + + cmd + .command('get ') + .description('Read config value') + .action( + withErrorHandling( + 'config.get', + async (key: string, _options: unknown, command: Command) => { + const config = await loadConfig(); + const value = config[key as keyof typeof config]; + const formatter = createFormatter({ + json: command.optsWithGlobals().json, + compact: command.optsWithGlobals().compact, + }); + formatter.output('config.get', { key, value } as ConfigOutput); + }, + ), + ); + + cmd + .command('set ') + .description('Set a config value') + .action( + withErrorHandling( + 'config.set', + async (key: string, value: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + compact: global.compact, + }); + const parsed = parseValue(value); + const next = await writeConfig({ [key]: parsed } as Partial); + formatter.output('config.set', next); + }, + ), + ); + + cmd + .command('list') + .description('List all config values') + .action( + withErrorHandling( + 'config.list', + async (command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + compact: global.compact, + }); + const cfg = await loadConfig(); + formatter.output('config.list', cfg); + }, + ), + ); + + cmd + .command('clear') + .description('Clear config values by deleting the file') + .action( + withErrorHandling( + 'config.clear', + async (command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + compact: global.compact, + }); + await resetConfig(); + formatter.output('config.clear', { removed: true }); + }, + ), + ); + + cmd + .command('auth-status') + .description('Check whether the CLI has usable auth credentials') + .action( + withErrorHandling( + 'config.auth-status', + async (command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + compact: global.compact, + }); + const cfg = await loadConfig(); + const status = { + hasToken: Boolean(global.token || process.env.T49_API_TOKEN || cfg.token), + tokenSource: global.token + ? 'flag' + : process.env.T49_API_TOKEN + ? 'env' + : cfg.token + ? 'config' + : 'missing', + }; + formatter.output('config.auth-status', status); + }, + ), + ); + + cmd + .command('client-check') + .description('Verify client can be instantiated') + .action( + withErrorHandling( + 'config.client-check', + async (command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + compact: global.compact, + }); + await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + }); + formatter.output('config.client-check', { ok: true }); + }, + ), + ); +} + +function parseValue(value: string): unknown { + if (value === 'true') return true; + if (value === 'false') return false; + const numberValue = Number(value); + if (!Number.isNaN(numberValue) && value.trim() !== '') return numberValue; + return value; +} diff --git a/sdks/typescript-sdk-cli/src/commands/containers.ts b/sdks/typescript-sdk-cli/src/commands/containers.ts new file mode 100644 index 00000000..4e5fe201 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/containers.ts @@ -0,0 +1,340 @@ +/** + * t49 containers + * + * Subcommands: get, list, events, route, raw-events, refresh, demurrage, rail + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type SetCustomFieldOptions = { + value?: string; +}; + +function parseCustomFieldValue(value: string | undefined): unknown { + if (value === undefined) { + throw new Error('Missing --value for set-custom-field'); + } + try { + return JSON.parse(value); + } catch { + return value; + } +} + +export function registerContainersCommand(program: Command): void { + const cmd = program.command('containers').description('Container lookup and operations'); + + cmd + .command('get ') + .description('Fetch a container by id') + .option('--include ', 'Comma-separated include list') + .action( + withErrorHandling( + 'containers.get', + async (id: string, options: { include?: string }, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const include = + options.include + ?.split(',') + .map((value) => value.trim()) + .filter(Boolean) ?? ['shipment', 'pod_terminal']; + const result = await client.containers.get(id, include); + formatter.output('containers.get', result); + }, + ), + ); + + cmd + .command('list') + .description('List containers') + .option('--status ', 'Filter by status') + .option('--port ', 'Filter by pod locode') + .option('--carrier ', 'Filter by shipping line') + .option('--updated-after ', 'Filter by updated_at') + .option('--include ', 'Comma-separated include list for each container') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'containers.list', + async (options: { + status?: string; + port?: string; + carrier?: string; + updatedAfter?: string; + include?: string; + page?: number; + pageSize?: number; + }, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.containers.list( + { + status: options.status, + port: options.port, + carrier: options.carrier, + updatedAfter: options.updatedAfter, + include: options.include, + }, + { + page: options.page, + pageSize: options.pageSize, + format: global.format as 'raw' | 'mapped' | 'both', + }, + ); + formatter.output('containers.list', result); + }, + ), + ); + + cmd + .command('events ') + .description('Fetch transport events for a container') + .action( + withErrorHandling('containers.events', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.containers.events(id); + formatter.output('containers.events', result); + }), + ); + + cmd + .command('route ') + .description('Fetch container route') + .action( + withErrorHandling('containers.route', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.containers.route(id); + formatter.output('containers.route', result); + }), + ); + + cmd + .command('map ') + .description('Fetch container route geometry as GeoJSON') + .action( + withErrorHandling('containers.map', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.containers.map(id); + formatter.output('containers.map', result); + }), + ); + + cmd + .command('custom-fields ') + .description('Get custom fields for a container') + .action( + withErrorHandling( + 'containers.custom-fields', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.containers.customFields(id); + formatter.output('containers.custom-fields', result); + }, + ), + ); + + cmd + .command('set-custom-field ') + .description('Set a container custom field value') + .requiredOption('--value ', 'Custom field value. Use JSON for objects/arrays/numbers/booleans') + .action( + withErrorHandling( + 'containers.set-custom-field', + async ( + id: string, + fieldId: string, + options: SetCustomFieldOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const value = parseCustomFieldValue(options.value); + const result = await client.containers.setCustomField(id, fieldId, value); + formatter.output('containers.set-custom-field', result); + }, + ), + ); + + cmd + .command('raw-events ') + .description('Fetch raw container events') + .action( + withErrorHandling( + 'containers.raw-events', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: true, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: 'raw', + maxRetries: global.maxRetries, + }); + const result = await client.containers.rawEvents(id); + formatter.output('containers.raw-events', result); + }, + ), + ); + + cmd + .command('refresh ') + .description('Refresh a container') + .action( + withErrorHandling( + 'containers.refresh', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + maxRetries: global.maxRetries, + }); + const result = await client.containers.refresh(id); + formatter.output('containers.refresh', result); + }, + ), + ); + + cmd + .command('demurrage ') + .description('Get demurrage summary for a container') + .action( + withErrorHandling( + 'containers.demurrage', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + maxRetries: global.maxRetries, + }); + const result = await client.containers.demurrage(id); + formatter.output('containers.demurrage', result); + }, + ), + ); + + cmd + .command('rail ') + .description('Get rail milestones for a container') + .action( + withErrorHandling('containers.rail', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + maxRetries: global.maxRetries, + }); + const result = await client.getRailMilestones(id); + formatter.output('containers.rail', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/custom-field-definitions.ts b/sdks/typescript-sdk-cli/src/commands/custom-field-definitions.ts new file mode 100644 index 00000000..026facf1 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/custom-field-definitions.ts @@ -0,0 +1,174 @@ +/** + * t49 custom-field-definitions + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type PayloadOptions = { + payload?: string; +}; + +function parsePayload(payload: string | undefined): Record { + if (!payload) return {}; + try { + const parsed = JSON.parse(payload); + if (!parsed || typeof parsed !== 'object') return {}; + return parsed as Record; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +export function registerCustomFieldDefinitionsCommand(program: Command): void { + const cmd = program + .command('custom-field-definitions') + .description('Manage custom field definitions'); + + cmd + .command('list') + .description('List custom field definitions') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'custom-field-definitions.list', + async ( + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldDefinitions.list({ + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('custom-field-definitions.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a custom field definition') + .action( + withErrorHandling( + 'custom-field-definitions.get', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldDefinitions.get(id); + formatter.output('custom-field-definitions.get', result); + }, + ), + ); + + cmd + .command('create') + .description('Create a custom field definition') + .requiredOption('--payload ', 'Custom field definition JSON payload') + .action( + withErrorHandling( + 'custom-field-definitions.create', + async (options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldDefinitions.create( + parsePayload(options.payload), + ); + formatter.output('custom-field-definitions.create', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update a custom field definition') + .requiredOption('--payload ', 'Custom field definition JSON payload') + .action( + withErrorHandling( + 'custom-field-definitions.update', + async (id: string, options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldDefinitions.update( + id, + parsePayload(options.payload), + ); + formatter.output('custom-field-definitions.update', result); + }, + ), + ); + + cmd + .command('delete ') + .description('Delete a custom field definition') + .action( + withErrorHandling( + 'custom-field-definitions.delete', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldDefinitions.delete(id); + formatter.output('custom-field-definitions.delete', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/custom-field-options.ts b/sdks/typescript-sdk-cli/src/commands/custom-field-options.ts new file mode 100644 index 00000000..5c807d4f --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/custom-field-options.ts @@ -0,0 +1,189 @@ +/** + * t49 custom-field-options + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type PayloadOptions = { + payload?: string; +}; + +function parsePayload(payload: string | undefined): Record { + if (!payload) return {}; + try { + const parsed = JSON.parse(payload); + if (!parsed || typeof parsed !== 'object') return {}; + return parsed as Record; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +export function registerCustomFieldOptionsCommand(program: Command): void { + const cmd = program + .command('custom-field-options') + .description('Manage options for a custom field definition'); + + cmd + .command('list ') + .description('List options for a custom field definition') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'custom-field-options.list', + async ( + definitionId: string, + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldOptions.list(definitionId, { + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('custom-field-options.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a custom field option') + .action( + withErrorHandling( + 'custom-field-options.get', + async ( + definitionId: string, + optionId: string, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldOptions.get(definitionId, optionId); + formatter.output('custom-field-options.get', result); + }, + ), + ); + + cmd + .command('create ') + .description('Create a custom field option') + .requiredOption('--payload ', 'Option object JSON payload') + .action( + withErrorHandling( + 'custom-field-options.create', + async ( + definitionId: string, + options: PayloadOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const payload = parsePayload(options.payload); + const result = await client.customFieldOptions.create(definitionId, payload); + formatter.output('custom-field-options.create', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update a custom field option') + .requiredOption('--payload ', 'Option object JSON payload') + .action( + withErrorHandling( + 'custom-field-options.update', + async ( + definitionId: string, + optionId: string, + options: PayloadOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const payload = parsePayload(options.payload); + const result = await client.customFieldOptions.update( + definitionId, + optionId, + payload, + ); + formatter.output('custom-field-options.update', result); + }, + ), + ); + + cmd + .command('delete ') + .description('Delete a custom field option') + .action( + withErrorHandling( + 'custom-field-options.delete', + async (definitionId: string, optionId: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFieldOptions.delete(definitionId, optionId); + formatter.output('custom-field-options.delete', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/custom-fields.ts b/sdks/typescript-sdk-cli/src/commands/custom-fields.ts new file mode 100644 index 00000000..6290e044 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/custom-fields.ts @@ -0,0 +1,167 @@ +/** + * t49 custom-fields + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type PayloadOptions = { + payload?: string; +}; + +function parsePayload(payload: string | undefined): Record { + if (!payload) return {}; + try { + const parsed = JSON.parse(payload); + if (!parsed || typeof parsed !== 'object') return {}; + return parsed as Record; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +export function registerCustomFieldsCommand(program: Command): void { + const cmd = program.command('custom-fields').description('Manage custom fields'); + + cmd + .command('list') + .description('List custom field assignments') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'custom-fields.list', + async ( + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFields.list({ + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('custom-fields.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a custom field') + .action( + withErrorHandling( + 'custom-fields.get', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFields.get(id); + formatter.output('custom-fields.get', result); + }, + ), + ); + + cmd + .command('create') + .description('Create a custom field assignment') + .requiredOption('--payload ', 'Custom field JSON payload') + .action( + withErrorHandling( + 'custom-fields.create', + async (options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFields.create(parsePayload(options.payload)); + formatter.output('custom-fields.create', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update a custom field assignment') + .requiredOption('--payload ', 'Custom field JSON payload') + .action( + withErrorHandling( + 'custom-fields.update', + async (id: string, options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFields.update(id, parsePayload(options.payload)); + formatter.output('custom-fields.update', result); + }, + ), + ); + + cmd + .command('delete ') + .description('Delete a custom field assignment') + .action( + withErrorHandling( + 'custom-fields.delete', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.customFields.delete(id); + formatter.output('custom-fields.delete', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/metro-areas.ts b/sdks/typescript-sdk-cli/src/commands/metro-areas.ts new file mode 100644 index 00000000..4a41ff23 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/metro-areas.ts @@ -0,0 +1,38 @@ +/** + * t49 metro-areas + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerMetroAreasCommand(program: Command): void { + const cmd = program + .command('metro-areas') + .alias('metro_areas') + .description('Get metro area by id or locode'); + + cmd + .command('get ') + .description('Get a metro area') + .action( + withErrorHandling('metro-areas.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.metroAreas.get(id); + formatter.output('metro-areas.get', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/parties.ts b/sdks/typescript-sdk-cli/src/commands/parties.ts new file mode 100644 index 00000000..887db32f --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/parties.ts @@ -0,0 +1,69 @@ +/** + * t49 parties + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerPartiesCommand(program: Command): void { + const cmd = program.command('parties').description('List and get parties'); + + cmd + .command('list') + .description('List parties') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'parties.list', + async ( + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.parties.list({ + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('parties.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a party') + .action( + withErrorHandling('parties.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.parties.get(id); + formatter.output('parties.get', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/ports.ts b/sdks/typescript-sdk-cli/src/commands/ports.ts new file mode 100644 index 00000000..2a8fba6a --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/ports.ts @@ -0,0 +1,38 @@ +/** + * t49 ports + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerPortsCommand(program: Command): void { + const cmd = program + .command('ports') + .alias('port') + .description('Get port by id or locode'); + + cmd + .command('get ') + .description('Get a port') + .action( + withErrorHandling('ports.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.ports.get(id); + formatter.output('ports.get', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/search.ts b/sdks/typescript-sdk-cli/src/commands/search.ts new file mode 100644 index 00000000..f1f8cc2f --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/search.ts @@ -0,0 +1,37 @@ +/** + * t49 search + * + * Global search across shipments, containers, and tracking requests. + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerSearchCommand(program: Command): void { + const cmd = program.command('search').description('Search terminal49 resources'); + + cmd + .argument('') + .description('Search query') + .action( + withErrorHandling('search', async (query: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.search(query); + formatter.output('search', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/shipments.ts b/sdks/typescript-sdk-cli/src/commands/shipments.ts new file mode 100644 index 00000000..f2ee335e --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/shipments.ts @@ -0,0 +1,279 @@ +/** + * t49 shipments + * + * Subcommands: get, list, update, stop-tracking, resume-tracking + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type UpdateOptions = { + payload?: string; + attr?: string[]; +}; + +function parsePayload(payload: string | undefined, attrs?: string[]): Record { + const base = payload ? safeParseJson(payload) : {}; + const additional = parseAttrs(attrs); + return { ...base, ...additional }; +} + +function safeParseJson(payload: string): Record { + try { + const parsed = JSON.parse(payload); + return parsed && typeof parsed === 'object' ? (parsed as Record) : {}; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +function parseAttrs(attrs?: string[]): Record { + if (!attrs || attrs.length === 0) return {}; + const out: Record = {}; + attrs.forEach((pair) => { + const [rawKey, rawValue] = pair.split('=', 2); + if (!rawKey || rawValue === undefined) return; + out[rawKey.trim()] = rawValue; + }); + return out; +} + +type SetCustomFieldOptions = { + value?: string; +}; + +function parseCustomFieldValue(value: string | undefined): unknown { + if (value === undefined) { + throw new Error('Missing --value for set-custom-field'); + } + try { + return JSON.parse(value); + } catch { + return value; + } +} + +export function registerShipmentsCommand(program: Command): void { + const cmd = program.command('shipments').description('Shipment lookup and operations'); + + cmd + .command('get ') + .description('Fetch a shipment by id') + .option('--no-include-containers', 'Exclude containers in response') + .action( + withErrorHandling('shipments.get', async (id: string, options: { includeContainers?: boolean }, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.shipments.get(id, options.includeContainers ?? true); + formatter.output('shipments.get', result); + }), + ); + + cmd + .command('list') + .description('List shipments') + .option('--status ', 'Filter by status') + .option('--port ', 'Filter by pod locode') + .option('--carrier ', 'Filter by shipping line') + .option('--updated-after ', 'Filter by updated_at') + .option('--no-include-containers', 'Exclude container fields for each shipment') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'shipments.list', + async ( + options: { + status?: string; + port?: string; + carrier?: string; + updatedAfter?: string; + includeContainers?: boolean; + page?: number; + pageSize?: number; + }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.shipments.list( + { + status: options.status, + port: options.port, + carrier: options.carrier, + updatedAfter: options.updatedAfter, + includeContainers: options.includeContainers ?? true, + }, + { + page: options.page, + pageSize: options.pageSize, + format: global.format as 'raw' | 'mapped' | 'both', + }, + ); + formatter.output('shipments.list', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update shipment attributes') + .option('--payload ', 'JSON payload for update body') + .option('--attr ', 'Individual attributes to set', (value: string, previous: string[] = []) => [...previous, value]) + .action( + withErrorHandling( + 'shipments.update', + async (id: string, options: UpdateOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const attrs = parsePayload(options.payload, options.attr); + const result = await client.shipments.update(id, attrs); + formatter.output('shipments.update', result); + }, + ), + ); + + cmd + .command('stop-tracking ') + .description('Stop tracking a shipment') + .action( + withErrorHandling( + 'shipments.stop-tracking', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + maxRetries: global.maxRetries, + }); + const result = await client.shipments.stopTracking(id); + formatter.output('shipments.stop-tracking', result); + }, + ), + ); + + cmd + .command('resume-tracking ') + .description('Resume shipment tracking') + .action( + withErrorHandling( + 'shipments.resume-tracking', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + maxRetries: global.maxRetries, + }); + const result = await client.shipments.resumeTracking(id); + formatter.output('shipments.resume-tracking', result); + }, + ), + ); + + cmd + .command('custom-fields ') + .description('Get custom fields for a shipment') + .action( + withErrorHandling( + 'shipments.custom-fields', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.shipments.customFields(id); + formatter.output('shipments.custom-fields', result); + }, + ), + ); + + cmd + .command('set-custom-field ') + .description('Set a shipment custom field value') + .requiredOption('--value ', 'Custom field value. Use JSON for objects/arrays/numbers/booleans') + .action( + withErrorHandling( + 'shipments.set-custom-field', + async ( + id: string, + fieldId: string, + options: SetCustomFieldOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const value = parseCustomFieldValue(options.value); + const result = await client.shipments.setCustomField(id, fieldId, value); + formatter.output('shipments.set-custom-field', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/shipping-lines.ts b/sdks/typescript-sdk-cli/src/commands/shipping-lines.ts new file mode 100644 index 00000000..c1b2aabc --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/shipping-lines.ts @@ -0,0 +1,44 @@ +/** + * t49 shipping-lines + * + * Subcommands: list + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerShippingLinesCommand(program: Command): void { + const cmd = program + .command('shipping-lines') + .alias('shipping_lines') + .description('List shipping lines'); + + cmd + .command('list') + .description('List shipping lines') + .option('--search ', 'Search shipping lines by name or code') + .action( + withErrorHandling( + 'shipping-lines.list', + async (options: { search?: string }, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.shippingLines.list(options.search); + formatter.output('shipping-lines.list', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/terminals.ts b/sdks/typescript-sdk-cli/src/commands/terminals.ts new file mode 100644 index 00000000..9c2258a8 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/terminals.ts @@ -0,0 +1,37 @@ +/** + * t49 terminals + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerTerminalsCommand(program: Command): void { + const cmd = program + .command('terminals') + .description('Get terminal by id'); + + cmd + .command('get ') + .description('Get a terminal') + .action( + withErrorHandling('terminals.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.terminals.get(id); + formatter.output('terminals.get', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/track.ts b/sdks/typescript-sdk-cli/src/commands/track.ts new file mode 100644 index 00000000..afe29a64 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/track.ts @@ -0,0 +1,70 @@ +/** + * t49 track + * + * Smart tracking shortcut — auto-detects carrier and number type + * via the SDK's inferNumber + createFromInfer flow. + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type TrackOptions = { + scac?: string; + type?: string; + refNumbers?: string; + shipmentTags?: string; +}; + +function splitList(value?: string): string[] | undefined { + if (!value) return undefined; + return value + .split(',') + .map((item) => item.trim()) + .filter(Boolean); +} + +export function registerTrackCommand(program: Command): void { + const cmd = program + .command('track') + .description('Infer tracking number and create a request'); + + cmd + .argument('') + .option('--scac ', 'Carrier SCAC') + .option('--type ', 'Request type override') + .option('--ref-numbers ', 'Comma-separated reference numbers') + .option('--shipment-tags ', 'Comma-separated shipment tags') + .action( + withErrorHandling( + 'track', + async ( + value: string, + options: TrackOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.createFromInfer(value, { + scac: options.scac, + numberType: options.type, + refNumbers: splitList(options.refNumbers), + shipmentTags: splitList(options.shipmentTags), + }); + formatter.output('track', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/tracking-requests.ts b/sdks/typescript-sdk-cli/src/commands/tracking-requests.ts new file mode 100644 index 00000000..7abf5b16 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/tracking-requests.ts @@ -0,0 +1,263 @@ +/** + * t49 tracking-requests + * + * Subcommands: list, get, create, update, infer + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type TrackingRequestType = 'container' | 'bill_of_lading' | 'booking_number'; + +type TrackingCreateOptions = { + scac?: string; + refNumbers?: string; + shipmentTags?: string; +}; + +type TrackingUpdateOptions = { + payload?: string; +}; + +type TrackingInferOptions = { + scac?: string; + type?: string; + refNumbers?: string; + shipmentTags?: string; +}; + +function parseList(values: string | undefined): string[] | undefined { + if (!values || values.trim() === '') return undefined; + return values + .split(',') + .map((value) => value.trim()) + .filter(Boolean); +} + +function safeParse(payload: string | undefined): Record { + if (!payload) return {}; + try { + const parsed = JSON.parse(payload); + if (!parsed || typeof parsed !== 'object') return {}; + return parsed as Record; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +function mapType(value: string | undefined): TrackingRequestType { + if (!value) return 'container'; + if (value === 'booking') return 'booking_number'; + if (value === 'booking_number' || value === 'container' || value === 'bill_of_lading') { + return value; + } + throw new Error('Invalid tracking request type'); +} + +export function registerTrackingRequestsCommand(program: Command): void { + const cmd = program + .command('tracking-requests') + .alias('tracking_requests') + .description('Tracking request commands'); + + cmd + .command('list') + .description('List tracking requests') + .option('--status ', 'Filter by status') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'tracking-requests.list', + async ( + options: { + status?: string; + page?: number; + pageSize?: number; + }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.list( + options.status ? { status: options.status } : {}, + { + page: options.page, + pageSize: options.pageSize, + format: global.format as 'raw' | 'mapped' | 'both', + }, + ); + formatter.output('tracking-requests.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a tracking request by id') + .action( + withErrorHandling('tracking-requests.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.get(id); + formatter.output('tracking-requests.get', result); + }), + ); + + cmd + .command('create ') + .description('Create a tracking request') + .option('--scac ', 'Carrier SCAC') + .option('--ref-numbers ', 'Comma-separated reference numbers') + .option('--shipment-tags ', 'Comma-separated shipment tags') + .action( + withErrorHandling( + 'tracking-requests.create', + async ( + type: string, + requestNumber: string, + options: TrackingCreateOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.create({ + requestType: mapType(type), + requestNumber, + scac: options.scac, + refNumbers: parseList(options.refNumbers), + shipmentTags: parseList(options.shipmentTags), + }); + formatter.output('tracking-requests.create', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update a tracking request') + .option('--payload ', 'JSON payload') + .action( + withErrorHandling( + 'tracking-requests.update', + async (id: string, options: TrackingUpdateOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const attrs = safeParse(options.payload); + const result = await client.trackingRequests.update(id, attrs); + formatter.output('tracking-requests.update', result); + }, + ), + ); + + cmd + .command('infer ') + .description('Infer SCAC and request type') + .action( + withErrorHandling( + 'tracking-requests.infer', + async (number: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.inferNumber(number); + formatter.output('tracking-requests.infer', result); + }, + ), + ); + + cmd + .command('create-from-infer ') + .description('Infer and create tracking request') + .option('--scac ', 'Carrier SCAC') + .option('--type ', 'Request type override') + .option('--ref-numbers ', 'Comma-separated reference numbers') + .option('--shipment-tags ', 'Comma-separated shipment tags') + .action( + withErrorHandling( + 'tracking-requests.create-from-infer', + async ( + number: string, + options: TrackingInferOptions, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.trackingRequests.createFromInfer(number, { + scac: options.scac, + numberType: options.type, + refNumbers: parseList(options.refNumbers), + shipmentTags: parseList(options.shipmentTags), + }); + formatter.output('tracking-requests.create-from-infer', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/vessels.ts b/sdks/typescript-sdk-cli/src/commands/vessels.ts new file mode 100644 index 00000000..cbb85ac8 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/vessels.ts @@ -0,0 +1,113 @@ +/** + * t49 vessels + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerVesselsCommand(program: Command): void { + const cmd = program.command('vessels').description('Vessel lookup and forecasting'); + + cmd + .command('get ') + .description('Get a vessel by id') + .action( + withErrorHandling('vessels.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.vessels.get(id); + formatter.output('vessels.get', result); + }), + ); + + cmd + .command('get-by-imo ') + .description('Get a vessel by IMO') + .action( + withErrorHandling( + 'vessels.get-by-imo', + async (imo: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.vessels.getByImo(imo); + formatter.output('vessels.get-by-imo', result); + }, + ), + ); + + cmd + .command('future-positions ') + .description('Get vessel future positions') + .action( + withErrorHandling( + 'vessels.future-positions', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.vessels.futurePositions(id); + formatter.output('vessels.future-positions', result); + }, + ), + ); + + cmd + .command('future-positions-coords ') + .description('Get vessel future positions with coordinates') + .action( + withErrorHandling( + 'vessels.future-positions-coords', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.vessels.futurePositionsWithCoords(id); + formatter.output('vessels.future-positions-coords', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/webhook-notifications.ts b/sdks/typescript-sdk-cli/src/commands/webhook-notifications.ts new file mode 100644 index 00000000..0a701c24 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/webhook-notifications.ts @@ -0,0 +1,102 @@ +/** + * t49 webhook-notifications + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +export function registerWebhookNotificationsCommand(program: Command): void { + const cmd = program + .command('webhook-notifications') + .alias('webhook_notifications') + .description('List webhook notification events'); + + cmd + .command('list') + .description('List webhook notifications') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'webhook-notifications.list', + async ( + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhookNotifications.list({ + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('webhook-notifications.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a webhook notification') + .action( + withErrorHandling( + 'webhook-notifications.get', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhookNotifications.get(id); + formatter.output('webhook-notifications.get', result); + }, + ), + ); + + cmd + .command('examples') + .description('Get webhook payload examples') + .option('--event ', 'Filter by event type') + .action( + withErrorHandling( + 'webhook-notifications.examples', + async (options: { event?: string }, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhookNotifications.examples(options.event); + formatter.output('webhook-notifications.examples', result); + }, + ), + ); +} diff --git a/sdks/typescript-sdk-cli/src/commands/webhooks.ts b/sdks/typescript-sdk-cli/src/commands/webhooks.ts new file mode 100644 index 00000000..934f9f30 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/commands/webhooks.ts @@ -0,0 +1,189 @@ +/** + * t49 webhooks + */ + +import { Command } from 'commander'; +import { createClient } from '../client-factory.js'; +import { createFormatter } from '../output/formatter.js'; +import { withErrorHandling } from '../errors.js'; + +type PayloadOptions = { + payload?: string; +}; + +function parsePayload(payload: string | undefined): Record { + if (!payload) return {}; + try { + const parsed = JSON.parse(payload); + if (!parsed || typeof parsed !== 'object') return {}; + return parsed as Record; + } catch { + throw new Error('Invalid JSON in --payload'); + } +} + +export function registerWebhooksCommand(program: Command): void { + const cmd = program.command('webhooks').description('Manage webhooks'); + + cmd + .command('list') + .description('List webhooks') + .option('--page ', 'Page number', (value) => Number.parseInt(value, 10)) + .option('--page-size ', 'Page size', (value) => Number.parseInt(value, 10)) + .action( + withErrorHandling( + 'webhooks.list', + async ( + options: { page?: number; pageSize?: number }, + command: Command, + ) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhooks.list({ + page: options.page, + pageSize: options.pageSize, + }); + formatter.output('webhooks.list', result); + }, + ), + ); + + cmd + .command('get ') + .description('Get a webhook') + .action( + withErrorHandling('webhooks.get', async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhooks.get(id); + formatter.output('webhooks.get', result); + }), + ); + + cmd + .command('create') + .description('Create a webhook') + .requiredOption('--payload ', 'Webhook object JSON payload') + .action( + withErrorHandling( + 'webhooks.create', + async (options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const payload = parsePayload(options.payload); + const result = await client.webhooks.create(payload); + formatter.output('webhooks.create', result); + }, + ), + ); + + cmd + .command('update ') + .description('Update a webhook') + .requiredOption('--payload ', 'Webhook object JSON payload') + .action( + withErrorHandling( + 'webhooks.update', + async (id: string, options: PayloadOptions, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const payload = parsePayload(options.payload); + const result = await client.webhooks.update(id, payload); + formatter.output('webhooks.update', result); + }, + ), + ); + + cmd + .command('delete ') + .description('Delete a webhook') + .action( + withErrorHandling( + 'webhooks.delete', + async (id: string, _options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhooks.delete(id); + formatter.output('webhooks.delete', result); + }, + ), + ); + + cmd + .command('ips') + .description('List webhook IP ranges') + .action( + withErrorHandling('webhooks.ips', async (_options: unknown, command: Command) => { + const global = command.optsWithGlobals(); + const formatter = createFormatter({ + json: global.json, + table: global.table, + compact: global.compact, + fields: global.fields, + }); + const client = await createClient({ + token: global.token, + baseUrl: global.baseUrl, + format: global.format as 'raw' | 'mapped' | 'both', + maxRetries: global.maxRetries, + }); + const result = await client.webhooks.ips(); + formatter.output('webhooks.ips', result); + }), + ); +} diff --git a/sdks/typescript-sdk-cli/src/config.ts b/sdks/typescript-sdk-cli/src/config.ts new file mode 100644 index 00000000..180f644c --- /dev/null +++ b/sdks/typescript-sdk-cli/src/config.ts @@ -0,0 +1,156 @@ +import { constants } from 'node:fs'; +import { promises as fs } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +export type CliOutputMode = 'json' | 'table'; +export type CliResponseFormat = 'raw' | 'mapped' | 'both'; +export interface CliConfig { + version: number; + token?: string; + baseUrl?: string; + defaultFormat?: CliResponseFormat; + defaultOutput?: CliOutputMode; + maxRetries?: number; + color?: boolean; + authScheme?: 'Token' | 'Bearer'; +} + +const CONFIG_VERSION = 1; + +function getConfigDirectory(): string { + const explicitXdg = process.env.XDG_CONFIG_HOME; + if (explicitXdg && explicitXdg.trim() !== '') return explicitXdg; + + if (process.platform === 'win32') { + const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA; + if (localAppData && localAppData.trim() !== '') { + return path.join(localAppData, 'terminal49'); + } + return path.join(os.homedir(), 'AppData', 'Roaming', 'terminal49'); + } + + return path.join(os.homedir(), '.config', 'terminal49'); +} + +function sanitizeConfig(raw: Record): CliConfig { + const output: CliConfig = { version: CONFIG_VERSION }; + if (typeof raw.version === 'number') output.version = raw.version; + if (typeof raw.token === 'string' && raw.token.trim() !== '') { + output.token = raw.token.trim(); + } + if (typeof raw.baseUrl === 'string' && raw.baseUrl.trim() !== '') { + output.baseUrl = raw.baseUrl.trim(); + } + if (raw.defaultFormat === 'raw' || raw.defaultFormat === 'mapped' || raw.defaultFormat === 'both') { + output.defaultFormat = raw.defaultFormat; + } + if (raw.defaultOutput === 'json' || raw.defaultOutput === 'table') { + output.defaultOutput = raw.defaultOutput; + } + if (typeof raw.maxRetries === 'number' && Number.isFinite(raw.maxRetries)) { + output.maxRetries = Math.max(0, Math.floor(raw.maxRetries)); + } + if (raw.authScheme === 'Token' || raw.authScheme === 'Bearer') { + output.authScheme = raw.authScheme; + } + if (typeof raw.color === 'boolean') { + output.color = raw.color; + } + return output; +} + +function defaultConfigPath(): string { + return path.join(getConfigDirectory(), 'config.json'); +} + +export function getConfigPath(): string { + return defaultConfigPath(); +} + +export async function loadConfig(): Promise { + const configPath = defaultConfigPath(); + try { + const content = await fs.readFile(configPath, 'utf8'); + const parsed = JSON.parse(content); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + return sanitizeConfig(parsed as Record); + } + } catch (error) { + if (typeof error === 'object' && (error as NodeJS.ErrnoException).code === 'ENOENT') { + return { version: CONFIG_VERSION }; + } + } + return { version: CONFIG_VERSION }; +} + +export async function writeConfig(input: Partial): Promise { + const merged = sanitizeConfig({ + ...((await loadConfig()) as unknown as Record), + ...(input as unknown as Record), + }); + + const configPath = defaultConfigPath(); + const configDir = path.dirname(configPath); + await fs.mkdir(configDir, { recursive: true, mode: 0o700 }); + + const tmp = `${configPath}.tmp.${Date.now()}.json`; + const payload = JSON.stringify(merged, null, 2); + await fs.writeFile(tmp, payload, { + encoding: 'utf8', + mode: 0o600, + }); + await fs.chmod(tmp, 0o600); + await fs.rename(tmp, configPath); + return merged; +} + +export async function readConfigValue( + key: K, +): Promise { + const cfg = await loadConfig(); + return cfg[key]; +} + +export async function deleteConfigValue(key: K): Promise { + const cfg = await loadConfig(); + const next = { ...cfg }; + if (key in next) { + delete (next as Record)[key]; + } + return writeConfig(next); +} + +export async function resetConfig(): Promise { + const configPath = defaultConfigPath(); + try { + await fs.unlink(configPath); + } catch (error) { + if (typeof error !== 'object' || (error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error; + } + } +} + +export async function secureUnlinkConfig(pathToDelete = defaultConfigPath()): Promise { + try { + await fs.unlink(pathToDelete); + } catch (error) { + if (typeof error === 'object' && (error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error; + } + } +} + +export async function ensureConfigAccessConfigPermissions(): Promise<{ + configured: boolean; + readable: boolean; +}> { + const configPath = defaultConfigPath(); + try { + await fs.access(configPath, constants.R_OK | constants.W_OK); + return { configured: true, readable: true }; + } catch { + return { configured: false, readable: false }; + } +} diff --git a/sdks/typescript-sdk-cli/src/errors.ts b/sdks/typescript-sdk-cli/src/errors.ts new file mode 100644 index 00000000..d4418ca5 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/errors.ts @@ -0,0 +1,120 @@ +/** + * CLI error handling. + * + * Maps SDK errors (Terminal49Error hierarchy) to CLI-specific error codes + * and POSIX exit codes. Formats errors for both human (stderr) and + * machine (JSON envelope) consumption. + * + * Exit codes: + * 0 - Success + * 1 - General error + * 2 - Usage / argument error + * 3 - Authentication error + * 4 - Authorization / feature error + * 5 - Not found + * 6 - Validation error + * 7 - Rate limited + * 8 - Upstream / server error + * 9 - Network / connection error + */ + +import { Command } from 'commander'; +import { + AuthenticationError, + AuthorizationError, + FeatureNotEnabledError, + NotFoundError, + RateLimitError, + Terminal49Error, + UpstreamError, + ValidationError, +} from '@terminal49/sdk'; +import { createFormatter } from './output/formatter.js'; + +export type CliExitCode = + | 0 + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9; + +export interface ErrorContext { + command: Command | null; +} + +function getCommandContext(args: any[]): ErrorContext { + const command = args.find((candidate) => candidate instanceof Command) as Command | null; + return { command }; +} + +function resolveContext(context: ErrorContext): { json: boolean; compact: boolean } { + const opts = context.command?.optsWithGlobals?.() ?? {}; + return { + json: Boolean((opts as { json?: boolean }).json), + compact: Boolean((opts as { compact?: boolean }).compact), + }; +} + +function getExitCode(error: unknown): CliExitCode { + if (error instanceof ValidationError) return 6; + if (error instanceof AuthenticationError) return 3; + if (error instanceof FeatureNotEnabledError) return 4; + if (error instanceof AuthorizationError) return 4; + if (error instanceof NotFoundError) return 5; + if (error instanceof RateLimitError) return 7; + if (error instanceof UpstreamError) return 8; + if (error instanceof Terminal49Error) return 1; + if (error instanceof Error) { + if (/(network|ENOTFOUND|fetch)/i.test(error.message)) return 9; + } + return 1; +} + +function getErrorCode(error: unknown): string { + if (error instanceof ValidationError) return 'VALIDATION_ERROR'; + if (error instanceof AuthenticationError) return 'AUTH_ERROR'; + if (error instanceof FeatureNotEnabledError) return 'FEATURE_DISABLED'; + if (error instanceof AuthorizationError) return 'FORBIDDEN'; + if (error instanceof NotFoundError) return 'NOT_FOUND'; + if (error instanceof RateLimitError) return 'RATE_LIMITED'; + if (error instanceof UpstreamError) return 'UPSTREAM_ERROR'; + if (error instanceof Terminal49Error) return error.name; + return 'INTERNAL_ERROR'; +} + +function getErrorMessage(error: unknown): string { + if (error instanceof Error) return error.message; + return 'Unknown error'; +} + +export function withErrorHandling( + commandName: string, + action: (...args: TArgs) => Promise, +) { + return async (...args: TArgs): Promise => { + try { + await action(...args); + return; + } catch (error) { + const { command } = getCommandContext(args); + const { json, compact } = resolveContext({ command }); + const exitCode = getExitCode(error); + const formatter = createFormatter({ + json, + compact, + }); + formatter.outputError(commandName, { + code: getErrorCode(error), + message: getErrorMessage(error), + status: (error as { status?: number }).status, + details: (error as Terminal49Error)?.details, + }); + process.exit(exitCode); + } + }; +} diff --git a/sdks/typescript-sdk-cli/src/index.ts b/sdks/typescript-sdk-cli/src/index.ts new file mode 100644 index 00000000..af61fdf1 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/index.ts @@ -0,0 +1,90 @@ +/** + * CLI program definition. + * + * Sets up Commander with global flags and registers all command modules. + * Exported as a factory so integration tests can create isolated instances. + */ + +import { createRequire } from 'node:module'; +import { Command, InvalidArgumentError } from 'commander'; +import { registerContainersCommand } from './commands/containers.js'; +import { registerConfigCommand } from './commands/config.js'; +import { registerCommandsCommand } from './commands/commands.js'; +import { registerCustomFieldDefinitionsCommand } from './commands/custom-field-definitions.js'; +import { registerCustomFieldOptionsCommand } from './commands/custom-field-options.js'; +import { registerCustomFieldsCommand } from './commands/custom-fields.js'; +import { registerSearchCommand } from './commands/search.js'; +import { registerShippingLinesCommand } from './commands/shipping-lines.js'; +import { registerShipmentsCommand } from './commands/shipments.js'; +import { registerMetroAreasCommand } from './commands/metro-areas.js'; +import { registerPartiesCommand } from './commands/parties.js'; +import { registerPortsCommand } from './commands/ports.js'; +import { registerTerminalsCommand } from './commands/terminals.js'; +import { registerTrackCommand } from './commands/track.js'; +import { registerTrackingRequestsCommand } from './commands/tracking-requests.js'; +import { registerVesselsCommand } from './commands/vessels.js'; +import { registerWebhooksCommand } from './commands/webhooks.js'; +import { registerWebhookNotificationsCommand } from './commands/webhook-notifications.js'; + +export function createProgram(): Command { + const require = createRequire(import.meta.url); + const { version } = require('../package.json') as { version: string }; + + const program = new Command(); + + program + .name('t49') + .description( + 'Terminal49 container tracking CLI — for LLM agents, chat interfaces, and humans', + ) + .version(version) + // Global flags + .option('--json', 'Force JSON output (mutually exclusive with --table)') + .option('--table', 'Force table output (mutually exclusive with --json)') + .option('--compact', 'Minified JSON (reduces LLM token usage)') + .option('--fields ', 'Comma-separated field projection') + .option('--auth-scheme ', 'Authorization header scheme (Token|Bearer)') + .option('--token ', 'API token (overrides env/config)') + .option('--base-url ', 'API base URL override') + .option('--format ', 'Response format: raw | mapped | both', 'mapped') + .option('--max-retries ', 'Retry attempts for 429/5xx responses', (value) => + Number.parseInt(value, 10), + ) + .option('-q, --quiet', 'Suppress non-data output') + .option('-v, --verbose', 'Verbose diagnostics to stderr') + .option('--no-color', 'Disable color output'); + + program.hook('preAction', (_command: Command, actionCommand: Command) => { + const global = actionCommand.optsWithGlobals(); + + // Enforce mutual exclusion of --json and --table + if (global.json && global.table) { + throw new InvalidArgumentError('--json and --table are mutually exclusive'); + } + + if (typeof global.authScheme === 'string') { + process.env.T49_AUTH_SCHEME = global.authScheme; + } + }); + + registerContainersCommand(program); + registerShipmentsCommand(program); + registerTrackingRequestsCommand(program); + registerTrackCommand(program); + registerShippingLinesCommand(program); + registerSearchCommand(program); + registerConfigCommand(program); + registerCommandsCommand(program); + registerWebhooksCommand(program); + registerWebhookNotificationsCommand(program); + registerVesselsCommand(program); + registerPortsCommand(program); + registerTerminalsCommand(program); + registerPartiesCommand(program); + registerMetroAreasCommand(program); + registerCustomFieldsCommand(program); + registerCustomFieldDefinitionsCommand(program); + registerCustomFieldOptionsCommand(program); + + return program; +} diff --git a/sdks/typescript-sdk-cli/src/output/fields.ts b/sdks/typescript-sdk-cli/src/output/fields.ts new file mode 100644 index 00000000..2cbdeb78 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/output/fields.ts @@ -0,0 +1,71 @@ +/** + * Field projection for --fields flag. + * + * Selects specific fields from output objects to reduce + * payload size — particularly useful for LLM agents to + * minimize token consumption. + * + * Supports dot notation for nested fields: "ports.portOfDischarge.eta" + */ + +function normalizePathList(fields?: string): string[] { + if (!fields || typeof fields !== 'string') return []; + return fields + .split(',') + .map((field) => field.trim()) + .filter(Boolean); +} + +function getValue(input: unknown, path: string): unknown { + const parts = path.split('.'); + return parts.reduce((current, part) => { + if (current === null || current === undefined) return undefined; + if (typeof current !== 'object' || Array.isArray(current)) { + return undefined; + } + return (current as Record)[part]; + }, input); +} + +function setValue(target: Record, path: string, value: unknown) { + const parts = path.split('.'); + let cursor: Record = target; + parts.forEach((part, idx) => { + if (idx === parts.length - 1) { + cursor[part] = value; + return; + } + if ( + typeof cursor[part] !== 'object' || + cursor[part] === null || + Array.isArray(cursor[part]) + ) { + cursor[part] = {}; + } + cursor = cursor[part] as Record; + }); +} + +function projectObject(value: Record, fields: string[]): Record { + const result: Record = {}; + fields.forEach((path) => { + const projected = getValue(value, path); + if (projected !== undefined) { + setValue(result, path, projected); + } + }); + return result; +} + +export function projectFields(value: T, fields?: string): T { + const fieldList = normalizePathList(fields); + if (fieldList.length === 0) return value; + + if (Array.isArray(value)) { + return value.map((row) => projectObject(row as Record, fieldList)) as T; + } + if (value && typeof value === 'object') { + return projectObject(value as Record, fieldList) as T; + } + return value; +} diff --git a/sdks/typescript-sdk-cli/src/output/formatter.ts b/sdks/typescript-sdk-cli/src/output/formatter.ts new file mode 100644 index 00000000..51aa5533 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/output/formatter.ts @@ -0,0 +1,69 @@ +/** + * Output dispatcher. + * + * Selects between JSON and table output based on: + * 1. Explicit --json or --table flag + * 2. TTY detection (TTY → table, pipe → JSON) + * + * Applies --fields projection and --compact mode before output. + */ + +import { isOutputTTY } from '../util/tty.js'; +import { createErrorEnvelope, createSuccessEnvelope, serializeEnvelope } from './json.js'; +import { projectFields } from './fields.js'; +import { renderTable } from './table.js'; + +export interface FormatterOptions { + json?: boolean; + table?: boolean; + compact?: boolean; + fields?: string; + raw?: boolean; + format?: 'raw' | 'mapped' | 'both'; + noColor?: boolean; +} + +export interface Formatter { + output( + command: string, + data: T, + meta?: { pagination?: unknown; meta?: unknown }, + ): void; + outputError(command: string, error: { + code: string; + message: string; + status?: number; + details?: unknown; + }): void; +} + +export function createFormatter(opts: FormatterOptions = {}): Formatter { + const isJson = () => { + if (opts.json) return true; + if (opts.table) return false; + return !isOutputTTY(); + }; + + return { + output(command: string, data: T, meta?: { pagination?: unknown; meta?: unknown }) { + const payload = projectFields(data, opts.fields); + if (isJson()) { + const envelope = createSuccessEnvelope( + command, + payload, + meta?.pagination, + meta?.meta, + ); + process.stdout.write(`${serializeEnvelope(envelope, Boolean(opts.compact))}\n`); + return; + } + + process.stdout.write(renderTable(command, payload)); + }, + outputError(command, error) { + const payload = createErrorEnvelope(command, error); + const body = serializeEnvelope(payload, Boolean(opts.compact)); + process.stderr.write(`${body}\n`); + }, + }; +} diff --git a/sdks/typescript-sdk-cli/src/output/json.ts b/sdks/typescript-sdk-cli/src/output/json.ts new file mode 100644 index 00000000..fb781c55 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/output/json.ts @@ -0,0 +1,64 @@ +/** + * JSON envelope formatter. + * + * Wraps all CLI output in a predictable envelope: + * { ok: true, command: "...", data: {...}, pagination?: {...}, meta?: {...} } + * { ok: false, command: "...", error: { code, message, status, details } } + * + * Supports --compact for token-efficient LLM output. + */ + +export interface CliSuccessEnvelope { + ok: true; + command: string; + data: T; + pagination?: unknown; + meta?: unknown; +} + +export interface CliErrorEnvelope { + ok: false; + command: string; + error: { + code: string; + message: string; + status?: number; + details?: unknown; + }; +} + +export function createSuccessEnvelope( + command: string, + data: T, + pagination?: unknown, + meta?: unknown, +): CliSuccessEnvelope { + const envelope: CliSuccessEnvelope = { + ok: true, + command, + data, + }; + if (pagination !== undefined) envelope.pagination = pagination; + if (meta !== undefined) envelope.meta = meta; + return envelope; +} + +export function createErrorEnvelope( + command: string, + error: CliErrorEnvelope['error'], +): CliErrorEnvelope { + return { + ok: false, + command, + error: { + code: error.code, + message: error.message, + status: error.status, + details: error.details, + }, + }; +} + +export function serializeEnvelope(envelope: CliSuccessEnvelope | CliErrorEnvelope, compact = false): string { + return compact ? JSON.stringify(envelope) : JSON.stringify(envelope, null, 2); +} diff --git a/sdks/typescript-sdk-cli/src/output/table.ts b/sdks/typescript-sdk-cli/src/output/table.ts new file mode 100644 index 00000000..afb4c429 --- /dev/null +++ b/sdks/typescript-sdk-cli/src/output/table.ts @@ -0,0 +1,516 @@ +/** + * Table renderer for TTY output. + * + * Uses cli-table3 to render resource-specific tables. + * Each resource type (container, shipment, tracking request) + * has its own column definition. + */ + +import Table from 'cli-table3'; + +interface ColumnDef { + key: string; + title: string; +} + +type Resource = 'shipments' | 'containers' | 'tracking-requests' | 'search' | 'shipping-lines' | 'unknown'; + +const resourceColumns: Record = { + shipments: [ + { key: 'id', title: 'ID' }, + { key: 'billOfLading', title: 'BOL' }, + { key: 'shippingLineScac', title: 'SCAC' }, + { key: 'status', title: 'Status' }, + { key: 'customerName', title: 'Customer' }, + { key: 'tracking.lineTrackingStoppedReason', title: 'Tracking' }, + ], + containers: [ + { key: 'id', title: 'ID' }, + { key: 'number', title: 'Number' }, + { key: 'status', title: 'Status' }, + { key: 'equipment.type', title: 'Type' }, + { key: 'equipment.length', title: 'Len' }, + { key: 'location.currentLocation', title: 'Location' }, + { key: 'terminals.podTerminal.name', title: 'POD Terminal' }, + ], + 'tracking-requests': [ + { key: 'id', title: 'ID' }, + { key: 'requestType', title: 'Type' }, + { key: 'requestNumber', title: 'Number' }, + { key: 'scac', title: 'SCAC' }, + { key: 'status', title: 'Status' }, + ], + search: [ + { key: 'match', title: 'Match' }, + { key: 'resultType', title: 'Result Type' }, + { key: 'resultId', title: 'Result ID' }, + { key: 'status', title: 'Status' }, + { key: 'details', title: 'Details' }, + { key: 'score', title: 'Score' }, + ], + 'shipping-lines': [ + { key: 'scac', title: 'SCAC' }, + { key: 'name', title: 'Name' }, + { key: 'shortName', title: 'Short Name' }, + ], + unknown: [{ key: 'id', title: 'ID' }], +}; + +const unknownColumnCandidates: ColumnDef[] = [ + { key: 'id', title: 'ID' }, + { key: 'type', title: 'Type' }, + { key: 'number', title: 'Number' }, + { key: 'name', title: 'Name' }, + { key: 'status', title: 'Status' }, + { key: 'scac', title: 'SCAC' }, + { key: 'requestNumber', title: 'Request' }, + { key: 'billOfLading', title: 'BOL' }, + { key: 'attributes.entity_type', title: 'Entity' }, + { key: 'attributes.api_slug', title: 'Slug' }, + { key: 'attributes.display_name', title: 'Display Name' }, + { key: 'attributes.data_type', title: 'Data Type' }, + { key: 'attributes.number', title: 'Number' }, + { key: 'attributes.name', title: 'Name' }, + { key: 'attributes.company_name', title: 'Company' }, + { key: 'attributes.status', title: 'Status' }, + { key: 'attributes.event', title: 'Event' }, +]; + +function valueAtPath(row: Record, path: string): unknown { + return path.split('.').reduce((cursor, part) => { + if (cursor === null || cursor === undefined) return undefined; + if (typeof cursor !== 'object' || Array.isArray(cursor)) return undefined; + const container = cursor as Record; + const next = container[part]; + return next; + }, row); +} + +function findValueDeep(input: unknown, key: string, depth = 6): unknown { + if (depth < 0 || input === null || input === undefined) return undefined; + if (Array.isArray(input)) { + return input + .map((item) => findValueDeep(item, key, depth - 1)) + .find((value) => value !== undefined); + } + if (typeof input !== 'object') return undefined; + + const obj = input as Record; + if (Object.prototype.hasOwnProperty.call(obj, key)) return obj[key]; + + for (const value of Object.values(obj)) { + const found = findValueDeep(value, key, depth - 1); + if (found !== undefined) return found; + } + return undefined; +} + +function findNumeric(input: unknown, key: string, depth = 2): number | undefined { + const value = findValueDeep(input, key, depth); + if (typeof value === 'number' && Number.isFinite(value)) return value; + if (typeof value === 'string') { + const parsed = Number.parseFloat(value); + return Number.isFinite(parsed) ? parsed : undefined; + } + return undefined; +} + +function findNumericByKeyPattern( + input: unknown, + keyPattern: RegExp, + depth = 6, +): number | undefined { + if (depth < 0 || input === null || input === undefined) return undefined; + if (Array.isArray(input)) { + return input + .map((item) => findNumericByKeyPattern(item, keyPattern, depth - 1)) + .find((value) => value !== undefined); + } + if (typeof input !== 'object') return undefined; + + const obj = input as Record; + for (const [name, value] of Object.entries(obj)) { + if (keyPattern.test(name)) { + if (typeof value === 'number' && Number.isFinite(value)) return value; + if (typeof value === 'string') { + const parsed = Number.parseFloat(value); + if (Number.isFinite(parsed)) return parsed; + } + } + } + + for (const value of Object.values(obj)) { + const found = findNumericByKeyPattern(value, keyPattern, depth - 1); + if (found !== undefined) return found; + } + return undefined; +} + +function normalizeResultType(input: string): string { + const normalized = input.trim().toLowerCase(); + if (!normalized || normalized === 'search_result' || normalized === 'search-result') { + return ''; + } + if (normalized.includes('shipment')) return 'shipment'; + if (normalized.includes('container')) return 'container'; + if (normalized.includes('tracking')) return 'tracking_request'; + if (normalized.includes('bill')) return 'shipment'; + if (normalized.includes('booking')) return 'tracking_request'; + return normalized.replace(/\s+/g, '_'); +} + +function deriveResultType(flattened: Record, match: string): string { + const typeCandidate = pickFirstDefined([ + valueAtPath(flattened, 'attributes.entity_type'), + valueAtPath(flattened, 'attributes.entityType'), + valueAtPath(flattened, 'resultType'), + valueAtPath(flattened, 'resourceType'), + valueAtPath(flattened, 'resource_type'), + valueAtPath(flattened, 'trackedObjectType'), + valueAtPath(flattened, 'tracked_object_type'), + valueAtPath(flattened, 'entityType'), + valueAtPath(flattened, 'entity_type'), + valueAtPath(flattened, 'entity'), + valueAtPath(flattened, '_index'), + valueAtPath(flattened, 'type'), + findValueDeep(flattened, 'resultType'), + findValueDeep(flattened, 'resourceType'), + findValueDeep(flattened, 'resource_type'), + findValueDeep(flattened, 'trackedObjectType'), + findValueDeep(flattened, 'tracked_object_type'), + findValueDeep(flattened, 'entityType'), + findValueDeep(flattened, 'entity_type'), + findValueDeep(flattened, 'entity'), + findValueDeep(flattened, '_index'), + findValueDeep(flattened, 'type'), + ]); + + const normalized = normalizeResultType(typeCandidate); + if (normalized) return normalized; + + if (/^[A-Z]{4}\d{7}$/i.test(match)) return 'container'; + if (/^[A-Z]{4}\d{6,}$/i.test(match)) return 'shipment'; + if (/^\d{6,}$/.test(match)) return 'tracking_request'; + return 'unknown'; +} + +function deriveResultId(flattened: Record, resultType: string): string { + const wrapperId = pickFirstDefined([ + valueAtPath(flattened, 'id'), + valueAtPath(flattened, '_id'), + ]); + const resourceId = pickFirstDefined([ + valueAtPath(flattened, 'resultId'), + valueAtPath(flattened, 'resourceId'), + valueAtPath(flattened, 'resource_id'), + valueAtPath(flattened, 'trackedObjectId'), + valueAtPath(flattened, 'tracked_object_id'), + valueAtPath(flattened, 'shipmentId'), + valueAtPath(flattened, 'containerId'), + valueAtPath(flattened, 'trackingRequestId'), + valueAtPath(flattened, 'requestId'), + valueAtPath(flattened, 'attributes.shipment_id'), + valueAtPath(flattened, 'attributes.container_id'), + valueAtPath(flattened, 'attributes.tracking_request_id'), + findValueDeep(flattened, 'resultId'), + findValueDeep(flattened, 'resourceId'), + findValueDeep(flattened, 'resource_id'), + findValueDeep(flattened, 'trackedObjectId'), + findValueDeep(flattened, 'tracked_object_id'), + findValueDeep(flattened, 'shipmentId'), + findValueDeep(flattened, 'containerId'), + findValueDeep(flattened, 'trackingRequestId'), + findValueDeep(flattened, 'requestId'), + findValueDeep(flattened, 'shipment_id'), + findValueDeep(flattened, 'container_id'), + findValueDeep(flattened, 'tracking_request_id'), + ]); + + if (resourceId) return resourceId; + if (resultType === 'unknown') return ''; + return wrapperId; +} + +function deriveDetails(flattened: Record): string { + const scac = pickFirstDefined([ + valueAtPath(flattened, 'shippingLineScac'), + valueAtPath(flattened, 'lineScac'), + valueAtPath(flattened, 'scac'), + findValueDeep(flattened, 'shippingLineScac'), + findValueDeep(flattened, 'lineScac'), + findValueDeep(flattened, 'scac'), + ]); + const containers = findNumeric(flattened, 'containerCount', 6) + ?? findNumeric(flattened, 'containersCount', 6) + ?? findNumericByKeyPattern(flattened, /container(s)?_?count/i, 6); + + const origin = pickFirstDefined([ + valueAtPath(flattened, 'origin'), + valueAtPath(flattened, 'originName'), + valueAtPath(flattened, 'polName'), + valueAtPath(flattened, 'attributes.port_of_lading_name'), + valueAtPath(flattened, 'attributes.pol_name'), + valueAtPath(flattened, 'ports.portOfLading.name'), + valueAtPath(flattened, 'ports.portOfLading.locode'), + findValueDeep(flattened, 'origin'), + findValueDeep(flattened, 'originName'), + findValueDeep(flattened, 'polName'), + ]); + const destination = pickFirstDefined([ + valueAtPath(flattened, 'destination'), + valueAtPath(flattened, 'destinationName'), + valueAtPath(flattened, 'podName'), + valueAtPath(flattened, 'attributes.port_of_discharge_name'), + valueAtPath(flattened, 'attributes.pod_name'), + valueAtPath(flattened, 'ports.portOfDischarge.name'), + valueAtPath(flattened, 'ports.portOfDischarge.locode'), + findValueDeep(flattened, 'destination'), + findValueDeep(flattened, 'destinationName'), + findValueDeep(flattened, 'podName'), + ]); + + const parts: string[] = []; + if (scac) parts.push(`SCAC ${scac}`); + if (containers !== undefined) { + parts.push(`${containers} ${containers === 1 ? 'container' : 'containers'}`); + } + if (origin && destination) { + parts.push(`${origin} -> ${destination}`); + } + return parts.join(' | '); +} + +function unwrapSearchRow(row: Record): Record { + const source = valueAtPath(row, '_source'); + if (source && typeof source === 'object' && !Array.isArray(source)) { + return { + ...source as Record, + ...row, + id: row.id ?? row._id ?? (source as Record).id, + _id: row._id, + _score: row._score, + score: row.score, + } as Record; + } + const fields = valueAtPath(row, 'fields'); + if (fields && typeof fields === 'object' && !Array.isArray(fields)) { + return { + ...fields as Record, + ...row, + id: row.id ?? row._id ?? (fields as Record).id, + _id: row._id, + } as Record; + } + return row; +} + +function flattenSearchMetadata(row: Record): Record { + const sourceLike = unwrapSearchRow(row); + const result: Record = { ...sourceLike }; + const metaType = valueAtPath(row, '_index'); + if (metaType !== undefined) result.type = result.type ?? metaType; + return result; +} + +function formatCell(value: unknown): string { + if (value === undefined || value === null) return ''; + if (typeof value === 'string') return value; + if (typeof value === 'boolean' || typeof value === 'number') return String(value); + if (Array.isArray(value)) return value.join(', '); + if (typeof value === 'object') return JSON.stringify(value); + return String(value); +} + +function normalizeRows(data: unknown): Record[] { + if (!data) return []; + if (Array.isArray(data)) return data as Record[]; + if (Array.isArray((data as { items?: unknown }).items)) { + return (data as { items?: unknown }).items as Record[]; + } + const hitsValue = (data as { hits?: unknown }).hits; + if (Array.isArray(hitsValue)) { + return hitsValue as Record[]; + } + if (hitsValue && typeof hitsValue === 'object' && !Array.isArray(hitsValue)) { + const nestedHits = (hitsValue as Record).hits; + if (Array.isArray(nestedHits)) return nestedHits as Record[]; + } + if (Array.isArray((data as { data?: unknown }).data)) { + return (data as { data?: unknown }).data as Record[]; + } + const singleData = (data as { data?: unknown }).data; + if (singleData && typeof singleData === 'object' && !Array.isArray(singleData)) { + return [singleData as Record]; + } + return [data as Record]; +} + +function hasRenderableValue(value: unknown): boolean { + if (value === undefined || value === null) return false; + if (typeof value === 'string') return value.trim() !== ''; + if (Array.isArray(value)) return value.length > 0; + if (typeof value === 'object') return Object.keys(value as Record).length > 0; + return true; +} + +function inferUnknownColumns(rows: Record[]): ColumnDef[] { + const selected: ColumnDef[] = []; + for (const candidate of unknownColumnCandidates) { + if (rows.some((row) => hasRenderableValue(valueAtPath(row, candidate.key)))) { + selected.push(candidate); + } + if (selected.length >= 6) break; + } + if (selected.length > 0) return selected; + + const first = rows[0] ?? {}; + const firstKeys = Object.keys(first) + .filter((key) => hasRenderableValue(first[key])) + .slice(0, 6); + if (firstKeys.length > 0) { + return firstKeys.map((key) => ({ key, title: key })); + } + return resourceColumns.unknown; +} + +function pickFirstDefined(values: unknown[]): string { + for (const value of values) { + if (typeof value === 'string' && value.trim() !== '') return value; + if (typeof value === 'number') return String(value); + } + return ''; +} + +function enrichSearchRow(row: Record): Record { + const flattened = flattenSearchMetadata(row); + + const reference = pickFirstDefined([ + valueAtPath(flattened, 'reference'), + valueAtPath(flattened, 'referenceNumber'), + valueAtPath(flattened, 'refNumber'), + valueAtPath(flattened, 'requestNumber'), + valueAtPath(flattened, 'request_number'), + valueAtPath(flattened, 'billOfLading'), + valueAtPath(flattened, 'number'), + valueAtPath(flattened, 'containerNumber'), + valueAtPath(flattened, 'trackingNumber'), + valueAtPath(flattened, 'container_number'), + valueAtPath(flattened, 'request'), + valueAtPath(flattened, 'name'), + valueAtPath(flattened, 'title'), + findValueDeep(flattened, 'reference'), + findValueDeep(flattened, 'referenceNumber'), + findValueDeep(flattened, 'requestNumber'), + findValueDeep(flattened, 'request_number'), + findValueDeep(flattened, 'billOfLading'), + findValueDeep(flattened, 'containerNumber'), + findValueDeep(flattened, 'trackingNumber'), + ]); + + const id = pickFirstDefined([ + valueAtPath(flattened, 'id'), + valueAtPath(flattened, '_id'), + valueAtPath(flattened, 'docId'), + findValueDeep(flattened, 'id'), + findValueDeep(flattened, '_id'), + ]); + + const match = pickFirstDefined([ + valueAtPath(flattened, 'match'), + valueAtPath(flattened, 'requestNumber'), + valueAtPath(flattened, 'request_number'), + valueAtPath(flattened, 'billOfLading'), + valueAtPath(flattened, 'containerNumber'), + valueAtPath(flattened, 'container_number'), + valueAtPath(flattened, 'number'), + valueAtPath(flattened, 'name'), + valueAtPath(flattened, 'title'), + valueAtPath(flattened, 'resource.name'), + valueAtPath(flattened, 'resource.id'), + valueAtPath(flattened, 'attributes.name'), + valueAtPath(flattened, 'attributes.number'), + valueAtPath(flattened, 'attributes.reference_number'), + valueAtPath(flattened, 'attributes.bill_of_lading'), + valueAtPath(flattened, 'attributes.requestNumber'), + valueAtPath(flattened, 'attributes.request_number'), + valueAtPath(flattened, 'query'), + findValueDeep(flattened, 'name'), + findValueDeep(flattened, 'title'), + findValueDeep(flattened, 'resourceName'), + findValueDeep(flattened, 'resource.title'), + findValueDeep(flattened, 'attributes.name'), + findValueDeep(flattened, 'attributes.number'), + findValueDeep(flattened, 'reference_number'), + findValueDeep(flattened, 'bill_of_lading'), + findValueDeep(flattened, 'requestNumber'), + findValueDeep(flattened, 'tracking_number'), + findValueDeep(flattened, 'request_number'), + ]); + + const status = pickFirstDefined([ + valueAtPath(flattened, 'status'), + valueAtPath(flattened, 'state'), + findValueDeep(flattened, 'status'), + findValueDeep(flattened, 'state'), + findValueDeep(flattened, 'lifecycle'), + ]); + + const score = findNumeric(flattened, 'score', 6) ?? + findNumeric(flattened, '_score', 6) ?? + findNumeric(flattened, 'relevanceScore', 6) ?? + findNumeric(flattened, 'searchScore', 6) ?? + findNumeric(flattened, 'hitScore', 6) ?? + findNumericByKeyPattern(flattened, /score/i, 6); + + const resultType = deriveResultType(flattened, match); + const resultId = deriveResultId(flattened, resultType); + const details = deriveDetails(flattened); + const finalReference = reference || match; + const effectiveMatch = match || finalReference; + const enriched: Record = { ...flattened }; + if (finalReference) enriched.reference = finalReference; + if (effectiveMatch) { + enriched.match = effectiveMatch; + } + if (resultType) enriched.resultType = resultType; + if (resultId) enriched.resultId = resultId; + if (details) enriched.details = details; + if (score !== undefined) enriched.score = score; + if (status) enriched.status = status; + if (id) enriched.id = id; + return enriched; +} + +function resolveResource(command: string): Resource { + if (command.includes('container')) return 'containers'; + if (command.includes('shipment')) return 'shipments'; + if (command.includes('tracking-request') || command.includes('track')) return 'tracking-requests'; + if (command.includes('shipping-line')) return 'shipping-lines'; + if (command.includes('search')) return 'search'; + return 'unknown'; +} + +export function renderTable(command: string, data: unknown): string { + const rows = normalizeRows(data); + const resource = resolveResource(command); + const baseCols = resourceColumns[resource] || resourceColumns.unknown; + const commandIsSearch = command.includes('search'); + if (rows.length === 0) { + return 'No rows.\n'; + } + + const renderedRows = commandIsSearch ? rows.map(enrichSearchRow) : rows; + const cols = resource === 'unknown' ? inferUnknownColumns(renderedRows) : baseCols; + + const table = new Table({ + head: cols.map((c) => c.title), + style: { head: [], border: [] }, + wordWrap: true, + }); + + renderedRows.forEach((row) => { + table.push(cols.map((column) => formatCell(valueAtPath(row, column.key)))); + }); + + return `${table.toString()}\n`; +} diff --git a/sdks/typescript-sdk-cli/src/util/pagination.ts b/sdks/typescript-sdk-cli/src/util/pagination.ts new file mode 100644 index 00000000..cb98c46f --- /dev/null +++ b/sdks/typescript-sdk-cli/src/util/pagination.ts @@ -0,0 +1,43 @@ +/** + * Automatic page iteration for --all flag. + * + * Iterates through all pages of a list endpoint, + * emitting NDJSON (one JSON object per line) for + * streaming into LLM context or processing pipelines. + */ + +export interface PaginationRequest { + fetchPage: (page: number) => Promise; + isLastPage: (response: T, page: number) => boolean; +} + +export interface NDJSONSink { + write(line: string): void; +} + +export async function collectAllPages( + request: PaginationRequest, + startPage = 1, +): Promise { + const results: T[] = []; + let page = startPage; + // eslint-disable-next-line no-constant-condition + while (true) { + const pageResult = await request.fetchPage(page); + results.push(pageResult); + if (request.isLastPage(pageResult, page)) break; + page += 1; + } + return results; +} + +export async function emitAllPagesAsNdjson( + request: PaginationRequest, + sink: NDJSONSink, + startPage = 1, +): Promise { + const pageResults = await collectAllPages(request, startPage); + pageResults.forEach((result) => { + sink.write(`${JSON.stringify(result)}\n`); + }); +} diff --git a/sdks/typescript-sdk-cli/src/util/polling.ts b/sdks/typescript-sdk-cli/src/util/polling.ts new file mode 100644 index 00000000..a513f35e --- /dev/null +++ b/sdks/typescript-sdk-cli/src/util/polling.ts @@ -0,0 +1,37 @@ +/** + * Polling support for --poll flag. + * + * Repeatedly fetches a resource at a given interval + * until a condition is met (--until "status=arrived"). + * Useful for agent workflows waiting on status changes. + */ + +export type Poller = (attempt: number) => Promise; +export type PollCondition = (value: T) => boolean; + +export interface PollOptions { + intervalMs: number; + maxAttempts?: number; +} + +export async function pollUntil( + poller: Poller, + condition: PollCondition, + options: PollOptions, +): Promise { + const intervalMs = Math.max(250, options.intervalMs || 1000); + const maxAttempts = + options.maxAttempts === undefined ? Infinity : options.maxAttempts; + + let attempt = 0; + while (attempt < maxAttempts) { + attempt += 1; + const result = await poller(attempt); + if (condition(result)) return result; + if (attempt < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + } + + throw new Error('Polling timeout: condition not met'); +} diff --git a/sdks/typescript-sdk-cli/src/util/tty.ts b/sdks/typescript-sdk-cli/src/util/tty.ts new file mode 100644 index 00000000..834d39bb --- /dev/null +++ b/sdks/typescript-sdk-cli/src/util/tty.ts @@ -0,0 +1,19 @@ +export function isOutputTTY(stream: 'stdout' | 'stderr' = 'stdout'): boolean { + if (stream === 'stderr') { + return Boolean(process.stderr.isTTY); + } + return Boolean(process.stdout.isTTY); +} + +export function isColorEnabled(): boolean { + if (process.env.NO_COLOR) return false; + if (process.env.FORCE_COLOR) return true; + if (process.env.TERM === 'dumb') return false; + if (process.env.CLICOLOR_FORCE === '0') return false; + return process.stdout.isTTY === true; +} + +export function supportsAnsi(stream: 'stdout' | 'stderr' = 'stdout'): boolean { + if (!isColorEnabled()) return false; + return isOutputTTY(stream); +} diff --git a/sdks/typescript-sdk-cli/test/config.test.ts b/sdks/typescript-sdk-cli/test/config.test.ts new file mode 100644 index 00000000..bed71723 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/config.test.ts @@ -0,0 +1,45 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { + loadConfig, + writeConfig, + getConfigPath, + resetConfig, +} from '../src/config.js'; + +describe('config persistence', () => { + const originalConfigHome = process.env.XDG_CONFIG_HOME; + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 't49-cli-config-')); + + beforeEach(() => { + process.env.XDG_CONFIG_HOME = tempRoot; + }); + + afterEach(() => { + process.env.XDG_CONFIG_HOME = originalConfigHome; + }); + + it('writes and reads typed config values', async () => { + const cfgPath = getConfigPath(); + await resetConfig(); + await writeConfig({ token: 'token-1', maxRetries: 2, defaultFormat: 'mapped' }); + + const loaded = await loadConfig(); + expect(loaded.token).toBe('token-1'); + expect(loaded.maxRetries).toBe(2); + expect(loaded.defaultFormat).toBe('mapped'); + expect(loaded.version).toBe(1); + + const raw = JSON.parse(await fs.promises.readFile(cfgPath, 'utf8')); + expect(raw.token).toBe('token-1'); + }); + + it('returns default config when file missing', async () => { + await resetConfig(); + const loaded = await loadConfig(); + expect(loaded.version).toBe(1); + expect(loaded.token).toBeUndefined(); + }); +}); diff --git a/sdks/typescript-sdk-cli/test/fields.test.ts b/sdks/typescript-sdk-cli/test/fields.test.ts new file mode 100644 index 00000000..453f4b2d --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fields.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest'; +import { projectFields } from '../src/output/fields.js'; + +describe('projectFields', () => { + it('projects simple object fields', () => { + const input = { + id: 'abc', + container: { + number: 'MSCU1234567', + status: 'in_transit', + }, + nested: { + deep: { + code: 'X1', + }, + }, + ignored: true, + }; + + const out = projectFields(input, 'id,container.number,nested.deep.code'); + expect(out).toEqual({ + id: 'abc', + container: { number: 'MSCU1234567' }, + nested: { deep: { code: 'X1' } }, + }); + expect((out as { ignored?: boolean }).ignored).toBeUndefined(); + }); + + it('projects arrays using top-level object projection', () => { + const input = [ + { id: 'a', value: 1, nested: { keep: 'x' } }, + { id: 'b', nested: { keep: 'y' } }, + ]; + + const out = projectFields(input, 'id,nested.keep'); + expect(Array.isArray(out)).toBe(true); + expect(out).toEqual([ + { id: 'a', nested: { keep: 'x' } }, + { id: 'b', nested: { keep: 'y' } }, + ]); + }); +}); diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.demurrage.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.demurrage.json new file mode 100644 index 00000000..47b1952e --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.demurrage.json @@ -0,0 +1,14 @@ +{ + "ok": true, + "command": "containers.demurrage", + "data": { + "container_id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "pickup_lfd": "2026-02-26T05:00:00Z", + "pickup_appointment_at": null, + "available_for_pickup": false, + "fees_at_pod_terminal": [], + "holds_at_pod_terminal": [], + "pod_arrived_at": "2026-02-20T17:45:11Z", + "pod_discharged_at": "2026-02-20T21:33:25Z" + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.events.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.events.json new file mode 100644 index 00000000..1b6903b0 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.events.json @@ -0,0 +1,168 @@ +{ + "ok": true, + "command": "containers.events", + "data": [ + { + "id": "f42dfc85-6a98-4c71-823d-4410d6577dc2", + "event": "container.transport.full_out", + "createdAt": "2026-02-26T16:25:38Z", + "voyageNumber": null, + "timestamp": "2026-02-26T14:53:00Z", + "dataSource": "shipping_line", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": null, + "terminal": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "name": "NS - Livernois", + "nickname": "NS - Livernois", + "firmsCode": "H796" + } + }, + { + "id": "d12d1930-9c23-47bb-beb8-e8d2c1258c95", + "event": "container.transport.vessel_discharged", + "createdAt": "2026-02-25T19:47:30Z", + "voyageNumber": "068E", + "timestamp": "2026-02-20T21:33:25Z", + "dataSource": "terminal", + "invalidatedAt": null, + "locationLocode": "USORF", + "timezone": "America/New_York", + "value": null, + "terminal": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "name": "Virginia International Gateway", + "nickname": "Virginia (VIG)", + "firmsCode": "N195" + } + }, + { + "id": "aa70a3b0-26d4-491c-9146-82a410959e14", + "event": "container.transport.available", + "createdAt": "2026-02-25T18:54:14Z", + "voyageNumber": null, + "timestamp": "2026-02-25T11:40:00Z", + "dataSource": "rail", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": null, + "terminal": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "name": "NS - Livernois", + "nickname": "NS - Livernois", + "firmsCode": "H796" + } + }, + { + "id": "b0054875-c11d-49d5-afed-dc0123402ed7", + "event": "container.pickup_lfd.changed", + "createdAt": "2026-02-25T18:54:07Z", + "voyageNumber": null, + "timestamp": "2026-02-25T18:54:07Z", + "dataSource": "rail", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": "2026-02-26T05:00:00Z" + }, + { + "id": "d4f02aab-6230-4c7e-92d1-f9379dc72bed", + "event": "container.pickup_lfd_rail.changed", + "createdAt": "2026-02-25T18:54:02Z", + "voyageNumber": null, + "timestamp": "2026-02-25T18:54:02Z", + "dataSource": "rail", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": "2026-02-26T05:00:00Z" + }, + { + "id": "78823f78-a5ee-47e2-aade-be5183699f12", + "event": "container.transport.estimated.arrived_at_inland_destination", + "createdAt": "2026-02-25T15:03:54Z", + "voyageNumber": null, + "timestamp": "2026-02-25T17:00:00Z", + "dataSource": "shipping_line", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": null, + "terminal": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "name": "NS - Livernois", + "nickname": "NS - Livernois", + "firmsCode": "H796" + } + }, + { + "id": "da2c4758-00ca-473e-9b6b-e6bbc2b3903d", + "event": "container.transport.rail_unloaded", + "createdAt": "2026-02-25T15:03:53Z", + "voyageNumber": null, + "timestamp": "2026-02-25T10:26:00Z", + "dataSource": "shipping_line", + "invalidatedAt": null, + "locationLocode": "USDET", + "timezone": "America/Detroit", + "value": null, + "terminal": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "name": "NS - Livernois", + "nickname": "NS - Livernois", + "firmsCode": "H796" + } + }, + { + "id": "b6f3f2f0-395f-43d4-8187-a36f87cedb8b", + "event": "container.transport.transshipment_departed", + "createdAt": "2026-02-25T15:03:53Z", + "voyageNumber": "068E", + "timestamp": "2026-01-15T02:54:00Z", + "dataSource": "shipping_line", + "invalidatedAt": null, + "locationLocode": "CNSHG", + "timezone": "Asia/Shanghai", + "value": null + }, + { + "id": "a1e6df65-6a6a-474d-8ce0-814692fb58d1", + "event": "container.transport.transshipment_arrived", + "createdAt": "2026-02-25T15:03:53Z", + "voyageNumber": "39N", + "timestamp": "2026-01-08T21:59:00Z", + "dataSource": "shipping_line", + "invalidatedAt": null, + "locationLocode": "CNSHG", + "timezone": "Asia/Shanghai", + "value": null + }, + { + "id": "7da152d2-9f4d-406b-913f-bea2ef827c21", + "event": "container.transport.vessel_departed", + "createdAt": "2026-02-25T15:03:52Z", + "voyageNumber": "068E", + "timestamp": "2025-12-25T06:01:23Z", + "dataSource": "ais", + "invalidatedAt": null, + "locationLocode": "IDJKT", + "timezone": "Asia/Jakarta", + "value": null + }, + { + "id": "f3ef41c2-b74c-416d-802a-d8301239d8b5", + "event": "container.transport.vessel_arrived", + "createdAt": "2026-02-25T15:03:52Z", + "voyageNumber": "068E", + "timestamp": "2026-02-20T17:45:11Z", + "dataSource": "ais", + "invalidatedAt": null, + "locationLocode": "USORF", + "timezone": "America/New_York", + "value": null + } + ] +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.get.json new file mode 100644 index 00000000..705f17bd --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.get.json @@ -0,0 +1,305 @@ +{ + "ok": true, + "command": "containers.get", + "data": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container", + "attributes": { + "number": "OOLU9970918", + "seal_number": null, + "created_at": "2026-02-25T15:03:44Z", + "ref_numbers": [], + "pod_arrived_at": "2026-02-20T17:45:11Z", + "pod_discharged_at": "2026-02-20T21:33:25Z", + "final_destination_full_out_at": "2026-02-26T14:53:00Z", + "holds_at_pod_terminal": [], + "available_for_pickup": false, + "delivered_at": null, + "current_status": "picked_up", + "empty_out_at": null, + "pol_full_in_at": null, + "pol_vessel_loaded_at": null, + "pol_vessel_departed_at": "2025-12-25T06:01:23Z", + "equipment_type": "dry", + "equipment_length": 40, + "equipment_height": "high_cube", + "pod_full_out_at": null, + "empty_terminated_at": null, + "terminal_checked_at": "2026-02-28T22:09:48Z", + "fees_at_pod_terminal": [], + "pickup_lfd": "2026-02-26T05:00:00Z", + "pickup_appointment_at": null, + "pod_full_out_chassis_number": null, + "location_at_pod_terminal": null, + "pod_last_tracking_request_at": "2026-02-28T22:05:43Z", + "shipment_last_tracking_request_at": "2026-02-28T19:15:21Z", + "rail_last_tracking_request_at": "2026-02-25T18:54:43Z", + "availability_known": true, + "pod_timezone": "America/New_York", + "final_destination_timezone": "America/Detroit", + "weight_in_lbs": 23272, + "empty_terminated_timezone": "America/Detroit", + "pod_rail_carrier_scac": "NSRR", + "ind_rail_carrier_scac": "NSRR", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_eta_at": null, + "ind_ata_at": null, + "ind_rail_unloaded_at": "2026-02-25T10:26:00Z", + "ind_facility_holds": null, + "ind_facility_fees": null, + "ind_facility_lfd_on": "2026-02-26T05:00:00Z", + "import_deadlines": { + "pickup_lfd_terminal": null, + "pickup_lfd_rail": "2026-02-26T05:00:00Z", + "pickup_lfd_line": null + }, + "ssl_lfd": null + }, + "relationships": { + "shipment": { + "data": { + "id": "0406ec2a-736e-4312-a4b7-a2b1f6266bcd", + "type": "shipment" + } + }, + "pickup_facility": { + "data": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + } + }, + "pod_terminal": { + "data": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "type": "terminal" + } + }, + "transport_events": { + "data": [ + { + "id": "f42dfc85-6a98-4c71-823d-4410d6577dc2", + "type": "transport_event" + }, + { + "id": "d12d1930-9c23-47bb-beb8-e8d2c1258c95", + "type": "transport_event" + }, + { + "id": "aa70a3b0-26d4-491c-9146-82a410959e14", + "type": "transport_event" + }, + { + "id": "b0054875-c11d-49d5-afed-dc0123402ed7", + "type": "transport_event" + }, + { + "id": "d4f02aab-6230-4c7e-92d1-f9379dc72bed", + "type": "transport_event" + }, + { + "id": "78823f78-a5ee-47e2-aade-be5183699f12", + "type": "transport_event" + }, + { + "id": "da2c4758-00ca-473e-9b6b-e6bbc2b3903d", + "type": "transport_event" + }, + { + "id": "b6f3f2f0-395f-43d4-8187-a36f87cedb8b", + "type": "transport_event" + }, + { + "id": "a1e6df65-6a6a-474d-8ce0-814692fb58d1", + "type": "transport_event" + }, + { + "id": "7da152d2-9f4d-406b-913f-bea2ef827c21", + "type": "transport_event" + }, + { + "id": "f3ef41c2-b74c-416d-802a-d8301239d8b5", + "type": "transport_event" + } + ] + }, + "raw_events": { + "data": [ + { + "id": "f9f159e1-300c-43ef-82dc-4ab6fa826f35", + "type": "raw_event" + }, + { + "id": "a2c2de6e-21d4-4c85-8126-7ae65de7fed0", + "type": "raw_event" + }, + { + "id": "206ea09d-1e73-420a-ab6b-8560e5043b68", + "type": "raw_event" + }, + { + "id": "769d0ca9-4ff1-47c5-83ae-19145018b422", + "type": "raw_event" + }, + { + "id": "8a96db5f-ed01-4925-94e5-9bc397b89bcb", + "type": "raw_event" + }, + { + "id": "8e31ae64-a8c5-4e3b-80f4-3b08ec161a94", + "type": "raw_event" + }, + { + "id": "9fe6d776-7b3c-4a42-8e68-f16ddb437837", + "type": "raw_event" + }, + { + "id": "666fc420-cc59-45de-ae3b-d4feb55c3dce", + "type": "raw_event" + }, + { + "id": "ad9b9a41-8c30-4b04-9ea4-454cb4729ac2", + "type": "raw_event" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/containers/d094ee98-a9bd-4edf-a278-7f6cf73d025a?include=shipment%2Cpod_terminal" + }, + "included": [ + { + "id": "0406ec2a-736e-4312-a4b7-a2b1f6266bcd", + "type": "shipment", + "attributes": { + "created_at": "2026-02-25T15:03:42Z", + "ref_numbers": [], + "tags": [], + "bill_of_lading_number": "COSU6436376650", + "normalized_number": "6436376650", + "shipping_line_scac": "COSU", + "shipping_line_name": "COSCO", + "shipping_line_short_name": "COSCO", + "customer_name": "Sodor Steamworks", + "port_of_lading_locode": "IDJKT", + "port_of_lading_name": "Jakarta, Java", + "port_of_discharge_locode": "USORF", + "port_of_discharge_name": "Virginia", + "pod_vessel_name": "OOCL FRANCE", + "pod_vessel_imo": "9622617", + "pod_voyage_number": "068E", + "destination_locode": "USDET", + "destination_name": "Detroit", + "destination_timezone": "America/Detroit", + "destination_ata_at": null, + "destination_eta_at": "2026-02-25T17:00:00Z", + "pol_etd_at": null, + "pol_atd_at": "2025-12-25T06:01:23Z", + "pol_timezone": "Asia/Jakarta", + "pod_eta_at": null, + "pod_original_eta_at": null, + "pol_original_etd_at": null, + "destination_original_eta_at": "2026-02-25T17:00:00Z", + "pod_ata_at": "2026-02-20T17:45:11Z", + "pod_timezone": "America/New_York", + "line_tracking_last_attempted_at": "2026-02-28T19:15:13Z", + "line_tracking_last_succeeded_at": "2026-02-28T19:15:21Z", + "line_tracking_stopped_at": null, + "line_tracking_stopped_reason": null + }, + "links": { + "self": "/v2/shipments/0406ec2a-736e-4312-a4b7-a2b1f6266bcd" + }, + "relationships": { + "port_of_lading": { + "data": { + "id": "379ee729-42fb-449e-80fa-da6512c26db3", + "type": "port" + } + }, + "port_of_discharge": { + "data": { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "type": "port" + } + }, + "pod_terminal": { + "data": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "type": "terminal" + } + }, + "destination": { + "data": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port" + } + }, + "destination_terminal": { + "data": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + } + }, + "line_tracking_stopped_by_user": { + "data": null + }, + "containers": { + "data": [ + { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + ] + } + } + }, + { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "type": "terminal", + "attributes": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "nickname": "Virginia (VIG)", + "name": "Virginia International Gateway", + "firms_code": "N195", + "smdg_code": "VIG", + "bic_facility_code": "USORFUNYS", + "provided_data": { + "pickup_lfd": true, + "pod_full_out_at": true, + "pickup_lfd_notes": "", + "available_for_pickup": true, + "fees_at_pod_terminal": false, + "holds_at_pod_terminal": true, + "pickup_appointment_at": false, + "location_at_pod_terminal": true, + "available_for_pickup_notes": "", + "fees_at_pod_terminal_notes": "", + "holds_at_pod_terminal_notes": "", + "pickup_appointment_at_notes": "", + "pod_full_out_chassis_number": false, + "location_at_pod_terminal_notes": "", + "pod_full_out_chassis_number_notes": "" + }, + "street": "1000 Virginia International Gateway Blvd", + "city": "Portsmouth", + "state": "Virginia", + "state_abbr": "VA", + "zip": "23703-2361", + "country": "United States", + "facility_type": "ocean_terminal" + }, + "relationships": { + "port": { + "data": { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "type": "port" + } + } + } + } + ] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.list.json new file mode 100644 index 00000000..51b96fac --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.list.json @@ -0,0 +1,207 @@ +{ + "ok": true, + "command": "containers.list", + "data": { + "items": [ + { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "number": "OOLU9970918", + "sealNumber": null, + "createdAt": "2026-02-25T15:03:44Z", + "refNumbers": [], + "podArrivedAt": "2026-02-20T17:45:11Z", + "podDischargedAt": "2026-02-20T21:33:25Z", + "finalDestinationFullOutAt": "2026-02-26T14:53:00Z", + "holdsAtPodTerminal": [], + "availableForPickup": false, + "deliveredAt": null, + "currentStatus": "picked_up", + "emptyOutAt": null, + "polFullInAt": null, + "polVesselLoadedAt": null, + "polVesselDepartedAt": "2025-12-25T06:01:23Z", + "equipmentType": "dry", + "equipmentLength": 40, + "equipmentHeight": "high_cube", + "podFullOutAt": null, + "emptyTerminatedAt": null, + "terminalCheckedAt": "2026-02-28T22:09:48Z", + "feesAtPodTerminal": [], + "pickupLfd": "2026-02-26T05:00:00Z", + "pickupAppointmentAt": null, + "podFullOutChassisNumber": null, + "locationAtPodTerminal": null, + "podLastTrackingRequestAt": "2026-02-28T22:05:43Z", + "shipmentLastTrackingRequestAt": "2026-02-28T19:15:21Z", + "railLastTrackingRequestAt": "2026-02-25T18:54:43Z", + "availabilityKnown": true, + "podTimezone": "America/New_York", + "finalDestinationTimezone": "America/Detroit", + "weightInLbs": 23272, + "emptyTerminatedTimezone": "America/Detroit", + "podRailCarrierScac": "NSRR", + "indRailCarrierScac": "NSRR", + "podRailLoadedAt": null, + "podRailDepartedAt": null, + "indEtaAt": null, + "indAtaAt": null, + "indRailUnloadedAt": "2026-02-25T10:26:00Z", + "indFacilityHolds": null, + "indFacilityFees": null, + "indFacilityLfdOn": "2026-02-26T05:00:00Z", + "importDeadlines": { + "pickup_lfd_terminal": null, + "pickup_lfd_rail": "2026-02-26T05:00:00Z", + "pickup_lfd_line": null + }, + "sslLfd": null, + "equipment": { + "type": "dry", + "length": 40, + "height": "high_cube", + "weightLbs": 23272 + }, + "location": { + "currentLocation": null, + "availableForPickup": false, + "podArrivedAt": "2026-02-20T17:45:11Z", + "podDischargedAt": "2026-02-20T21:33:25Z" + }, + "demurrage": { + "pickupLfd": "2026-02-26T05:00:00Z", + "pickupAppointmentAt": null, + "fees": [], + "holds": [] + }, + "terminals": { + "podTerminal": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "name": "Virginia International Gateway", + "nickname": "Virginia (VIG)", + "firmsCode": "N195" + }, + "destinationTerminal": null + }, + "rail": { + "podRailCarrierScac": "NSRR", + "indRailCarrierScac": "NSRR", + "podRailLoadedAt": null, + "podRailDepartedAt": null, + "indRailUnloadedAt": "2026-02-25T10:26:00Z", + "indEtaAt": null, + "indAtaAt": null + }, + "events": [], + "shipment": { + "id": "0406ec2a-736e-4312-a4b7-a2b1f6266bcd", + "billOfLading": "COSU6436376650", + "shippingLineScac": "COSU" + } + }, + { + "id": "f0ccc7b1-ff45-4afe-ba1e-27d91557eb27", + "number": "TGBU7481618", + "sealNumber": null, + "createdAt": "2026-02-23T15:03:14Z", + "refNumbers": [], + "podArrivedAt": "2026-02-18T01:02:58Z", + "podDischargedAt": "2026-02-18T05:00:00Z", + "finalDestinationFullOutAt": null, + "holdsAtPodTerminal": [], + "availableForPickup": true, + "deliveredAt": null, + "currentStatus": "available", + "emptyOutAt": "2026-01-20T23:00:00Z", + "polFullInAt": "2026-01-21T23:00:00Z", + "polVesselLoadedAt": "2026-01-28T23:00:00Z", + "polVesselDepartedAt": "2026-01-29T23:01:13Z", + "equipmentType": "dry", + "equipmentLength": 40, + "equipmentHeight": "high_cube", + "podFullOutAt": null, + "emptyTerminatedAt": null, + "terminalCheckedAt": "2026-02-28T22:09:34Z", + "feesAtPodTerminal": [], + "pickupLfd": "2027-02-26T05:00:00Z", + "pickupAppointmentAt": null, + "podFullOutChassisNumber": null, + "locationAtPodTerminal": "Yard - C1009B", + "podLastTrackingRequestAt": "2026-02-28T22:09:33Z", + "shipmentLastTrackingRequestAt": "2026-02-28T19:19:58Z", + "railLastTrackingRequestAt": null, + "availabilityKnown": true, + "podTimezone": "America/New_York", + "finalDestinationTimezone": null, + "weightInLbs": 0, + "emptyTerminatedTimezone": "America/New_York", + "podRailCarrierScac": null, + "indRailCarrierScac": null, + "podRailLoadedAt": null, + "podRailDepartedAt": null, + "indEtaAt": null, + "indAtaAt": null, + "indRailUnloadedAt": null, + "indFacilityHolds": null, + "indFacilityFees": null, + "indFacilityLfdOn": null, + "importDeadlines": { + "pickup_lfd_terminal": "2027-02-26T05:00:00Z", + "pickup_lfd_rail": null, + "pickup_lfd_line": null + }, + "sslLfd": null, + "equipment": { + "type": "dry", + "length": 40, + "height": "high_cube", + "weightLbs": 0 + }, + "location": { + "currentLocation": "Yard - C1009B", + "availableForPickup": true, + "podArrivedAt": "2026-02-18T01:02:58Z", + "podDischargedAt": "2026-02-18T05:00:00Z" + }, + "demurrage": { + "pickupLfd": "2027-02-26T05:00:00Z", + "pickupAppointmentAt": null, + "fees": [], + "holds": [] + }, + "terminals": { + "podTerminal": { + "id": "b859f5c3-8515-41da-bf20-39c0a5ada887", + "name": "Port Newark Container Terminal", + "nickname": "PNCT", + "firmsCode": "F577" + }, + "destinationTerminal": null + }, + "rail": { + "podRailCarrierScac": null, + "indRailCarrierScac": null, + "podRailLoadedAt": null, + "podRailDepartedAt": null, + "indRailUnloadedAt": null, + "indEtaAt": null, + "indAtaAt": null + }, + "events": [], + "shipment": { + "id": "b8035cb3-a9a5-45a2-8c52-d8a4df99393b", + "billOfLading": "MEDUK9654337", + "shippingLineScac": "MSCU" + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/containers?include=shipment%2Cpod_terminal&page[size]=2", + "current": "https://api.terminal49.com/v2/containers?include=shipment,pod_terminal&page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/containers?include=shipment,pod_terminal&page[number]=2&page[size]=2", + "last": "https://api.terminal49.com/v2/containers?include=shipment,pod_terminal&page[number]=150076&page[size]=2" + }, + "meta": { + "total": 300151 + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.map.error.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.map.error.json new file mode 100644 index 00000000..e69de29b diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.rail.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.rail.json new file mode 100644 index 00000000..de6ae223 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.rail.json @@ -0,0 +1,15 @@ +{ + "ok": true, + "command": "containers.rail", + "data": { + "container_id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "pod_rail_carrier_scac": "NSRR", + "ind_rail_carrier_scac": "NSRR", + "pod_rail_loaded_at": null, + "pod_rail_departed_at": null, + "ind_rail_unloaded_at": "2026-02-25T10:26:00Z", + "ind_eta_at": null, + "ind_ata_at": null, + "rail_events": [] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.raw-events.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.raw-events.json new file mode 100644 index 00000000..e6e6c8fd --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.raw-events.json @@ -0,0 +1,600 @@ +{ + "ok": true, + "command": "containers.raw-events", + "data": { + "data": [ + { + "id": "f9f159e1-300c-43ef-82dc-4ab6fa826f35", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2025-12-25T05:43:00Z", + "will_occur_at": null, + "event": "vessel_departed", + "timestamp": "2025-12-25T05:43:00Z", + "estimated": false, + "invalidated_at": null, + "index": 0, + "original_event": null, + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": "39N", + "location_name": "Jakarta, Java", + "location_locode": "IDJKT", + "vessel_name": "SEASPAN EMERALD", + "vessel_imo": "9407134", + "timezone": "Asia/Jakarta" + }, + "relationships": { + "location": { + "data": { + "id": "379ee729-42fb-449e-80fa-da6512c26db3", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "3cd1a860-90b2-4c46-adf0-702d10c5e5f1", + "type": "vessel" + } + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "a2c2de6e-21d4-4c85-8126-7ae65de7fed0", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2026-01-08T21:59:00Z", + "will_occur_at": null, + "event": "transshipment_arrived", + "timestamp": "2026-01-08T21:59:00Z", + "estimated": false, + "invalidated_at": null, + "index": 1, + "original_event": null, + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": "39N", + "location_name": "Shanghai", + "location_locode": "CNSHG", + "vessel_name": "SEASPAN EMERALD", + "vessel_imo": "9407134", + "timezone": "Asia/Shanghai" + }, + "relationships": { + "location": { + "data": { + "id": "c8db7333-7912-4092-a370-9823965d1394", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "3cd1a860-90b2-4c46-adf0-702d10c5e5f1", + "type": "vessel" + } + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "206ea09d-1e73-420a-ab6b-8560e5043b68", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2026-01-15T02:54:00Z", + "will_occur_at": null, + "event": "transshipment_departed", + "timestamp": "2026-01-15T02:54:00Z", + "estimated": false, + "invalidated_at": null, + "index": 2, + "original_event": null, + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": "068E", + "location_name": "Shanghai", + "location_locode": "CNSHG", + "vessel_name": "OOCL FRANCE", + "vessel_imo": "9622617", + "timezone": "Asia/Shanghai" + }, + "relationships": { + "location": { + "data": { + "id": "c8db7333-7912-4092-a370-9823965d1394", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "6424d362-709e-4b3f-8116-507e6cfd612a", + "type": "vessel" + } + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "769d0ca9-4ff1-47c5-83ae-19145018b422", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2026-02-20T08:53:00Z", + "will_occur_at": null, + "event": "vessel_arrived", + "timestamp": "2026-02-20T08:53:00Z", + "estimated": false, + "invalidated_at": null, + "index": 3, + "original_event": null, + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": "068E", + "location_name": "Virginia", + "location_locode": "USORF", + "vessel_name": "OOCL FRANCE", + "vessel_imo": "9622617", + "timezone": "America/New_York" + }, + "relationships": { + "location": { + "data": { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "type": "port" + } + }, + "vessel": { + "data": { + "id": "6424d362-709e-4b3f-8116-507e6cfd612a", + "type": "vessel" + } + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": null + } + } + }, + { + "id": "8a96db5f-ed01-4925-94e5-9bc397b89bcb", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2026-02-25T10:26:00Z", + "will_occur_at": null, + "event": "rail_unloaded", + "timestamp": "2026-02-25T10:26:00Z", + "estimated": false, + "invalidated_at": null, + "index": 4, + "original_event": "De-ramp at Final Hub", + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": null, + "location_name": "Detroit", + "location_locode": "USDET", + "vessel_name": null, + "vessel_imo": null, + "timezone": "America/Detroit" + }, + "relationships": { + "location": { + "data": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port" + } + }, + "vessel": { + "data": null + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + } + } + } + }, + { + "id": "9fe6d776-7b3c-4a42-8e68-f16ddb437837", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": "2026-02-25T17:00:00Z", + "actual_at": null, + "will_occur_at": null, + "event": "arrived_at_destination", + "timestamp": "2026-02-25T17:00:00Z", + "estimated": true, + "invalidated_at": null, + "index": 5, + "original_event": null, + "created_at": "2026-02-25T15:03:47Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": null, + "location_name": "Detroit", + "location_locode": "USDET", + "vessel_name": null, + "vessel_imo": null, + "timezone": "America/Detroit" + }, + "relationships": { + "location": { + "data": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port" + } + }, + "vessel": { + "data": null + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + } + } + } + }, + { + "id": "666fc420-cc59-45de-ae3b-d4feb55c3dce", + "type": "raw_event", + "attributes": { + "actual_on": null, + "estimated_at": null, + "actual_at": "2026-02-26T14:53:00Z", + "will_occur_at": null, + "event": "full_out", + "timestamp": "2026-02-26T14:53:00Z", + "estimated": false, + "invalidated_at": null, + "index": 6, + "original_event": "Gate-out from Final Hub", + "created_at": "2026-02-26T16:25:20Z", + "data_source": "shipping_line", + "data_provider_name": "COSCO", + "data_provider_code": "COSU", + "voyage_number": null, + "location_name": "Detroit", + "location_locode": "USDET", + "vessel_name": null, + "vessel_imo": null, + "timezone": "America/Detroit" + }, + "relationships": { + "location": { + "data": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port" + } + }, + "vessel": { + "data": null + }, + "container": { + "data": { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "container" + } + }, + "terminal": { + "data": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + } + } + } + } + ], + "included": [ + { + "id": "3cd1a860-90b2-4c46-adf0-702d10c5e5f1", + "type": "vessel", + "attributes": { + "name": "SEASPAN EMERALD", + "imo": "9407134", + "mmsi": "563279800", + "latitude": 1.565, + "longitude": 120.383333333, + "nautical_speed_knots": 12, + "navigational_heading_degrees": 52, + "position_timestamp": "2026-02-28T21:57:46Z" + } + }, + { + "id": "379ee729-42fb-449e-80fa-da6512c26db3", + "type": "port", + "attributes": { + "id": "379ee729-42fb-449e-80fa-da6512c26db3", + "name": "Jakarta, Java", + "code": "IDJKT", + "state_abbr": "JK", + "city": "Jakarta, Java", + "country_code": "ID", + "latitude": "-6.098516936", + "longitude": "106.902376052", + "time_zone": "Asia/Jakarta" + }, + "relationships": { + "terminals": { + "data": [ + { + "id": "6c5e0f10-4d83-407b-88b6-f6a0b3e709d3", + "type": "terminal" + }, + { + "id": "b8231677-f37b-4ff3-9b7f-b1cbf79cefde", + "type": "terminal" + }, + { + "id": "062143aa-b248-4755-bc8d-6fe971497360", + "type": "terminal" + }, + { + "id": "53274a65-05f9-48d3-85c4-730073368136", + "type": "terminal" + }, + { + "id": "83b1c412-a7a2-45f8-a8ae-27c4f952ff4e", + "type": "terminal" + } + ] + } + } + }, + { + "id": "c8db7333-7912-4092-a370-9823965d1394", + "type": "port", + "attributes": { + "id": "c8db7333-7912-4092-a370-9823965d1394", + "name": "Shanghai", + "code": "CNSHG", + "state_abbr": "SH", + "city": "Shanghai", + "country_code": "CN", + "latitude": "31.36636", + "longitude": "121.6147", + "time_zone": "Asia/Shanghai" + }, + "relationships": { + "terminals": { + "data": [ + { + "id": "ecc03cdf-a3f5-44a8-a507-b2a8b3a14216", + "type": "terminal" + }, + { + "id": "b7ce2e7c-8471-47ca-8034-b0f79f8ccb27", + "type": "terminal" + }, + { + "id": "6f6ea26a-cf13-4d02-b9b3-b2f0cc367196", + "type": "terminal" + }, + { + "id": "603d15d2-b4d4-4491-9fca-2b9ee98c0611", + "type": "terminal" + }, + { + "id": "38d85f1c-c245-4403-ab47-0e18a73ed1b1", + "type": "terminal" + }, + { + "id": "145b7732-ea6c-4c05-8742-b0e16f3174b4", + "type": "terminal" + }, + { + "id": "0314d588-9bc8-4914-9cf6-fab21ad9731f", + "type": "terminal" + } + ] + } + } + }, + { + "id": "6424d362-709e-4b3f-8116-507e6cfd612a", + "type": "vessel", + "attributes": { + "name": "OOCL FRANCE", + "imo": "9622617", + "mmsi": "477222700", + "latitude": 14.888958333, + "longitude": -76.929898333, + "nautical_speed_knots": 15, + "navigational_heading_degrees": 202, + "position_timestamp": "2026-02-28T20:51:39Z" + } + }, + { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "type": "port", + "attributes": { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "name": "Virginia", + "code": "USORF", + "state_abbr": "VA", + "city": "Norfolk", + "country_code": "US", + "latitude": "36.910719502", + "longitude": "-76.324600462", + "time_zone": "America/New_York" + }, + "relationships": { + "terminals": { + "data": [ + { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "type": "terminal" + }, + { + "id": "4636c1c6-abc9-401f-ab4b-9a73a5f171a8", + "type": "terminal" + }, + { + "id": "7f65d03f-b3be-40b4-a080-b931cbcc8f84", + "type": "terminal" + }, + { + "id": "79424668-3fa6-4281-a94f-8fd461b945ab", + "type": "terminal" + }, + { + "id": "2fae67a7-a238-44db-8502-53fb4f5bf142", + "type": "terminal" + }, + { + "id": "a23aa451-be2c-403b-9a97-4ac6051426a6", + "type": "terminal" + } + ] + } + } + }, + { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port", + "attributes": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "name": "Detroit", + "code": "USDET", + "state_abbr": "MI", + "city": "Detroit", + "country_code": "US", + "latitude": "42.258", + "longitude": "-83.1225", + "time_zone": "America/Detroit" + }, + "relationships": { + "terminals": { + "data": [ + { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal" + }, + { + "id": "5967080f-faa4-4008-9612-e4a514f73e5f", + "type": "terminal" + }, + { + "id": "c1fefa76-b3ef-4140-8eb2-a55715c0f614", + "type": "terminal" + }, + { + "id": "9700a465-b4b8-41d4-91ac-322a4e075a38", + "type": "terminal" + } + ] + } + } + }, + { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "type": "terminal", + "attributes": { + "id": "1d9b95ec-2c3d-42e0-a6b7-7378b82625e7", + "nickname": "NS - Livernois", + "name": "NS - Livernois", + "firms_code": "H796", + "smdg_code": null, + "bic_facility_code": "USDETNSRL", + "provided_data": { + "pickup_lfd": false, + "pod_full_out_at": false, + "pickup_lfd_notes": "", + "empty_terminated_at": false, + "available_for_pickup": false, + "fees_at_pod_terminal": false, + "holds_at_pod_terminal": false, + "pickup_appointment_at": false, + "pod_full_out_at_notes": "", + "location_at_pod_terminal": false, + "empty_terminated_at_notes": "", + "available_for_pickup_notes": "", + "fees_at_pod_terminal_notes": "", + "holds_at_pod_terminal_notes": "", + "pickup_appointment_at_notes": "", + "pod_full_out_chassis_number": false, + "location_at_pod_terminal_notes": "", + "pod_full_out_chassis_number_notes": "" + }, + "street": null, + "city": "Jacksonville", + "state": null, + "state_abbr": null, + "zip": "32254", + "country": null, + "facility_type": "rail" + }, + "relationships": { + "port": { + "data": { + "id": "edee0f16-8aad-42a5-8836-9a3a8ecd2321", + "type": "port" + } + } + } + } + ] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.route.error.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.route.error.json new file mode 100644 index 00000000..aeae7688 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/containers.route.error.json @@ -0,0 +1,10 @@ +{ + "ok": false, + "command": "containers.route", + "error": { + "code": "NOT_FOUND", + "message": "\n\n\n The page you were looking for doesn't exist (404)\n \n \n\n\n\n \n
\n
\n

The page you were looking for doesn't exist.

\n

You may have mistyped the address or the page may have moved.

\n
\n

If you are the application owner check the logs for more information.

\n
\n\n\n", + "status": 404, + "details": "\n\n\n The page you were looking for doesn't exist (404)\n \n \n\n\n\n \n
\n
\n

The page you were looking for doesn't exist.

\n

You may have mistyped the address or the page may have moved.

\n
\n

If you are the application owner check the logs for more information.

\n
\n\n\n" + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.get.json new file mode 100644 index 00000000..672b7d0a --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.get.json @@ -0,0 +1,38 @@ +{ + "ok": true, + "command": "custom-field-definitions.get", + "data": { + "data": { + "id": "5596c4ec-7517-4221-b62b-54800cdb8b67", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "purchase_order_number", + "display_name": "Purchase Order Number", + "description": "Buyer’s order reference for procurement.", + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:08:04Z", + "updated_at": "2025-10-13T23:08:04Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/custom_field_definitions/5596c4ec-7517-4221-b62b-54800cdb8b67" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.list.json new file mode 100644 index 00000000..858abee6 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-definitions.list.json @@ -0,0 +1,312 @@ +{ + "ok": true, + "command": "custom-field-definitions.list", + "data": { + "data": [ + { + "id": "5596c4ec-7517-4221-b62b-54800cdb8b67", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "purchase_order_number", + "display_name": "Purchase Order Number", + "description": "Buyer’s order reference for procurement.", + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:08:04Z", + "updated_at": "2025-10-13T23:08:04Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "bfb5b95a-be0c-4dbb-9587-1211a6f2653f", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "sales_order_number", + "display_name": "Sales Order Number", + "description": "Seller’s internal sales order reference.", + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:08:55Z", + "updated_at": "2025-10-13T23:08:55Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "963013ae-bf31-46be-8422-96574f30875a", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "packing_list_number", + "display_name": "Packing List Number", + "description": "Reference number for associated packing list.", + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:10:02Z", + "updated_at": "2025-10-13T23:10:02Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "41c7cc1e-94a4-4042-b4f0-96b2663fe75f", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "customer_reference_number", + "display_name": "Customer Reference Number", + "description": "Shipper or consignee reference for shipment.", + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:10:32Z", + "updated_at": "2025-10-13T23:10:32Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "4dbd6dff-4d3d-4c0e-abd4-269fce83e104", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "incoterm", + "display_name": "Incoterm", + "description": "International trade term defining responsibilities.", + "data_type": "enum", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:16:21Z", + "updated_at": "2025-10-13T23:16:21Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "73367453-ad1c-44c0-9222-86551c13a0ae", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "fulfillment_type", + "display_name": "Fulfillment Type", + "description": "Fulfillment method (e.g., Stock, Direct).", + "data_type": "enum", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:20:45Z", + "updated_at": "2025-10-13T23:20:45Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [ + { + "id": "31bc2380-11fa-46b8-bfbd-fc12051d94b9", + "type": "custom_field_option" + }, + { + "id": "9f588dd4-d407-4bce-a5fe-2bc6285027ca", + "type": "custom_field_option" + }, + { + "id": "361b5f47-2f6e-4c2b-af3c-5896b5f76f9a", + "type": "custom_field_option" + }, + { + "id": "07744cf0-e089-42c1-9b5d-9d444d3279dd", + "type": "custom_field_option" + }, + { + "id": "75d98c81-f6f1-4a34-aa26-d14c79e997c9", + "type": "custom_field_option" + }, + { + "id": "9b9498de-8e47-410d-8e5f-9343d59360dd", + "type": "custom_field_option" + }, + { + "id": "76a24813-15f0-4b53-bc21-764ac17f82c2", + "type": "custom_field_option" + }, + { + "id": "d63d0e4c-882c-4f8a-8ca5-d901e5a3709b", + "type": "custom_field_option" + }, + { + "id": "e1e221ae-d14a-44ed-b536-8d83817d2d8d", + "type": "custom_field_option" + } + ] + } + } + }, + { + "id": "b13eb575-e7da-4f19-a07b-e55508f9d517", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "move_type", + "display_name": "Move Type", + "description": "Carrier-defined movement service. Normalize to one of the options below (e.g., Door-to-Door, Port-to-Port, CY/CY, CFS/CY). Used for planning, exceptions, and reporting.", + "data_type": "enum", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-13T23:23:25Z", + "updated_at": "2025-10-13T23:23:25Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "6528299d-86ac-48e5-b260-079f286c0561", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Cargo", + "api_slug": "hazmat_indicator", + "display_name": "Hazmat Indicator", + "description": "Flag if container is hazardous.", + "data_type": "boolean", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": false, + "template_id": null, + "created_at": "2025-10-13T23:30:58Z", + "updated_at": "2025-10-13T23:30:58Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + }, + { + "id": "624392bc-9318-4f44-adb6-e7592ea71cff", + "type": "custom_field_definition", + "attributes": { + "entity_type": "Shipment", + "api_slug": "internal_id_number", + "display_name": "Internal ID number", + "description": null, + "data_type": "short_text", + "reference_type": null, + "validation": null, + "default_format": null, + "default_value": null, + "template_id": null, + "created_at": "2025-10-14T05:09:50Z", + "updated_at": "2025-10-14T05:09:50Z" + }, + "relationships": { + "account": { + "data": null + }, + "fields": { + "data": [] + }, + "options": { + "data": [] + } + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/custom_field_definitions?page%5Bsize%5D=2", + "current": "https://api.terminal49.com/v2/custom_field_definitions?page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/custom_field_definitions?page[number]=2&page[size]=2", + "last": "https://api.terminal49.com/v2/custom_field_definitions?page[number]=5&page[size]=2" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-options.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-options.list.json new file mode 100644 index 00000000..73f9c0cc --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-field-options.list.json @@ -0,0 +1,11 @@ +{ + "ok": true, + "command": "custom-field-options.list", + "data": { + "links": { + "self": "https://api.terminal49.com/v2/custom_field_definitions/5596c4ec-7517-4221-b62b-54800cdb8b67/options?page%5Bsize%5D=2", + "current": "https://api.terminal49.com/v2/custom_field_definitions/5596c4ec-7517-4221-b62b-54800cdb8b67/options?page[number]=1&page[size]=2" + }, + "data": [] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-fields.list.error.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-fields.list.error.json new file mode 100644 index 00000000..5f291c4d --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/custom-fields.list.error.json @@ -0,0 +1,19 @@ +{ + "ok": false, + "command": "custom-fields.list", + "error": { + "code": "UPSTREAM_ERROR", + "message": "Internal Server Error", + "status": 500, + "details": { + "errors": [ + { + "status": "500", + "source": null, + "title": "Internal Server Error", + "detail": null + } + ] + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.get.json new file mode 100644 index 00000000..afefbca4 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.get.json @@ -0,0 +1,16 @@ +{ + "ok": true, + "command": "parties.get", + "data": { + "data": { + "id": "03164f9c-84d1-419e-8bc9-5a80068c2fb8", + "type": "party", + "attributes": { + "company_name": "Elenteny Imports" + } + }, + "links": { + "self": "https://api.terminal49.com/v2/parties/03164f9c-84d1-419e-8bc9-5a80068c2fb8" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.list.json new file mode 100644 index 00000000..a1d258c0 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/parties.list.json @@ -0,0 +1,38 @@ +{ + "ok": true, + "command": "parties.list", + "data": { + "data": [ + { + "id": "03164f9c-84d1-419e-8bc9-5a80068c2fb8", + "type": "party", + "attributes": { + "company_name": "Elenteny Imports" + } + }, + { + "id": "0a8976cb-305f-48f4-8870-52315420f394", + "type": "party", + "attributes": { + "company_name": "Revolution Supply" + } + } + ], + "meta": { + "size": 2, + "total": 7, + "pagination": { + "current": 1, + "next": 2, + "last": 4, + "records": 7 + } + }, + "links": { + "self": "https://api.terminal49.com/v2/parties?page[size]=2", + "current": "https://api.terminal49.com/v2/parties?page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/parties?page[number]=2&page[size]=2", + "last": "https://api.terminal49.com/v2/parties?page[number]=4&page[size]=2" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/ports.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/ports.get.json new file mode 100644 index 00000000..8d632f25 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/ports.get.json @@ -0,0 +1,62 @@ +{ + "ok": true, + "command": "ports.get", + "data": { + "data": { + "id": "a169d532-b79f-460b-aa6a-f9e0e0d225e2", + "type": "port", + "attributes": { + "id": "a169d532-b79f-460b-aa6a-f9e0e0d225e2", + "name": "Hamburg", + "code": "DEHAM", + "state_abbr": "HH", + "city": "Hamburg", + "country_code": "DE", + "latitude": "53.530404621", + "longitude": "9.905771237", + "time_zone": "Europe/Berlin" + }, + "relationships": { + "terminals": { + "data": [ + { + "id": "a721387d-cc26-4407-89dc-4d11e100947a", + "type": "terminal" + }, + { + "id": "e25980f6-bb12-4aa3-b4dd-2f734f4a371f", + "type": "terminal" + }, + { + "id": "93444dc2-5d43-43a4-9185-4cc63f21492d", + "type": "terminal" + }, + { + "id": "713fef39-cb57-47a8-9eb8-a4148844b6ee", + "type": "terminal" + }, + { + "id": "97fb5a98-2728-489a-aa35-9ea3f8ebf66a", + "type": "terminal" + }, + { + "id": "52659c0e-14f6-4172-b348-6acce4615d29", + "type": "terminal" + }, + { + "id": "ae8d82c9-e5cf-4aa1-b640-f4cb54ffae41", + "type": "terminal" + }, + { + "id": "9cd9bdfc-7d1d-4a5b-aa83-ea614bf645b8", + "type": "terminal" + } + ] + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/ports/DEHAM" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-list-number.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-list-number.json new file mode 100644 index 00000000..764f5147 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-list-number.json @@ -0,0 +1,32 @@ +{ + "ok": true, + "command": "search", + "data": { + "data": [ + { + "id": "d094ee98-a9bd-4edf-a278-7f6cf73d025a", + "type": "search_result", + "attributes": { + "entity_type": "cargo", + "number": "OOLU9970918", + "shipment_id": "0406ec2a-736e-4312-a4b7-a2b1f6266bcd", + "scac": "COSU", + "port_of_lading_name": null, + "port_of_discharge_name": null, + "containers_count": null, + "tracking_stopped": null, + "tracking_stopped_reason": null, + "status": null, + "failed_reason": null, + "ref_numbers": [], + "created_at": "2026-02-25T15:03:44Z", + "updated_at": "2026-02-28T22:09:51Z" + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/search?query=OOLU9970918", + "current": "https://api.terminal49.com/v2/search?page[number]=1&query=OOLU9970918" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-number.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-number.json new file mode 100644 index 00000000..6bd5ccb4 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.container-number.json @@ -0,0 +1,32 @@ +{ + "ok": true, + "command": "search", + "data": { + "data": [ + { + "id": "aa8036ff-274b-4bef-a373-d103a2bb98ec", + "type": "search_result", + "attributes": { + "entity_type": "shipment", + "number": "HLCUIT1251213429", + "shipment_id": null, + "scac": "HLCU", + "port_of_lading_name": "Itajai", + "port_of_discharge_name": "Ad Dammam", + "containers_count": 2, + "tracking_stopped": false, + "tracking_stopped_reason": null, + "status": null, + "failed_reason": null, + "ref_numbers": [], + "created_at": "2026-02-13T16:16:23Z", + "updated_at": "2026-02-28T18:02:12Z" + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/search?query=HLCUIT1251213429", + "current": "https://api.terminal49.com/v2/search?page[number]=1&query=HLCUIT1251213429" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/search.shipment-bol.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.shipment-bol.json new file mode 100644 index 00000000..bf21b1f0 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.shipment-bol.json @@ -0,0 +1,32 @@ +{ + "ok": true, + "command": "search", + "data": { + "data": [ + { + "id": "5fdb5605-647d-46fc-98f9-e93ccaba8bff", + "type": "search_result", + "attributes": { + "entity_type": "shipment", + "number": "HLCUHAM260113347", + "shipment_id": null, + "scac": "HLCU", + "port_of_lading_name": "Hamburg", + "port_of_discharge_name": "Port Everglades", + "containers_count": 1, + "tracking_stopped": false, + "tracking_stopped_reason": null, + "status": null, + "failed_reason": null, + "ref_numbers": [], + "created_at": "2026-02-28T18:58:30Z", + "updated_at": "2026-02-28T19:00:02Z" + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/search?query=HLCUHAM260113347", + "current": "https://api.terminal49.com/v2/search?page[number]=1&query=HLCUHAM260113347" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/search.tracking-number.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.tracking-number.json new file mode 100644 index 00000000..9efcb3e5 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/search.tracking-number.json @@ -0,0 +1,32 @@ +{ + "ok": true, + "command": "search", + "data": { + "data": [ + { + "id": "efec94ff-eb13-4625-9059-d395541524ff", + "type": "search_result", + "attributes": { + "entity_type": "shipment", + "number": "ZIMUNGB20745474", + "shipment_id": null, + "scac": "ZIMU", + "port_of_lading_name": "Ningbo", + "port_of_discharge_name": "Savannah", + "containers_count": 1, + "tracking_stopped": false, + "tracking_stopped_reason": null, + "status": null, + "failed_reason": null, + "ref_numbers": [], + "created_at": "2026-02-28T15:10:41Z", + "updated_at": "2026-02-28T19:21:38Z" + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/search?query=ZIMUNGB20745474", + "current": "https://api.terminal49.com/v2/search?page[number]=1&query=ZIMUNGB20745474" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.get.json new file mode 100644 index 00000000..4628779e --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.get.json @@ -0,0 +1,85 @@ +{ + "ok": true, + "command": "shipments.get", + "data": { + "id": "5fdb5605-647d-46fc-98f9-e93ccaba8bff", + "billOfLading": "HLCUHAM260113347", + "shippingLineScac": "HLCU", + "customerName": "Sodor Steamworks", + "containers": [ + { + "id": "cd085a4f-f981-4a78-9a9f-6cdf873a4931", + "number": "XHCU5741275" + } + ], + "createdAt": "2026-02-28T18:58:30Z", + "refNumbers": [], + "tags": [], + "billOfLadingNumber": "HLCUHAM260113347", + "normalizedNumber": "HLCUHAM260113347", + "shippingLineName": "Hapag-Lloyd", + "shippingLineShortName": "Hapag-Lloyd", + "portOfLadingLocode": "DEHAM", + "portOfLadingName": "Hamburg", + "portOfDischargeLocode": "USPEF", + "portOfDischargeName": "Port Everglades", + "podVesselName": "SEASPAN SAIGON", + "podVesselImo": "9301809", + "podVoyageNumber": "608N", + "destinationLocode": null, + "destinationName": null, + "destinationTimezone": null, + "destinationAtaAt": null, + "destinationEtaAt": null, + "polEtdAt": null, + "polAtdAt": "2026-01-16T15:15:57Z", + "polTimezone": "Europe/Berlin", + "podEtaAt": null, + "podOriginalEtaAt": null, + "polOriginalEtdAt": null, + "destinationOriginalEtaAt": null, + "podAtaAt": "2026-02-23T19:21:09Z", + "podTimezone": "America/New_York", + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:30Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:30Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null, + "vesselAtPod": { + "name": "SEASPAN SAIGON", + "imo": "9301809", + "voyageNumber": "608N" + }, + "ports": { + "portOfLading": { + "name": "Hamburg", + "code": "DEHAM", + "countryCode": "DE", + "etd": null, + "atd": "2026-01-16T15:15:57Z", + "timezone": "Europe/Berlin" + }, + "portOfDischarge": { + "name": "Port Everglades", + "code": "USPEF", + "countryCode": "US", + "eta": null, + "ata": "2026-02-23T19:21:09Z", + "originalEta": null, + "timezone": "America/New_York", + "terminal": { + "id": "4a55655c-5ad5-4027-af0c-a154e05abcfc", + "name": "Florida International Terminal", + "nickname": "FIT", + "firmsCode": "N751" + } + }, + "destination": null + }, + "tracking": { + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:30Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:30Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.list.json new file mode 100644 index 00000000..689a8fac --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipments.list.json @@ -0,0 +1,187 @@ +{ + "ok": true, + "command": "shipments.list", + "data": { + "items": [ + { + "id": "5fdb5605-647d-46fc-98f9-e93ccaba8bff", + "billOfLading": "HLCUHAM260113347", + "shippingLineScac": "HLCU", + "customerName": "Sodor Steamworks", + "containers": [ + { + "id": "cd085a4f-f981-4a78-9a9f-6cdf873a4931", + "number": "XHCU5741275" + } + ], + "createdAt": "2026-02-28T18:58:30Z", + "refNumbers": [], + "tags": [], + "billOfLadingNumber": "HLCUHAM260113347", + "normalizedNumber": "HLCUHAM260113347", + "shippingLineName": "Hapag-Lloyd", + "shippingLineShortName": "Hapag-Lloyd", + "portOfLadingLocode": "DEHAM", + "portOfLadingName": "Hamburg", + "portOfDischargeLocode": "USPEF", + "portOfDischargeName": "Port Everglades", + "podVesselName": "SEASPAN SAIGON", + "podVesselImo": "9301809", + "podVoyageNumber": "608N", + "destinationLocode": null, + "destinationName": null, + "destinationTimezone": null, + "destinationAtaAt": null, + "destinationEtaAt": null, + "polEtdAt": null, + "polAtdAt": "2026-01-16T15:15:57Z", + "polTimezone": "Europe/Berlin", + "podEtaAt": null, + "podOriginalEtaAt": null, + "polOriginalEtdAt": null, + "destinationOriginalEtaAt": null, + "podAtaAt": "2026-02-23T19:21:09Z", + "podTimezone": "America/New_York", + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:30Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:30Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null, + "vesselAtPod": { + "name": "SEASPAN SAIGON", + "imo": "9301809", + "voyageNumber": "608N" + }, + "ports": { + "portOfLading": { + "name": "Hamburg", + "code": "DEHAM", + "countryCode": "DE", + "etd": null, + "atd": "2026-01-16T15:15:57Z", + "timezone": "Europe/Berlin" + }, + "portOfDischarge": { + "name": "Port Everglades", + "code": "USPEF", + "countryCode": "US", + "eta": null, + "ata": "2026-02-23T19:21:09Z", + "originalEta": null, + "timezone": "America/New_York", + "terminal": { + "id": "4a55655c-5ad5-4027-af0c-a154e05abcfc", + "name": "Florida International Terminal", + "nickname": "FIT", + "firmsCode": "N751" + } + }, + "destination": null + }, + "tracking": { + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:30Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:30Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null + } + }, + { + "id": "c905cb3c-7f52-4bcc-bb84-a9446b8749ac", + "billOfLading": "HLCURTM260109546", + "shippingLineScac": "HLCU", + "customerName": "Sodor Steamworks", + "containers": [ + { + "id": "cb39a91a-a652-4afe-829a-5ace4719e043", + "number": "FANU1665305" + } + ], + "createdAt": "2026-02-28T18:58:17Z", + "refNumbers": [], + "tags": [], + "billOfLadingNumber": "HLCURTM260109546", + "normalizedNumber": "HLCURTM260109546", + "shippingLineName": "Hapag-Lloyd", + "shippingLineShortName": "Hapag-Lloyd", + "portOfLadingLocode": "NLRTM", + "portOfLadingName": "Rotterdam", + "portOfDischargeLocode": "USORF", + "portOfDischargeName": "Virginia", + "podVesselName": "MAERSK GARONNE", + "podVesselImo": "9235579", + "podVoyageNumber": "605W", + "destinationLocode": "USCVG", + "destinationName": "Cincinnati", + "destinationTimezone": "America/New_York", + "destinationAtaAt": null, + "destinationEtaAt": null, + "polEtdAt": null, + "polAtdAt": "2026-02-06T23:06:36Z", + "polTimezone": "Europe/Amsterdam", + "podEtaAt": null, + "podOriginalEtaAt": null, + "polOriginalEtdAt": null, + "destinationOriginalEtaAt": null, + "podAtaAt": "2026-02-23T10:07:45Z", + "podTimezone": "America/New_York", + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:17Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:17Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null, + "vesselAtPod": { + "name": "MAERSK GARONNE", + "imo": "9235579", + "voyageNumber": "605W" + }, + "ports": { + "portOfLading": { + "name": "Rotterdam", + "code": "NLRTM", + "countryCode": "NL", + "etd": null, + "atd": "2026-02-06T23:06:36Z", + "timezone": "Europe/Amsterdam" + }, + "portOfDischarge": { + "name": "Virginia", + "code": "USORF", + "countryCode": "US", + "eta": null, + "ata": "2026-02-23T10:07:45Z", + "originalEta": null, + "timezone": "America/New_York", + "terminal": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "name": "Virginia International Gateway", + "nickname": "Virginia (VIG)", + "firmsCode": "N195" + } + }, + "destination": { + "locode": "USCVG", + "name": "Cincinnati", + "eta": null, + "ata": null, + "timezone": "America/New_York", + "terminal": null + } + }, + "tracking": { + "lineTrackingLastAttemptedAt": "2026-02-28T18:58:17Z", + "lineTrackingLastSucceededAt": "2026-02-28T18:58:17Z", + "lineTrackingStoppedAt": null, + "lineTrackingStoppedReason": null + } + } + ], + "links": { + "self": "https://api.terminal49.com/v2/shipments?include=containers%2Cpod_terminal%2Cport_of_lading%2Cport_of_discharge%2Cdestination%2Cdestination_terminal&page[size]=2", + "current": "https://api.terminal49.com/v2/shipments?include=containers,pod_terminal,port_of_lading,port_of_discharge,destination,destination_terminal&page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/shipments?include=containers,pod_terminal,port_of_lading,port_of_discharge,destination,destination_terminal&page[number]=2&page[size]=2", + "last": "https://api.terminal49.com/v2/shipments?include=containers,pod_terminal,port_of_lading,port_of_discharge,destination,destination_terminal&page[number]=87346&page[size]=2" + }, + "meta": { + "total": 174691, + "size": 2 + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/shipping-lines.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipping-lines.list.json new file mode 100644 index 00000000..63dfe172 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/shipping-lines.list.json @@ -0,0 +1,234 @@ +{ + "ok": true, + "command": "shipping-lines.list", + "data": [ + { + "scac": "ACLU", + "name": "Atlantic Container Line", + "shortName": "ACL", + "bolPrefix": "ACLU" + }, + { + "scac": "ANNU", + "name": "Australia National Line", + "shortName": "ANL", + "bolPrefix": "ANLC" + }, + { + "scac": "APLU", + "name": "American President Lines", + "shortName": "APL", + "bolPrefix": "APLU" + }, + { + "scac": "ARKU", + "name": "Arkas Container Transport", + "shortName": "Arkas", + "bolPrefix": "ARKU" + }, + { + "scac": "CHVW", + "name": "Swire Shipping", + "shortName": "CHVW", + "bolPrefix": "CHVW" + }, + { + "scac": "CLAM", + "name": "Crowley", + "shortName": "Crowley", + "bolPrefix": "CLAM" + }, + { + "scac": "CMDU", + "name": "CMA CGM", + "shortName": "CMA CGM", + "bolPrefix": "CMDU" + }, + { + "scac": "COSU", + "name": "COSCO", + "shortName": "COSCO", + "bolPrefix": "CCMJ" + }, + { + "scac": "CULU", + "name": "China United Lines", + "shortName": "CULines", + "bolPrefix": "CULV" + }, + { + "scac": "EGLV", + "name": "Evergreen", + "shortName": "Evergreen", + "bolPrefix": "EISU" + }, + { + "scac": "GOSU", + "name": "Gold Star Line", + "shortName": "GSL", + "bolPrefix": "GOSU" + }, + { + "scac": "HDMU", + "name": "Hyundai Merchant Marine", + "shortName": "Hyundai", + "bolPrefix": "HDMU" + }, + { + "scac": "HDUJ", + "name": "HEDE SHipping", + "shortName": "HEDE", + "bolPrefix": "HDUJ" + }, + { + "scac": "HLCU", + "name": "Hapag-Lloyd", + "shortName": "Hapag-Lloyd", + "bolPrefix": "HLCU" + }, + { + "scac": "IILU", + "name": "Independent Container Line", + "shortName": "ICL", + "bolPrefix": "IILU" + }, + { + "scac": "MAEU", + "name": "Maersk", + "shortName": "Maersk", + "bolPrefix": "MAEU" + }, + { + "scac": "MATS", + "name": "Matson Navigation Company", + "shortName": "Matson", + "bolPrefix": "MATS" + }, + { + "scac": "MSCU", + "name": "Mediterranean Shipping Company", + "shortName": "MSC", + "bolPrefix": "MEDU" + }, + { + "scac": "ONEY", + "name": "Ocean Network Express", + "shortName": "ONE", + "bolPrefix": "ONEY" + }, + { + "scac": "OOLU", + "name": "Orient Overseas Container Line", + "shortName": "OOCL", + "bolPrefix": "OOLU" + }, + { + "scac": "PCIU", + "name": "Pacific International Lines", + "shortName": "PIL", + "bolPrefix": "PABV" + }, + { + "scac": "SAFM", + "name": "Safmarine Container Line", + "shortName": "Safmarine Container Line", + "bolPrefix": "SAFM" + }, + { + "scac": "SEAU", + "name": "Sealand Americas", + "shortName": "SeaLand Americas", + "bolPrefix": "SEAU" + }, + { + "scac": "SEJJ", + "name": "SeaLand Europe", + "shortName": "SeaLand Europe", + "bolPrefix": "SEJJ" + }, + { + "scac": "SMLM", + "name": "SM Line", + "shortName": "SM Line", + "bolPrefix": "SMLM" + }, + { + "scac": "SMLU", + "name": "Seaboard Marine", + "shortName": "Seaboard Marine", + "bolPrefix": "SMLU" + }, + { + "scac": "SQQY", + "name": "SeaLead", + "shortName": "SeaLead", + "bolPrefix": "SQQY" + }, + { + "scac": "SSBF", + "name": "Swire", + "shortName": "Swire", + "bolPrefix": "SSBF" + }, + { + "scac": "SSPH", + "name": "Seth Shipping", + "shortName": "Seth", + "bolPrefix": "SSPH" + }, + { + "scac": "TEST", + "name": "T49 Test Carrier", + "shortName": "T49 line", + "bolPrefix": "TEST" + }, + { + "scac": "TJFH", + "name": "Transfar", + "shortName": "Transfar", + "bolPrefix": "TJFH" + }, + { + "scac": "TRBR", + "name": "Trailer Bridge", + "shortName": "Trailer Bridge", + "bolPrefix": "TRBR" + }, + { + "scac": "TRKU", + "name": "Turkon Line", + "shortName": "Turkon", + "bolPrefix": "TRKU" + }, + { + "scac": "TXZJ", + "name": "T.S. Lines", + "shortName": "TS Lines", + "bolPrefix": "TXZJ" + }, + { + "scac": "WDSB", + "name": "World Direct Shipping", + "shortName": "WDS", + "bolPrefix": "WDSB" + }, + { + "scac": "WHLC", + "name": "Wan Hai Lines", + "shortName": "Wan Hai Lines", + "bolPrefix": "WHLC" + }, + { + "scac": "YMLU", + "name": "Yangming Marine Transport", + "shortName": "Yangming", + "bolPrefix": "YMLU" + }, + { + "scac": "ZIMU", + "name": "Zim American Integrated Shipping Services", + "shortName": "Zim Line", + "bolPrefix": "ZIMU" + } + ] +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/terminals.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/terminals.get.json new file mode 100644 index 00000000..b7847a82 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/terminals.get.json @@ -0,0 +1,53 @@ +{ + "ok": true, + "command": "terminals.get", + "data": { + "data": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "type": "terminal", + "attributes": { + "id": "359c10f2-b69b-481d-b26e-d030b03fa9bf", + "nickname": "Virginia (VIG)", + "name": "Virginia International Gateway", + "firms_code": "N195", + "smdg_code": "VIG", + "bic_facility_code": "USORFUNYS", + "provided_data": { + "pickup_lfd": true, + "pod_full_out_at": true, + "pickup_lfd_notes": "", + "available_for_pickup": true, + "fees_at_pod_terminal": false, + "holds_at_pod_terminal": true, + "pickup_appointment_at": false, + "location_at_pod_terminal": true, + "available_for_pickup_notes": "", + "fees_at_pod_terminal_notes": "", + "holds_at_pod_terminal_notes": "", + "pickup_appointment_at_notes": "", + "pod_full_out_chassis_number": false, + "location_at_pod_terminal_notes": "", + "pod_full_out_chassis_number_notes": "" + }, + "street": "1000 Virginia International Gateway Blvd", + "city": "Portsmouth", + "state": "Virginia", + "state_abbr": "VA", + "zip": "23703-2361", + "country": "United States", + "facility_type": "ocean_terminal" + }, + "relationships": { + "port": { + "data": { + "id": "c4554ebb-5476-4b5e-b79f-1e873f7b86fa", + "type": "port" + } + } + } + }, + "links": { + "self": "https://api.terminal49.com/v2/terminals/359c10f2-b69b-481d-b26e-d030b03fa9bf" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.get.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.get.json new file mode 100644 index 00000000..5f95ca68 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.get.json @@ -0,0 +1,20 @@ +{ + "ok": true, + "command": "tracking-requests.get", + "data": { + "id": "843ef3c7-fc89-40a6-9b44-bef69c91b005", + "requestType": "bill_of_lading", + "requestNumber": "ZIMUNGB20745474", + "status": "created", + "scac": "ZIMU", + "refNumbers": [], + "shipment": null, + "container": null, + "shipmentTags": [], + "createdAt": "2026-02-28T15:10:36Z", + "updatedAt": "2026-02-28T15:11:18Z", + "failedReason": null, + "isRetrying": false, + "retryCount": null + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.list.json new file mode 100644 index 00000000..6c18ee7c --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/tracking-requests.list.json @@ -0,0 +1,56 @@ +{ + "ok": true, + "command": "tracking-requests.list", + "data": { + "items": [ + { + "id": "843ef3c7-fc89-40a6-9b44-bef69c91b005", + "requestType": "bill_of_lading", + "requestNumber": "ZIMUNGB20745474", + "status": "created", + "scac": "ZIMU", + "refNumbers": [], + "shipment": null, + "container": null, + "shipmentTags": [], + "createdAt": "2026-02-28T15:10:36Z", + "updatedAt": "2026-02-28T15:11:18Z", + "failedReason": null, + "isRetrying": false, + "retryCount": null + }, + { + "id": "053c758a-c5c5-47d5-8413-85728c1cfb95", + "requestType": "bill_of_lading", + "requestNumber": "ZIMUBKK80436326", + "status": "created", + "scac": "ZIMU", + "refNumbers": [], + "shipment": null, + "container": null, + "shipmentTags": [], + "createdAt": "2026-02-28T15:10:35Z", + "updatedAt": "2026-02-28T15:10:51Z", + "failedReason": null, + "isRetrying": false, + "retryCount": null + } + ], + "links": { + "self": "https://api.terminal49.com/v2/tracking_requests?page[size]=2", + "current": "https://api.terminal49.com/v2/tracking_requests?page[number]=1&page[size]=2", + "next": "https://api.terminal49.com/v2/tracking_requests?page[number]=2&page[size]=2", + "last": "https://api.terminal49.com/v2/tracking_requests?page[number]=92515&page[size]=2" + }, + "meta": { + "size": 2, + "total": 185030, + "pagination": { + "current": 1, + "next": 2, + "last": 92515, + "records": 185030 + } + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/vessels.get-by-imo.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/vessels.get-by-imo.json new file mode 100644 index 00000000..539e7e4d --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/vessels.get-by-imo.json @@ -0,0 +1,23 @@ +{ + "ok": true, + "command": "vessels.get-by-imo", + "data": { + "data": { + "id": "29243ca0-f50c-4027-b2e9-0b190c5a968a", + "type": "vessel", + "attributes": { + "name": "SEASPAN SAIGON", + "imo": "9301809", + "mmsi": "563281100", + "latitude": 36.543893333, + "longitude": -75.035903333, + "nautical_speed_knots": 13, + "navigational_heading_degrees": 270, + "position_timestamp": "2026-02-28T22:01:24Z" + } + }, + "links": { + "self": "https://api.terminal49.com/v2/vessels/9301809" + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.examples.error.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.examples.error.json new file mode 100644 index 00000000..24e368d9 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.examples.error.json @@ -0,0 +1,21 @@ +{ + "ok": false, + "command": "webhook-notifications.examples", + "error": { + "code": "VALIDATION_ERROR", + "message": "Bad Request", + "status": 400, + "details": { + "errors": [ + { + "status": "400", + "source": { + "parameter": "event" + }, + "title": "Bad Request", + "detail": null + } + ] + } + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.list.json new file mode 100644 index 00000000..9228c020 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhook-notifications.list.json @@ -0,0 +1,19 @@ +{ + "ok": true, + "command": "webhook-notifications.list", + "data": { + "meta": { + "size": 0, + "total": 0, + "pagination": { + "current": 1, + "records": 0 + } + }, + "links": { + "self": "https://api.terminal49.com/v2/webhook_notifications?page[size]=2", + "current": "https://api.terminal49.com/v2/webhook_notifications?page[number]=1&page[size]=2" + }, + "data": [] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.ips.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.ips.json new file mode 100644 index 00000000..3c2377b3 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.ips.json @@ -0,0 +1,12 @@ +{ + "ok": true, + "command": "webhooks.ips", + "data": { + "webhook_notification_ips": [ + "35.222.62.171", + "3.230.67.145", + "44.217.15.129" + ], + "last_updated": "2023-10-17T21:23:16Z" + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.list.json b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.list.json new file mode 100644 index 00000000..1021a5cc --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/api/live/webhooks.list.json @@ -0,0 +1,11 @@ +{ + "ok": true, + "command": "webhooks.list", + "data": { + "links": { + "self": "https://api.terminal49.com/v2/webhooks?page[size]=2", + "current": "https://api.terminal49.com/v2/webhooks?page[number]=1&page[size]=2" + }, + "data": [] + } +} diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/containers.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/containers.list.txt new file mode 100644 index 00000000..51a75485 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/containers.list.txt @@ -0,0 +1,7 @@ +┌──────────────────────────────────────┬─────────────┬────────┬──────┬─────┬──────────────┬────────────────────────────────┐ +│ ID │ Number │ Status │ Type │ Len │ Location │ POD Terminal │ +├──────────────────────────────────────┼─────────────┼────────┼──────┼─────┼──────────────┼────────────────────────────────┤ +│ ecee7751-24a8-43ba-b03d-194bdb3d0b81 │ UACU3807837 │ │ dry │ 20 │ │ Virginia International Gateway │ +├──────────────────────────────────────┼─────────────┼────────┼──────┼─────┼──────────────┼────────────────────────────────┤ +│ b25ff433-51aa-489b-b1b1-50bd8b5cf1b5 │ SEGU5188380 │ │ dry │ 40 │ Y-YTI-6B34N1 │ Yusen Terminals │ +└──────────────────────────────────────┴─────────────┴────────┴──────┴─────┴──────────────┴────────────────────────────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/custom-field-definitions.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/custom-field-definitions.list.txt new file mode 100644 index 00000000..7bf83373 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/custom-field-definitions.list.txt @@ -0,0 +1,21 @@ +┌──────────────────────────────────────┬─────────────────────────┬──────────┬───────────────────────────┬───────────────────────────┬────────────┐ +│ ID │ Type │ Entity │ Slug │ Display Name │ Data Type │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 5596c4ec-7517-4221-b62b-54800cdb8b67 │ custom_field_definition │ Shipment │ purchase_order_number │ Purchase Order Number │ short_text │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ bfb5b95a-be0c-4dbb-9587-1211a6f2653f │ custom_field_definition │ Shipment │ sales_order_number │ Sales Order Number │ short_text │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 963013ae-bf31-46be-8422-96574f30875a │ custom_field_definition │ Shipment │ packing_list_number │ Packing List Number │ short_text │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 41c7cc1e-94a4-4042-b4f0-96b2663fe75f │ custom_field_definition │ Shipment │ customer_reference_number │ Customer Reference Number │ short_text │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 4dbd6dff-4d3d-4c0e-abd4-269fce83e104 │ custom_field_definition │ Shipment │ incoterm │ Incoterm │ enum │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 73367453-ad1c-44c0-9222-86551c13a0ae │ custom_field_definition │ Shipment │ fulfillment_type │ Fulfillment Type │ enum │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ b13eb575-e7da-4f19-a07b-e55508f9d517 │ custom_field_definition │ Shipment │ move_type │ Move Type │ enum │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 6528299d-86ac-48e5-b260-079f286c0561 │ custom_field_definition │ Cargo │ hazmat_indicator │ Hazmat Indicator │ boolean │ +├──────────────────────────────────────┼─────────────────────────┼──────────┼───────────────────────────┼───────────────────────────┼────────────┤ +│ 624392bc-9318-4f44-adb6-e7592ea71cff │ custom_field_definition │ Shipment │ internal_id_number │ Internal ID number │ short_text │ +└──────────────────────────────────────┴─────────────────────────┴──────────┴───────────────────────────┴───────────────────────────┴────────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/parties.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/parties.list.txt new file mode 100644 index 00000000..7e942aa4 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/parties.list.txt @@ -0,0 +1,7 @@ +┌──────────────────────────────────────┬───────┬───────────────────┐ +│ ID │ Type │ Company │ +├──────────────────────────────────────┼───────┼───────────────────┤ +│ 03164f9c-84d1-419e-8bc9-5a80068c2fb8 │ party │ Elenteny Imports │ +├──────────────────────────────────────┼───────┼───────────────────┤ +│ 0a8976cb-305f-48f4-8870-52315420f394 │ party │ Revolution Supply │ +└──────────────────────────────────────┴───────┴───────────────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/search.container-number.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/search.container-number.txt new file mode 100644 index 00000000..557be272 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/search.container-number.txt @@ -0,0 +1,5 @@ +┌──────────────────┬─────────────┬──────────────────────────────────────┬────────┬────────────────────────────────────────────────┬───────┐ +│ Match │ Result Type │ Result ID │ Status │ Details │ Score │ +├──────────────────┼─────────────┼──────────────────────────────────────┼────────┼────────────────────────────────────────────────┼───────┤ +│ HLCUIT1251213429 │ shipment │ aa8036ff-274b-4bef-a373-d103a2bb98ec │ │ SCAC HLCU | 2 containers | Itajai -> Ad Dammam │ │ +└──────────────────┴─────────────┴──────────────────────────────────────┴────────┴────────────────────────────────────────────────┴───────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/shipments.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/shipments.list.txt new file mode 100644 index 00000000..816f921b --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/shipments.list.txt @@ -0,0 +1,7 @@ +┌──────────────────────────────────────┬──────────────────┬──────┬────────┬──────────────────┬──────────┐ +│ ID │ BOL │ SCAC │ Status │ Customer │ Tracking │ +├──────────────────────────────────────┼──────────────────┼──────┼────────┼──────────────────┼──────────┤ +│ 805a0381-5da1-44a6-be10-82c4bcbe7dc7 │ HLCUKHI260111614 │ HLCU │ │ Sodor Steamworks │ │ +├──────────────────────────────────────┼──────────────────┼──────┼────────┼──────────────────┼──────────┤ +│ 5fdb5605-647d-46fc-98f9-e93ccaba8bff │ HLCUHAM260113347 │ HLCU │ │ Sodor Steamworks │ │ +└──────────────────────────────────────┴──────────────────┴──────┴────────┴──────────────────┴──────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/shipping-lines.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/shipping-lines.list.txt new file mode 100644 index 00000000..dc914911 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/shipping-lines.list.txt @@ -0,0 +1,79 @@ +┌──────┬───────────────────────────────────────────┬──────────────────────────┐ +│ SCAC │ Name │ Short Name │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ ACLU │ Atlantic Container Line │ ACL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ ANNU │ Australia National Line │ ANL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ APLU │ American President Lines │ APL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ ARKU │ Arkas Container Transport │ Arkas │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ CHVW │ Swire Shipping │ CHVW │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ CLAM │ Crowley │ Crowley │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ CMDU │ CMA CGM │ CMA CGM │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ COSU │ COSCO │ COSCO │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ CULU │ China United Lines │ CULines │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ EGLV │ Evergreen │ Evergreen │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ GOSU │ Gold Star Line │ GSL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ HDMU │ Hyundai Merchant Marine │ Hyundai │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ HDUJ │ HEDE SHipping │ HEDE │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ HLCU │ Hapag-Lloyd │ Hapag-Lloyd │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ IILU │ Independent Container Line │ ICL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ MAEU │ Maersk │ Maersk │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ MATS │ Matson Navigation Company │ Matson │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ MSCU │ Mediterranean Shipping Company │ MSC │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ ONEY │ Ocean Network Express │ ONE │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ OOLU │ Orient Overseas Container Line │ OOCL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ PCIU │ Pacific International Lines │ PIL │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SAFM │ Safmarine Container Line │ Safmarine Container Line │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SEAU │ Sealand Americas │ SeaLand Americas │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SEJJ │ SeaLand Europe │ SeaLand Europe │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SMLM │ SM Line │ SM Line │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SMLU │ Seaboard Marine │ Seaboard Marine │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SQQY │ SeaLead │ SeaLead │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SSBF │ Swire │ Swire │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ SSPH │ Seth Shipping │ Seth │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ TEST │ T49 Test Carrier │ T49 line │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ TJFH │ Transfar │ Transfar │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ TRBR │ Trailer Bridge │ Trailer Bridge │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ TRKU │ Turkon Line │ Turkon │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ TXZJ │ T.S. Lines │ TS Lines │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ WDSB │ World Direct Shipping │ WDS │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ WHLC │ Wan Hai Lines │ Wan Hai Lines │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ YMLU │ Yangming Marine Transport │ Yangming │ +├──────┼───────────────────────────────────────────┼──────────────────────────┤ +│ ZIMU │ Zim American Integrated Shipping Services │ Zim Line │ +└──────┴───────────────────────────────────────────┴──────────────────────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/terminals.get.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/terminals.get.txt new file mode 100644 index 00000000..1e8a0524 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/terminals.get.txt @@ -0,0 +1,5 @@ +┌──────────────────────────────────────┬──────────┬────────────────────────────────┐ +│ ID │ Type │ Name │ +├──────────────────────────────────────┼──────────┼────────────────────────────────┤ +│ 359c10f2-b69b-481d-b26e-d030b03fa9bf │ terminal │ Virginia International Gateway │ +└──────────────────────────────────────┴──────────┴────────────────────────────────┘ diff --git a/sdks/typescript-sdk-cli/test/fixtures/table/live/tracking-requests.list.txt b/sdks/typescript-sdk-cli/test/fixtures/table/live/tracking-requests.list.txt new file mode 100644 index 00000000..c0446d42 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/fixtures/table/live/tracking-requests.list.txt @@ -0,0 +1,7 @@ +┌──────────────────────────────────────┬────────────────┬─────────────────┬──────┬─────────┐ +│ ID │ Type │ Number │ SCAC │ Status │ +├──────────────────────────────────────┼────────────────┼─────────────────┼──────┼─────────┤ +│ 843ef3c7-fc89-40a6-9b44-bef69c91b005 │ bill_of_lading │ ZIMUNGB20745474 │ ZIMU │ created │ +├──────────────────────────────────────┼────────────────┼─────────────────┼──────┼─────────┤ +│ 053c758a-c5c5-47d5-8413-85728c1cfb95 │ bill_of_lading │ ZIMUBKK80436326 │ ZIMU │ created │ +└──────────────────────────────────────┴────────────────┴─────────────────┴──────┴─────────┘ diff --git a/sdks/typescript-sdk-cli/test/index.test.ts b/sdks/typescript-sdk-cli/test/index.test.ts new file mode 100644 index 00000000..9bb165cb --- /dev/null +++ b/sdks/typescript-sdk-cli/test/index.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { createProgram } from '../src/index.js'; + +function withFakeTty(fn: () => void): void { + const original = process.stdout.isTTY; + (process.stdout as unknown as { isTTY: boolean | undefined }).isTTY = true; + try { + fn(); + } finally { + (process.stdout as unknown as { isTTY: boolean | undefined }).isTTY = original; + } +} + +describe('CLI program', () => { + it('registers expected command groups', () => { + const program = createProgram(); + const names = program.commands.map((command) => command.name()).sort(); + + expect(names).toContain('containers'); + expect(names).toContain('shipments'); + expect(names).toContain('tracking-requests'); + expect(names).toContain('track'); + expect(names).toContain('shipping-lines'); + expect(names).toContain('search'); + expect(names).toContain('config'); + expect(names).toContain('commands'); + }); + + it('supports --help command discovery', () => { + withFakeTty(() => { + const program = createProgram(); + const help = program.helpInformation(); + expect(help.includes('Usage: t49')).toBe(true); + }); + }); +}); diff --git a/sdks/typescript-sdk-cli/test/live-fixture-smoke.test.ts b/sdks/typescript-sdk-cli/test/live-fixture-smoke.test.ts new file mode 100644 index 00000000..337e81a3 --- /dev/null +++ b/sdks/typescript-sdk-cli/test/live-fixture-smoke.test.ts @@ -0,0 +1,73 @@ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import { renderTable } from '../src/output/table.js'; + +const fixtureDir = join( + dirname(fileURLToPath(import.meta.url)), + 'fixtures', + 'api', + 'live', +); + +function readFixture(name: string): T { + const raw = readFileSync(join(fixtureDir, name), 'utf8'); + return JSON.parse(raw) as T; +} + +describe('live fixture smoke', () => { + it('renders high-signal search table rows from live payload', () => { + const payload = readFixture('search.container-number.json'); + expect(payload.ok).toBe(true); + + const output = renderTable('search', payload.data); + expect(output).toContain('Match'); + expect(output).toContain('Result Type'); + expect(output).toContain('Details'); + expect(output).toContain('HLCUIT1251213429'); + expect(output).toContain('shipment'); + expect(output).toContain('SCAC HLCU'); + }); + + it('renders useful columns for custom field definitions raw list', () => { + const payload = readFixture('custom-field-definitions.list.json'); + expect(payload.ok).toBe(true); + + const output = renderTable('custom-field-definitions.list', payload.data); + expect(output).toContain('Entity'); + expect(output).toContain('Slug'); + expect(output).toContain('Display Name'); + expect(output).toContain('Data Type'); + }); + + it('renders useful columns for parties raw list', () => { + const payload = readFixture('parties.list.json'); + expect(payload.ok).toBe(true); + + const output = renderTable('parties.list', payload.data); + expect(output).toContain('ID'); + expect(output).toContain('Company'); + }); + + it('renders JSON:API single-resource payloads in table mode', () => { + const payload = readFixture('terminals.get.json'); + expect(payload.ok).toBe(true); + + const terminalId = payload?.data?.data?.id; + const output = renderTable('terminals.get', payload.data); + expect(output).toContain('ID'); + expect(output).toContain('Type'); + expect(output).toContain(String(terminalId)); + }); + + it('captures upstream and validation error envelope fixtures', () => { + const upstream = readFixture('custom-fields.list.error.json'); + const validation = readFixture('webhook-notifications.examples.error.json'); + + expect(upstream.ok).toBe(false); + expect(upstream.error.code).toBe('UPSTREAM_ERROR'); + expect(validation.ok).toBe(false); + expect(validation.error.code).toBe('VALIDATION_ERROR'); + }); +}); diff --git a/sdks/typescript-sdk-cli/test/table.test.ts b/sdks/typescript-sdk-cli/test/table.test.ts new file mode 100644 index 00000000..7605f19c --- /dev/null +++ b/sdks/typescript-sdk-cli/test/table.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from 'vitest'; +import { renderTable } from '../src/output/table.js'; + +describe('renderTable', () => { + it('prints useful search columns and normalized result info', () => { + const output = renderTable('search', [ + { + type: 'search_result', + id: '22f754cd-0979-45ea-9da6-f14da90ea218', + resourceType: 'shipment', + resourceId: 'shp-123', + score: 0.97, + billOfLading: 'ABC123', + shippingLineScac: 'HLCU', + containersCount: 2, + origin: 'Itajai', + destination: 'Ad Dammam', + }, + ]); + + expect(output).toContain('Match'); + expect(output).toContain('Result Type'); + expect(output).toContain('Result ID'); + expect(output).toContain('Details'); + expect(output).toContain('Score'); + expect(output).toContain('shipment'); + expect(output).toContain('shp-123'); + expect(output).toContain('ABC123'); + expect(output).toContain('SCAC HLCU'); + expect(output).toContain('2 containers'); + expect(output).toContain('Itajai -> Ad Dammam'); + expect(output).toContain('0.97'); + }); + + it('reads search results from hits envelope', () => { + const output = renderTable('search', { + hits: [ + { + type: 'search_result', + id: '22f754cd-0979-45ea-9da6-f14da90ea218', + score: 0.74, + requestNumber: 'HAMU4017834', + }, + ], + }); + + expect(output).toContain('HAMU4017834'); + expect(output).toContain('0.74'); + }); + + it('extracts nested search fields when top-level keys are missing', () => { + const output = renderTable('search', { + hits: [ + { + type: 'search_result', + _id: 'deep-id-1', + data: { + referenceNumber: 'BL987654321', + status: 'open', + _score: 0.91, + }, + nested: { + attributes: { + title: 'Container HLCUIT1251213429', + }, + }, + }, + ], + }); + + expect(output).toContain('0.91'); + expect(output).toContain('Container HLCUIT1251213429'); + expect(output).toContain('open'); + }); + + it('does not use wrapper id as result id when result type is unknown', () => { + const output = renderTable('search', { + hits: [ + { + _id: 'deep-id-2', + type: 'search_result', + _score: 0.55, + reference: 'CONTAINER-2', + }, + ], + }); + + expect(output).toContain('CONTAINER-2'); + expect(output).toContain('0.55'); + expect(output).toContain('unknown'); + }); + + it('supports elasticsearch-style hit envelopes', () => { + const output = renderTable('search', { + hits: { + hits: [ + { + _id: 'es-id-100', + _score: 0.44, + _source: { + type: 'search_result', + request_number: 'REQ-9001', + container_number: 'MSC123', + status: 'in_transit', + title: 'REQ-9001', + }, + }, + ], + }, + }); + + expect(output).toContain('REQ-9001'); + expect(output).toContain('0.44'); + expect(output).toContain('in_transit'); + expect(output).toContain('unknown'); + }); +}); diff --git a/sdks/typescript-sdk-cli/tsconfig.json b/sdks/typescript-sdk-cli/tsconfig.json new file mode 100644 index 00000000..cbc265cc --- /dev/null +++ b/sdks/typescript-sdk-cli/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "nodenext", + "rootDir": ".", + "outDir": "./dist", + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*", "bin/**/*"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/sdks/typescript-sdk-cli/vitest.config.ts b/sdks/typescript-sdk-cli/vitest.config.ts new file mode 100644 index 00000000..4043b1d5 --- /dev/null +++ b/sdks/typescript-sdk-cli/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig, configDefaults } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + exclude: [...configDefaults.exclude, 'dist/**'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html', 'lcov'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.test.ts', 'bin/**'], + thresholds: { + lines: 90, + branches: 85, + functions: 90, + statements: 90, + }, + }, + }, +}); diff --git a/sdks/typescript-sdk/src/client.ts b/sdks/typescript-sdk/src/client.ts index f63617f5..70ea72ec 100644 --- a/sdks/typescript-sdk/src/client.ts +++ b/sdks/typescript-sdk/src/client.ts @@ -58,6 +58,7 @@ export interface Terminal49ClientConfig { maxRetries?: number; fetchImpl?: typeof fetch; defaultFormat?: ResponseFormat; + authScheme?: 'Token' | 'Bearer'; } export type TrackingRequestType = @@ -101,6 +102,7 @@ export class Terminal49Client { private client: Client; private jsona: Jsona; private defaultFormat: ResponseFormat; + private authScheme: 'Token' | 'Bearer'; private authedFetch: typeof fetch; constructor(config: Terminal49ClientConfig) { @@ -112,6 +114,7 @@ export class Terminal49Client { this.apiBaseUrl = normalizeBaseUrl(config.apiBaseUrl); this.maxRetries = config.maxRetries ?? 2; this.defaultFormat = config.defaultFormat ?? 'raw'; + this.authScheme = config.authScheme ?? 'Token'; this.authedFetch = this.buildFetch(config.fetchImpl ?? fetch); this.client = createClient({ baseUrl: this.apiBaseUrl, @@ -145,6 +148,14 @@ export class Terminal49Client { ) => this.listShipments(filters, options), update: (id: string, attrs: Record, options?: CallOptions) => this.updateShipment(id, attrs, options), + customFields: (id: string, options?: CallOptions) => + this.getShipmentCustomFields(id, options), + setCustomField: ( + id: string, + fieldId: string, + value: unknown, + options?: CallOptions, + ) => this.setShipmentCustomField(id, fieldId, value, options), stopTracking: (id: string, options?: CallOptions) => this.stopTrackingShipment(id, options), resumeTracking: (id: string, options?: CallOptions) => @@ -168,10 +179,127 @@ export class Terminal49Client { this.getContainerTransportEvents(id, options), route: (id: string, options?: CallOptions) => this.getContainerRoute(id, options), + map: (id: string, options?: CallOptions) => + this.getContainerMapGeojson(id, options), rawEvents: (id: string, options?: CallOptions) => this.getContainerRawEvents(id, options), refresh: (id: string, options?: CallOptions) => this.refreshContainer(id, options), + demurrage: (id: string) => this.getDemurrage(id), + rail: (id: string) => this.getRailMilestones(id), + customFields: (id: string, options?: CallOptions) => this.getContainerCustomFields(id, options), + setCustomField: ( + id: string, + fieldId: string, + value: unknown, + options?: CallOptions, + ) => this.setContainerCustomField(id, fieldId, value, options), + }; + + public webhooks = { + list: (options?: ListOptions) => this.listWebhooks(options), + get: (id: string, options?: CallOptions) => this.getWebhook(id, options), + create: (payload: Record, options?: CallOptions) => + this.createWebhook(payload, options), + update: (id: string, payload: Record, options?: CallOptions) => + this.updateWebhook(id, payload, options), + delete: (id: string, options?: CallOptions) => this.deleteWebhook(id, options), + ips: (options?: CallOptions) => this.getWebhookIps(options), + }; + + public webhookNotifications = { + list: (options?: ListOptions) => this.listWebhookNotifications(options), + get: (id: string, options?: CallOptions) => this.getWebhookNotification(id, options), + examples: (event?: string, options?: CallOptions) => + this.getWebhookNotificationExamples(event, options), + }; + + public vessels = { + get: (id: string, options?: CallOptions) => this.getVessel(id, options), + getByImo: (imo: string, options?: CallOptions) => + this.getVesselByImo(imo, options), + futurePositions: (id: string, options?: CallOptions) => + this.getVesselFuturePositions(id, options), + futurePositionsWithCoords: (id: string, options?: CallOptions) => + this.getVesselFuturePositionsWithCoords(id, options), + }; + + public ports = { + get: (id: string, options?: CallOptions) => this.getPort(id, options), + }; + + public terminals = { + get: (id: string, options?: CallOptions) => + this.getTerminal(id, options), + }; + + public parties = { + list: (options?: ListOptions) => this.listParties(options), + get: (id: string, options?: CallOptions) => this.getParty(id, options), + }; + + public metroAreas = { + get: (id: string, options?: CallOptions) => this.getMetroArea(id, options), + }; + + public customFieldDefinitions = { + list: (options?: ListOptions) => this.listCustomFieldDefinitions(options), + get: (id: string, options?: CallOptions) => + this.getCustomFieldDefinition(id, options), + create: (payload: Record, options?: CallOptions) => + this.createCustomFieldDefinition(payload, options), + update: ( + id: string, + payload: Record, + options?: CallOptions, + ) => this.updateCustomFieldDefinition(id, payload, options), + delete: (id: string, options?: CallOptions) => + this.deleteCustomFieldDefinition(id, options), + }; + + public customFieldOptions = { + list: (definitionId: string, options?: ListOptions) => + this.listCustomFieldOptions(definitionId, options), + get: ( + definitionId: string, + optionId: string, + options?: CallOptions, + ) => this.getCustomFieldOption(definitionId, optionId, options), + create: ( + definitionId: string, + payload: Record, + options?: CallOptions, + ) => this.createCustomFieldOption(definitionId, payload, options), + update: ( + definitionId: string, + optionId: string, + payload: Record, + options?: CallOptions, + ) => this.updateCustomFieldOption( + definitionId, + optionId, + payload, + options, + ), + delete: ( + definitionId: string, + optionId: string, + options?: CallOptions, + ) => this.deleteCustomFieldOption(definitionId, optionId, options), + }; + + public customFields = { + list: (options?: ListOptions) => this.listCustomFields(options), + get: (id: string, options?: CallOptions) => this.getCustomField(id, options), + create: (payload: Record, options?: CallOptions) => + this.createCustomField(payload, options), + update: ( + id: string, + payload: Record, + options?: CallOptions, + ) => this.updateCustomField(id, payload, options), + delete: (id: string, options?: CallOptions) => + this.deleteCustomField(id, options), }; public shippingLines = { @@ -567,6 +695,45 @@ export class Terminal49Client { return this.formatResult(raw, options?.format); } + async getContainerMapGeojson( + id: string, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.GET('/containers/{id}/map_geojson', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getContainerCustomFields(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.executeManual(`${this.apiBaseUrl}/containers/${id}/custom_fields`), + ); + return this.formatResult(raw, options?.format); + } + + async setContainerCustomField( + id: string, + fieldId: string, + value: unknown, + options?: CallOptions, + ): Promise { + const payload = { data: { type: 'custom_field', id: fieldId, value } }; + const raw = await this.executeManual( + `${this.apiBaseUrl}/containers/${id}/custom_fields`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + return this.formatResult(raw, options?.format); + } + async refreshContainer(id: string, options?: CallOptions): Promise { const raw = await this.execute(() => this.client.PATCH('/containers/{id}/refresh', { @@ -576,6 +743,435 @@ export class Terminal49Client { return this.formatResult(raw, options?.format); } + async listWebhooks( + options?: ListOptions, + ): Promise>> { + const params: Record = {}; + this.applyPagination(params, options); + const raw = await this.execute(() => + this.client.GET('/webhooks', { + params: { query: params as any }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getWebhook(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/webhooks/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async createWebhook( + payload: Record, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.POST('/webhooks', { + body: payload as any, + }), + ); + return this.formatResult(raw, options?.format); + } + + async updateWebhook( + id: string, + payload: Record, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.PATCH('/webhooks/{id}', { + params: { path: { id } }, + body: payload as any, + }), + ); + return this.formatResult(raw, options?.format); + } + + async deleteWebhook(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.DELETE('/webhooks/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getWebhookIps(options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/webhooks/ips', { + params: {}, + }), + ); + return this.formatResult(raw, options?.format); + } + + async listWebhookNotifications( + options?: ListOptions, + ): Promise>> { + const params: Record = {}; + this.applyPagination(params, options); + const raw = await this.execute(() => + this.client.GET('/webhook_notifications', { + params: { query: params as any }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getWebhookNotification( + id: string, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.GET('/webhook_notifications/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getWebhookNotificationExamples( + event?: string, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.GET('/webhook_notifications/examples', { + params: { + query: event ? ({ event } as any) : undefined, + }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getVessel(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/vessels/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getVesselByImo(imo: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/vessels/{imo}', { + params: { path: { imo } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getVesselFuturePositions(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/vessels/{id}/future_positions', { + params: { path: { id } } as any, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getVesselFuturePositionsWithCoords( + id: string, + options?: CallOptions, + ): Promise { + const raw = await this.execute(() => + this.client.GET('/vessels/{id}/future_positions_with_coordinates', { + params: { path: { id } } as any, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getPort(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/ports/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getTerminal(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/terminals/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async listParties(options?: ListOptions): Promise>> { + const params: Record = {}; + this.applyPagination(params, options); + const raw = await this.execute(() => + this.client.GET('/parties', { + params: { query: params as any }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getParty(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/parties/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async getMetroArea(id: string, options?: CallOptions): Promise { + const raw = await this.execute(() => + this.client.GET('/metro_areas/{id}', { + params: { path: { id } }, + }), + ); + return this.formatResult(raw, options?.format); + } + + async listCustomFieldDefinitions( + options?: ListOptions, + ): Promise>> { + const query = this.applyListOptions(options); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions${query}`, + ); + return this.formatResult(raw, options?.format); + } + + async getCustomFieldDefinition( + id: string, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedId}`, + ); + return this.formatResult(raw, options?.format); + } + + async createCustomFieldDefinition( + payload: Record, + options?: CallOptions, + ): Promise { + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }, + ); + return this.formatResult(raw, options?.format); + } + + async updateCustomFieldDefinition( + id: string, + payload: Record, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedId}`, + { + method: 'PATCH', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }, + ); + return this.formatResult(raw, options?.format); + } + + async deleteCustomFieldDefinition( + id: string, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedId}`, + { + method: 'DELETE', + }, + ); + return this.formatResult(raw, options?.format); + } + + async listCustomFieldOptions( + definitionId: string, + options?: ListOptions, + ): Promise>> { + const encodedDefinitionId = encodeURIComponent(definitionId); + const query = this.applyListOptions(options); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedDefinitionId}/options${query}`, + ); + return this.formatResult(raw, options?.format); + } + + async getCustomFieldOption( + definitionId: string, + optionId: string, + options?: CallOptions, + ): Promise { + const encodedDefinitionId = encodeURIComponent(definitionId); + const encodedOptionId = encodeURIComponent(optionId); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedDefinitionId}/options/${encodedOptionId}`, + ); + return this.formatResult(raw, options?.format); + } + + async createCustomFieldOption( + definitionId: string, + payload: Record, + options?: CallOptions, + ): Promise { + const encodedDefinitionId = encodeURIComponent(definitionId); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedDefinitionId}/options`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }, + ); + return this.formatResult(raw, options?.format); + } + + async updateCustomFieldOption( + definitionId: string, + optionId: string, + payload: Record, + options?: CallOptions, + ): Promise { + const encodedDefinitionId = encodeURIComponent(definitionId); + const encodedOptionId = encodeURIComponent(optionId); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedDefinitionId}/options/${encodedOptionId}`, + { + method: 'PATCH', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }, + ); + return this.formatResult(raw, options?.format); + } + + async deleteCustomFieldOption( + definitionId: string, + optionId: string, + options?: CallOptions, + ): Promise { + const encodedDefinitionId = encodeURIComponent(definitionId); + const encodedOptionId = encodeURIComponent(optionId); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_field_definitions/${encodedDefinitionId}/options/${encodedOptionId}`, + { + method: 'DELETE', + }, + ); + return this.formatResult(raw, options?.format); + } + + async listCustomFields( + options?: ListOptions, + ): Promise>> { + const query = this.applyListOptions(options); + const raw = await this.executeManual(`${this.apiBaseUrl}/custom_fields${query}`); + return this.formatResult(raw, options?.format); + } + + async getCustomField(id: string, options?: CallOptions): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_fields/${encodedId}`, + ); + return this.formatResult(raw, options?.format); + } + + async createCustomField( + payload: Record, + options?: CallOptions, + ): Promise { + const raw = await this.executeManual(`${this.apiBaseUrl}/custom_fields`, { + method: 'POST', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }); + return this.formatResult(raw, options?.format); + } + + async updateCustomField( + id: string, + payload: Record, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_fields/${encodedId}`, + { + method: 'PATCH', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }, + ); + return this.formatResult(raw, options?.format); + } + + async deleteCustomField(id: string, options?: CallOptions): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/custom_fields/${encodedId}`, + { + method: 'DELETE', + }, + ); + return this.formatResult(raw, options?.format); + } + + async getShipmentCustomFields( + id: string, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const raw = await this.executeManual( + `${this.apiBaseUrl}/shipments/${encodedId}/custom_fields`, + ); + return this.formatResult(raw, options?.format); + } + + async setShipmentCustomField( + id: string, + fieldId: string, + value: unknown, + options?: CallOptions, + ): Promise { + const encodedId = encodeURIComponent(id); + const payload = { + data: { + type: 'custom_field', + id: fieldId, + value, + }, + }; + const raw = await this.executeManual( + `${this.apiBaseUrl}/shipments/${encodedId}/custom_fields`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + return this.formatResult(raw, options?.format); + } + async listTrackingRequests( filters: Record = {}, options?: ListOptions, @@ -640,15 +1236,17 @@ export class Terminal49Client { init?: RequestInit, ): Promise => { const headers = new Headers(init?.headers); - const authHeader = this.apiToken.startsWith('Token ') + const hasPrefix = /^(Token|Bearer)\s+/i.test(this.apiToken); + const normalizedScheme = this.authScheme === 'Bearer' ? 'Bearer' : 'Token'; + const authHeader = hasPrefix ? this.apiToken - : `Token ${this.apiToken}`; + : `${normalizedScheme} ${this.apiToken}`; headers.set('Authorization', authHeader); - headers.set('Accept', 'application/json'); - if (init?.body !== undefined && !headers.has('Content-Type')) { + + const contentTypeAware = headers.get('Content-Type') ?? headers.get('content-type'); + if (contentTypeAware === null && init?.body !== undefined && typeof init.body === 'string') { headers.set('Content-Type', 'application/json'); } - return fetchImpl(input, { ...init, headers }); }; } @@ -726,6 +1324,13 @@ export class Terminal49Client { params['page[size]'] = String(options.pageSize); } + private applyListOptions(options?: ListOptions): string { + const params: Record = {}; + this.applyPagination(params, options); + const query = new URLSearchParams(params); + return query.toString() ? `?${query.toString()}` : ''; + } + private normalizeInferNumberType( numberType?: string, ): TrackingRequestType | null {