Docs

API reference

Every endpoint, parameter and field of the AIS Trail API. Base URL https://api.aistrail.com. New here? Start with the Quickstart. The machine-readable contract is openapi.json (OpenAPI 3.1).

Conventions

  • Authentication. Send your key on every request: Authorization: Bearer ap_live_…. Keys are created in your dashboard. Data endpoints never accept the key in the URL. Call the API from your server or scripts: browsers on other websites are not allowed to call it directly (CORS), and a key in browser code would be public. /v1/status, /v1/sources and /v1/openapi.json need no key.
  • Units. Times are ISO 8601 in UTC (responses end in Z); a date alone means midnight UTC and a time without an offset is read as UTC. Speeds in knots, angles in degrees, positions in decimal degrees.
  • Embargo. The most recent 24 hours are not available on any plan. A later to is clamped; meta.embargo_cutoff says where.
  • Quota. Only successful (2xx) responses count toward your monthly quota, including every page of a paged result and calls to /v1/usage; 4xx and 5xx responses are free. The quota is shared by all keys of your account and resets at 00:00 UTC on the first day of each month (X-RateLimit-Reset).
  • Rate limits. Each key may send up to your plan's requests per second (Free 1, Pro 10, Business 20), with short bursts up to twice that; above it you get 429 Rate limited with Retry-After: 1. Requests without a valid key are limited to 5 per second per IP address.
  • Concurrency. At most 2 requests per key are processed at a time; a third gets 429. A query that runs longer than the server limit is stopped with 503: narrow the box or the time range.
  • Errors. RFC 9457 problem details. Every status and title is listed on Errors.

Plan limits

PlanRequests / monthRequests / secondHistoryArea queriesRows per page
Free1,0001Last 7 daysNot included1,000
Pro300,00010Last 12 monthsUp to 2° × 2°50,000
Business1,000,00020Last 12 monthsUp to 10° × 10°50,000

Prices are in the pricing section. On Free, from may be at most 7 days in the past (403 History limit otherwise).

Longest range per request

resolutionWhat you getLongest from–to range
raw (default)Every fix, as received24 hours
1mThe latest real fix per vessel per minute7 days
1hThe latest real fix per vessel per hour366 days

Nothing is interpolated. A longer range returns 400 Window too large: split it into several requests or use a coarser resolution.

Endpoints

EndpointWhat it answersKey
GET /v1/vessels/searchSearch vessels by name, call sign, MMSI prefix or IMORequired
GET /v1/vessels/{mmsi}Vessel identity by MMSI: current static data and observation logRequired
GET /v1/vessels/imo/{imo}MMSIs that have reported this IMO number, newest firstRequired
GET /v1/vessels/{mmsi}/trackTrack of one MMSI over a time rangeRequired
GET /v1/vessels/imo/{imo}/trackTrack of one ship by IMO number, across MMSI changesRequired
GET /v1/areaHistorical positions inside a bounding box (Pro and Business plans)Required
GET /v1/usagePlan, limits and usage in the current billing periodRequired
GET /v1/sourcesData sources, licenses, required attribution and coverage (public)Not needed
GET /v1/statusService status and data coverage per source (public)Not needed
GET /v1/openapi.jsonThis API as an OpenAPI 3.1 document (public)Not needed

Vessels

GET /v1/vessels/{mmsi}

Vessel identity by MMSI: current static data and observation log.

Response: mmsi, current (latest known imo, name, callsign, ship_type, length_m, width_m, draught_m, destination, eta), first_seen, last_seen and observations: the distinct static reports the vessel has sent (up to 50, newest first), each with its first_seen, last_seen and sources. The response also has a live_position field; it is null on all current plans.

Passing a 7-digit IMO number here returns 400 with the IMO URL to use instead.

NameInTypeRequired / defaultDescription
mmsipathintegerrequired9-digit MMSI

Status codes: 200, 400, 401, 404, 429, 503.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
  "https://api.aistrail.com/v1/vessels/276859000"

GET /v1/vessels/imo/{imo}

MMSIs that have reported this IMO number, newest first.

Ships change MMSI when they change flag. This lists every MMSI that has reported this IMO number, with the period (first_seen, last_seen), name and callsign; newest first, at most 50 (truncated is true beyond that). The IMO number must have a valid check digit.

NameInTypeRequired / defaultDescription
imopathintegerrequired7-digit IMO number with a valid check digit

Status codes: 200, 400, 401, 404, 429, 503.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
  "https://api.aistrail.com/v1/vessels/imo/9892690"

Tracks

GET /v1/vessels/{mmsi}/track

Track of one MMSI over a time range.

Positions of one MMSI between from and to, ordered by time. Paged: see Paging. The default range is the 24 hours before to.

NameInTypeRequired / defaultDescription
mmsipathintegerrequired9-digit MMSI
fromqueryISO 8601 date-timeoptionalISO 8601 start (inclusive); default: to minus 24 h
toqueryISO 8601 date-timeoptionalISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff)
resolutionqueryraw | 1m | 1hdefault rawraw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation)
formatqueryjson | geojson | csvdefault jsonjson, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers)
limitqueryintegerdefault 5000; 1000 on FreeRows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business)
cursorquerystringoptionalOpaque cursor from meta.next_cursor. When given, from, to, resolution and the subject are taken from the cursor

Status codes: 200, 400, 401, 403, 429, 503.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
  "https://api.aistrail.com/v1/vessels/276859000/track?from=2026-10-08T00:00:00Z&to=2026-10-09T00:00:00Z&resolution=1m"

On Free, use a day from the last 7 days: an older from returns 403 History limit.

Response, trimmed to the first position. embargo_cutoff is the time of your request minus 24 hours.

{
  "meta": {
    "mmsi": 276859000,
    "from": "2026-10-08T00:00:00Z",
    "to": "2026-10-09T00:00:00Z",
    "resolution": "1m",
    "count": 654,
    "complete": true,
    "next_cursor": null,
    "next": null,
    "embargo_cutoff": "2026-10-10T09:30:00Z",
    "attribution": [
      { "source": "<source id>",
        "text": "<attribution text of this source, see /v1/sources>",
        "license": "CC BY 4.0",
        "license_url": "https://creativecommons.org/licenses/by/4.0/" }
    ]
  },
  "positions": [
    { "mmsi": 276859000, "ts": "2026-10-08T00:17:23.383000Z",
      "lat": 60.14799, "lon": 24.91475, "sog": 0.0, "cog": 15.6,
      "heading": 207, "nav_status": 5, "source": "<source id>", "flags": null },
    …
  ]
}

Each source is a source id; the ids and their attribution are listed at /v1/sources.

GET /v1/vessels/imo/{imo}/track

Track of one ship by IMO number, across MMSI changes.

Follows one ship across MMSI changes. Each MMSI is included only for the periods in which it reported this IMO (±10 minutes), so positions of an unrelated ship that later reused the MMSI are not mixed in. Every row carries its mmsi. Same parameters and paging as the MMSI track.

NameInTypeRequired / defaultDescription
imopathintegerrequired7-digit IMO number with a valid check digit
fromqueryISO 8601 date-timeoptionalISO 8601 start (inclusive); default: to minus 24 h
toqueryISO 8601 date-timeoptionalISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff)
resolutionqueryraw | 1m | 1hdefault rawraw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation)
formatqueryjson | geojson | csvdefault jsonjson, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers)
limitqueryintegerdefault 5000; 1000 on FreeRows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business)
cursorquerystringoptionalOpaque cursor from meta.next_cursor. When given, from, to, resolution and the subject are taken from the cursor

Status codes: 200, 400, 401, 403, 429, 503.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
  "https://api.aistrail.com/v1/vessels/imo/9892690/track?from=2026-10-08&to=2026-10-09&resolution=1m"

Area queries

GET /v1/area

Historical positions inside a bounding box (Pro and Business plans).

Every position inside a bounding box over a time range. Pro plan: each side up to 2°; Business plan: up to 10°. Free keys get 403 Plan limit. Boxes that cross the antimeridian are not supported. A box larger than your plan allows returns 400 Bad request. The default range is the hour before to.

Rows come day by day and, at raw resolution, 1° tile by tile; inside a tile they are ordered by vessel and time. Each page is time-boxed on the server, so a page can hold fewer rows than limit (even zero) while complete is still false: keep following next.

Area queries at 1m or 1h resolution, or with a side longer than 2°, run one at a time across the service: they wait in a queue instead of failing.

NameInTypeRequired / defaultDescription
bboxquerystringrequired unless cursor is givenlat_min,lon_min,lat_max,lon_max in decimal degrees; each side at most 2° on Pro, 10° on Business
fromqueryISO 8601 date-timeoptionalISO 8601 start (inclusive); default: to minus 1 h
toqueryISO 8601 date-timeoptionalISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff)
resolutionqueryraw | 1m | 1hdefault rawraw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation)
formatqueryjson | geojson | csvdefault jsonjson, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers)
limitqueryintegerdefault 5000; 1000 on FreeRows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business)
cursorquerystringoptionalOpaque cursor from meta.next_cursor. When given, from, to, resolution and the subject are taken from the cursor

Status codes: 200, 400, 401, 403, 429, 503.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
  "https://api.aistrail.com/v1/area?bbox=54.95,10.6,55.6,11.25&from=2026-10-08T10:00:00Z&to=2026-10-08T12:00:00Z&resolution=1m"

Account

GET /v1/usage

Plan, limits and usage in the current billing period.

Your plan and how much of it you have used: plan, label, limits, sources, requests_used (this billing period) and period_resets_at. The same numbers are in your dashboard. Each call counts as one request toward your quota.

Status codes: 200, 401, 429.

curl -H "Authorization: Bearer $AIS_TRAIL_KEY" "https://api.aistrail.com/v1/usage"

Service

GET /v1/sources

Data sources, licenses, required attribution and coverage (public).

Public, no key needed. Each source with its id, name, license, license_url, homepage, required attribution text and live coverage (first_date, last_date, days, missing, positions, vessels), plus available_until (the embargo cutoff). See Data sources.

Status codes: 200.

curl "https://api.aistrail.com/v1/sources"

GET /v1/status

Service status and data coverage per source (public).

Public, no key needed. status, uptime_s, coverage per source, available_until and disk usage.

Status codes: 200.

curl "https://api.aistrail.com/v1/status"

GET /v1/openapi.json

This API as an OpenAPI 3.1 document (public).

Public. This API as an OpenAPI 3.1 document, for code generators and API clients. A copy is published at /docs/openapi.json.

Status codes: 200.

curl "https://api.aistrail.com/v1/openapi.json"

Paging

Track and area results come in pages (default 5,000 rows, 1,000 on Free; set limit up to your plan's rows per page). Follow meta.next, a relative URL, until meta.complete is true. The cursor is opaque and signed by the server; when you pass cursor, the range, resolution and subject come from the cursor, and your plan's rules are applied again on every page. Rows never repeat or go missing at page boundaries.

For CSV, paging is in the headers: X-Next-Cursor and Link: <…>; rel="next" (both absent on the last page), X-Complete and X-Count.

Formats

  • format=json (default): {"meta": {…}, "positions": […]}.
  • format=geojson (application/geo+json): a track is a FeatureCollection with one LineString per vessel (a single fix is a Point); properties has mmsi, count, coordTimes (the time of every coordinate) and sources. An area is a FeatureCollection of Points whose properties are the position fields. Paging is in the top-level meta.
  • format=csv (text/csv): columns mmsi, ts, lat, lon, sog, cog, heading, nav_status, source, flags; empty cell for null. Paging and attribution are in the response headers.

Objects

Position

FieldTypeDescription
mmsiintegerMaritime Mobile Service Identity of the transmitting vessel (9 digits)
tsISO 8601 date-timeMessage timestamp (UTC) as reported by the source
latnumberLatitude, decimal degrees
lonnumberLongitude, decimal degrees
sognumber | nullSpeed over ground, knots
cognumber | nullCourse over ground, degrees
headinginteger | nullTrue heading, degrees; null if unavailable (raw 511)
nav_statusinteger | nullITU-R M.1371 navigational status 0-14
sourcestringData source id, see /v1/sources
flagsstring | nullQuality flag, e.g. implausible_jump (kept, never dropped)

Page meta

Track responses also carry mmsi or imo; area responses carry bbox.

FieldTypeDescription
fromISO 8601 date-timeStart of the range actually served (inclusive)
toISO 8601 date-timeEnd of the range actually served (exclusive), after embargo clamping
resolutionraw | 1m | 1hResolution of this page
countintegerRows in this page
completebooleantrue when there are no more pages
next_cursorstring | nullPass as ?cursor= to get the next page
nextstring | nullRelative URL of the next page
embargo_cutoffISO 8601 date-timeData newer than this is not available on your plan
attributionarrayOne entry per source in this page: source, text, license, license_url

Problem (errors)

FieldTypeDescription
typestringURI of the error type: a link to its entry on the Errors page, or about:blank
titlestringShort, stable title, e.g. Window too large; branch on this and the status
statusintegerHTTP status code
detailstringHuman-readable explanation of this occurrence
retry_after_sintegerSeconds to wait before retrying. Rate limited, Too many concurrent requests and Server busy also send it as a Retry-After header
upgrade_urlstringPricing page, on errors that a higher plan would avoid

upgrade_url appears on errors that a higher plan would avoid. See Errors.

Response headers

Every response to a request with a valid key:

HeaderMeaning
X-RateLimit-LimitMonthly request quota
X-RateLimit-RemainingRequests left in the billing period
X-RateLimit-ResetUnix time when the quota resets

Track and area responses in CSV:

HeaderMeaning
X-CountRows in this page
X-Completetrue when there are no more pages
X-Next-CursorCursor of the next page (absent on the last page)
Linkrel="next" URL of the next page
X-AttributionRequired attribution for the sources in this page

Attribution

Track and area responses list the sources on that page with their license in meta.attribution (CSV: X-Attribution). The vessel endpoint names the source of each observation; search and IMO lookups carry no source fields. The attribution for all of them is at /v1/sources and on Data sources.

Real-time: early access

Real-time streaming (REST and WebSocket) is in early access for the northern Baltic coverage (Fintraffic / Digitraffic) and is not included in any plan today: the live endpoints (GET /v1/positions, GET /v1/vessels/{mmsi}/position and the WebSocket /v1/stream) answer 403 Plan limit on every current plan (a WebSocket connection is closed with code 4003), and the live_position field of a vessel is null. It is switched on per account; to try it, write to [email protected].

Found a mistake, or something unclear? Suggest an edit and we will fix the page.