The official Python library for the Datalastic Maritime API. This Python maritime API client covers real-time AIS vessel tracking, port search, sea route calculation, maritime intelligence data, and asynchronous report management.
pip install datalasticRequires Python 3.8 or later and the requests library.
from datalastic import Client
client = Client("YOUR_API_KEY")
# Real-time position of a single vessel
vessel = client.vessels.get(mmsi="636092297")
print(vessel.name, vessel.lat, vessel.lon, vessel.speed)
# Account usage
stat = client.stat()
print(stat.requests_remaining)Every request includes your API key as the x-api-key HTTP header, set once on the session when you construct the client. You never need to set headers yourself. Pass the key once when constructing the client:
client = Client("YOUR_API_KEY")
# Optional: override the default 30-second timeout
client = Client("YOUR_API_KEY", timeout=10)Transient failures are retried for you. By default the client retries connection errors, request timeouts, and HTTP 429 up to three times with exponential backoff.
client = Client(
"YOUR_API_KEY",
timeout=30, # per-request timeout, seconds
max_retries=3, # 0 disables retrying entirely
backoff_factor=0.5, # waits 0.5s, 1s, 2s between attempts
retry_after_max=60, # ceiling for any single wait, seconds
retry_on_status=(429,), # statuses worth repeating
)| Setting | Default | Notes |
|---|---|---|
timeout |
30 |
Per-request timeout in seconds. Must be a positive number; None is rejected so a request can never hang forever. |
max_retries |
3 |
Maximum retries per request. 0 sends exactly one request, as in v0.1.0. |
backoff_factor |
0.5 |
Wait before retry n (zero-based) is backoff_factor * 2 ** n. |
retry_after_max |
60 |
Ceiling in seconds for every wait, including one derived from a Retry-After header. Nothing waits longer than this. |
retry_on_status |
(429,) |
Only 408, 429, and 5xx may be listed. Passing 401, 402, 404, or any other client error raises ValueError. |
Details worth knowing:
Retry-Afteris honored on 429. Both RFC 7231 forms are supported: delta-seconds (120) and an HTTP-date (Wed, 21 Oct 2026 07:28:00 GMT). A date in the past means "retry now". A missing, malformed, or absurd header falls back to the backoff schedule, and every wait is capped atretry_after_max.- Client errors are never retried. 401, 402, 404, and other 4xx statuses describe a problem with the request; repeating it cannot help. They raise immediately.
- 5xx is not retried by default. The SDK cannot tell a transient upstream blip from a deterministic server-side failure, and retrying every 5xx amplifies load during an incident. If you sit behind a gateway that returns occasional 502/503/504, opt in with
retry_on_status=(429, 502, 503, 504). - POST is treated as non-idempotent.
reports.submitcreates a report, so a timeout or dropped connection is not retried: the request may well have reached the server, and repeating it would risk a duplicate report. A retryable status is retried on POST, because the server answered and therefore refused the request rather than performing it. - When retries are exhausted the original error surfaces:
RateLimitErrorif the last attempt was a 429, otherwise anAPIErrorcarrying the endpoint and the number of attempts made.
Every method returns the parsed data payload, and every returned object also carries the response envelope on .meta — pagination tokens, credit and usage counters, and the API's success flag.
vessel = client.vessels.get(mmsi="636092297")
print(vessel.name) # unchanged
print(vessel.meta) # {'success': True, ...}Methods that return a collection return a ResultList. That is a real list subclass: iteration, indexing, len(), slicing, and equality with a plain list all work exactly as before. It just adds .meta and .next_token.
ports = client.ports.find(country_iso="NL")
print(len(ports), ports[0].port_name) # ordinary list access
print(ports.meta) # the envelope.meta is always a dict — an empty one when a response carries no envelope, so result.meta.get("...") is always safe.
vessels.find and vessels.in_radius are paginated. Read .next_token off the result and pass it back as next to fetch the following page. .next_token is None on the last page.
page = client.vessels.find(name="MAERSK", vessel_type="cargo")
vessels = list(page)
while page.next_token:
page = client.vessels.find(
name="MAERSK", vessel_type="cargo", next=page.next_token
)
vessels.extend(page)
print(len(vessels))vessels.in_radius returns a single VesselInRadiusResult rather than a list, but exposes the same .next_token:
page = client.vessels.in_radius(port_unlocode="NLRTM", radius=20)
found = list(page.vessels)
while page.next_token:
page = client.vessels.in_radius(
port_unlocode="NLRTM", radius=20, next=page.next_token
)
found.extend(page.vessels)The Datalastic Python SDK exposes eight vessel methods. At least one of uuid, mmsi, or imo is required for all single-vessel lookups.
vessel = client.vessels.get(mmsi="636092297")
vessel = client.vessels.get(imo="9320403")
vessel = client.vessels.get(uuid="...")Returns a Vessel.
Adds draught, departure/destination ports, ATD, and ETA fields.
vessel = client.vessels.pro(mmsi="636092297")
print(vessel.current_draught, vessel.dest_port, vessel.eta_UTC)Returns a VesselPro.
Pass a list or a comma-separated string for each identifier type. Identifiers are sent as repeated query parameters.
result = client.vessels.bulk(mmsi=["636092297", "538005989"])
result = client.vessels.bulk(imo="9320403,9301491")
print(result.total)
for vessel in result.vessels:
print(vessel.name)Returns a VesselBulkResult.
radius (nautical miles) is required. An anchor is also required: either lat + lon, port_uuid, port_unlocode, uuid, mmsi, or imo. Optional filters include type, type_specific, exclude, nav_status, next, and empty.
# By coordinates
near = client.vessels.in_radius(lat=51.95, lon=4.14, radius=50, type="cargo")
# By port UN/LOCODE
near = client.vessels.in_radius(port_unlocode="NLRTM", radius=20)
# Include vessels with no recent AIS position
near = client.vessels.in_radius(lat=51.95, lon=4.14, radius=50, empty=True)Returns a VesselInRadiusResult.
history = client.vessels.history(mmsi="636092297", days=7)
history = client.vessels.history(
imo="9320403", from_date="2026-01-01", to_date="2026-02-01"
)
for point in history.positions:
print(point["lat"], point["lon"])from_date and to_date are sent to the API as from and to. Returns a VesselHistory.
info = client.vessels.info(imo="9320403")
print(info.gross_tonnage, info.deadweight, info.year_built)Returns a VesselInfo.
At least one real search criterion is required. fuzzy and next alone do not qualify. vessel_type is sent to the API as the type parameter. Supports the empty boolean to include vessels with no recent AIS data.
matches = client.vessels.find(name="MAERSK", vessel_type="cargo")
matches = client.vessels.find(
country_iso="DK",
gross_tonnage_min=50000,
year_built_min=2010,
)
# Include vessels not currently transmitting
matches = client.vessels.find(name="NORDIC", empty=True)Returns a ResultList of VesselInfo (a list that also carries .meta and .next_token).
Uses the extended base URL. Returns the latest satellite-estimated position when terrestrial AIS is unavailable.
est = client.vessels.estimated(mmsi="636092297")
print(est.estimated_position)Returns a VesselEstimated.
At least one parameter is required. lat and lon must be provided together.
ports = client.ports.find(name="ROTTERDAM")
ports = client.ports.find(country_iso="NL")
ports = client.ports.find(lat=51.95, lon=4.14, radius=30)
ports = client.ports.find(unlocode="NLRTM")
ports = client.ports.find(port_type="sea")Returns a ResultList of Port.
port = client.ports.get(unlocode="NLRTM")
print(port.port_name, len(port.terminals))
port = client.ports.get(name="ROTTERDAM")Returns a PortDetail.
Calculates the sea route between an origin and a destination. Each endpoint can be specified as a coordinate pair, a port UUID, or a port UN/LOCODE.
# By coordinates
route = client.routes.calculate(
lat_from=51.95, lon_from=4.14,
lat_to=40.65, lon_to=-74.05,
)
# By UN/LOCODE
route = client.routes.calculate(
port_unlocode_from="NLRTM",
port_unlocode_to="USNYC",
)
print(route.from_point)
print(route.to_point)
print(route.route["total_dist"])Returns a SeaRoute.
All intel methods call the maritime_reports base URL and return a ResultList of typed records.
Dry dock schedule records. Filter by imo, name, dry_dock_from, or dry_dock_to.
records = client.intel.dry_dock(imo="9320403")
for r in records:
print(r.vessel_name, r.dry_dock_date, r.country_name)Returns a ResultList of DryDockRecord.
Vessel casualty records. Filter by imo, name, from_date, or to_date.
records = client.intel.casualties(
imo="9320403", from_date="2025-01-01", to_date="2026-01-01"
)Returns a ResultList of CasualtyRecord.
Port state control inspection records. Filter by imo, name, from_date, or to_date.
records = client.intel.inspections(imo="9320403")
for r in records:
print(r.inspection_date, r.inspection_authority, r.detention)Returns a ResultList of InspectionRecord.
Sale, purchase, and demolition records. Filter by imo, name, from_date, or to_date.
records = client.intel.spd(imo="9320403")
for r in records:
print(r.sales_report_date, r.buyer, r.sales_price_usd_mio)Returns a ResultList of SPDRecord.
Beneficial ownership and management records. Filter by imo, name, beneficial_owner, operator, technical_manager, commercial_manager, or updated_from.
records = client.intel.ownership(beneficial_owner="MAERSK")
for r in records:
print(r.vessel_name, r.beneficial_owner_country, r.technical_manager)Returns a ResultList of OwnershipRecord.
Classification society records. Filter by imo, name, fuzzy, beneficial_owner, beneficial_owner_imo, technical_manager, technical_manager_imo, or updated_from.
records = client.intel.class_society(imo="9320403")
for r in records:
print(r.class1_code, r.special_survey_date)Returns a ResultList of ClassSocietyRecord.
Engine and propulsion records. Filter by imo, name, fuzzy, or updated_from.
records = client.intel.engine(imo="9320403")
for r in records:
print(r.engine_designation, r.engine_builder, r.mco, r.mco_unit)Returns a ResultList of EngineRecord.
Maritime company records. Filter by company_imo, name, or updated_from.
records = client.intel.companies(company_imo="0123456")
for r in records:
print(r.short_name, r.company_type, r.country_code)Returns a ResultList of CompanyRecord.
Reports are asynchronous. Submit a request, then poll by ID until the status is "done".
Pass extra report parameters as keyword arguments.
report = client.reports.submit(
"inradius_history",
port_uuid="...",
days=30,
)
print(report.report_id, report.status)Valid report types:
| Type | Description |
|---|---|
inradius_history |
Historical positions within a radius |
vessel_list |
Filtered vessel list |
port_list |
Filtered port list |
request_usage |
Account request usage |
dry_dock_dates |
Dry dock schedule export |
casualty |
Casualty data export |
inspections |
PSC inspection data export |
sales_purchase_demolitions |
SPD transactions export |
ownership |
Ownership data export |
class_society |
Classification society export |
engine |
Engine data export |
companies |
Company data export |
Returns a Report.
Poll the status of a single report.
report = client.reports.get(report.report_id)
if report.status == "done":
print(report.result_url)Returns a Report.
List every report associated with the API key.
for r in client.reports.list_all():
print(r.report_id, r.report_type, r.status)Returns a ResultList of Report.
All exceptions inherit from DatalasticError.
from datalastic import (
Client,
DatalasticError,
AuthenticationError,
InsufficientCreditsError,
NotFoundError,
RateLimitError,
APIError,
)
client = Client("YOUR_API_KEY")
try:
vessel = client.vessels.get(mmsi="636092297")
except AuthenticationError:
print("Invalid or expired API key")
except InsufficientCreditsError:
print("Account credits exhausted")
except NotFoundError:
print("Vessel not found")
except RateLimitError:
print("Rate limit exceeded")
except APIError as exc:
print("API error", exc.status_code, exc)
except DatalasticError:
print("Other SDK error")| Exception | HTTP status | When raised |
|---|---|---|
AuthenticationError |
401 | Invalid or expired API key |
InsufficientCreditsError |
402 | No credits remaining |
NotFoundError |
404 | Resource does not exist |
RateLimitError |
429 | More than 600 requests per minute |
APIError |
other / timeout | Any other error; carries status_code |
DatalasticError |
-- | Base class for all of the above |
Input validation errors (ValueError) are raised before any HTTP request is made, so they are never wrapped in a DatalasticError. Invalid client configuration -- a negative max_retries, a non-numeric backoff_factor, a non-retryable entry in retry_on_status -- is also rejected with ValueError at construction, before the client can be used.
The API can answer with HTTP 200 and still report a failure in the envelope. When meta.success is explicitly false the SDK raises APIError rather than handing back an empty model, and includes meta.message and the endpoint in the error text:
# {"meta": {"success": false, "message": "Vessel not in coverage"}}
try:
vessel = client.vessels.get(mmsi="636092297")
except APIError as exc:
print(exc) # [200] API reported failure (GET .../vessel): Vessel not in coverageResponses with no meta at all, or a meta without a success key, are treated as successful -- endpoints vary, and only an explicit falsy success is an error.
Every model exposes the raw API payload as .raw for fields not surfaced as named attributes, and the response envelope as .meta.
Every model listed below carries two fields in addition to the named ones: raw, the untouched API payload, and meta, the response envelope (plus the derived next_token property). ResultList -- returned by every collection endpoint -- is a list subclass carrying the same meta and next_token.
| Field | Type | Notes |
|---|---|---|
uuid |
str | Datalastic vessel UUID |
name |
str | Vessel name |
mmsi |
str | MMSI identifier |
imo |
str | IMO number |
eni |
str | ENI number (inland waterways) |
country_iso |
str | Flag state ISO code |
type |
str | Vessel type |
type_specific |
str | Specific vessel type |
lat |
float | Last known latitude |
lon |
float | Last known longitude |
speed |
float | Speed over ground (knots) |
course |
float | Course over ground (degrees) |
navigation_status |
str | AIS navigational status |
heading |
float | True heading (degrees) |
destination |
str | AIS-reported destination |
last_position_epoch |
int | Unix timestamp of last AIS fix |
last_position_UTC |
str | UTC datetime of last AIS fix |
eta_epoch |
int | ETA as Unix timestamp |
eta_UTC |
str | ETA as UTC datetime string |
raw |
dict | Full API response payload |
All Vessel fields plus:
| Field | Type | Notes |
|---|---|---|
current_draught |
float | Current draught (metres) |
dest_port |
str | Destination port name |
dest_port_unlocode |
str | Destination port UN/LOCODE |
dep_port |
str | Last departure port name |
dep_port_unlocode |
str | Last departure port UN/LOCODE |
atd_epoch |
int | Actual time of departure as Unix timestamp |
atd_UTC |
str | Actual time of departure as UTC string |
All VesselPro fields plus:
| Field | Notes |
|---|---|
estimated_position |
SAT-E estimated position data |
| Field | Notes |
|---|---|
total |
Count of matching vessels |
vessels |
list[Vessel] |
raw |
Full API response payload |
| Field | Notes |
|---|---|
point |
The anchor point used for the search |
total |
Count of vessels found |
vessels |
list[Vessel] (each vessel's .raw includes distance) |
raw |
Full API response payload |
uuid, name, mmsi, imo, eni, country_iso, type, type_specific, positions (list of position dicts), raw
uuid, name, name_ais, mmsi, imo, eni, country_iso, country_name, callsign, type, type_specific, gross_tonnage, deadweight, teu, liquid_gas, length, breadth, draught_avg, draught_max, speed_avg, speed_max, year_built, is_navaid, home_port, raw
uuid, port_name, country_iso, country_name, unlocode, port_type, lat, lon, area_lvl1, area_lvl2, raw
All Port fields plus terminals (list).
| Field | Notes |
|---|---|
from_point |
Origin point data from the API |
route |
Route details dict (includes total_dist) |
to_point |
Destination point data from the API |
raw |
Full API response payload |
user_id, key_status, requests_made, requests_remaining, raw
report_id, report_type, status, result_url, created_at, updated_at, params, raw
id, imo, vessel_name, special_survey_date, dry_dock_date, iopp_issue_date, iopp_exp_date, technical_manager, country_code, country_name, website, email, phone, address, linkedin, modified_at, raw
id, imo, vessel_name, casualty_date, casualty_type, casualty_details, modified_at, raw
id, imo, vessel_name, vessel_type_code, flag_code, inspection_date, inspection_authority, inspection_port, inspection_type, detention, ship_deficiencies, deficiency_description, technical_ism_manager, country_code, website, email, phone, address, company_imo, modified_at, raw
id, sales_report_date, imo, vessel_name, flag_name, vessel_type_code, built_year, dwt_design, gt, ldt, seller, buyer, sales_price_usd_mio, sales_price_usd_per_ldt, destination, sales_type, dry_dock_date, special_survey_date, sales_note, previous_sales_record, modified_at, raw
id, imo, vessel_name, beneficial_owner, beneficial_owner_country, operator, operator_country, flag_name, vessel_type_code, built_year, buyer, dwt_design, class1_code, technical_manager, technical_manager_country, commercial_manager, commercial_manager_country, modified_at, raw
imo, vessel_name, vessel_type_code, flag_name, built_year, dwt_design, special_survey_date, dry_dock_date, class1_code, beneficial_owner_imo, beneficial_owner, technical_manager_imo, technical_manager, draft_design, nt, gt, loa, lbp, depth, beam_moduled, engine_builder, engine_designer, propulsion_type_code, modified_at, raw
imo, vessel_name, vessel_type_code, propulsion_type_code, mco, mco_unit, mco_rpm, trading_category_code, built_year, gt, engine_designation, engine_builder, engine_designer, modified_at, raw
id, short_name, long_name, company_type, country_code, company_imo, website, company_status, email, phone, address, linkedin, parent_company_imo, parent_company_name, modified_at, raw
MIT. See LICENSE.