Developers

Build on the MG Ship API

Pull real-time shipment visibility into your own systems with a simple, versioned REST API.

Overview

The MG Ship API is a REST API that returns JSON over HTTPS. It gives your systems programmatic access to shipment visibility data — listings, full shipment records, and tracking timelines — so you can pull milestones into your own TMS, ERP, or customer portal.

Every endpoint lives under the versioned base path below. The API is versioned in the URL path (v1). Breaking changes ship under a new path (v2) with a 12-month deprecation window for the previous version, so an integration built against v1 keeps working.

Base URLs

Pick the base URL for the environment you are integrating against. Every endpoint path below is relative to it. Staging and Test (VPS) are for development and testing; use Production only with live keys.

EnvironmentBase URL
Productionhttps://api.mglobalship.com/api/v1/external
Staginghttps://api.staging.mglobalship.com/api/v1/external
Test (VPS)https://mgs.aipipis.com/api/v1/external

Authentication

Authenticate every request with an API key sent as a Bearer token in the Authorization header. Keys are issued per tenant from the API Portal, are shown only once at creation, carry explicit scopes (for example shipments.read), and can be rotated or revoked at any time.

Keep keys server-side — never ship them in browser or mobile code. If a key is exposed, rotate it from the API Portal and the old key stops working immediately.

bash
curl https://api.staging.mglobalship.com/api/v1/external/health \
  -H "Authorization: Bearer mgs_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Conventions

Pagination

List endpoints are cursor-paginated. Pass limit (1–100, default 20) to size a page. When more results exist the response sets has_more to true and returns next_cursor; pass it back as the cursor query parameter to fetch the next page. Cursors are opaque — treat them as a token, do not construct them yourself.

Rate limits

Requests are rate-limited per minute based on your account tier. Every response includes the headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until the window resets). Exceeding the limit returns 429 with a retry_after_seconds value in the body. Individual keys can be granted a higher ceiling on request.

TierRequests / min
Bronze60
Silver300
Gold1,000
Platinum5,000
EnterpriseUnlimited

Idempotency

Write endpoints (such as POST /shipments) require an Idempotency-Key header. Retrying a request with the same key returns the original result instead of creating a duplicate, so a network retry is always safe. Read-only (GET) endpoints do not need it.

Endpoints

GET/health

Check that your key works and see your current tier and rate-limit status. A lightweight call to verify connectivity.

bash
curl https://api.staging.mglobalship.com/api/v1/external/health \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "status": "ok",
  "tenant_id": 123,
  "tier": "silver",
  "rate_limit": {
    "limit": 300,
    "remaining": 299,
    "reset_seconds": 59
  }
}
POST/shipments

Register a shipment so the platform starts tracking it. Requires the shipments.write scope and a unique Idempotency-Key header. The shipment counts against your plan's monthly quota.

ParameterTypeDescription
Idempotency-Key*string (header)Unique per request; retrying with the same value returns the original shipment instead of creating a duplicate.
tracking_number*stringThe AWB / B-L / tracking number.
carrier_code*stringCarrier or airline code (PCS code, IATA airline code, or AfterShip courier slug).
mode*stringTransport mode: sea, air, or courier.
bash
curl -X POST https://api.staging.mglobalship.com/api/v1/external/shipments \
  -H "Authorization: Bearer mgs_live_..." \
  -H "Idempotency-Key: a1b2c3d4-e5f6-7890" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_number": "160-12345675",
    "carrier_code": "pcs",
    "mode": "air"
  }'
json
{
  "object": "shipment",
  "id": 12346,
  "tracking_number": "160-12345675",
  "carrier_code": "pcs",
  "mode": "air",
  "status": "pending",
  "awb_number": "160-12345675",
  "_links": {
    "self": "/api/v1/external/shipments/12346",
    "tracking": "/api/v1/external/shipments/12346/tracking"
  }
}
GET/shipments

List your shipments, newest first, with cursor pagination and optional filters.

