Signal intelligence
Fetch explainability breakdowns for spoofing, cargo, and motif signals on a vessel with score components, evidence timelines, and analyst feedback.
Explainability breakdowns and analyst feedback calibration for spoofing, cargo, and motif signals.
Use these endpoints to understand why a signal was generated, what data sources contributed to it, and to review how analysts have classified past signals for threshold tuning.
GET /api/v1/signals/explainability
Returns human-readable explainability breakdowns for three signal types on a vessel: spoofing, cargo estimation, and temporal motif detection. Any of spoofing, cargo, or motif is null if no matching row exists for the vessel (or at all, when imo is omitted).
Each breakdown includes detector version, score components, source hierarchy or signature families, evidence timeline, and confidence figures. Use this to audit detection logic or build transparency reports.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
imo | string | 7-digit IMO number. Omit (or pass a value that isn't exactly 7 digits) to return the latest signal of each type across all vessels. |
Response
{
"imo": "9876543",
"spoofing": {
"vesselName": "string",
"imo": "9876543",
"violationType": "froude_number",
"impliedSpeedKnots": 0,
"maxAllowedSpeedKnots": 0,
"distanceNm": 0,
"gapHours": 0,
"detectedAt": "2026-04-18T03:22:00Z",
"violationReason": "string",
"confidence": 0,
"detectorVersion": "detect-dark-events@2026-04",
"dominantFamily": null,
"severity": "high",
"explanation": {
"detectorVersion": "detect-dark-events@2026-04",
"subtype": null,
"dominantFamily": null,
"firedFamilies": [],
"coordinatedSpoofConfidence": 0,
"kinematicAnomalyScore": 0,
"temporalIdentityScore": 0,
"signatures": [],
"scoreComponents": [],
"evidenceTimeline": []
}
},
"cargo": {
"vesselName": "string",
"imo": "9876543",
"draftArrival": 0,
"draftDeparture": 0,
"maxDraft": 0,
"estimatedTons": 0,
"method": "hydrostatics",
"confidenceLabel": "medium",
"variancePct": 0.035,
"densityCorrection": 1,
"vesselType": "unknown",
"uncertaintyLow": 0,
"uncertaintyHigh": 0,
"detectorVersion": "cargo-estimation@2026-04",
"explanation": {
"detectorVersion": "cargo-estimation@2026-04",
"method": "hydrostatics",
"methodLabel": "Physics-Informed Hydrostatics",
"confidenceLabel": "medium",
"uncertaintyLow": 0,
"uncertaintyHigh": 0,
"variancePct": 0.035,
"draftChangeMeters": 0,
"sourceHierarchy": [],
"evidenceTimeline": [],
"draftSurvey": null
}
},
"motif": {
"motifName": "string",
"vesselName": "IMO:9876543",
"confidence": 0,
"detectedAt": "2026-04-18T03:22:00Z",
"isSpurious": false,
"robustnessScore": 0,
"refutationRationale": null,
"detectorVersion": "temporal-watchlist@2026-04",
"compositeRiskScore": null,
"confidenceComposition": [],
"evidenceTimeline": [],
"explanation": {
"detectorVersion": "temporal-watchlist@2026-04",
"motifName": "string",
"confidence": 0,
"confidenceLabel": "medium",
"baseConfidence": 0,
"compositeRiskScore": null,
"compositeRiskLabel": null,
"compositeContributingMotifs": [],
"confidenceComposition": [],
"evidenceTimeline": [],
"pathEdgeIds": []
}
},
"tier": "pro"
}The response is built from application logic, not a raw table row, and uses camelCase keys throughout (not snake_case). spoofing.violationType is froude_number or ou_bounding_cone; spoofing.explanation.signatures entries carry family, label, fired, score, reason, observed, threshold. spoofing.explanation.scoreComponents, cargo.explanation.evidenceTimeline/motif.explanation.evidenceTimeline, and motif.explanation.confidenceComposition entries carry key/title, label/detail, score, detail/timestamp (timeline items use title, detail, timestamp; score-style items use key, label, score, detail). cargo.explanation.sourceHierarchy entries carry label, status, detail. cargo.explanation.draftSurvey (when not null) carries arrivalDensityCorrectionFactor, departureDensityCorrectionFactor, arrivalDeductiblesTons, departureDeductiblesTons, arrivalNetDisplacementTons, departureNetDisplacementTons.
Example
curl -X GET -H "X-API-Key: YOUR_KEY" \
https://www.axiomoverwatch.io/api/v1/signals/explainability?imo=9876543GET /api/v1/signals/feedback-calibration
admin-tier API key (or an internal/dashboard caller); any other tier gets 403.Aggregated view of all analyst-adjudicated signal feedback across signal types. Use this to build calibration dashboards, track confirmed true positives versus dismissed false positives, and tune detection thresholds.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
imo | string | Filter by vessel IMO | |
status | string | 'confirmed', 'dismissed', or 'needs_review' | |
signal_type | string | 'dark_event', 'spoofing_alert', or 'motif_alert' | |
cursor | number | Pagination offset (default 0) | |
limit | number | Results per page, 1–1000 (default 200) |
Response
{
"rows": [],
"summary": {
"totalRows": 312,
"confirmedCount": 198,
"dismissedCount": 87,
"needsReviewCount": 27,
"bySignalType": {
"dark_event": 145,
"spoofing_alert": 102,
"motif_alert": 65
},
"byAdjudicationStatus": {
"confirmed": 198,
"dismissed": 87,
"needs_review": 27
}
},
"tier": "admin",
"pagination": { "limit": 200, "has_more": true, "next_cursor": "200" }
}Each rows entry carries signalType, signalId, entityId, detectedAt, adjudicationStatus, adjudicationReasonCode, analystUpdatedAt, confidence, severity, detectorKind, dedupeKey, suppressionWindowHours, confirmed, dismissed, needsReview, metadata. pagination.next_cursor is a stringified offset (or null on the last page), not the cursor value itself.
Errors
403—Admin API key required.(caller's tier isn'tadminor an internal call)
Example
curl -X GET -H "X-API-Key: YOUR_KEY" \
https://www.axiomoverwatch.io/api/v1/signals/feedback-calibration?status=confirmed&signal_type=spoofing_alertGET /api/v1/vessels/{imo}/signal-feedback
admin-tier API key (or an internal/dashboard caller); any other tier gets 403.Returns the signal feedback panel for a specific vessel — all recent dark events, spoofing alerts, and motif alerts with their adjudication status. Includes suppression state for repeated signals.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
imo | string | ✓ | 7-digit IMO number (path param) |
Response
{
"feedback_items": [
{
"signalType": "spoofing_alert",
"signalId": "abc-123",
"title": "Spoofing Alert · Combined",
"summary": "Kinematic anomaly detected",
"detectedAt": "2026-04-15T08:30:00Z",
"severity": "high",
"detectorKey": "combined",
"adjudicationStatus": "confirmed",
"adjudicationReasonCode": "confirmed_external_intel",
"adjudicationReasonLabel": "External intelligence confirmed it",
"analystNotes": null,
"analystUpdatedAt": "2026-04-15T09:00:00Z",
"analystUpdatedBy": "api_key:abc123",
"suppressionWindowHours": 24,
"suppressionUntil": "2026-04-16T09:00:00Z",
"dedupeKey": "spoofing_alert:9876543:combined",
"suppressedRepeatCount": 3,
"lastRepeatAt": null
}
],
"summary": {
"totalSignals": 12,
"pendingSignals": 4,
"adjudicatedSignals": 8,
"calibration": {
"totalRows": 8,
"confirmedCount": 5,
"dismissedCount": 2,
"needsReviewCount": 1,
"bySignalType": { "dark_event": 3, "spoofing_alert": 4, "motif_alert": 1 },
"byAdjudicationStatus": { "confirmed": 5, "dismissed": 2, "needs_review": 1 }
}
},
"status_options": ["confirmed", "dismissed", "needs_review"],
"reason_options": [
{ "code": "confirmed_external_intel", "label": "External intelligence confirmed it", "allowedStatuses": ["confirmed"] },
{ "code": "multisource_corroboration", "label": "Corroborated by multiple signals", "allowedStatuses": ["confirmed"] },
{ "code": "expected_behavior", "label": "Expected vessel behavior", "allowedStatuses": ["dismissed"] },
{ "code": "sensor_noise", "label": "Sensor noise or AIS artifact", "allowedStatuses": ["dismissed"] },
{ "code": "insufficient_evidence", "label": "Insufficient evidence", "allowedStatuses": ["dismissed", "needs_review"] },
{ "code": "duplicate_unchanged_alert", "label": "Duplicate unchanged alert", "allowedStatuses": ["dismissed", "needs_review"] },
{ "code": "monitoring_only", "label": "Monitor only for now", "allowedStatuses": ["needs_review"] },
{ "code": "escalated_manual_review", "label": "Escalated for manual review", "allowedStatuses": ["needs_review"] },
{ "code": "other", "label": "Other", "allowedStatuses": ["confirmed", "dismissed", "needs_review"] }
],
"suppression_options_hours": [0, 12, 24, 72, 168],
"default_suppression_windows_hours": { "confirmed": 24, "dismissed": 72, "needs_review": 12 }
}reason_options is an array of objects (code, label, allowedStatuses), not bare strings — each reason code is only valid for the statuses listed in allowedStatuses. suppression_options_hours is [0, 12, 24, 72, 168]. summary.calibration and default_suppression_windows_hours are not optional — they're always present.
Errors
403—Admin API key required.(caller's tier isn'tadminor an internal call)400—Invalid IMO number — must be exactly 7 digits.
Example
curl -X GET -H "X-API-Key: YOUR_KEY" \
https://www.axiomoverwatch.io/api/v1/vessels/9876543/signal-feedbackPATCH /api/v1/vessels/{imo}/signal-feedback
admin-tier API key (or an internal/dashboard caller); any other tier gets 403.Submit an analyst adjudication for a signal on a vessel. You can confirm, dismiss, or flag a signal for review, and optionally suppress repeat alerts for a configurable window.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
imo | string | ✓ | 7-digit IMO number (path param) |
signal_type | string | ✓ | 'dark_event', 'spoofing_alert', or 'motif_alert' |
signal_id | string | ✓ | Signal identifier |
adjudication_status | string | ✓ | 'confirmed', 'dismissed', or 'needs_review' |
reason_code | string | Structured reason (e.g. 'sensor_noise', 'confirmed_external_intel'). Each code is only valid for certain statuses — see reason_options on the GET endpoint above for the full code → allowedStatuses mapping. | |
analyst_notes | string | Free-form notes | |
suppression_window_hours | number | Hours to suppress similar signals, clamped to 0–720. Defaults by status when omitted: confirmed 24h, dismissed 72h, needs_review 12h. |
Response
{
"signal_feedback": {
"signalType": "spoofing_alert",
"signalId": "abc-123",
"adjudicationStatus": "confirmed",
"adjudicationReasonCode": "confirmed_external_intel",
"analystNotes": null,
"suppressionWindowHours": 24,
"suppressionUntil": "2026-04-16T09:00:00Z",
"dedupeKey": "spoofing_alert:9876543:combined",
"updatedAt": "2026-04-15T09:00:00Z"
}
}The response nests everything under signal_feedback with camelCase keys — there is no top-level ok field.
Errors
403—Admin API key required.(caller's tier isn'tadminor an internal call)400—Invalid IMO number — must be exactly 7 digits.400—Invalid JSON body.400—signal_type, signal_id, and adjudication_status are required.400—reason_code is invalid.400—reason_code is not valid for the selected adjudication_status.400—Signal does not belong to the requested vessel.404— Signal not found (Supabase lookup error message, orSignal not found.)500— Database update error
Example
curl -X PATCH -H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
https://www.axiomoverwatch.io/api/v1/vessels/9876543/signal-feedback \
-d '{"signal_type":"spoofing_alert","signal_id":"abc-123","adjudication_status":"confirmed","reason_code":"confirmed_external_intel"}'Risk Intelligence
Fourteen-factor vessel risk scoring with dark fleet detection, STS transfers, loitering, identity changes, draft anomalies, and PSC inspection signals.
Investigation case files
List investigation case files for a vessel — motif-anchored dossiers with evidence, auto-generated narratives, and analyst annotations from Overwatch.