REST API v1

TMS Integration API

Push vessel, voyage, and crew data directly into CanalClear from Veson, Q88, Dataloy, ShipNet, or any custom system. One API, every waterway.

Get API Key โ†’ Quickstart

Quickstart

Three steps to push your first vessel:

  1. Generate an API key from your developer portal
  2. POST vessel data to /api/v1/vessels
  3. 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"
  }'
Base URL: 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.

Key shown once. The full key is only returned at creation time. Store it immediately in your TMS or environment variables โ€” it cannot be retrieved again.
Looking for the public REST API instead? This page documents the inbound TMS push API (/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)" }
  ]
}
StatusError codeMeaning
401missing_auth / invalid_keyNo or invalid API key
422validation_failedIMO invalid, missing required field, bad date
429rate_limited100 req/min exceeded; check retry_after
500server_errorContact support@canalclear.com

Vessels

Push vessel particulars. Data is stored against the IMO number โ€” subsequent pushes update the existing record (upsert).

POST /api/v1/vessels Create or update vessel

Request body

FieldTypeRequiredDescription
imostringREQUIRED7-digit IMO number (check digit validated)
vessel_namestringoptionalName of vessel
flagstringoptionalFlag state (ISO 3166-1 alpha-2, e.g. "PA")
gross_tonnagenumberoptionalGross tonnage (GT)
net_tonnagenumberoptionalNet tonnage (NT)
loanumberoptionalLength overall in metres
beamnumberoptionalBeam in metres
draftnumberoptionalMaximum draft in metres
vessel_classstringoptionalClassification society class notation
vessel_typestringoptionale.g. "bulk_carrier", "tanker", "container"
call_signstringoptionalRadio call sign
mmsistringoptional9-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"
  }
}
GET /api/v1/vessels/:imo Retrieve vessel by IMO

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.

POST /api/v1/voyages Create or update voyage

Request body

FieldTypeRequiredDescription
vessel_imostringREQUIREDIMO of the vessel
voyage_refstringoptionalTMS voyage number โ€” used as upsert key
originstringoptionalDeparture port (UN/LOCODE or name)
destinationstringoptionalArrival port
etastringoptionalISO 8601 datetime e.g. 2026-06-01T08:00:00Z
etdstringoptionalISO 8601 datetime โ€” must be after eta if both provided
cargo_typestringoptionale.g. "bulk", "crude_oil", "containers", "LNG"
cargo_quantitynumberoptionalMetric tons
waterwaystringoptionalOne of: panama, suez, bosporus, malacca, cape, kiel, saint-lawrence
GET /api/v1/voyages List voyages

Query parameters

ParamDefaultDescription
vessel_imoโ€”Filter to a specific vessel
limit50Max results (max 200)
offset0Pagination offset

Crew

Push crew manifests for a vessel. Each POST appends the provided members โ€” it does not replace existing crew.

POST /api/v1/crew Add crew members

Request body

FieldTypeRequiredDescription
vessel_imostringREQUIREDIMO of the vessel
voyage_refstringoptionalAssociate with a voyage
crewarrayREQUIREDArray of crew member objects (see below)

Crew member object

FieldTypeRequiredDescription
full_namestringREQUIREDFull name of crew member
nationalitystringoptionalISO 3166-1 alpha-2 e.g. "PH"
rankstringoptionale.g. "Master", "Chief Officer", "AB"
cert_numberstringoptionalSTCW certificate number
cert_typestringoptionale.g. "STCW", "GMDSS"
cert_expirystringoptionalYYYY-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.

POST /api/v1/webhook Generic webhook receiver

Query parameters

ParamDescription
sourceLabel for this source (e.g. veson, q88) โ€” appears in delivery log
mapping_idID 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"
    }
  }'
Dot notation: "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:

Veson IMOS
โœ“ REST webhook
Q88
โœ“ Vessel API
Dataloy
โœ“ REST export
ShipNet
โœ“ Event webhook
OneOcean
โœ“ REST API
Custom
โœ“ Any HTTP client
Need a pre-built mapping for your TMS? Email support@canalclear.com โ€” typical turnaround is 1 business day.

API Key Management

These endpoints use session auth (your dashboard login), not API key auth โ€” use them from your fleet dashboard or internal scripts.

POST /api/developer/keys Generate new API key

Body: { "label": "Veson prod" }

The raw key is only shown once. Store it immediately.
GET /api/developer/keys List all API keys (prefix only)

Returns all active and revoked keys for your account. Raw keys are never returned.

DELETE /api/developer/keys/:id Revoke a key

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.

GET /api/developer/webhook-logs List recent deliveries

Returns the 50 most recent webhook deliveries with status, source, and a payload preview.

GET /api/developer/webhook-logs/:id Full payload inspection

Returns the complete headers and JSON payload for a single delivery. Statuses: received, mapped, ingested, invalid, error.

GET /api/developer/activity Inbound data activity feed

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_review and the subscription's failed_at is 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

GET /api/settings/webhooks List your webhooks (URLs are masked)

Returns all subscriptions owned by the current user with their event types, last delivery status, and linked API key prefix.

POST /api/settings/webhooks Create a webhook subscription

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.

POST /api/settings/webhooks/:id/test Send a synthetic filing.test event

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.

GET /api/settings/webhooks/:id/deliveries Paginated delivery log

Returns rows for the given webhook with status, attempt count, response status, and a truncated response body. Filter with ?limit=50&offset=0.