Terminal49
Migrate from Terminal49 to TimeToCargo API v2: authentication, requests and response handling
This guide covers migration from Terminal49 API v2 to TimeToCargo API v2. Terminal49 uses JSON:API with separate tracking request, shipment and container records. The tables below show how to initiate tracking and preserve relationships between those records.
See the migration workflow for importing references and setting up notifications. Full TimeToCargo schemas are in the API reference.
Authentication
Terminal49 uses the Authorization: Token … header. The Terminal49 base URL is https://api.terminal49.com/v2. For requests with a body, use Content-Type: application/json. Remove the JSON:API data.attributes wrapper and use the new request schema.
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
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
tracking_request | batch task (partial) | Use the task only to retrieve initiation results. Once it completes, save the permanent tracking ID and export tracking_request history separately. |
shipment | shipment + tracking data | Save a new ID for each tracked reference. Containers for a bill of lading or booking are nested within that record's result. |
container | containers[] | Find a container by containers[].number. There is no separate permanent container ID or container management API here. |
transport_event | containers[].events[] | Read events from the selected container's array. Update code handling and actual/planned date handling using the tables below. |
Operation mapping
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
POST /tracking_requests | POST /v2/tracking/batch → GET /v2/tracking/batch | Submit references in a batch, retrieve the task results, and save the permanent tracking IDs. |
GET /tracking_requests/{id} | GET /v2/tracking/batch?task_id=… (partial) | Poll the task until PROCESSED, then use IDs from data.results[].data.tracking_metadata.id. Tasks are retained for a limited time. |
GET /tracking_requests | No direct equivalent | Keep submitted tasks and their results in your import log. There is no operation to list every task. |
GET /shipments, GET /containers | GET /v2/shipments | Fetch tracking-record pages. Each record's containers are in tracked_item.data.data.containers[]. |
GET /shipments/{id}, GET /containers/{id} | GET /v2/shipments | Find the record by its saved new ID in the list pages. For an individual container, select it by number within the record. |
GET /containers/{id}/transport_events | GET /v2/shipments → containers[].events[] | Read the selected container's events from the stored record. There is no separate paginated event endpoint. |
PATCH /tracking_requests/{id} | PATCH /v2/shipments/{id} (partial) | Move ref_number into note if it fits within 40 characters. This operation cannot change the carrier reference or SCAC. |
GET /shipping_lines | GET /v2/companies | Find the carrier by SCAC and check support for your reference type in cn, bl and bk. |
PATCH /shipments/{id}/stop_tracking, PATCH /shipments/{id}/resume_tracking | DELETE /v2/shipments / new initiation | To stop tracking, pass the new IDs in shipmentIds. Reactivate with a tracking request according to the lifecycle guide. |
/webhooks, /webhook_notifications | /v2/webhooks, /v2/webhook-notifications | Recreate webhooks and update signature verification and notification parsing. Delivery logs are available through /v2/webhook-notifications. |
Request parameters
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
data.attributes.request_number | tracked_items[].track_number.value | Copy request_number into tracked_items[].track_number.value. Remove the JSON:API data.attributes wrapper. |
data.attributes.request_type | tracked_items[].track_number.type | Keep container, replace bill_of_lading with bl, and replace booking_number with bk. |
data.attributes.scac | tracked_items[].company | Verify the SCAC through GET /v2/companies and pass it in tracked_items[].company. |
auto_detect_vocc_scac | company=AUTO (partial) | For automatic carrier detection, use company: "AUTO" only with type container. Supply a SCAC for a bill of lading or booking. |
POST: ref_numbers[] / initial_custom_fields, PATCH: ref_number | note + your own store | After obtaining the ID, save one label of up to 40 characters as note. Keep reference arrays and custom fields in your database. |
include / filter / page | page / limit / status / need_route | Use page, limit and status for lists, and need_route=true for routes. Remove include and update relationship handling because there is no included array. |
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.
| Terminal49 | TimeToCargo | Response handling |
|---|---|---|
data.id on tracking_request | data.task_id (partial) | Do not use task_id as a permanent ID. Retrieve the task result and save data.results[].data.tracking_metadata.id. |
attributes.status: pending / created / failed | data.status + results[].success / status | Check the task's data.status, then each result's success and status. PROCESSED means processing finished, not that every reference succeeded. |
relationships.tracked_object.data | results[].data.tracking_metadata.id | Map the source tracked_object to the new permanent ID. The new response has no JSON:API relationships object. |
container.attributes.number | tracked_item.data.data.containers[].number | Read the number from containers[].number. When several containers are returned, select the required entry by number. |
transport_event.attributes.event | containers[].events[].status_code / actual | Handle status_code and actual separately. For example, origin-port loading is CLL and transshipment loading is CLT. |
transport_event.attributes.timestamp / timezone / voyage_number | containers[].events[].date / voyage | Read the event date from date and voyage from voyage. Account for timezone when comparing with the old timestamp. Do not use record creation time as the event date. |
pickup_lfd / holds_at_pod_terminal / fees_at_pod_terminal | No direct equivalent | Keep the last free pickup day, release holds and fees separately. They cannot be derived from the overall shipment_status. |
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.
Tracking request and background task
Terminal49 — POST /tracking_requests
Shortened response.
{
"data": {
"id": "33333333-3333-4333-8333-333333333333",
"type": "tracking_request",
"attributes": {
"request_number": "MSCU1234567",
"request_type": "container",
"scac": "MSCU",
"ref_numbers": ["Order 123"],
"status": "pending"
},
"relationships": {
"tracked_object": {
"data": null
}
}
}
}TimeToCargo — POST /v2/tracking/batch
Shortened response.
{
"success": true,
"status": "OK",
"data": {
"task_id": "22222222-2222-4222-8222-222222222222"
}
}How to map
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
data.id = "33333333-3333-4333-8333-333333333333" | data.task_id = "22222222-2222-4222-8222-222222222222" | The source ID is persistent, while task_id is temporary. Wait for the task result to obtain the permanent tracking ID. |
data.attributes.request_type = "container" | No direct equivalent | Pass the type in tracked_items[].track_number.type when submitting. The short task-creation response does not repeat it. |
data.type = "tracking_request" | No direct equivalent | Remove JSON:API data.type handling. The reference type is separate and accepts container, bl or bk. |
data.attributes.ref_numbers = ["Order 123"] | No direct equivalent | Save the label with a separate PATCH /v2/shipments/{id} after obtaining the permanent ID. The task-creation response does not yet contain note. |
The creation response contains only the task ID. After PROCESSED, retrieve the permanent ID from data.results[].data.tracking_metadata.id. Use it to locate the stored record and update its label.
Container, parent shipment and equipment
Terminal49 — GET /containers/{id}
Fragment: data.
{
"id": "33333333-3333-4333-8333-333333333333",
"type": "container",
"attributes": {
"number": "MSCU1234567",
"current_status": "on_ship",
"ref_numbers": ["Order 123"],
"equipment_length": 40,
"equipment_height": "high_cube",
"equipment_type": "dry"
},
"relationships": {
"shipment": {
"data": {
"id": "66666666-6666-4666-8666-666666666666",
"type": "shipment"
}
}
}
}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": {
"shipment_status": "IN_TRANSIT",
"containers": [
{
"number": "MSCU1234567",
"type": "40' High Cube Dry"
}
]
}
}
}
}How to map
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
attributes.number = "MSCU1234567" | tracked_item.data.data.containers[0].number = "MSCU1234567" | The container number remains unchanged. Use it to select an entry in containers[]. |
id = "33333333-3333-4333-8333-333333333333" | id = "11111111-1111-4111-8111-111111111111" | Map the Terminal49 container ID to the new tracking ID and container number. One document result may include several containers. |
relationships.shipment.data.id = "66666666-6666-4666-8666-666666666666" | No direct equivalent | Retain the source parent shipment ID in your relationship table. The new response has no separate field for that ID. |
attributes.current_status = "on_ship" | tracked_item.data.data.shipment_status = "IN_TRANSIT" | This example has one container in transit, so the status is IN_TRANSIT. For a multi-container result, do not assign one container's status to all others. |
attributes.ref_numbers = ["Order 123"] | note = "Order 123" | Move Order 123 into note. Retain other ref_numbers entries separately. |
attributes.equipment_height = "high_cube" | tracked_item.data.data.containers[0].type = "40' High Cube Dry" | Size, height and type are combined in the containers[].type string. There is no separate equipment_height. |
Transport events: codes, dates and actual status
Terminal49 — GET /containers/{id}/transport_events
Fragment: data.
[
{
"id": "77777777-7777-4777-8777-777777777777",
"type": "transport_event",
"attributes": {
"event": "container.transport.vessel_loaded",
"timestamp": "2026-10-01T04:00:00Z",
"timezone": "Asia/Singapore",
"location_locode": "SGSIN",
"voyage_number": "126E"
}
},
{
"id": "88888888-8888-4888-8888-888888888888",
"type": "transport_event",
"attributes": {
"event": "container.transport.estimated.vessel_arrived",
"timestamp": "2026-10-12T06:00:00Z",
"timezone": "Europe/Amsterdam",
"location_locode": "NLRTM",
"voyage_number": "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",
"events": [
{
"location": 1,
"status": "Vessel arrival",
"status_code": "VAD",
"date": "2026-10-12T08:00:00",
"actual": false,
"voyage": "126E"
},
{
"location": 0,
"status": "Loaded",
"status_code": "CLL",
"date": "2026-10-01T12:00:00",
"actual": true,
"voyage": "126E"
}
]
}
]
}How to map
| Terminal49 | TimeToCargo | Required change |
|---|---|---|
[0].attributes.event = "container.transport.vessel_loaded" | containers[0].events[1].status_code = "CLL" | Origin-port loading in this example corresponds to CLL. Use CLT for a transshipment loading event. |
[1].attributes.event = "container.transport.estimated.vessel_arrived" | containers[0].events[0].actual = false | Check actual=false for estimated arrival. The vessel-arrival code remains VAD. |
[0].attributes.timestamp = "2026-10-01T04:00:00Z" | containers[0].events[1].date = "2026-10-01T12:00:00" | In this example, 04:00 UTC is 12:00 in Singapore. Use the source timezone when converting dates rather than removing Z. |
[0].attributes.location_locode = "SGSIN" | locations[0].name = "Singapore" | Resolve event.location through locations. Use UN/LOCODE SGSIN to verify the source location. |
[0].attributes.voyage_number = "126E" | containers[0].events[1].voyage = "126E" | Read the voyage from event.voyage instead of voyage_number. The value 126E remains unchanged. |
[0].id = "77777777-7777-4777-8777-777777777777" | No direct equivalent | New response events do not have this ID. Keep the source ID with archived data if your system references it. |
Data to retain separately
Separately retain terminal details, last free pickup dates, release holds, fees and custom fields. For each container tracked through a bill of lading or booking, keep the Terminal49 container ID, new tracking ID and container number. This preserves links to former records.
Last updated on