ParameterTypeDescription
limitinteger1–100, default 20.
cursorstringOpaque cursor from a previous page.
statusstringFilter by shipment status, e.g. in_transit.
qstringFull-text search across shipment fields.
modestringair, sea, rail, or road.
carrier_codestringFilter by carrier, e.g. maersk.
postringFilter by purchase-order number.
bash
curl "https://api.staging.mglobalship.com/api/v1/external/shipments?status=in_transit&limit=20" \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "object": "list",
  "data": [
    {
      "object": "shipment",
      "id": 12345,
      "tracking_number": "MAEU1234567",
      "carrier_code": "maersk",
      "mode": "sea",
      "status": "in_transit",
      "origin": { "country": "CN", "city": "Shanghai", "port_code": "CNSHA" },
      "destination": { "country": "MY", "city": "Port Klang", "port_code": "MYPKG" },
      "po_number": "PO-2026-001",
      "estimated_arrival": "2026-04-30T14:00:00+08:00",
      "_links": {
        "self": "/api/v1/external/shipments/12345",
        "tracking": "/api/v1/external/shipments/12345/tracking"
      }
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "_links": { "self": "/api/v1/external/shipments" }
}
GET/shipments/{id}

Retrieve a single shipment with full cargo details and its milestone history.

ParameterTypeDescription
id*integerThe shipment id (path parameter).
bash
curl https://api.staging.mglobalship.com/api/v1/external/shipments/12345 \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "object": "shipment",
  "id": 12345,
  "tracking_number": "MAEU1234567",
  "carrier_code": "maersk",
  "mode": "sea",
  "status": "in_transit",
  "origin": { "country": "CN", "city": "Shanghai", "port_code": "CNSHA" },
  "destination": { "country": "MY", "city": "Port Klang", "port_code": "MYPKG" },
  "po_number": "PO-2026-001",
  "container_no": "MAEU123456789",
  "estimated_arrival": "2026-04-30T14:00:00+08:00",
  "vessel_name": "MSC GULSUN",
  "voyage_number": "2026W0105",
  "cargo": {
    "weight_kg": 18000.5,
    "volume_cbm": 45.25,
    "pieces": 120,
    "description": "Electronics",
    "hs_code": "8471.30",
    "container_size": "40HQ",
    "declared_value": { "amount": 499900, "currency": "USD" }
  },
  "milestones": [
    {
      "object": "milestone",
      "status_code": "PICKED_UP",
      "status_text": "Picked up",
      "location": "Shanghai",
      "occurred_at": "2026-04-22T08:15:00+08:00",
      "source": "maersk_direct_api"
    }
  ],
  "_links": {
    "self": "/api/v1/external/shipments/12345",
    "tracking": "/api/v1/external/shipments/12345/tracking"
  }
}
GET/shipments/{id}/weather

Get current/forecast weather (Open-Meteo) at the shipment's origin and destination. On-demand enrichment, cached ~30 min; a leg is null when its position cannot be resolved.

ParameterTypeDescription
id*integerThe shipment id (path parameter).
bash
curl https://api.staging.mglobalship.com/api/v1/external/shipments/12345/weather \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "object": "weather",
  "shipment_id": 12345,
  "origin": {
    "label": "Singapore, SG",
    "lat": 1.29,
    "lng": 103.85,
    "observed_at": "2026-06-25T12:00:00+00:00",
    "temperature_c": 29.4,
    "wind_mps": 2.7,
    "condition_id": 803,
    "condition_main": "Clouds",
    "severity": "low",
    "summary": "Clouds",
    "alerts_count": 0
  },
  "destination": {
    "label": "New York, US",
    "lat": 40.71,
    "lng": -74.0,
    "observed_at": "2026-06-25T12:00:00+00:00",
    "temperature_c": 17.1,
    "wind_mps": 0.5,
    "condition_id": 803,
    "condition_main": "Clouds",
    "severity": "low",
    "summary": "Clouds",
    "alerts_count": 0
  },
  "_links": {
    "self": "/api/v1/external/shipments/12345/weather",
    "shipment": "/api/v1/external/shipments/12345"
  }
}
GET/shipments/{id}/risk

Get conflict / civil-unrest events near the shipment's origin and destination (within 100 km over the last 7 days). On-demand enrichment with cached provider results.

