Axiomancer
API reference — Overview

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

Requires API key. Any authenticated tier can call this — despite the name, the handler does not gate on analyst/admin access (unlike the other three endpoints on this page).

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

NameTypeRequiredDescription
imostring7-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=9876543

GET /api/v1/signals/feedback-calibration

Requires API key with analyst access — in practice this means an 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

NameTypeRequiredDescription
imostringFilter by vessel IMO
statusstring'confirmed', 'dismissed', or 'needs_review'
signal_typestring'dark_event', 'spoofing_alert', or 'motif_alert'
cursornumberPagination offset (default 0)
limitnumberResults 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

  • 403Admin API key required. (caller's tier isn't admin or 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_alert

GET /api/v1/vessels/{imo}/signal-feedback

Requires API key with analyst access — in practice this means an 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

NameTypeRequiredDescription
imostring7-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

  • 403Admin API key required. (caller's tier isn't admin or an internal call)
  • 400Invalid 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-feedback

PATCH /api/v1/vessels/{imo}/signal-feedback

Requires API key with analyst access — in practice this means an 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

NameTypeRequiredDescription
imostring7-digit IMO number (path param)
signal_typestring'dark_event', 'spoofing_alert', or 'motif_alert'
signal_idstringSignal identifier
adjudication_statusstring'confirmed', 'dismissed', or 'needs_review'
reason_codestringStructured 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 codeallowedStatuses mapping.
analyst_notesstringFree-form notes
suppression_window_hoursnumberHours 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

  • 403Admin API key required. (caller's tier isn't admin or an internal call)
  • 400Invalid IMO number — must be exactly 7 digits.
  • 400Invalid JSON body.
  • 400signal_type, signal_id, and adjudication_status are required.
  • 400reason_code is invalid.
  • 400reason_code is not valid for the selected adjudication_status.
  • 400Signal does not belong to the requested vessel.
  • 404 — Signal not found (Supabase lookup error message, or Signal 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"}'
Was this page helpful?

On this page