VesselAPI Documentation

Welcome to VesselAPI. This API provides access to AIS vessel tracking (live and short-term historical), port events, NAVTEX safety messages, global port data, and navigation infrastructure.

Base URL: https://api.vesselapi.com/v1

Quick Start

  1. Sign up for an API key at vesselapi.com
  2. Include your API key in the Authorization header
  3. Make requests to the endpoints
  4. Process the JSON responses

Developer Tools

Official SDKs and AI integration tools for working with the VesselAPI.

SDKs & MCP Server

Official client libraries for TypeScript, Python, and Go. MCP server for AI assistant integration with Claude, Cursor, and Windsurf.

API Categories

Documentation is organized by API category:

Authentication

All API requests require authentication using an API key. Include your API key in the Authorization header of every request.

Header Format

Authorization: Bearer YOUR_API_KEY

Example Request

curl -X GET "https://api.vesselapi.com/v1/vessel/123456789?filter.idType=mmsi" \
     -H "Authorization: Bearer YOUR_API_KEY"

Rate Limits & Quotas

VesselAPI enforces several limits to keep response times reliable across all customers. Exceeding any short-window rate limit returns HTTP 429 with a Retry-After header, and the block lifts automatically as your request rate drops back under the threshold. Clients that keep pushing through repeated 429s instead of backing off will trip a separate, longer-duration block — see Sustained-abuse blocks below.

Short-window rate limits

The following rolling 5-minute windows are evaluated independently. A single request can trigger any of them.