ParameterTypeDescription
id*integerThe shipment id (path parameter).
bash
curl https://api.staging.mglobalship.com/api/v1/external/shipments/12345/risk \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "object": "geopolitical_risk",
  "shipment_id": 12345,
  "origin": {
    "label": "Singapore, SG",
    "lat": 1.29,
    "lng": 103.85,
    "observed_at": "2026-06-25T12:00:00+00:00",
    "severity": "high",
    "radius_km": 100,
    "since_days": 7,
    "summary": "High: 1 violence_civilians event",
    "events_count": 1,
    "top_events": [
      {
        "occurred_on": "2026-06-21",
        "type": "violence_civilians",
        "sub_type": "Attack",
        "location": "Singapore",
        "fatalities": 0,
        "distance_km": 12.0,
        "description": "High: 1 violence_civilians event"
      }
    ]
  },
  "destination": null,
  "_links": {
    "self": "/api/v1/external/shipments/12345/risk",
    "shipment": "/api/v1/external/shipments/12345"
  }
}
GET/shipments/{id}/tracking

Get the compact tracking timeline (ordered milestone events) for a shipment.

ParameterTypeDescription
id*integerThe shipment id (path parameter).
bash
curl https://api.staging.mglobalship.com/api/v1/external/shipments/12345/tracking \
  -H "Authorization: Bearer mgs_live_..."
json
{
  "object": "tracking",
  "shipment_id": 12345,
  "status": "in_transit",
  "estimated_arrival": "2026-04-30T14:00:00+08:00",
  "last_event_at": "2026-04-23T06:00:00+08:00",
  "events": [
    {
      "object": "milestone",
      "status_code": "PICKED_UP",
      "status_text": "Picked up at origin",
      "location": "Shanghai",
      "occurred_at": "2026-04-22T08:15:00+08:00",
      "source": "maersk_direct_api"
    },
    {
      "object": "milestone",
      "status_code": "DEPARTED_ORIGIN",
      "status_text": "Departed origin port",
      "location": "Shanghai",
      "occurred_at": "2026-04-23T06:00:00+08:00",
      "source": "maersk_direct_api"
    }
  ],
  "_links": {
    "self": "/api/v1/external/shipments/12345/tracking",
    "shipment": "/api/v1/external/shipments/12345"
  }
}

Webhooks

Instead of polling, subscribe to webhooks to be notified when something happens — for example when a shipment hits an exception. You configure endpoint URLs and select event types in the API Portal, and we POST a JSON payload to your URL for each event.

Every delivery is signed so you can verify it came from us. We send the signature in the X-MGS-Signature header, the event type in X-MGS-Event, and a unique delivery id in X-MGS-Delivery-Id (reuse it to make your handler idempotent). Deliveries are retried with exponential backoff over 24 hours.

X-MGS-Signature has the form t=<timestamp>,v1=<signature>, where signature is the hex HMAC-SHA256 of the string "<timestamp>.<raw-request-body>" keyed with your endpoint secret. To verify: split out t and v1, recompute the HMAC over the timestamp, a literal dot, and the exact raw body, and compare. Reject if it does not match or if the timestamp is too old.

http
X-MGS-Event: shipment.exception
X-MGS-Delivery-Id: whd_8f3c2a1b
X-MGS-Signature: t=1715241600,v1=4e2c...hex...sha256

Errors

Errors use standard HTTP status codes and a consistent JSON envelope. The error object always carries a stable code and a human-readable message; some errors add context fields. Every response includes a request_id — quote it when contacting support.

StatusCodeWhen
400validation_errorThe request body is missing or invalid (or the Idempotency-Key header is absent).
401auth.token_invalidMissing or invalid API key.
402shipment.overage_payment_requiredShipment quota exhausted; settle outstanding overage first.
403auth.insufficient_scopeKey lacks the required scope, e.g. shipments.read.
403auth.tenant_suspendedThe tenant account is not active.
404shipment.not_foundThe requested resource does not exist.
409shipment.duplicateA shipment with this tracking number and carrier already exists.
429rate_limit_exceededRate limit hit; see retry_after_seconds.
json
{
  "error": {
    "code": "auth.token_invalid",
    "message": "Token invalid."
  },
  "request_id": "req_9a8b7c6d"
}
json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded for Silver tier (300 req/min). Wait 23s.",
    "retry_after_seconds": 23
  },
  "request_id": "req_1f2e3d4c"
}

Get started