ShipsGo
Migrate from ShipsGo to TimeToCargo API v2: authentication, requests and response handling
This guide covers migration from ShipsGo Ocean API v2 to TimeToCargo API v2. It covers tracking creation, stored data retrieval and route retrieval separately. Legacy ContainerService v1.2 uses different parameters.
See the migration workflow for importing references and setting up notifications. Full TimeToCargo schemas are in the API reference.
Authentication
ShipsGo uses the X-Shipsgo-User-Token header. The ShipsGo Ocean v2 base URL is https://api.shipsgo.com/v2.
To switch to TimeToCargo:
- Create a key in your TimeToCargo account.
- Change the base URL to
https://tracking.timetocargo.com/v2. For example, the full tracking endpoint ishttps://tracking.timetocargo.com/v2/tracking. - Send the new key in the
api_keyquery parameter of every request, including batch tasks and webhook management. Keys and tokens from the former provider cannot be reused.
See authentication for key usage and request limits.
Tracking records and identifiers
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
shipment.id | shipment.id | Map the numeric ShipsGo ID to the new tracking ID. Use the new ID to manage the record. |
shipment.containers[] | tracked_item.data.data.containers[] | In the selected list record, read containers from tracked_item.data.data.containers[]. A bill of lading may contain several. |
shipment.reference / tags | note + your own store | Move one label of up to 40 characters into note. Store remaining tags and metadata in your system. |
Operation mapping
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
POST /ocean/shipments | POST /v2/tracking/batch → GET /v2/tracking/batch | Submit references through POST /v2/tracking/batch, retrieve results through GET /v2/tracking/batch, and save data.results[].data.tracking_metadata.id. |
GET /ocean/shipments | GET /v2/shipments | Retrieve records with page and limit. The status filter selects active or archived subscriptions. |
GET /ocean/shipments/{shipment_id} | GET /v2/shipments | Fetch list pages and find the record by its saved new ID. There is no individual GET by ID. |
PATCH /ocean/shipments/{shipment_id} | PATCH /v2/shipments/{id} | Send {"note":"your label"} in the body. The limit is 40 characters. This operation does not update other record fields. |
DELETE /ocean/shipments/{shipment_id} | DELETE /v2/shipments | Pass new IDs in the shipmentIds array. Follow the archiving and deletion rules in the lifecycle guide. |
GET /ocean/shipments/{shipment_id}/geojson | GET /v2/shipments?need_route=true | Read route_info from the stored record. If your map needs GeoJSON, convert the point arrays and reverse coordinate order. |
GET /ocean/carriers | GET /v2/companies | Find the carrier SCAC and check container, bill of lading or booking support in cn, bl and bk. |
POST /…/{shipment_id}/tags, DELETE /…/{shipment_id}/tags/{tag_id} | PATCH /v2/shipments/{id} (partial) | Use note for one label only. Maintain tag collections, individual tag updates and tag-based searches in your own system. |
POST/DELETE /…/followers | No direct equivalent | Move recipient addresses to your own email system. Webhooks send HTTP notifications, not email subscriptions. |
Request parameters
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
container_number | tracked_items[].track_number.value | In the batch body, pass the number in tracked_items[].track_number.value and set track_number.type to container. |
booking_number | track_number.value + type | Determine whether the field holds a booking or a bill of lading. Put the reference in track_number.value and set type to bk or bl respectively. |
carrier | tracked_items[].company | Replace the ShipsGo carrier code with a SCAC from GET /v2/companies. Map codes such as SG_… before sending company. |
reference / tags | note after creation | Obtain the permanent tracking ID first, then save the label through PATCH /v2/shipments/{id}. The batch request has no note field. |
filters / order_by / skip / take | status / page / limit | Replace skip and take with page and limit. Apply additional filters and sorting in your application after fetching the required pages. |
Response fields
Select a record from data.data[] in the GET /v2/shipments response. Paths starting with tracked_item are relative to that record. Containers are in tracked_item.data.data.containers[], with events in each container's events[]. In GET /v2/tracking, shipment data is directly under data.
| ShipsGo | TimeToCargo | Response handling |
|---|---|---|
shipment.id | data.data[].id | Read the permanent ID from data.data[].id and map it to the source shipment.id. Do not use the temporary task_id for this mapping. |
shipment.carrier.scac | tracked_item.data.data.summary.company.scacs[] | Read codes from summary.company.scacs. It is an array, so check whether it contains the required SCAC. |
shipment.status | tracked_item.data.data.shipment_status | Use IN_TRANSIT instead of SAILING for an ongoing journey. Handle arrival, discharge and delivery as separate stages. |
shipment.containers[].movements[] | tracked_item.data.data.containers[].events[] | Process containers[].events[]. Read the timestamp from date, the actual flag from actual, and the code from status_code. |
shipment.route | tracked_item.data.data.summary / route_info | Read ports from summary.pol and summary.pod. With need_route=true, read geometry from route_info.route[].path. |
Response examples
Each pair labels the former-provider and TimeToCargo responses. Expand the longer JSON examples when you need them; the mapping tables remain visible. These are shortened examples with illustrative data. The path above each JSON block identifies the part shown. Paths in the following table start from that part.
Record, label and shipment state
ShipsGo — GET /ocean/shipments/{shipment_id}
Fragment: shipment.
{
"id": 1001,
"reference": "Order 123",
"container_number": "MSCU1234567",
"booking_number": null,
"carrier": {
"scac": "MSCU",
"name": "MSC"
},
"status": "SAILING",
"containers": [
{
"number": "MSCU1234567",
"size": 40,
"type": "High Cube Dry"
}
],
"tags": [
{
"id": 7,
"name": "IMPORT"
}
],
"discarded_at": null
}TimeToCargo — GET /v2/shipments
Fragment: data.data[0].
{
"id": "11111111-1111-4111-8111-111111111111",
"status": "ACTIVE",
"note": "Order 123",
"track_number": "MSCU1234567",
"tracked_item": {
"track_number": "MSCU1234567",
"track_number_type": "container",
"data": {
"success": true,
"status": "OK",
"data": {
"summary": {
"company": {
"name": "MSC",
"scacs": ["MEDU", "MSCU"]
}
},
"shipment_status": "IN_TRANSIT",
"containers": [
{
"number": "MSCU1234567",
"type": "40' High Cube Dry"
}
]
}
}
}
}How to map
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
id = 1001 | id = "11111111-1111-4111-8111-111111111111" | Save a ShipsGo-to-new-ID mapping. Do not pass 1001 to operations that manage the new tracking record. |
reference = "Order 123" | note = "Order 123" | Save Order 123 as note in a separate request after tracking is created. The maximum length is 40 characters. |
container_number = "MSCU1234567" | tracked_item.track_number = "MSCU1234567" | The number remains unchanged. In the list record, it is in tracked_item.track_number and its type in tracked_item.track_number_type. |
status = "SAILING" | tracked_item.data.data.shipment_status = "IN_TRANSIT" | For the stage shown, use shipment_status: "IN_TRANSIT" instead of SAILING. |
containers[0].type = "High Cube Dry" | tracked_item.data.data.containers[0].type = "40' High Cube Dry" | The new response combines container size and type in containers[].type. Update how those details are displayed. |
tags | No direct equivalent | If note already holds the order reference, keep tags separately. One note cannot represent multiple independent tags. |
Container movements, estimates and locations
ShipsGo — GET /ocean/shipments/{shipment_id}
Fragment: shipment.containers[0].
{
"number": "MSCU1234567",
"movements": [
{
"event": "LOAD",
"status": "ACT",
"timestamp": "2026-10-01T12:00:00+08:00",
"location": {
"code": "SGSIN",
"name": "Singapore",
"timezone": "Asia/Singapore",
"country": {
"code": "SG",
"name": "Singapore"
}
},
"vessel": {
"name": "EXAMPLE VESSEL",
"imo": 9000003
},
"voyage": "126E"
},
{
"event": "ARRV",
"status": "EST",
"timestamp": "2026-10-12T08:00:00+02:00",
"location": {
"code": "NLRTM",
"name": "Rotterdam",
"timezone": "Europe/Amsterdam",
"country": {
"code": "NL",
"name": "Netherlands"
}
},
"vessel": {
"name": "EXAMPLE VESSEL",
"imo": 9000003
},
"voyage": "126E"
}
]
}TimeToCargo — GET /v2/shipments
Fragment: data.data[0].tracked_item.data.data.
{
"locations": [
{
"id": 0,
"name": "Singapore",
"country": "Singapore",
"lat": 1.25,
"lng": 103.75
},
{
"id": 1,
"name": "Rotterdam",
"country": "Netherlands",
"lat": 51.95,
"lng": 4.14
}
],
"containers": [
{
"number": "MSCU1234567",
"type": "40' High Cube Dry",
"events": [
{
"location": 1,
"status": "Vessel arrival",
"status_code": "VAD",
"date": "2026-10-12T08:00:00",
"actual": false,
"vessel": "EXAMPLE VESSEL",
"voyage": "126E"
},
{
"location": 0,
"status": "Loaded",
"status_code": "CLL",
"date": "2026-10-01T12:00:00",
"actual": true,
"vessel": "EXAMPLE VESSEL",
"voyage": "126E"
}
]
}
]
}How to map
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
number = "MSCU1234567" | containers[0].number = "MSCU1234567" | Find the container by containers[].number, then process its events. |
movements[0].event = "LOAD" | containers[0].events[1].status_code = "CLL" | Origin-port loading in this example is CLL. For transshipment loading, check CLT. |
movements[1].status = "EST" | containers[0].events[0].actual = false | Replace ACT with actual=true and EST with actual=false. Do not treat an estimate as a completed event. |
movements[1].timestamp = "2026-10-12T08:00:00+02:00" | containers[0].events[0].date = "2026-10-12T08:00:00" | Parse the +02:00 offset when present. Determine whether the event is actual from actual, not the timestamp format. |
movements[0].location | locations[0] | Instead of a nested location object, look up the entry in locations using event.location. |
movements[0].vessel.name = "EXAMPLE VESSEL" | containers[0].events[1].vessel = "EXAMPLE VESSEL" | Read the vessel name from the event.vessel string. Do not expect a nested object with a name and IMO. |
Dates illustrate field formats. Preserve source offset and timezone meaning: deleting Z or +08:00 is not a timezone conversion.
GeoJSON and route coordinates
ShipsGo — GET /ocean/shipments/{shipment_id}/geojson
Fragment: geojson.features[0].
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[103.75, 1.25],
[103.4, 1.1]
]
},
"properties": {
"status": "CURRENT",
"current": {
"index": 0,
"coordinates": [103.75, 1.25]
}
}
}TimeToCargo — GET /v2/shipments?need_route=true
Fragment: data.data[0].tracked_item.data.data.
{
"route_info": {
"current_position": [1.25, 103.75],
"last_updated": "2026-10-03T09:00:00",
"route": [
{
"type": "sea",
"path": [
[1.25, 103.75],
[1.1, 103.4]
]
}
]
}
}How to map
| ShipsGo | TimeToCargo | Required change |
|---|---|---|
geometry.coordinates | route_info.route[0].path | When converting GeoJSON, reverse each point from [longitude, latitude] to [latitude, longitude]. |
properties.current.coordinates = [103.75,1.25] | route_info.current_position = [1.25,103.75] | Read the current point separately from route_info.current_position. It also uses [latitude, longitude] order. |
geometry.type = "LineString" | route_info.route[0].type = "sea" | Use sea as the transport mode. LineString describes GeoJSON geometry and is not a replacement for sea. |
properties.status = "CURRENT" | No direct equivalent | Do not convert CURRENT into a shipment or subscription status. It describes a ShipsGo route segment and has no dedicated field here. |
Data to retain separately
Use GET /v2/shipments?need_route=true to display a stored route. GET /v2/tracking?need_route=true and POST /v2/tracking/batch?need_route=true initiate tracking and may consume credits. If your map uses GeoJSON, convert points from [latitude, longitude] to [longitude, latitude].
Last updated on