Developers

Wingo Marine API reference

REST · JSON · X-API-Key · v1

Live AIS vessel positions, tracks of the last 7 days, vessels in any area, ports by UN/LOCODE, your fleets and on-demand satellite position checks — one header, clean JSON and a published credit cost for every call. Fleet events reach your systems as signed webhooks.

Base URL/api/public/v1
Rate limit10req/s per key
Track history7days
Free on sign-up200credits

Quick start

  1. Create a free account (no card). New accounts get 200 API credits to try the API.
  2. Open Developer in the app and create a key. It is shown once — keep it server-side.
  3. Ask for any vessel by MMSI or IMO:
curl -H "X-API-Key: $WINGO_KEY" \
  https://marine.wingo.ro/api/public/v1/vessels/211331640

The response headers X-Credits-Cost and X-Credits-Remaining tell you what the call cost and what is left.

Authentication

Send your key in the X-API-Key header on every request. There is no OAuth on the public API. You can hold up to 10 active keys and revoke any of them in the Developer page, which also charts your daily usage.

X-API-Key: ais_••••••••••••••••••••••••••••••••
  • Missing header → 401 Cheie API lipsă; unknown or revoked key → 401 Cheie API invalidă.
  • Disabled account → 403 Cont dezactivat.
  • Never put the key in a browser or mobile app: call the API from your server.

Credits and limits

Every call spends its published cost. Credits come first from your plan's monthly bucket (refilled at each renewal), then from top-up packs (never expire). When both are empty the API answers 402 and charges nothing — there are no overage fees. Plans start at €9.99/month; one-time credit packs need no subscription.

  • Rate limit: 10 requests per second per key → 429 above it.
  • Lists: pageSize defaults to 50 and is clamped to 100. /vessels/in-bbox returns at most 1000 vessels.
  • Track history: up to 168 hours (7 days) of sampled positions.
  • Fleet changes need an active subscription and respect the plan's vessel limit; bulk adds take up to 200 ids.

Plans, credit packs and costs →

Endpoints

All routes live under https://marine.wingo.ro/api/public/v1, answer JSON and spend the credit cost shown (live values; the operator can change them, and every response tells you what it cost).

MethodPathWhat it returnsCost
GET/vesselsSearch vessels by name, MMSI or IMO (paginated, pageSize ≤ 100)2 credits
GET/vessels/{id}Vessel details and last position (?verify=true adds a live satellite check)1 credit
GET/vessels/{id}/trackRecent track, sampled positions (hours ≤ 168)5 credits
GET/vessels/in-bboxVessels in a bounding box (≤ 1000, grid-sampled by real density)5 credits
GET/portsSearch ports (paginated)1 credit
GET/ports/{locode}Port details by UN/LOCODE1 credit
GET/fleetsThe key owner's fleets with vessel counts1 credit
GET/fleets/{fleetId}One fleet with its vessels1 credit
GET/fleets/{fleetId}/vesselsFleet memberships only1 credit
GET/fleets/{fleetId}/positionsPositions of the whole fleet in one call (?verify=true supported)2 credits
POST/fleets/{fleetId}/vesselsAdd a vessel { "id", "label?" } (active subscription required)2 credits
POST/fleets/{fleetId}/vessels/bulkAdd up to 200 vessels { "ids": [...] }2 credits
PATCH/fleets/{fleetId}/vessels/{mmsi}Update a label { "label" }1 credit
DELETE/fleets/{fleetId}/vessels/{mmsi}Remove a vessel from a fleet1 credit

Vessels

Vessels are addressed by MMSI (9 digits) or IMO (7 digits) — never by name, because names are not unique.

GET/vessels2 credits

Search vessels

Paginated vessel search. search matches every word against the name, and digits as an MMSI or IMO prefix; prefix matches rank first. Combine it with the filters below. List rows carry no ETA (use the detail route).

ParameterDescription
searchquery · stringName, MMSI or IMO (max 64 characters).
categoryquery · string listVessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown.
shipTypequery · integer listAIS ship type codes (comma-separated list allowed). Range 0–99.
navStatusquery · integer listAIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15.
minSpeedquery · numberMinimum speed over ground, in knots. Minimum 0.
flagquery · string listFlag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed).
destinationquery · stringCase-insensitive substring of the AIS destination (max 32 characters).
signalAgeMaxMinquery · integerOnly vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080.
minLengthquery · numberMinimum length overall in metres (from the AIS dimensions). Minimum 0.
maxLengthquery · numberMaximum length overall in metres. Minimum 0.
includeUnverifiedquery · booleanAlso return vessels known only by name from a source without MMSI (mmsi: null, unverified: true). Not a stable identity. Default false.
pagequery · integerPage number (1-based). Minimum 1. Default 1.
pageSizequery · integerItems per page. Values above 100 are clamped to 100. Minimum 1. Default 50.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/vessels?search=maersk&category=cargo&pageSize=20"