Limit Aggregation Scope
500 / 5 min Source IP address All endpoints
3,000 / 5 min API key (Authorization header) All endpoints
300 / 5 min API key (Authorization header) All /v1/location/* endpoints combined (12 routes: the bounding-box and radius variants for vessels, ports, dgps, lightaids, modu, and radiobeacons)

Example 429 response

Response headers include Retry-After: 60. The body:

{
  "error": "rate_limited",
  "message": "You have exceeded the allowed request rate for this API key or IP. Reduce request volume and retry after the Retry-After period. See https://docs.vesselapi.com/rate-limits",
  "retry_after_seconds": 60
}

Note: short-window 429s are returned by the edge rate limiter, so the body is flat and error is the string "rate_limited". Application-level 429s (monthly quota exhausted, or the concurrency limit) use the standard error envelope instead, with error.type set to rate_limit_error (see Error Handling).

Handling 429 in client code

Respect the Retry-After header value (in seconds) and retry after that delay. Short-window blocks lift automatically as your rolling 5-minute count drops below the limit — there is no separate "unblock" action for these. If your client keeps retrying inside the Retry-After window instead of waiting it out, you'll trip the sustained-abuse block instead, which is much harder to undo.

// Example: minimal backoff respecting Retry-After
async function withRetry(fn) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const res = await fn();
    if (res.status !== 429) return res;
    const wait = Number(res.headers.get("Retry-After") || 60);
    await new Promise(r => setTimeout(r, wait * 1000));
  }
  throw new Error("rate limited after retries");
}

Concurrency limit

There is a second, independent cause of HTTP 429: having more than 20 concurrent in-flight requests on your account. The 21st simultaneous request is rejected immediately with Retry-After: 1 and a standard error envelope (not the flat edge-limiter body above). Rejected requests never consume a slot, so the condition clears as soon as some of your in-flight requests complete. Cap your client's parallelism at 20 or below and retry after the one-second delay.

Sustained-abuse blocks

A client that ignores Retry-After and keeps grinding requests through repeated 429s — roughly 50 consecutive rate-limit responses on the same API key in a short window — will trip a sustained-abuse block. When this fires, the offending source IP is blocked at the network edge and the API key is suspended for up to 6 hours. Any further requests from that IP to api.vesselapi.com are rejected with HTTP 403 before they reach the application; the dashboard and marketing site remain reachable so you can still manage your account.

There are two ways to clear a sustained-abuse block:

  • Wait for the block to expire (up to 6 hours from when it fired).
  • Upgrade to (or purchase) a paid plan. Activating a paid subscription automatically clears any active block on your account within ~30 seconds, so the upgrade is immediately useful.

Avoid the block entirely: implement Retry-After-aware backoff (see the example above) and cap your request concurrency. Tight retry loops that ignore Retry-After are the single most common cause of these blocks.

Time window cap on location searches

Separately from the rate limits above, /v1/location/vessels/bounding-box and /v1/location/vessels/radius reject any single query whose time window (time.to minus time.from) exceeds 4 hours. The default time window when neither bound is supplied is 2 hours, which is well within the cap. Queries whose search area and time window together would match too many positions are likewise rejected with a 400 carrying the code bounding_box_too_dense: narrow the area or pass explicit time.from / time.to bounds.

If you need to retrieve positions across a longer range, split the request into sequential 4-hour slices and stitch the results client-side. The time predicate is inclusive on both ends, so to avoid duplicates at the boundaries use half-open intervals: set one slice's time.to to T and the next slice's time.from to T + 1ms. The API accepts millisecond-precision RFC3339.

# Single 12-hour request — rejected with HTTP 400:
GET /v1/location/vessels/bounding-box
    ?filter.lonLeft=101.76&filter.lonRight=103.71
    &filter.latBottom=0.77&filter.latTop=2.72
    &time.from=2026-04-21T06:00:00.000Z
    &time.to=2026-04-21T18:00:00.000Z

# Three 4-hour slices stitched client-side — accepted:
GET .../bounding-box?...&time.from=2026-04-21T06:00:00.000Z&time.to=2026-04-21T10:00:00.000Z
GET .../bounding-box?...&time.from=2026-04-21T10:00:00.001Z&time.to=2026-04-21T14:00:00.000Z
GET .../bounding-box?...&time.from=2026-04-21T14:00:00.001Z&time.to=2026-04-21T18:00:00.000Z

Pagination across slices. A nextToken is only valid when reused with the same time.from / time.to as the call that issued it. The API does not reject mismatched bounds; it silently returns rows from the wrong slice. Paginate within each 4-hour slice independently.

If the 4-hour cap doesn't fit your use case, contact support and we can advise on alternatives.

Monthly quotas

Each subscription tier has a monthly request quota. When you exceed your monthly quota the API returns HTTP 429. Track remaining usage via the X-RateLimit-Remaining response header.

How quota counting works

  • The quota is per account, not per API key: all keys on the same account draw from one shared quota.
  • The quota period is a monthly window anchored to your billing date, not the calendar month.
  • Only successful (2xx) responses count against the quota. Errors, 404s, and rate-limited requests do not.
  • Satellite-sourced responses (X-Data-Source: satellite) do not count against the quota; they are paid for with satellite credits instead.
  • Usage is flushed to the billing store in roughly 30-second batches, so X-RateLimit-Remaining can lag your true usage slightly during bursts.
  • X-RateLimit-Remaining is either a number or the literal string Unlimited on plans without a request cap.

Best practice: monitor X-RateLimit-Remaining to avoid hitting your monthly quota, and implement Retry-After-aware backoff to handle short-window limits gracefully.

Error Handling

The API uses standard HTTP status codes and returns detailed error information in JSON format.

Error Types

invalid_request_error

The request had invalid parameters or was malformed.

HTTP Status: 400 Bad Request

authentication_error

Invalid API key or authentication failure.

HTTP Status: 401 Unauthorized

insufficient satellite credits

Only returned by /v1/vessel/{id}/position?filter.sat=true when a satellite lookup is needed but your account has no satellite credits left. Top up credits or retry without filter.sat.

HTTP Status: 402 Payment Required

forbidden_error

The request was authenticated but not permitted. Two causes: the API key is temporarily suspended for sustained quota abuse (code forbidden, with a Retry-After header carrying the remaining block duration), or your subscription plan does not include the requested feature (code feature_not_available).

HTTP Status: 403 Forbidden

not_found_error

The requested resource was not found.

HTTP Status: 404 Not Found

rate_limit_error

Monthly request quota exceeded, or more than 20 concurrent in-flight requests on your account (the latter is returned with Retry-After: 1; see Concurrency limit).

HTTP Status: 429 Too Many Requests

api_error

An internal server error occurred. These errors include a tracking ID for support.

HTTP Status: 500 Internal Server Error

service_unavailable_error

The service is temporarily unavailable. Retry after a brief delay.

HTTP Status: 503 Service Unavailable

Error Codes

Code Description
resource_missing The requested resource (vessel, port, etc.) could not be found
invalid_parameter A parameter value is invalid or malformed
invalid_mmsi The provided MMSI (Maritime Mobile Service Identity) is not valid. MMSI must be a 9-digit number
invalid_imo The provided IMO number is not valid. IMO must be a 7-digit number
invalid_coordinates The provided latitude/longitude coordinates are invalid
invalid_time_range The time range is invalid (e.g., end time before start time, or range too large)
bounding_box_too_dense The search area and time window together would match too many positions. Narrow the area or pass explicit time.from / time.to bounds
missing_parameter A required parameter was not provided
invalid_api_key The provided API key is invalid or has been revoked
forbidden The request was authenticated but not permitted, e.g. the API key is temporarily suspended for sustained quota abuse (check the Retry-After header)
feature_not_available Your subscription plan does not include the requested feature. Upgrade your plan to access it
rate_limit_exceeded Your account has exceeded its monthly request quota, or has too many concurrent in-flight requests
internal_error An unexpected error occurred on the server. Contact support with the error_id if it persists
database_error A database operation failed. This is a transient error; retry the request after a brief delay
service_unavailable The service is temporarily unavailable. Retry after a brief delay

Error Response Format

All errors return a JSON object with an error key. The fields included vary by error type.

400 Bad Request

Includes param and doc_url fields to help identify the issue.

{
  "error": {
    "type": "invalid_request_error",
    "message": "Invalid MMSI: abc123",
    "code": "invalid_mmsi",
    "param": "mmsi",
    "doc_url": "https://vesselapi.com/docs#invalid_mmsi"
  }
}

Strict parameter validation: query parameters the endpoint does not recognize are rejected with HTTP 400 rather than ignored. A misspelled parameter name, or a valid parameter sent to an endpoint that does not support it, fails the whole request. This is a common integration trap; double-check parameter names (including the filter., time., and pagination. prefixes) against each endpoint's documentation.

Location searches with a time window over 4 hours are rejected with a 400 so callers know to split into shorter slices:

{
  "error": {
    "type": "invalid_request_error",
    "message": "time window (time.to - time.from) must not exceed 4h0m0s. Split larger ranges into sequential calls and stitch results client-side; use half-open intervals (e.g. one slice's time.to = T, next slice's time.from = T + 1ms) to avoid boundary duplicates.",
    "code": "invalid_parameter"
  }
}

This applies to /location/vessels/bounding-box and /location/vessels/radius. See the Time window cap section above for the splitting recipe.

401 Unauthorized

{
  "error": {
    "type": "authentication_error",
    "message": "api key is invalid or not found",
    "code": "invalid_api_key"
  }
}

404 Not Found

{
  "error": {
    "type": "not_found_error",
    "message": "Vessel with MMSI 999999999 not found.",
    "code": "resource_missing"
  }
}

429 Too Many Requests

{
  "error": {
    "type": "rate_limit_error",
    "message": "API monthly quota exceeded",
    "code": "rate_limit_exceeded"
  }
}

500 Internal Server Error

Includes a unique tracking ID and timestamp for support.

{
  "error": {
    "type": "api_error",
    "message": "An internal error occurred. Please reference error ID 550e8400-e29b-41d4-a716-446655440000 when contacting support if this issue persists.",
    "code": "internal_error",
    "error_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": "2025-10-31T15:30:45Z"
  }
}

Reference the error_id when contacting support for faster resolution.

503 Service Unavailable

{
  "error": {
    "type": "service_unavailable_error",
    "message": "The service is temporarily unavailable. Retry after a brief delay.",
    "code": "service_unavailable"
  }
}

Pagination

List endpoints support pagination using cursor-based navigation.

Parameters

pagination.limit

Number of results to return per page (max 50, default 20)

pagination.nextToken

Cursor for the next page of results (provided in response)

Response Format

{
  "vessels": [...],
  "nextToken": "eyJsYXN0X2lkIjoxMjM0NTY3ODl9"
}

The array key varies by endpoint (e.g., vessels, vesselPositions, vesselETAs, ports, portEvents, navtexMessages, warnings, emissions, casualties, dgpsStations, lightAids, radioBeacons, modus). The nextToken field is at the top level of the response. The vessel inspections endpoint is not paginated; its inspections array is always returned in full.

Best Practices

Cache Responses

Static vessel data and port data are updated daily. Cache these responses to reduce API usage. AIS position data changes every 2-30 seconds for moving vessels, so cache TTLs should match your freshness requirements.

Handle Rate Limits

Implement exponential backoff when receiving 429 errors. Start with a 1-second delay and double it for each subsequent retry.

Use Pagination Efficiently

Request only the data you need. Use reasonable page sizes (20-50 items) and store pagination keys for subsequent requests.

Validate Input

Validate MMSI (9 digits) and IMO (7 digits) numbers on the client side before making API requests.

Monitor Error IDs

Log error IDs from 500 errors for troubleshooting. Include them when contacting support.

Ready to Get Started?

Try the endpoints interactively in the API Explorer or sign up for an API key.