Docs
Quickstart
Everything is plain HTTPS and JSON. Base URL: https://api.aistrail.com. All times are UTC (ISO 8601), speeds in knots, angles in degrees.
1. Create an account and a key
Sign up with your email address and a password, then open the link we email you to confirm the address (it works for 48 hours). Your account starts on the Free plan, no card needed. Create a key on the API keys page of your dashboard; you can keep up to 5 active keys, for example one per app or environment. A key looks like ap_live_… and is shown once, when you create it.
Send the key in a header on every request:
Authorization: Bearer ap_live_YOUR_KEY
Data endpoints never accept the key in the URL, so it does not end up in logs, browser history or proxies. Call the API from your server or scripts, not from browser code: a key in a web page would be public.
The examples below read the key from an environment variable:
export AIS_TRAIL_KEY=ap_live_YOUR_KEY
Pro and Business plans: choose Request upgrade on the Plan page of your dashboard and we email you to set up billing. Your current plan stays active until then.
2. Find a vessel
curl -H "Authorization: Bearer $AIS_TRAIL_KEY" \
"https://api.aistrail.com/v1/vessels/search?q=MYSTAR"
Searches name, call sign, MMSI prefix and exact IMO. Results are ordered by when the vessel was last seen; each has mmsi, name, imo, callsign, ship_type, length_m and last_seen. Use limit (up to 50) and offset; the response carries next_offset.
GET /v1/vessels/{mmsi} returns the current identity (name, IMO, call sign, ship type, length, width, draught, destination, ETA) and a log of the distinct static reports the vessel has sent.
3. Fetch a track
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 a Free key, use a date within the last 7 days: an older from returns 403 History limit.
The response is {"meta": {…}, "positions": […]}. Each position has mmsi, ts, lat, lon, sog, cog, heading, nav_status (ITU-R M.1371 code), source and flags.
resolution=rawreturns every fix;1mand1hreturn the latest real fix per vessel per minute or hour (no interpolation).- One query may span at most raw 24 hours, 1m 7 days, 1h 366 days. Longer ranges: split them.
fromdefaults totominus 24 hours;todefaults to now minus 24 hours (the most recent 24 hours are not available, seemeta.embargo_cutoff).- Fixes that imply a physically impossible jump are kept and marked
flags: "implausible_jump", so you can decide what to do with them.
By IMO number
Ships change MMSI when they change flag. GET /v1/vessels/imo/{imo}/track follows the ship across MMSIs: each MMSI is counted only for the periods it reported this IMO (±10 minutes), and every row carries its mmsi. GET /v1/vessels/imo/{imo} lists the MMSIs and their periods. If you pass an IMO where an MMSI is expected, the API answers 400 with the right URL.
4. Query an area
Pro and Business plans can ask for every vessel inside a bounding box (Pro up to 2°×2°, Business up to 10°×10°):
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"
bbox is lat_min,lon_min,lat_max,lon_max. Rows come day by day and, at raw resolution, 1° tile by tile; inside a tile they are ordered by vessel and time. The default window is one hour.
5. Page through results
Large results are split into pages: 5,000 rows by default (1,000 on Free); set limit up to your plan's maximum of 1,000 rows on Free or 50,000 on Pro and Business. Follow meta.next until meta.complete is true. The cursor is opaque and signed; rows never repeat or go missing at page boundaries. Area pages are time-boxed on the server, so a page can hold fewer rows than limit: keep following next.
# Python
import os, time, requests
s = requests.Session()
s.headers["Authorization"] = "Bearer " + os.environ["AIS_TRAIL_KEY"]
url = "https://api.aistrail.com/v1/vessels/imo/9892690/track?from=2026-10-08&to=2026-10-09&resolution=raw"
rows = []
while url:
r = s.get(url, timeout=60)
if r.status_code == 429: # rate limit: wait and retry the same page
time.sleep(int(r.headers.get("Retry-After", "1")))
continue
r.raise_for_status()
page = r.json()
rows += page["positions"]
nxt = page["meta"]["next"]
url = "https://api.aistrail.com" + nxt if nxt else None
print(len(rows))
// JavaScript (Node 18+), an area query (Pro and Business)
const KEY = process.env.AIS_TRAIL_KEY;
const sleep = (s) => new Promise((done) => setTimeout(done, s * 1000));
(async () => {
let url = "/v1/area?bbox=54.95,10.6,55.6,11.25&from=2026-10-08T10:00:00Z&to=2026-10-08T12:00:00Z";
const rows = [];
while (url) {
const r = await fetch("https://api.aistrail.com" + url, { headers: { Authorization: `Bearer ${KEY}` } });
if (r.status === 429) { await sleep(Number(r.headers.get("Retry-After") || 1)); continue; }
if (!r.ok) throw new Error(`${r.status}: ${(await r.json()).detail}`);
const page = await r.json();
rows.push(...page.positions);
url = page.meta.next;
}
console.log(rows.length);
})();
6. JSON, GeoJSON or CSV
format=geojson(application/geo+json): a track is one LineString per vessel with the fix times inproperties.coordTimes; an area is a FeatureCollection of Points. Paging info is in the top-levelmeta.format=csv: columnsmmsi, ts, lat, lon, sog, cog, heading, nav_status, source, flags. Paging and attribution come in headers:X-Next-Cursor,Link: <…>; rel="next",X-Complete,X-Count,X-Attribution.
curl -sD headers.txt -H "Authorization: Bearer $AIS_TRAIL_KEY" -o page1.csv \
"https://api.aistrail.com/v1/vessels/276859000/track?from=2026-10-08&to=2026-10-09&format=csv"
grep -i '^link:' headers.txt # next page, if any
7. Limits and errors
- Only successful (2xx) requests count toward your monthly quota, including every page and calls to
/v1/usage. The quota is shared by all keys of your account; the per-second limit applies to each key. Every response to a request with a valid key carriesX-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset. Your dashboard shows the same usage. - At most 2 requests per key are processed at a time; queries that run longer than the server limit are stopped with 503 and are not billed.
- Errors are RFC 9457 problem details (
application/problem+json) with atitleand a human-readabledetail. See Errors.
8. Attribution
The data comes from public sources whose licenses require attribution when you redistribute it. Every track and area response lists the sources on that page in meta.attribution (or the X-Attribution header for CSV). Details: Data sources.
Found a mistake, or something unclear? Suggest an edit and we will fix the page.