Returns: A page of vessels.

GET/vessels/{id}1 credit

Vessel detail

Identity, last position, voyage (destination, ETA, draught) and AIS dimensions of one vessel. With verify=true the position is checked on demand first and the response carries a verification object.

verify=true adds 10 credits per vessel actually checked.

ParameterDescription
idpath · string · requiredMMSI (9 digits) or IMO number (7 digits).
verifyquery · booleanRun an on-demand satellite position check before answering (true or 1). Adds the vesselVerifyNow cost per vessel actually checked; see "Satellite position checks". Default false.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/vessels/211331640"

Returns: The vessel.

GET/vessels/{id}/track5 credits

Vessel track

Sampled positions of the last hours hours (default 24, at most 168 = 7 days), oldest first. History is sampled, not every raw AIS message.

ParameterDescription
idpath · string · requiredMMSI (9 digits) or IMO number (7 digits).
hoursquery · integerLook-back window in hours. Range 1–168. Default 24.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/vessels/211331640/track?hours=48"

Returns: Track points, oldest first.

GET/vessels/in-bbox5 credits

Vessels in an area

Vessels with a position inside the box, at most 1000. When the box holds more, it is split into a grid and every cell gets a share proportional to its real number of vessels (larger ships first, then the freshest), so sparse regions are not crowded out by dense ones. Accepts the same filters as the search.

ParameterDescription
minLatquery · number · requiredSouth edge (latitude). Range -90–90.
minLonquery · number · requiredWest edge (longitude). A west edge greater than the east edge wraps the antimeridian. Range -180–180.
maxLatquery · number · requiredNorth edge (latitude). Range -90–90.
maxLonquery · number · requiredEast edge (longitude). Range -180–180.
categoryquery · string listVessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown.
shipTypequery · integer listAIS ship type codes (comma-separated list allowed). Range 0–99.
navStatusquery · integer listAIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15.
minSpeedquery · numberMinimum speed over ground, in knots. Minimum 0.
flagquery · string listFlag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed).
destinationquery · stringCase-insensitive substring of the AIS destination (max 32 characters).
signalAgeMaxMinquery · integerOnly vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080.
minLengthquery · numberMinimum length overall in metres (from the AIS dimensions). Minimum 0.
maxLengthquery · numberMaximum length overall in metres. Minimum 0.
includeUnverifiedquery · booleanAlso return vessels known only by name from a source without MMSI (mmsi: null, unverified: true). Not a stable identity. Default false.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/vessels/in-bbox?minLat=51.5&minLon=3.5&maxLat=52.2&maxLon=4.6"

Returns: Vessels inside the box.

Ports

Ports are addressed by UN/LOCODE (5 characters, e.g. ROCND).

GET/ports1 credit

Search ports

Paginated port search: every word of search must match the name, UN/LOCODE or country.

ParameterDescription
searchquery · stringPort name, UN/LOCODE or country.
countryquery · stringCase-insensitive substring of the country name.
pagequery · integerPage number (1-based). Minimum 1. Default 1.
pageSizequery · integerItems per page. Values above 100 are clamped to 100. Minimum 1. Default 50.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/ports?search=constanta"

Returns: A page of ports.

GET/ports/{locode}1 credit

Port detail

One port by its UN/LOCODE (5 letters or digits, case-insensitive).

ParameterDescription
locodepath · string · requiredUN/LOCODE.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/ports/ROCND"

Returns: The port.

Fleets

Fleets are watch lists of vessels; the key sees only the fleets of its own account. Reads work on any key; changes need an active subscription.

GET/fleets1 credit

List fleets

Fleets of the account that owns the API key, with their vessel counts.

curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/fleets"

Returns: Your fleets.

GET/fleets/{fleetId}1 credit

Fleet detail

One of your fleets with its vessel memberships.

ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/fleets/3"

Returns: The fleet and its vessels.

GET/fleets/{fleetId}/vessels1 credit

Fleet vessels

The vessel memberships of one fleet: MMSI, label, name, IMO, when it was added and its automatic satellite-check schedule.

ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/fleets/3/vessels"

