Quick start
- Create a free account (no card). New accounts get 200 API credits to try the API.
- Open Developer in the app and create a key. It is shown once — keep it server-side.
- 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).
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).
| Parameter | Description |
|---|
searchquery · string | Name, MMSI or IMO (max 64 characters). |
categoryquery · string list | Vessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown. |
shipTypequery · integer list | AIS ship type codes (comma-separated list allowed). Range 0–99. |
navStatusquery · integer list | AIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15. |
minSpeedquery · number | Minimum speed over ground, in knots. Minimum 0. |
flagquery · string list | Flag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed). |
destinationquery · string | Case-insensitive substring of the AIS destination (max 32 characters). |
signalAgeMaxMinquery · integer | Only vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080. |
minLengthquery · number | Minimum length overall in metres (from the AIS dimensions). Minimum 0. |
maxLengthquery · number | Maximum length overall in metres. Minimum 0. |
includeUnverifiedquery · boolean | Also return vessels known only by name from a source without MMSI (mmsi: null, unverified: true). Not a stable identity. Default false. |
pagequery · integer | Page number (1-based). Minimum 1. Default 1. |
pageSizequery · integer | Items 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.
| Parameter | Description |
|---|
idpath · string · required | MMSI (9 digits) or IMO number (7 digits). |
verifyquery · boolean | Run 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.
| Parameter | Description |
|---|
idpath · string · required | MMSI (9 digits) or IMO number (7 digits). |
hoursquery · integer | Look-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.
| Parameter | Description |
|---|
minLatquery · number · required | South edge (latitude). Range -90–90. |
minLonquery · number · required | West edge (longitude). A west edge greater than the east edge wraps the antimeridian. Range -180–180. |
maxLatquery · number · required | North edge (latitude). Range -90–90. |
maxLonquery · number · required | East edge (longitude). Range -180–180. |
categoryquery · string list | Vessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown. |
shipTypequery · integer list | AIS ship type codes (comma-separated list allowed). Range 0–99. |
navStatusquery · integer list | AIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15. |
minSpeedquery · number | Minimum speed over ground, in knots. Minimum 0. |
flagquery · string list | Flag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed). |
destinationquery · string | Case-insensitive substring of the AIS destination (max 32 characters). |
signalAgeMaxMinquery · integer | Only vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080. |
minLengthquery · number | Minimum length overall in metres (from the AIS dimensions). Minimum 0. |
maxLengthquery · number | Maximum length overall in metres. Minimum 0. |
includeUnverifiedquery · boolean | Also 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.
| Parameter | Description |
|---|
searchquery · string | Port name, UN/LOCODE or country. |
countryquery · string | Case-insensitive substring of the country name. |
pagequery · integer | Page number (1-based). Minimum 1. Default 1. |
pageSizequery · integer | Items 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).
| Parameter | Description |
|---|
locodepath · string · required | UN/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.
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id 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.
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id 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.
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id of one of your fleets (from GET /fleets). |
verifyquery · boolean | Run 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
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id 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
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id 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
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id of one of your fleets (from GET /fleets). |
mmsipath · integer · required | MMSI 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
| Parameter | Description |
|---|
fleetIdpath · integer · required | Id of one of your fleets (from GET /fleets). |
mmsipath · integer · required | MMSI 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.
| Status | Meaning | Billed |
|---|
ok | Fresh check; the position is applied if newer. | yes |
cached | Answered by a check from the last 5 minutes. | no |
queued | Still running after ~20 s; you get the stored position and the job finishes in the background. | yes |
skipped | Left out by the batch rules or the balance (see reason). | no |
ineligible | The vessel lacks MMSI, IMO or name. | no |
failed | The 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:
| Parameter | Description |
|---|
categoryquery · string list | Vessel category (comma-separated list allowed). Values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown. |
shipTypequery · integer list | AIS ship type codes (comma-separated list allowed). Range 0–99. |
navStatusquery · integer list | AIS navigational status codes (0 under way, 1 at anchor, 5 moored…). Range 0–15. |
minSpeedquery · number | Minimum speed over ground, in knots. Minimum 0. |
flagquery · string list | Flag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed). |
destinationquery · string | Case-insensitive substring of the AIS destination (max 32 characters). |
signalAgeMaxMinquery · integer | Only vessels whose last position is at most this many minutes old (up to 7 days). Range 1–10080. |
minLengthquery · number | Minimum length overall in metres (from the AIS dimensions). Minimum 0. |
maxLengthquery · number | Maximum length overall in metres. Minimum 0. |
includeUnverifiedquery · boolean | Also 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
| Code | Meaning |
|---|
400 | Invalid parameter (MMSI/IMO/LOCODE, bbox, pagination, filters, bulk body). |
401 | Missing or invalid API key. |
402 | Not enough credits — nothing was charged. |
403 | Disabled account, or a fleet change without an active subscription. |
404 | Vessel, port or fleet not found (other accounts’ fleets included). |
409 | The vessel is already in the fleet. |
429 | Rate 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.
| Event | When |
|---|
fleet.position | Sampled position of a watched vessel. |
fleet.port_call | A vessel entered or left a port geofence (phase: enter|exit). |
fleet.gone_silent | A vessel stopped reporting for longer than the silence threshold. |
fleet.destination_changed | The 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: