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/sourcesand/v1/openapi.jsonneed 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
tois clamped;meta.embargo_cutoffsays 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
| Plan | Requests / month | Requests / second | History | Area queries | Rows per page |
|---|---|---|---|---|---|
| Free | 1,000 | 1 | Last 7 days | Not included | 1,000 |
| Pro | 300,000 | 10 | Last 12 months | Up to 2° × 2° | 50,000 |
| Business | 1,000,000 | 20 | Last 12 months | Up 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
resolution | What you get | Longest from–to range |
|---|---|---|
raw (default) | Every fix, as received | 24 hours |
1m | The latest real fix per vessel per minute | 7 days |
1h | The latest real fix per vessel per hour | 366 days |
Nothing is interpolated. A longer range returns 400 Window too large: split it into several requests or use a coarser resolution.
Endpoints
| Endpoint | What it answers | Key |
|---|---|---|
GET /v1/vessels/search | Search vessels by name, call sign, MMSI prefix or IMO | Required |
GET /v1/vessels/{mmsi} | Vessel identity by MMSI: current static data and observation log | Required |
GET /v1/vessels/imo/{imo} | MMSIs that have reported this IMO number, newest first | Required |
GET /v1/vessels/{mmsi}/track | Track of one MMSI over a time range | Required |
GET /v1/vessels/imo/{imo}/track | Track of one ship by IMO number, across MMSI changes | Required |
GET /v1/area | Historical positions inside a bounding box (Pro and Business plans) | Required |
GET /v1/usage | Plan, limits and usage in the current billing period | Required |
GET /v1/sources | Data sources, licenses, required attribution and coverage (public) | Not needed |
GET /v1/status | Service status and data coverage per source (public) | Not needed |
GET /v1/openapi.json | This API as an OpenAPI 3.1 document (public) | Not needed |
Vessels
GET /v1/vessels/search
Search vessels by name, call sign, MMSI prefix or IMO.
Matches the vessel name or call sign (substring, case-insensitive), the start of an MMSI, or an exact IMO number. Results are ordered by when the vessel was last seen, newest first.
Response: query, offset, results (each with mmsi, name, imo, callsign, ship_type, length_m, last_seen) and next_offset (null on the last page). Search results carry no source field; the attribution is at /v1/sources.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
q | query | string | required | 2 to 64 characters |
limit | query | integer | default 20 | 1-50 |
offset | query | integer | default 0 | 0-1000; use next_offset from the previous page |
Status codes: 200, 400, 401, 429, 503.
curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
"https://api.aistrail.com/v1/vessels/search?q=MYSTAR"
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.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
mmsi | path | integer | required | 9-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.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
imo | path | integer | required | 7-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.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
mmsi | path | integer | required | 9-digit MMSI |
from | query | ISO 8601 date-time | optional | ISO 8601 start (inclusive); default: to minus 24 h |
to | query | ISO 8601 date-time | optional | ISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff) |
resolution | query | raw | 1m | 1h | default raw | raw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation) |
format | query | json | geojson | csv | default json | json, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers) |
limit | query | integer | default 5000; 1000 on Free | Rows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business) |
cursor | query | string | optional | Opaque 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.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
imo | path | integer | required | 7-digit IMO number with a valid check digit |
from | query | ISO 8601 date-time | optional | ISO 8601 start (inclusive); default: to minus 24 h |
to | query | ISO 8601 date-time | optional | ISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff) |
resolution | query | raw | 1m | 1h | default raw | raw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation) |
format | query | json | geojson | csv | default json | json, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers) |
limit | query | integer | default 5000; 1000 on Free | Rows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business) |
cursor | query | string | optional | Opaque 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.
| Name | In | Type | Required / default | Description |
|---|---|---|---|---|
bbox | query | string | required unless cursor is given | lat_min,lon_min,lat_max,lon_max in decimal degrees; each side at most 2° on Pro, 10° on Business |
from | query | ISO 8601 date-time | optional | ISO 8601 start (inclusive); default: to minus 1 h |
to | query | ISO 8601 date-time | optional | ISO 8601 end (exclusive); default: now minus 24 hours, the embargo cutoff. Later values are clamped (see meta.embargo_cutoff) |
resolution | query | raw | 1m | 1h | default raw | raw = every fix; 1m / 1h = latest fix per vessel per minute / hour (no interpolation) |
format | query | json | geojson | csv | default json | json, geojson (application/geo+json) or csv (text/csv; paging in Link / X-Next-Cursor headers) |
limit | query | integer | default 5000; 1000 on Free | Rows per page, up to your plan's maximum (1,000 on Free, 50,000 on Pro and Business) |
cursor | query | string | optional | Opaque 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);propertieshasmmsi,count,coordTimes(the time of every coordinate) andsources. An area is a FeatureCollection of Points whosepropertiesare the position fields. Paging is in the top-levelmeta.format=csv(text/csv): columnsmmsi, ts, lat, lon, sog, cog, heading, nav_status, source, flags; empty cell for null. Paging and attribution are in the response headers.
Objects
Position
| Field | Type | Description |
|---|---|---|
mmsi | integer | Maritime Mobile Service Identity of the transmitting vessel (9 digits) |
ts | ISO 8601 date-time | Message timestamp (UTC) as reported by the source |
lat | number | Latitude, decimal degrees |
lon | number | Longitude, decimal degrees |
sog | number | null | Speed over ground, knots |
cog | number | null | Course over ground, degrees |
heading | integer | null | True heading, degrees; null if unavailable (raw 511) |
nav_status | integer | null | ITU-R M.1371 navigational status 0-14 |
source | string | Data source id, see /v1/sources |
flags | string | null | Quality flag, e.g. implausible_jump (kept, never dropped) |
Page meta
Track responses also carry mmsi or imo; area responses carry bbox.
| Field | Type | Description |
|---|---|---|
from | ISO 8601 date-time | Start of the range actually served (inclusive) |
to | ISO 8601 date-time | End of the range actually served (exclusive), after embargo clamping |
resolution | raw | 1m | 1h | Resolution of this page |
count | integer | Rows in this page |
complete | boolean | true when there are no more pages |
next_cursor | string | null | Pass as ?cursor= to get the next page |
next | string | null | Relative URL of the next page |
embargo_cutoff | ISO 8601 date-time | Data newer than this is not available on your plan |
attribution | array | One entry per source in this page: source, text, license, license_url |
Problem (errors)
| Field | Type | Description |
|---|---|---|
type | string | URI of the error type: a link to its entry on the Errors page, or about:blank |
title | string | Short, stable title, e.g. Window too large; branch on this and the status |
status | integer | HTTP status code |
detail | string | Human-readable explanation of this occurrence |
retry_after_s | integer | Seconds to wait before retrying. Rate limited, Too many concurrent requests and Server busy also send it as a Retry-After header |
upgrade_url | string | Pricing 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:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Monthly request quota |
X-RateLimit-Remaining | Requests left in the billing period |
X-RateLimit-Reset | Unix time when the quota resets |
Track and area responses in CSV:
| Header | Meaning |
|---|---|
X-Count | Rows in this page |
X-Complete | true when there are no more pages |
X-Next-Cursor | Cursor of the next page (absent on the last page) |
Link | rel="next" URL of the next page |
X-Attribution | Required 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.