Returns: Memberships.

GET/fleets/{fleetId}/positions2 credits

Fleet positions

The last position of every vessel in the fleet in one call, with silentHours since its last report. With verify=true the response becomes { items, batch }: stale vessels are checked on demand (capped per request, batch.capApplied says when the cap cut the batch).

verify=true adds 10 credits per vessel actually checked.

ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
verifyquery · booleanRun an on-demand satellite position check before answering (true or 1). Adds the vesselVerifyNow cost per vessel actually checked; see "Satellite position checks". Default false.
curl -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/fleets/3/positions"

Returns: Positions (or { items, batch } with verify=true).

POST/fleets/{fleetId}/vessels2 credits

Add a vessel to a fleet

Adds one vessel by MMSI or IMO, with an optional label. Counts against the plan's unique-vessel limit (the same vessel in two fleets uses one slot).

Needs an active subscription
ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
curl -X POST -H "X-API-Key: $WINGO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":"211331640","label":"Charter 12"}' \
  "https://marine.wingo.ro/api/public/v1/fleets/3/vessels"

Returns: The new membership.

POST/fleets/{fleetId}/vessels/bulk2 credits

Add vessels in bulk

Adds up to 200 vessels (MMSI or IMO) in one call; each id succeeds or fails on its own.

Needs an active subscription
ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
curl -X POST -H "X-API-Key: $WINGO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["211331640","9450648"]}' \
  "https://marine.wingo.ro/api/public/v1/fleets/3/vessels/bulk"

Returns: Per-id outcome.

PATCH/fleets/{fleetId}/vessels/{mmsi}1 credit

Update a label

Sets or clears (null) the label of a fleet vessel.

Needs an active subscription
ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
mmsipath · integer · requiredMMSI of a vessel in the fleet.
curl -X PATCH -H "X-API-Key: $WINGO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"Charter 12"}' \
  "https://marine.wingo.ro/api/public/v1/fleets/3/vessels/211331640"

Returns: The updated membership.

DELETE/fleets/{fleetId}/vessels/{mmsi}1 credit

Remove a vessel from a fleet

Removes the vessel from the fleet.

Needs an active subscription
ParameterDescription
fleetIdpath · integer · requiredId of one of your fleets (from GET /fleets).
mmsipath · integer · requiredMMSI of a vessel in the fleet.
curl -X DELETE -H "X-API-Key: $WINGO_KEY" \
  "https://marine.wingo.ro/api/public/v1/fleets/3/vessels/211331640"

Returns: { "ok": true }

Satellite position checks

Stored positions are as fresh as the last report our feeds received. When you need a ship's position now, add verify=true to GET /vessels/{id} or GET /fleets/{fleetId}/positions: the server asks an external tracking source for the vessel's latest position and applies it only if it is newer than the stored one. Each vessel actually checked adds 10 credits; a result from the last 5 minutes is served from cache for free.

  • A vessel needs an MMSI, an IMO and a name to be checked (otherwise ineligible, free).
  • Fleet batches check only vessels whose position is older than 15 minutes, at most 25 per request; batch.capApplied tells you when the cap cut the batch, and vessels your balance cannot cover come back skipped with reason: "no_credits".
  • In the app, fleet vessels can also be checked automatically on a schedule (from every 15 minutes); the schedule switches itself off when credits run out.
StatusMeaningBilled
okFresh check; the position is applied if newer.yes
cachedAnswered by a check from the last 5 minutes.no
queuedStill running after ~20 s; you get the stored position and the job finishes in the background.yes
skippedLeft out by the batch rules or the balance (see reason).no
ineligibleThe vessel lacks MMSI, IMO or name.no
failedThe run failed; reason explains.yes

Responses

Lists are paginated as { "items": [...], "total": N, "page": 1, "pageSize": 50 }. A vessel detail looks like this (list rows have the same fields without dimensions and with eta: null):

{
  "mmsi": 211331640,
  "imo": 9450648,
  "name": "EXAMPLE CARRIER",
  "callsign": "DHLB",
  "shipType": 70,
  "category": "cargo",
  "lat": 51.95,
  "lon": 4.13,
  "sog": 12.4,
  "cog": 88.1,
  "heading": 90,
  "navStatus": 0,
  "destination": "ROTTERDAM",
  "eta": "2026-10-20T06:00:00.000Z",
  "positionTs": "2026-10-19T09:12:00.000Z",
  "dimA": 200, "dimB": 199, "dimC": 16, "dimD": 16,
  "draught": 12.5,
  "unverified": false,
  "marineTrafficId": null
}

