Quickstart
Three steps to push your first vessel:
- Generate an API key from your developer portal
- POST vessel data to
/api/v1/vessels - Data auto-fills your filing wizards
# Push a vessel โ replace with your API key and real IMO curl -X POST https://canalclear.org/api/v1/vessels \ -H "Authorization: Bearer cc_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "imo": "9321483", "vessel_name": "EVER GIVEN", "flag": "PA", "gross_tonnage": 219079, "loa": 399.94, "beam": 58.8, "draft": 14.5, "vessel_type": "container" }'
https://canalclear.org/api/v1
ยท All requests and responses are JSON.
Authentication
Every request must include your API key as a Bearer token:
Authorization: Bearer cc_live_YOUR_KEY_HERE
API keys are generated from your developer portal. Each key is scoped to your account. Keys start with cc_live_ followed by 32 hex characters.
/api/v1/*). For the outbound, ops-facing REST API (/api/public/* โ vessels, filings, compliance, fleet, webhooks), see the interactive Swagger UI or grab the OpenAPI 3.0 spec.
Rate Limits
100 requests per minute per API key. Rate limit state is tracked in a sliding window per key.
Every response includes rate limit headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1748000060 # Unix timestamp when window resets
Exceeding the limit returns 429 with a retry_after field (seconds).
Error Responses
All errors return JSON with consistent structure:
{
"success": false,
"error": "validation_failed",
"message": "Payload failed validation",
"errors": [
{ "field": "imo", "message": "IMO check digit invalid (expected 3, got 2)" }
]
}
| Status | Error code | Meaning |
|---|---|---|
| 401 | missing_auth / invalid_key | No or invalid API key |
| 422 | validation_failed | IMO invalid, missing required field, bad date |
| 429 | rate_limited | 100 req/min exceeded; check retry_after |
| 500 | server_error | Contact support@canalclear.com |
Vessels
Push vessel particulars. Data is stored against the IMO number โ subsequent pushes update the existing record (upsert).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| imo | string | REQUIRED | 7-digit IMO number (check digit validated) |
| vessel_name | string | optional | Name of vessel |
| flag | string | optional | Flag state (ISO 3166-1 alpha-2, e.g. "PA") |
| gross_tonnage | number | optional | Gross tonnage (GT) |
| net_tonnage | number | optional | Net tonnage (NT) |
| loa | number | optional | Length overall in metres |
| beam | number | optional | Beam in metres |
| draft | number | optional | Maximum draft in metres |
| vessel_class | string | optional | Classification society class notation |
| vessel_type | string | optional | e.g. "bulk_carrier", "tanker", "container" |
| call_sign | string | optional | Radio call sign |
| mmsi | string | optional | 9-digit MMSI |
Response 200
// Returns the stored/updated vessel record { "success": true, "vessel": { "id": 42, "imo_number": "9321483", "vessel_name": "EVER GIVEN", "flag": "PA", "gross_tonnage": 219079, "loa": "399.94", "source": "api", "updated_at": "2026-05-13T10:22:00Z" } }
Returns the stored particulars for a vessel by its 7-digit IMO number.
curl https://canalclear.org/api/v1/vessels/9321483 \ -H "Authorization: Bearer cc_live_YOUR_KEY"
Voyages
Push voyage scheduling data. If voyage_ref is provided, subsequent pushes with the same ref update the record. Otherwise a new voyage is created.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| vessel_imo | string | REQUIRED | IMO of the vessel |
| voyage_ref | string | optional | TMS voyage number โ used as upsert key |
| origin | string | optional | Departure port (UN/LOCODE or name) |
| destination | string | optional | Arrival port |
| eta | string | optional | ISO 8601 datetime e.g. 2026-06-01T08:00:00Z |
| etd | string | optional | ISO 8601 datetime โ must be after eta if both provided |
| cargo_type | string | optional | e.g. "bulk", "crude_oil", "containers", "LNG" |
| cargo_quantity | number | optional | Metric tons |
| waterway | string | optional | One of: panama, suez, bosporus, malacca, cape, kiel, saint-lawrence |
Query parameters
| Param | Default | Description |
|---|---|---|
| vessel_imo | โ | Filter to a specific vessel |
| limit | 50 | Max results (max 200) |
| offset | 0 | Pagination offset |
Crew
Push crew manifests for a vessel. Each POST appends the provided members โ it does not replace existing crew.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| vessel_imo | string | REQUIRED | IMO of the vessel |
| voyage_ref | string | optional | Associate with a voyage |
| crew | array | REQUIRED | Array of crew member objects (see below) |
Crew member object
| Field | Type | Required | Description |
|---|---|---|---|
| full_name | string | REQUIRED | Full name of crew member |
| nationality | string | optional | ISO 3166-1 alpha-2 e.g. "PH" |
| rank | string | optional | e.g. "Master", "Chief Officer", "AB" |
| cert_number | string | optional | STCW certificate number |
| cert_type | string | optional | e.g. "STCW", "GMDSS" |
| cert_expiry | string | optional | YYYY-MM-DD format |
# Example: push 2 crew members curl -X POST https://canalclear.org/api/v1/crew \ -H "Authorization: Bearer cc_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "vessel_imo": "9321483", "voyage_ref": "VOY-2026-041", "crew": [ { "full_name": "Juan Santos", "nationality": "PH", "rank": "Master", "cert_number": "STCW-PH-20241", "cert_expiry": "2028-03-15" }, { "full_name": "Li Wei", "nationality": "CN", "rank": "Chief Officer" } ] }'
Webhook Receiver
Configure your TMS to send webhooks to a single endpoint. CanalClear logs every delivery and optionally maps fields using a stored template before ingesting.
Query parameters
| Param | Description |
|---|---|
| source | Label for this source (e.g. veson, q88) โ appears in delivery log |
| mapping_id | ID of a stored field mapping template to apply before ingestion |
Configure your Veson IMOS webhook URL as:
https://canalclear.org/api/v1/webhook?source=veson&mapping_id=1 Authorization: Bearer cc_live_YOUR_KEY
Response 202
{
"success": true,
"delivery_id": 187, // use this to inspect the delivery log
"status": "ingested",
"ingested": {
"vessel": "9321483",
"voyage": 55
}
}
Field Mapping
TMS systems use different field names. Create a mapping template once, then reference it with ?mapping_id=N on the webhook endpoint.
Mapping keys are CanalClear target fields. Values are dot-notation paths into the source JSON payload.
# Create a mapping template for Veson IMOS curl -X POST https://canalclear.org/api/developer/webhook-mappings \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Veson IMOS Vessel Update", "source_name": "veson", "mapping": { "imo": "vessel.imo_no", "vessel_name": "vessel.vessel_code", "vessel_type": "vessel.vessel_type_description", "gross_tonnage": "vessel.gt", "loa": "vessel.loa", "beam": "vessel.beam", "draft": "vessel.draft_max" } }'
"vessel.imo_no" maps to payload.vessel.imo_no in the raw JSON. Nested objects work as expected.
TMS Compatibility
Works with any system that can make HTTP POST requests. Pre-built field mapping templates available for:
API Key Management
These endpoints use session auth (your dashboard login), not API key auth โ use them from your fleet dashboard or internal scripts.
Body: { "label": "Veson prod" }
Returns all active and revoked keys for your account. Raw keys are never returned.
Immediately revokes the key. All future requests using it return 401. Irreversible.
Webhook Delivery Log
Every webhook delivery is logged. Use this to debug failed pushes or inspect raw payloads.
Returns the 50 most recent webhook deliveries with status, source, and a payload preview.
Returns the complete headers and JSON payload for a single delivery. Statuses: received, mapped, ingested, invalid, error.
Last 50 inbound data pushes across vessels, voyages, and crew โ with entity type, reference, source, and timestamp.
Outbound Webhooks โ Push Events to Your Systems
Subscribe to compliance events with a signed POST to your endpoint. Use the inbound /app/settings/webhooks page (or /api/developer/webhooks) to create a subscription. Each webhook is scoped to one of your API keys and signed with an HMAC-SHA256 secret you choose.
Supported event types
filing.submitted, filing.approved, filing.rejected,
filing.status_changed, vessel.compliance_score_changed,
alert.triggered, alert.resolved, certificate.expiring.
Pick any combination per webhook.
Delivery semantics
- First attempt โ fires inline from the approval workflow, target seconds < 30.
- Retries โ 3 attempts total with exponential backoff: 1 minute โ 5 minutes โ 15 minutes.
- Timeout โ 10 seconds per attempt. Aborted requests count as failed.
- Success โ any 2xx HTTP response. The connection is marked
delivered. - Failure โ non-2xx, network error, or timeout. After 3 attempts the delivery is flagged
admin_reviewand the subscription'sfailed_atis set so admins can investigate.
Request headers
Content-Type: application/json
X-CanalClear-Signature: sha256=<hex hmac of raw body>
X-CanalClear-Event: filing.approved
X-CanalClear-Delivery: 12345 (delivery row id for log lookup)
User-Agent: CanalClear-Webhook/1.0
Payload envelope
{
"event": "filing.approved",
"timestamp": "2026-06-16T10:30:00.000Z",
"data": {
"filing_id": "42",
"filing_type": "sp1",
"vessel_name": "MV Northern Star",
"vessel_imo": "9876543",
"waterway": "bosporus",
"status": "approved",
"submitted_at": "2026-06-15T08:12:00.000Z",
"approved_at": "2026-06-16T10:30:00.000Z",
"confirmation_number": null
}
}
Verifying the signature
Compute HMAC-SHA256 over the raw request body (do not re-serialise a parsed JSON object) using the secret you set when creating the webhook. Compare the result to the X-CanalClear-Signature header using a constant-time comparison to avoid timing attacks.
Node.js:
const crypto = require('crypto');
function verify(req, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody) // raw bytes โ keep rawBody before JSON.parse
.digest('hex');
const provided = req.headers['x-canalclear-signature'] || '';
// timingSafeEqual requires equal-length buffers
const expBuf = Buffer.from(expected);
const gotBuf = Buffer.from(provided);
if (expBuf.length !== gotBuf.length) return false;
return crypto.timingSafeEqual(expBuf, gotBuf);
}
Python:
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header or '')
curl (compute signature on the command line for one-off debugging):
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
echo "sha256=$SIG"
API endpoints
Returns all subscriptions owned by the current user with their event types, last delivery status, and linked API key prefix.
Body: { url, event_types: [...], api_key_id?: number }. Returns raw_secret โ store it immediately, it is shown only once. Identical endpoints also available under /api/developer/webhooks.
Enqueues a filing.test event for immediate dispatch. Useful for endpoint smoke tests. No retry on failure โ the UI test button is meant for liveness only.
Returns rows for the given webhook with status, attempt count, response status, and a truncated response body. Filter with ?limit=50&offset=0.