PortSignal
Marketplace
Sign InSign Up
PortSignal

Signal-powered maritime quality. Bridging the gap between data sellers and global intelligence buyers.

Product

  • Marketplace
  • API Documentation
  • FAQ

Company

  • About Us

Legal

  • Privacy Policy
  • Terms of Service
  • Contact
© 2026 PortSignal

API Documentation

Integrate real-time maritime lineup data into your systems using our REST API.

  • Authentication
  • GET /lineups
  • CSV & Excel / Sheets / Power BI
  • GET /sources
  • GET /schema
  • Pagination
  • Rate Limits
  • Error Format
  • Code Snippets
  • AI-Readable Docs

Authentication

All API requests require an X-API-Key header. You can find your API key in the API Keys page.

HTTP Header

X-API-Key: ps_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

GET /v1/lineups

Returns lineup records from your subscribed feeds. Supports filtering and cursor-based pagination.

Query Parameters

ParameterTypeDescription
sourcestring"all" (default) or a specific feed UUID
portstringUN/LOCODE to filter by port (e.g. FRLEH)
countrystringCountry name to filter by, case-insensitive (e.g. France, Brazil)
port_namestringPort name to filter by, case-insensitive partial match (e.g. Rouen)
fromstringStart date (YYYY-MM-DD) for ETA/ETB/ETD filter
tostringEnd date (YYYY-MM-DD) for ETA/ETB/ETD filter
latestbooleanIf true, return only the most recent record per vessel+port
commoditystringComma-separated list of commodity values. Each value may be a normalized leaf, group, or subgroup name (e.g. "Crude,Containers,VLCC dirty"). Case-insensitive; matched against commodity_normalized, commodity_normalized_group, and commodity_normalized_subgroup.
mergestringControls how near-duplicate records are collapsed. Default "all" merges across every feed you subscribe to (same vessel call reported by multiple sources collapses to one row; source_id and source_name join with ";" alphabetically, deduped). "by_source" merges only within each feed (same vessel from two feeds stays as two rows). "none" returns raw pass-through, no merging. Merge key = vessel + port + commodity (vessel = IMO when present, else normalized name; commodity = signalCommodityTreeId when resolved, else normalized raw string). ±7d transitive ETA window. Split on disagreeing shipper / receiver / charterer. Latest-updatedAt wins for other fields; blank values (null, "", "N/A", "TBC", "TBN") fall back to the most recent non-blank sibling. Date filters from/to apply AFTER merge.
limitnumberPage size (default: 100, max: 500)
cursorstringOpaque pagination cursor from previous response

Response Fields

FieldTypeDescription
idstringUnique record identifier (UUID)
source_idstringSource feed identifier (UUID)
source_namestringSource display name (or anonymous ID)
port_unlocodestringPort UN/LOCODE
port_namestringPort name
country_namestringFull country name
berth_terminalstringBerth or terminal name
vessel_imostringIMO number
vessel_namestringVessel name
vessel_typestringVessel type (Bulk Carrier, Tanker, etc.)
dwtintDeadweight tonnage
operationstringload, disch, bunker, husbandry
commoditystringCommodity as captured from the source (scraped cargo type or AI-classified). Records without a commodity are filtered out.
commodity_normalizedstringSignal normalized leaf commodity (e.g. 'VLCC dirty'). Null if not yet resolved.
commodity_normalized_groupstringSignal normalized commodity group / L2 (e.g. 'Crude'). Null if not yet resolved.
commodity_normalized_subgroupstringSignal normalized commodity subgroup / L1 (e.g. 'Dirty'). Null if not yet resolved.
quantitynumberQuantity in specified unit
quantity_unitstringUnit of measurement (MT, BBL, Units)
etadateEstimated time of arrival (ISO 8601)
etbdateEstimated time of berthing (ISO 8601)
etddateEstimated time of departure (ISO 8601)
combined_datedateEarliest of ETA, ETB, ETD (ISO 8601)
vessel_statusstringexpected, anchored, berthed, departed
shipperstringShipper company
receiverstringReceiver company
chartererstringCharterer company
operatorstringVessel operator
ship_agentstringShip agent
created_atdateRecord creation timestamp (ISO 8601)
updated_atdateLast update timestamp (ISO 8601)

CSV & Excel / Sheets / Power BI

Every /lineups query supports a CSV response by adding ?format=csv. Output is UTF-8 with a BOM, RFC 4180 quoting, and a stable column order — drop it straight into Excel without re-mapping.

cURL

curl -H "X-API-Key: ps_xxx" \
  "https://api.port-signal.com/v1/lineups?source=<feedId>&format=csv" \
  -o lineups.csv

Refreshable URLs (Excel, Google Sheets, Power BI)

From the Data Explorer, click Copy Excel-ready URL to mint a share link. Each click creates a fresh, individually-revocable URL scoped to the current filters. The URL embeds a token, so no auth header is needed at refresh time.

  • Excel: Data → Get Data → From Web → paste URL → Load. Refresh All re-fetches the data.
  • Google Sheets: in any cell, =IMPORTDATA("<paste URL>").
  • Power BI: Get Data → Web → paste URL → Connect.

Treat the URL like a password.Anyone with it can read the data inside the scope. Entitlement is re-checked on every request — cancelling a subscription stops the link returning data — but revoke any unused link from the Data Explorer’s Share Links table.

GET /v1/sources

Returns the list of data sources (feeds) you are currently subscribed to, including port, frequency, and quality ratings.

GET /v1/schema/:sourceId

Returns the field schema and metadata for a specific source, including available fields, data types, and coverage statistics.

Pagination

The API uses cursor-based pagination. Each response includes a nextCursor field. Pass it as the cursor query parameter to fetch the next page. When nextCursor is null, you have reached the end.

Rate Limits

API requests are limited to 100 requests per minute per API key. The response headers include X-RateLimit-Remaining. If you exceed the limit, you will receive a 429 Too Many Requests response with a Retry-After header.

Error Format

All errors follow a consistent format:

JSON

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key",
    "details": null
  }
}

Common error codes: UNAUTHORIZED (401), FORBIDDEN (403), NOT_FOUND (404), VALIDATION_ERROR (400), RATE_LIMITED (429), INTERNAL_ERROR (500).

Code Snippets

cURL

curl -H "X-API-Key: YOUR_API_KEY" \
  "https://api.port-signal.com/v1/lineups?limit=10"

Python

import requests

headers = {"X-API-Key": "YOUR_API_KEY"}
resp = requests.get(
    "https://api.port-signal.com/v1/lineups",
    headers=headers,
    params={"limit": 10},
)
data = resp.json()
for record in data["data"]:
    print(record["vessel_name"], record["commodity"])

JavaScript

const resp = await fetch(
  "https://api.port-signal.com/v1/lineups?limit=10",
  { headers: { "X-API-Key": "YOUR_API_KEY" } },
);
const { data } = await resp.json();
console.log(data);

AI-Readable Documentation

These machine-readable files allow AI agents and automated tools to understand and interact with our API:

llm.txt (concise)llm-full.txt (complete reference)