Vessel search and area filters

GET /vessels and GET /vessels/in-bbox accept the same filters:

ParameterDescription
categoryquery · string listVessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown.
shipTypequery · integer listAIS ship type codes (comma-separated list allowed). Range 0–99.
navStatusquery · integer listAIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15.
minSpeedquery · numberMinimum speed over ground, in knots. Minimum 0.
flagquery · string listFlag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed).
destinationquery · stringCase-insensitive substring of the AIS destination (max 32 characters).
signalAgeMaxMinquery · integerOnly vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080.
minLengthquery · numberMinimum length overall in metres (from the AIS dimensions). Minimum 0.
maxLengthquery · numberMaximum length overall in metres. Minimum 0.
includeUnverifiedquery · booleanAlso return vessels known only by name from a source without MMSI (mmsi: null, unverified: true). Not a stable identity. Default false.

Unverified vessels

Some vessels are known only by name, from a source that does not report an MMSI. They are excluded by default; includeUnverified=true returns them with mmsi: null and unverified: true. Do not use them as a stable identity — they disappear once merged with the real vessel.

Sampling in an area

AIS coverage is very uneven, so in-bbox never returns "the N freshest" (that would be almost only the busiest coasts). When the box holds more than 1000 vessels it is split into a grid and each cell gets a share proportional to its real number of vessels, larger ships first.

Tracks and ports

Track: [{ "lat", "lon", "sog", "cog", "ts" }], oldest first. Port: { "id", "locode", "name", "country", "lat", "lon", "updatedAt" }.

Errors

CodeMeaning
400Invalid parameter (MMSI/IMO/LOCODE, bbox, pagination, filters, bulk body).
401Missing or invalid API key.
402Not enough credits — nothing was charged.
403Disabled account, or a fleet change without an active subscription.
404Vessel, port or fleet not found (other accounts’ fleets included).
409The vessel is already in the fleet.
429Rate limit exceeded (10 requests/second per key).

Body of a 402:

{ "statusCode": 402, "message": "Credite insuficiente", "required": 5, "remaining": 2 }

Webhooks

With a paid plan you register HTTPS endpoints in the Webhooks page of the app. Detectors watch every vessel in your fleets around the clock and POST a signed JSON event when something happens — optionally only for some fleets. Each endpoint has its own secret (shown once, rotatable), a test button and a delivery log.

EventWhen
fleet.positionSampled position of a watched vessel.
fleet.port_callA vessel entered or left a port geofence (phase: enter|exit).
fleet.gone_silentA vessel stopped reporting for longer than the silence threshold.
fleet.destination_changedThe AIS destination of a vessel changed.
POST /your/endpoint
Content-Type: application/json
X-AIS-Event: fleet.port_call
X-AIS-Timestamp: 1760865901000
X-AIS-Signature: 7f3c…e91a
Idempotency-Key: 81234
X-AIS-Delivery-Id: 81234
User-Agent: Wingo-Marine-Webhooks/2.0

{
  "event": "fleet.port_call",
  "phase": "enter",
  "port": { "id": 42, "name": "Constanța", "locode": "ROCND" },
  "vessel": { "mmsi": 244660920, "lat": 44.17, "lon": 28.65, "sog": 3.2,
    "cog": null, "heading": null, "navStatus": 0, "destination": null,
    "positionTs": "2026-10-19T12:05:00.000Z" },
  "fleets": [{ "id": 3, "name": "North Sea" }],
  "occurredAt": "2026-10-19T12:05:01.000Z"
}

Verify the signature

X-AIS-Signature is the hex HMAC-SHA256 of ${timestamp}.${rawBody} with the endpoint secret. Use Idempotency-Key to ignore retries of a delivery you already processed.

import { createHmac, timingSafeEqual } from 'crypto';

// rawBody: the request body exactly as received (a string, not re-serialised JSON)
export function verify(rawBody, headers, secret) {
  const ts = headers['x-ais-timestamp'];
  const expected = createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex');
  const got = headers['x-ais-signature'] ?? '';
  return got.length === expected.length &&
    timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}

Answer with any 2xx. Anything else — redirects included — is retried after 30 s, 2 min, 8 min, 30 min, 2 h; after 20 consecutive failures the endpoint is switched off until you re-enable it.

OpenAPI and LLMs

Generate a client, import the API into Postman or point an AI assistant at it: