{
  "openapi": "3.1.0",
  "info": {
    "title": "Wingo Marine public API",
    "version": "1.0.0",
    "summary": "Live AIS vessel positions, tracks, ports and fleets over REST.",
    "description": "REST API of Wingo Marine: vessels by MMSI or IMO, tracks of the last 7 days, vessels in an area, ports by UN/LOCODE, your fleets and on-demand satellite position checks.\n\nAuthenticate every request with the `X-API-Key` header (keys are created in the Developer page after a free sign-up).\n\nEvery call spends its published credit cost (`x-credits-cost` on each operation, live values) from your monthly plan credits, then your top-up credits; when both are empty the API answers 402 — there are no overage charges. Every successful response carries `X-Credits-Cost` and `X-Credits-Remaining`.\n\nRate limit: 10 requests per second per key (429 above it). Satellite checks on a fleet are capped at 25 vessels per request.\n\nWebhooks: up to 5 delivery attempts (retries after 30 s, 2 min, 8 min, 30 min, 2 h); an endpoint is disabled after 20 consecutive failures.\n\nHuman-readable reference: https://marine.wingo.ro/docs/api · LLM summary: https://marine.wingo.ro/llms-full.txt",
    "contact": {
      "name": "Wingo Marine",
      "url": "https://marine.wingo.ro/docs/api",
      "email": "office@freevox.ro"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://marine.wingo.ro/terms"
    }
  },
  "externalDocs": {
    "description": "API reference",
    "url": "https://marine.wingo.ro/docs/api"
  },
  "servers": [
    {
      "url": "https://marine.wingo.ro/api/public/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Vessels",
      "description": "Vessels by MMSI or IMO, search, tracks and areas."
    },
    {
      "name": "Ports",
      "description": "Ports by UN/LOCODE."
    },
    {
      "name": "Fleets",
      "description": "Your fleets (watch lists). Changes need an active subscription."
    },
    {
      "name": "Webhooks",
      "description": "Signed events pushed to your endpoints for the vessels in your fleets."
    }
  ],
  "paths": {
    "/vessels": {
      "get": {
        "operationId": "searchVessels",
        "summary": "Search vessels",
        "description": "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).\n\n**Cost:** 2 credits (`vesselSearch`).",
        "tags": [
          "Vessels"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Name, MMSI or IMO (max 64 characters).",
            "schema": {
              "type": "string"
            },
            "example": "maersk"
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Vessel category (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown."
            },
            "example": "cargo,tanker"
          },
          {
            "name": "shipType",
            "in": "query",
            "required": false,
            "description": "AIS ship type codes (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated integers."
            }
          },
          {
            "name": "navStatus",
            "in": "query",
            "required": false,
            "description": "AIS navigational status codes (0 under way, 1 at anchor, 5 moored…).",
            "schema": {
              "type": "string",
              "description": "Comma-separated integers."
            }
          },
          {
            "name": "minSpeed",
            "in": "query",
            "required": false,
            "description": "Minimum speed over ground, in knots.",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "flag",
            "in": "query",
            "required": false,
            "description": "Flag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated values."
            },
            "example": "NL,DE"
          },
          {
            "name": "destination",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring of the AIS destination (max 32 characters).",
            "schema": {
              "type": "string"
            },
            "example": "ROTTERDAM"
          },
          {
            "name": "signalAgeMaxMin",
            "in": "query",
            "required": false,
            "description": "Only vessels whose last position is at most this many minutes old (up to 7 days).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10080
            }
          },
          {
            "name": "minLength",
            "in": "query",
            "required": false,
            "description": "Minimum length overall in metres (from the AIS dimensions).",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "maxLength",
            "in": "query",
            "required": false,
            "description": "Maximum length overall in metres.",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "includeUnverified",
            "in": "query",
            "required": false,
            "description": "Also return vessels known only by name from a source without MMSI (`mmsi: null`, `unverified: true`). Not a stable identity.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (1-based).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Items per page. Values above 100 are clamped to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of vessels.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VesselPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "vesselSearch",
        "x-credits-cost": 2
      }
    },
    "/vessels/{id}": {
      "get": {
        "operationId": "getVessel",
        "summary": "Vessel detail",
        "description": "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.\n\n**Cost:** 1 credit (`vesselDetail`).\n\n`verify=true` adds 10 credits per vessel actually checked.",
        "tags": [
          "Vessels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MMSI (9 digits) or IMO number (7 digits).",
            "schema": {
              "type": "string",
              "pattern": "^(\\d{9}|\\d{7})$"
            },
            "example": "211331640"
          },
          {
            "name": "verify",
            "in": "query",
            "required": false,
            "description": "Run an on-demand satellite position check before answering (`true` or `1`). Adds the `vesselVerifyNow` cost per vessel actually checked; see \"Satellite position checks\".",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The vessel.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VesselDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "vesselDetail",
        "x-credits-cost": 1
      }
    },
    "/vessels/{id}/track": {
      "get": {
        "operationId": "getVesselTrack",
        "summary": "Vessel track",
        "description": "Sampled positions of the last `hours` hours (default 24, at most 168 = 7 days), oldest first. History is sampled, not every raw AIS message.\n\n**Cost:** 5 credits (`vesselTrack`).",
        "tags": [
          "Vessels"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MMSI (9 digits) or IMO number (7 digits).",
            "schema": {
              "type": "string",
              "pattern": "^(\\d{9}|\\d{7})$"
            },
            "example": "211331640"
          },
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "description": "Look-back window in hours.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 168,
              "default": 24
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Track points, oldest first.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TrackPoint"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "vesselTrack",
        "x-credits-cost": 5
      }
    },
    "/vessels/in-bbox": {
      "get": {
        "operationId": "vesselsInBbox",
        "summary": "Vessels in an area",
        "description": "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.\n\n**Cost:** 5 credits (`vesselsInBbox`).",
        "tags": [
          "Vessels"
        ],
        "parameters": [
          {
            "name": "minLat",
            "in": "query",
            "required": true,
            "description": "South edge (latitude).",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "example": 51.5
          },
          {
            "name": "minLon",
            "in": "query",
            "required": true,
            "description": "West edge (longitude). A west edge greater than the east edge wraps the antimeridian.",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            },
            "example": 3.5
          },
          {
            "name": "maxLat",
            "in": "query",
            "required": true,
            "description": "North edge (latitude).",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "example": 52.2
          },
          {
            "name": "maxLon",
            "in": "query",
            "required": true,
            "description": "East edge (longitude).",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            },
            "example": 4.6
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Vessel category (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated values: cargo, tanker, passenger, high_speed, fishing, tug, pleasure, sailing, military, special, other, unknown."
            },
            "example": "cargo,tanker"
          },
          {
            "name": "shipType",
            "in": "query",
            "required": false,
            "description": "AIS ship type codes (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated integers."
            }
          },
          {
            "name": "navStatus",
            "in": "query",
            "required": false,
            "description": "AIS navigational status codes (0 under way, 1 at anchor, 5 moored…).",
            "schema": {
              "type": "string",
              "description": "Comma-separated integers."
            }
          },
          {
            "name": "minSpeed",
            "in": "query",
            "required": false,
            "description": "Minimum speed over ground, in knots.",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "flag",
            "in": "query",
            "required": false,
            "description": "Flag state as ISO 3166-1 alpha-2 codes, derived from the MMSI (comma-separated list allowed).",
            "schema": {
              "type": "string",
              "description": "Comma-separated values."
            },
            "example": "NL,DE"
          },
          {
            "name": "destination",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring of the AIS destination (max 32 characters).",
            "schema": {
              "type": "string"
            },
            "example": "ROTTERDAM"
          },
          {
            "name": "signalAgeMaxMin",
            "in": "query",
            "required": false,
            "description": "Only vessels whose last position is at most this many minutes old (up to 7 days).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10080
            }
          },
          {
            "name": "minLength",
            "in": "query",
            "required": false,
            "description": "Minimum length overall in metres (from the AIS dimensions).",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "maxLength",
            "in": "query",
            "required": false,
            "description": "Maximum length overall in metres.",
            "schema": {
              "type": "number",
              "minimum": 0
            }
          },
          {
            "name": "includeUnverified",
            "in": "query",
            "required": false,
            "description": "Also return vessels known only by name from a source without MMSI (`mmsi: null`, `unverified: true`). Not a stable identity.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Vessels inside the box.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Vessel"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "vesselsInBbox",
        "x-credits-cost": 5
      }
    },
    "/ports": {
      "get": {
        "operationId": "searchPorts",
        "summary": "Search ports",
        "description": "Paginated port search: every word of `search` must match the name, UN/LOCODE or country.\n\n**Cost:** 1 credit (`ports`).",
        "tags": [
          "Ports"
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Port name, UN/LOCODE or country.",
            "schema": {
              "type": "string"
            },
            "example": "constanta"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring of the country name.",
            "schema": {
              "type": "string"
            },
            "example": "Romania"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (1-based).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Items per page. Values above 100 are clamped to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ports.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PortPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "ports",
        "x-credits-cost": 1
      }
    },
    "/ports/{locode}": {
      "get": {
        "operationId": "getPort",
        "summary": "Port detail",
        "description": "One port by its UN/LOCODE (5 letters or digits, case-insensitive).\n\n**Cost:** 1 credit (`ports`).",
        "tags": [
          "Ports"
        ],
        "parameters": [
          {
            "name": "locode",
            "in": "path",
            "required": true,
            "description": "UN/LOCODE.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]{5}$"
            },
            "example": "ROCND"
          }
        ],
        "responses": {
          "200": {
            "description": "The port.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Port"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "ports",
        "x-credits-cost": 1
      }
    },
    "/fleets": {
      "get": {
        "operationId": "listFleets",
        "summary": "List fleets",
        "description": "Fleets of the account that owns the API key, with their vessel counts.\n\n**Cost:** 1 credit (`fleets`).",
        "tags": [
          "Fleets"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Your fleets.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Fleet"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleets",
        "x-credits-cost": 1
      }
    },
    "/fleets/{fleetId}": {
      "get": {
        "operationId": "getFleet",
        "summary": "Fleet detail",
        "description": "One of your fleets with its vessel memberships.\n\n**Cost:** 1 credit (`fleets`).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "The fleet and its vessels.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FleetDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleets",
        "x-credits-cost": 1
      }
    },
    "/fleets/{fleetId}/vessels": {
      "get": {
        "operationId": "listFleetVessels",
        "summary": "Fleet vessels",
        "description": "The vessel memberships of one fleet: MMSI, label, name, IMO, when it was added and its automatic satellite-check schedule.\n\n**Cost:** 1 credit (`fleets`).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Memberships.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/FleetVessel"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleets",
        "x-credits-cost": 1
      },
      "post": {
        "operationId": "addFleetVessel",
        "summary": "Add a vessel to a fleet",
        "description": "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).\n\n**Cost:** 2 credits (`fleetAddVessel`).\n\nRequires an active subscription on the key owner's account (otherwise 403).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "The new membership.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FleetVessel"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleetAddVessel",
        "x-credits-cost": 2,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddFleetVessel"
              },
              "example": {
                "id": "211331640",
                "label": "Charter 12"
              }
            }
          }
        }
      }
    },
    "/fleets/{fleetId}/positions": {
      "get": {
        "operationId": "getFleetPositions",
        "summary": "Fleet positions",
        "description": "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).\n\n**Cost:** 2 credits (`fleetPositions`).\n\n`verify=true` adds 10 credits per vessel actually checked.",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          },
          {
            "name": "verify",
            "in": "query",
            "required": false,
            "description": "Run an on-demand satellite position check before answering (`true` or `1`). Adds the `vesselVerifyNow` cost per vessel actually checked; see \"Satellite position checks\".",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Positions (or `{ items, batch }` with `verify=true`).",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FleetVesselPosition"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/FleetPositionsVerified"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleetPositions",
        "x-credits-cost": 2
      }
    },
    "/fleets/{fleetId}/vessels/bulk": {
      "post": {
        "operationId": "addFleetVesselsBulk",
        "summary": "Add vessels in bulk",
        "description": "Adds up to 200 vessels (MMSI or IMO) in one call; each id succeeds or fails on its own.\n\n**Cost:** 2 credits (`fleetAddVessel`).\n\nRequires an active subscription on the key owner's account (otherwise 403).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          }
        ],
        "responses": {
          "200": {
            "description": "Per-id outcome.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FleetBulkAddResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleetAddVessel",
        "x-credits-cost": 2,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddFleetVesselsBulk"
              },
              "example": {
                "ids": [
                  "211331640",
                  "9450648"
                ]
              }
            }
          }
        }
      }
    },
    "/fleets/{fleetId}/vessels/{mmsi}": {
      "patch": {
        "operationId": "updateFleetVesselLabel",
        "summary": "Update a label",
        "description": "Sets or clears (`null`) the label of a fleet vessel.\n\n**Cost:** 1 credit (`fleetUpdateLabel`).\n\nRequires an active subscription on the key owner's account (otherwise 403).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          },
          {
            "name": "mmsi",
            "in": "path",
            "required": true,
            "description": "MMSI of a vessel in the fleet.",
            "schema": {
              "type": "integer"
            },
            "example": 211331640
          }
        ],
        "responses": {
          "200": {
            "description": "The updated membership.",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FleetVessel"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleetUpdateLabel",
        "x-credits-cost": 1,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFleetVesselLabel"
              },
              "example": {
                "label": "Charter 12"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeFleetVessel",
        "summary": "Remove a vessel from a fleet",
        "description": "Removes the vessel from the fleet.\n\n**Cost:** 1 credit (`fleetRemoveVessel`).\n\nRequires an active subscription on the key owner's account (otherwise 403).",
        "tags": [
          "Fleets"
        ],
        "parameters": [
          {
            "name": "fleetId",
            "in": "path",
            "required": true,
            "description": "Id of one of your fleets (from `GET /fleets`).",
            "schema": {
              "type": "integer"
            },
            "example": 3
          },
          {
            "name": "mmsi",
            "in": "path",
            "required": true,
            "description": "MMSI of a vessel in the fleet.",
            "schema": {
              "type": "integer"
            },
            "example": 211331640
          }
        ],
        "responses": {
          "200": {
            "description": "`{ \"ok\": true }`",
            "headers": {
              "X-Credits-Cost": {
                "$ref": "#/components/headers/X-Credits-Cost"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-credits-cost-key": "fleetRemoveVessel",
        "x-credits-cost": 1
      }
    }
  },
  "webhooks": {
    "fleet.position": {
      "post": {
        "operationId": "webhook_fleet_position",
        "summary": "fleet.position",
        "description": "Sampled position of a watched vessel. Delivered as a signed JSON POST to the endpoints you configure in the Webhooks page (paid plans).",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "X-AIS-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "fleet.position"
            }
          },
          {
            "name": "X-AIS-Timestamp",
            "in": "header",
            "required": true,
            "description": "Milliseconds since the epoch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-AIS-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of `${timestamp}.${rawBody}` with the endpoint secret.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Delivery id, stable across retries (also in `X-AIS-Delivery-Id`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FleetPositionEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Anything else (redirects included) is a failure and is retried."
          }
        }
      }
    },
    "fleet.port_call": {
      "post": {
        "operationId": "webhook_fleet_port_call",
        "summary": "fleet.port_call",
        "description": "A vessel entered or left a port geofence (`phase: enter|exit`). Delivered as a signed JSON POST to the endpoints you configure in the Webhooks page (paid plans).",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "X-AIS-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "fleet.port_call"
            }
          },
          {
            "name": "X-AIS-Timestamp",
            "in": "header",
            "required": true,
            "description": "Milliseconds since the epoch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-AIS-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of `${timestamp}.${rawBody}` with the endpoint secret.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Delivery id, stable across retries (also in `X-AIS-Delivery-Id`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FleetPortCallEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Anything else (redirects included) is a failure and is retried."
          }
        }
      }
    },
    "fleet.gone_silent": {
      "post": {
        "operationId": "webhook_fleet_gone_silent",
        "summary": "fleet.gone_silent",
        "description": "A vessel stopped reporting for longer than the silence threshold. Delivered as a signed JSON POST to the endpoints you configure in the Webhooks page (paid plans).",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "X-AIS-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "fleet.gone_silent"
            }
          },
          {
            "name": "X-AIS-Timestamp",
            "in": "header",
            "required": true,
            "description": "Milliseconds since the epoch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-AIS-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of `${timestamp}.${rawBody}` with the endpoint secret.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Delivery id, stable across retries (also in `X-AIS-Delivery-Id`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FleetGoneSilentEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Anything else (redirects included) is a failure and is retried."
          }
        }
      }
    },
    "fleet.destination_changed": {
      "post": {
        "operationId": "webhook_fleet_destination_changed",
        "summary": "fleet.destination_changed",
        "description": "The AIS destination of a vessel changed. Delivered as a signed JSON POST to the endpoints you configure in the Webhooks page (paid plans).",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "X-AIS-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "const": "fleet.destination_changed"
            }
          },
          {
            "name": "X-AIS-Timestamp",
            "in": "header",
            "required": true,
            "description": "Milliseconds since the epoch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-AIS-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of `${timestamp}.${rawBody}` with the endpoint secret.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Delivery id, stable across retries (also in `X-AIS-Delivery-Id`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FleetDestinationChangedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Anything else (redirects included) is a failure and is retried."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your API key (Developer page). Keep it server-side."
      }
    },
    "headers": {
      "X-Credits-Cost": {
        "description": "Credits spent by this request.",
        "schema": {
          "type": "string"
        }
      },
      "X-Credits-Remaining": {
        "description": "Credits left after this request (`unlimited` on unmetered accounts).",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameter (MMSI/IMO/LOCODE, bbox, pagination, filters, bulk body).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing (`Cheie API lipsă`) or invalid/revoked (`Cheie API invalidă`) API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Not enough credits for this call. Nothing was charged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PaymentRequired"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Disabled account, or a fleet change without an active subscription.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Vessel, port or fleet not found (fleets of other accounts are not found either).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The vessel is already in the fleet.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded for this key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Vessel": {
        "type": "object",
        "required": [
          "mmsi",
          "imo",
          "name",
          "callsign",
          "shipType",
          "category",
          "lat",
          "lon",
          "sog",
          "cog",
          "heading",
          "navStatus",
          "destination",
          "eta",
          "positionTs",
          "unverified",
          "marineTrafficId"
        ],
        "properties": {
          "mmsi": {
            "type": [
              "integer",
              "null"
            ],
            "description": "MMSI (9 digits). `null` for an unverified vessel, whose internal key is not an MMSI.",
            "example": 211331640
          },
          "imo": {
            "type": [
              "integer",
              "null"
            ],
            "description": "IMO number (7 digits), when reported.",
            "example": 9450648
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "EXAMPLE CARRIER"
          },
          "callsign": {
            "type": [
              "string",
              "null"
            ],
            "example": "DHLB"
          },
          "shipType": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "AIS ship type code.",
            "example": 70
          },
          "category": {
            "type": "string",
            "enum": [
              "cargo",
              "tanker",
              "passenger",
              "high_speed",
              "fishing",
              "tug",
              "pleasure",
              "sailing",
              "military",
              "special",
              "other",
              "unknown"
            ],
            "description": "Category derived from the AIS ship type.",
            "example": "cargo"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90,
            "example": 51.95
          },
          "lon": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180,
            "example": 4.13
          },
          "sog": {
            "type": [
              "number",
              "null"
            ],
            "description": "Speed over ground, knots.",
            "example": 12.4
          },
          "cog": {
            "type": [
              "number",
              "null"
            ],
            "description": "Course over ground, degrees.",
            "example": 88.1
          },
          "heading": {
            "type": [
              "integer",
              "null"
            ],
            "description": "True heading, degrees.",
            "example": 90
          },
          "navStatus": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 15,
            "description": "AIS navigational status.",
            "example": 0
          },
          "destination": {
            "type": [
              "string",
              "null"
            ],
            "description": "AIS destination as broadcast by the crew.",
            "example": "ROTTERDAM"
          },
          "eta": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "AIS ETA. Always `null` in list rows; filled in on the detail route."
          },
          "positionTs": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the last position was reported."
          },
          "unverified": {
            "type": "boolean",
            "description": "True when the vessel is known only by name from a source without MMSI."
          },
          "marineTrafficId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identifier of the vessel in a third-party catalogue, when known. Never an MMSI."
          }
        }
      },
      "VesselDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Vessel"
          },
          {
            "type": "object",
            "required": [
              "dimA",
              "dimB",
              "dimC",
              "dimD",
              "draught"
            ],
            "properties": {
              "dimA": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Metres from the AIS reference point to the bow."
              },
              "dimB": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Metres from the reference point to the stern (length = dimA + dimB)."
              },
              "dimC": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Metres from the reference point to port."
              },
              "dimD": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Metres from the reference point to starboard (beam = dimC + dimD)."
              },
              "draught": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Current draught, metres."
              },
              "verification": {
                "$ref": "#/components/schemas/Verification",
                "description": "Present only when the request carried `verify=true`."
              }
            }
          }
        ]
      },
      "VesselPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "pageSize"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vessel"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          }
        }
      },
      "TrackPoint": {
        "type": "object",
        "required": [
          "lat",
          "lon",
          "sog",
          "cog",
          "ts"
        ],
        "properties": {
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "sog": {
            "type": [
              "number",
              "null"
            ]
          },
          "cog": {
            "type": [
              "number",
              "null"
            ]
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Port": {
        "type": "object",
        "required": [
          "id",
          "locode",
          "name",
          "country",
          "lat",
          "lon",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "example": 42
          },
          "locode": {
            "type": [
              "string",
              "null"
            ],
            "description": "UN/LOCODE.",
            "example": "ROCND"
          },
          "name": {
            "type": "string",
            "example": "Constanța"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "example": "Romania"
          },
          "lat": {
            "type": "number",
            "example": 44.17
          },
          "lon": {
            "type": "number",
            "example": 28.65
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PortPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "pageSize"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Port"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          }
        }
      },
      "Verification": {
        "type": "object",
        "description": "Outcome of an on-demand satellite position check.",
        "required": [
          "status",
          "verifiedAt",
          "positionTs",
          "source",
          "reason"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "cached",
              "queued",
              "skipped",
              "ineligible",
              "failed"
            ],
            "description": "`ok` fresh check (billed); `cached` answered by a check from the last 5 minutes (free); `queued` still running after ~20 s — the stored position is returned and the job completes in the background (billed); `skipped` left out by the batch rules or the balance (free); `ineligible` the vessel lacks MMSI, IMO or name (free); `failed` the run failed (billed)."
          },
          "verifiedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the check ran."
          },
          "positionTs": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Report time of the position found."
          },
          "source": {
            "type": "string",
            "description": "Identifier of the tracking source that was read."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short explanation for skipped, ineligible and failed."
          }
        }
      },
      "Fleet": {
        "type": "object",
        "required": [
          "id",
          "name",
          "vesselCount",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "example": 3
          },
          "name": {
            "type": "string",
            "example": "North Sea"
          },
          "vesselCount": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FleetVessel": {
        "type": "object",
        "required": [
          "mmsi",
          "label",
          "addedAt",
          "name",
          "imo"
        ],
        "properties": {
          "mmsi": {
            "type": "integer"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "addedAt": {
            "type": "string",
            "format": "date-time"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "imo": {
            "type": [
              "integer",
              "null"
            ]
          },
          "verifyIntervalMinutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Automatic satellite-check interval in minutes (set in the app); `null` = off."
          },
          "verifyLastRunAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last automatic check."
          },
          "verifyLastStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "Status of the last automatic check (`no_credits` after it switched itself off)."
          }
        }
      },
      "FleetDetail": {
        "type": "object",
        "required": [
          "fleet",
          "vessels"
        ],
        "properties": {
          "fleet": {
            "$ref": "#/components/schemas/Fleet"
          },
          "vessels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetVessel"
            }
          }
        }
      },
      "FleetVesselPosition": {
        "type": "object",
        "description": "Membership joined with the stored position (a vessel never seen has `null` position fields).",
        "required": [
          "mmsi",
          "label",
          "name",
          "category",
          "lat",
          "lon",
          "sog",
          "cog",
          "heading",
          "navStatus",
          "destination",
          "positionTs",
          "silentHours"
        ],
        "properties": {
          "mmsi": {
            "type": "integer"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "cargo",
              "tanker",
              "passenger",
              "high_speed",
              "fishing",
              "tug",
              "pleasure",
              "sailing",
              "military",
              "special",
              "other",
              "unknown"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lon": {
            "type": [
              "number",
              "null"
            ]
          },
          "sog": {
            "type": [
              "number",
              "null"
            ]
          },
          "cog": {
            "type": [
              "number",
              "null"
            ]
          },
          "heading": {
            "type": [
              "integer",
              "null"
            ]
          },
          "navStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "destination": {
            "type": [
              "string",
              "null"
            ]
          },
          "positionTs": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Report time of the last position."
          },
          "silentHours": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Whole hours since the last position."
          },
          "verification": {
            "$ref": "#/components/schemas/Verification"
          }
        }
      },
      "FleetVerifyBatch": {
        "type": "object",
        "required": [
          "cap",
          "capApplied",
          "verified",
          "skipped"
        ],
        "properties": {
          "cap": {
            "type": "integer",
            "description": "Max vessels checked per request."
          },
          "capApplied": {
            "type": "boolean",
            "description": "True when the cap left eligible stale vessels out."
          },
          "verified": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          }
        }
      },
      "FleetPositionsVerified": {
        "type": "object",
        "required": [
          "items",
          "batch"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetVesselPosition"
            }
          },
          "batch": {
            "$ref": "#/components/schemas/FleetVerifyBatch"
          }
        }
      },
      "FleetBulkAddResult": {
        "type": "object",
        "required": [
          "added",
          "failed"
        ],
        "properties": {
          "added": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetVessel"
            }
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "message"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AddFleetVessel": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "MMSI (9 digits) or IMO (7 digits)."
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AddFleetVesselsBulk": {
        "type": "object",
        "required": [
          "ids"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": [
                "string",
                "integer"
              ]
            }
          }
        }
      },
      "UpdateFleetVesselLabel": {
        "type": "object",
        "required": [
          "label"
        ],
        "properties": {
          "label": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "const": true
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "statusCode",
          "message"
        ],
        "properties": {
          "statusCode": {
            "type": "integer"
          },
          "message": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ],
            "description": "Human-readable message (some are in Romanian)."
          },
          "error": {
            "type": "string"
          }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "required": [
          "statusCode",
          "message",
          "required"
        ],
        "properties": {
          "statusCode": {
            "const": 402
          },
          "message": {
            "type": "string",
            "example": "Credite insuficiente"
          },
          "required": {
            "type": [
              "integer",
              "string"
            ],
            "description": "Credits the call needed (or the cost key, for a satellite check)."
          },
          "remaining": {
            "type": "integer",
            "description": "Credits left."
          }
        }
      },
      "FleetRef": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "FleetPositionEvent": {
        "type": "object",
        "description": "Sampled position of a watched vessel.",
        "required": [
          "event",
          "vessel",
          "fleets",
          "occurredAt"
        ],
        "properties": {
          "event": {
            "const": "fleet.position"
          },
          "vessel": {
            "$ref": "#/components/schemas/WebhookVesselPosition"
          },
          "fleets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetRef"
            }
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FleetPortCallEvent": {
        "type": "object",
        "description": "A watched vessel entered or left a port geofence.",
        "required": [
          "event",
          "vessel",
          "fleets",
          "occurredAt",
          "phase",
          "port"
        ],
        "properties": {
          "event": {
            "const": "fleet.port_call"
          },
          "vessel": {
            "$ref": "#/components/schemas/WebhookVesselPosition"
          },
          "fleets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetRef"
            }
          },
          "phase": {
            "type": "string",
            "enum": [
              "enter",
              "exit"
            ]
          },
          "port": {
            "type": "object",
            "required": [
              "id",
              "name",
              "locode"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "locode": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FleetGoneSilentEvent": {
        "type": "object",
        "description": "A watched vessel stopped reporting for longer than the silence threshold.",
        "required": [
          "event",
          "vessel",
          "fleets",
          "occurredAt",
          "silentHours"
        ],
        "properties": {
          "event": {
            "const": "fleet.gone_silent"
          },
          "vessel": {
            "type": "object",
            "required": [
              "mmsi"
            ],
            "properties": {
              "mmsi": {
                "type": "integer"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "positionTs": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Last report."
              }
            }
          },
          "fleets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetRef"
            }
          },
          "silentHours": {
            "type": "integer"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FleetDestinationChangedEvent": {
        "type": "object",
        "description": "The AIS destination of a watched vessel changed.",
        "required": [
          "event",
          "vessel",
          "fleets",
          "occurredAt",
          "previousDestination",
          "destination"
        ],
        "properties": {
          "event": {
            "const": "fleet.destination_changed"
          },
          "vessel": {
            "$ref": "#/components/schemas/WebhookVesselPosition"
          },
          "fleets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FleetRef"
            }
          },
          "previousDestination": {
            "type": [
              "string",
              "null"
            ]
          },
          "destination": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookVesselPosition": {
        "type": "object",
        "properties": {
          "mmsi": {
            "type": "integer",
            "example": 244660920
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lon": {
            "type": [
              "number",
              "null"
            ]
          },
          "sog": {
            "type": [
              "number",
              "null"
            ]
          },
          "cog": {
            "type": [
              "number",
              "null"
            ]
          },
          "heading": {
            "type": [
              "integer",
              "null"
            ]
          },
          "navStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "destination": {
            "type": [
              "string",
              "null"
            ]
          },
          "positionTs": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Report time of the position."
          }
        },
        "required": [
          "mmsi"
        ]
      }
    }
  }
}