{
  "openapi": "3.1.0",
  "info": {
    "title": "Eyesea Marine Evidence API",
    "version": "1.2.1",
    "description": "Read-only access to Eyesea's evidence ledger: satellite screening cases carried from detection through context, corroboration and review to an explicit assessment, and Eyesea's in-situ field reports as a separately licensed resource. Every case exposes the competing explanations for the observed feature, the evidence for and against each, what is missing, a categorical confidence with its basis, a bounded conclusion with limitations, and a reconstructable history. Access is tiered by key scope; attribution material is withheld below attribution:read by construction.",
    "contact": {
      "name": "Eyesea",
      "url": "https://api.eyesea.org/contact"
    }
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Per-partner key, eysk_live_… . Case scopes: cases:read < ledger:read < attribution:read. Report scopes, licensed separately: reports:read < reports:annotations."
      }
    },
    "parameters": {
      "since": {
        "name": "since",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "ISO 8601; rows at or after this instant"
      },
      "limitCases": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        },
        "description": "Page size. Maximum 200, or 100 when expand is set."
      },
      "limitChanges": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 1000,
          "default": 500
        },
        "description": "Page size. Maximum 1000."
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "opaque next_cursor from the previous page"
      },
      "expand": {
        "name": "expand",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "comma list of evidence,decisions,reviews,assessment (needs ledger:read; caps limit at 100). assessment = the full assessment per case instead of the headline"
      },
      "bbox": {
        "name": "bbox",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "lon0,lat0,lon1,lat1 in EPSG:4326; order-insensitive"
      },
      "case_id": {
        "name": "case_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "report_id": {
        "name": "report_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "limitReports": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        },
        "description": "Page size. Maximum 200, or 100 with reports:annotations."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "required_scope": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Scores": {
        "type": "object",
        "description": "s1 cases carry detector_score, decision_source, p_oil_corpus, champion_* and challenger_*; s2 cases carry material_score",
        "properties": {
          "material_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "s2 only: Stage 2 adjudicator output for the detected class; ranks, is not a probability"
          },
          "detector_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "The score of the Stage 2 model named in decision_source. It ranks candidates and is not a probability."
          },
          "decision_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "challenger",
              "champion",
              "champion_large",
              null
            ],
            "description": "s1 only. The Stage 2 model whose score decided: challenger is the fine tuned model, champion the frozen model, champion_large a candidate of 10 km² or more that the frozen model scored higher. Dates at /v1/vocabulary under decision_sources."
          },
          "champion_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "s1 only. The frozen Stage 2 model's score for the same candidate."
          },
          "champion_spread": {
            "type": [
              "number",
              "null"
            ]
          },
          "p_oil_corpus": {
            "type": [
              "number",
              "null"
            ],
            "description": "Isotonic calibration of detector_score for the deciding model, fitted on scenes held out of training. Valid in corpus and before adjudication."
          },
          "challenger_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "s1 only. The fine tuned Stage 2 model's score for the same candidate."
          },
          "challenger_spread": {
            "type": [
              "number",
              "null"
            ]
          },
          "ensemble_spread": {
            "type": [
              "number",
              "null"
            ]
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Ref": {
        "type": "object",
        "description": "what an assessment entry points at; every claim is traceable to a ledger row or to the detection",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "detection",
              "evidence",
              "decision",
              "review",
              "verdict"
            ]
          },
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "null at cases:read (the snapshot carries no row ids) and for the verdict"
          },
          "type": {
            "type": "string",
            "description": "evidence type, for evidence refs"
          }
        }
      },
      "Relevance": {
        "type": "object",
        "description": "what one input means for one hypothesis",
        "properties": {
          "ref": {
            "$ref": "#/components/schemas/Ref"
          },
          "hypothesis": {
            "type": "string"
          },
          "effect": {
            "type": "string",
            "enum": [
              "supports",
              "contradicts",
              "neutral"
            ]
          },
          "reason": {
            "type": "string",
            "description": "cites the fields and thresholds used"
          },
          "source_class": {
            "type": "string",
            "enum": [
              "detector",
              "observation",
              "interpretation",
              "adjudication"
            ],
            "description": "Detector outputs are retained for traceability but do not contribute to hypothesis scoring."
          }
        }
      },
      "EvidenceRelevance": {
        "type": "object",
        "properties": {
          "hypothesis": {
            "type": "string"
          },
          "effect": {
            "type": "string",
            "enum": [
              "supports",
              "contradicts",
              "neutral"
            ]
          },
          "reason": {
            "type": "string"
          },
          "source_class": {
            "type": "string",
            "enum": [
              "interpretation",
              "adjudication"
            ],
            "description": "Present when the row is not an observation: MEMORY rows are interpretation, GROUND_TRUTH rows adjudication."
          }
        }
      },
      "Hypothesis": {
        "type": "object",
        "description": "a nature-level explanation of the observed feature with its score (support − contradiction, detector excluded)",
        "properties": {
          "hypothesis": {
            "type": "string",
            "enum": [
              "OIL_FILM",
              "BIOGENIC_FILM",
              "METEOROLOGICAL",
              "OCEANOGRAPHIC",
              "SHIP_WAKE",
              "ARTIFACT",
              "FLOATING_MATERIAL",
              "BENTHIC_OR_INTERTIDAL",
              "CLOUD_OR_HAZE",
              "FOAM_OR_WHITECAP",
              "FIXED_FEATURE",
              "VESSEL_OR_WAKE"
            ]
          },
          "label": {
            "type": "string"
          },
          "detector_origin": {
            "type": "boolean",
            "description": "true for the family the detector itself claims (OIL_FILM, FLOATING_MATERIAL)"
          },
          "score": {
            "type": "integer"
          },
          "support": {
            "type": "integer"
          },
          "contradiction": {
            "type": "integer"
          },
          "independent_observations": {
            "type": "integer"
          },
          "review_rank": {
            "type": [
              "integer",
              "null"
            ],
            "description": "where the latest independent review ranked this family, if it did"
          },
          "rule_flags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "reason codes of the latest rule decision that speak for this family"
          }
        }
      },
      "MissingEvidence": {
        "type": "object",
        "properties": {
          "evidence": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "absent",
              "pending",
              "failed",
              "withheld"
            ]
          },
          "reason": {
            "type": "string"
          },
          "would_discriminate": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Contradiction": {
        "type": "object",
        "description": "every material contradiction stays visible",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "evidence",
              "track_disagreement",
              "reviewer_disagreement",
              "data_conflict"
            ]
          },
          "detail": {
            "type": "string"
          },
          "against": {
            "type": "string"
          },
          "ref": {
            "$ref": "#/components/schemas/Ref"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "attribution:read: the reviewer's own conflict statements"
          }
        }
      },
      "HistoryEntry": {
        "type": "object",
        "description": "one dated event; status-moving entries carry from_status / to_status / changed so every transition is reconstructable",
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "kind": {
            "type": "string",
            "enum": [
              "detection",
              "enrichment",
              "evidence",
              "decision",
              "review",
              "verdict"
            ]
          },
          "from_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "to_status": {
            "type": "string"
          },
          "changed": {
            "type": "boolean"
          },
          "ref": {
            "$ref": "#/components/schemas/Ref"
          },
          "summary": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "quality": {
            "type": "string"
          },
          "effects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rule_version": {
            "type": "string"
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reason_meanings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "band": {
            "type": "string",
            "description": "attribution:read: the band of a triage decision"
          },
          "triage_signals": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "the triage signals that hold, filtered by scope"
          },
          "role": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "top_hypothesis": {
            "type": [
              "string",
              "null"
            ]
          },
          "optical_reading": {
            "type": [
              "string",
              "null"
            ]
          },
          "concurs": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "Assessment": {
        "type": "object",
        "description": "Derived, versioned, deterministic. cases:read receives the headline (assessment_version, assessed_at, current_status, confidence, primary_hypothesis {hypothesis, label, support, contradiction}, conclusion); ledger:read receives everything; attribution:read adds `attribution`.",
        "properties": {
          "assessment_version": {
            "type": "string"
          },
          "assessed_at": {
            "type": "string",
            "format": "date-time"
          },
          "current_status": {
            "type": "string"
          },
          "confidence": {
            "type": "string",
            "enum": [
              "none",
              "low",
              "moderate",
              "high",
              "confirmed"
            ],
            "description": "categorical and rule-derived, never a probability; see /v1/vocabulary"
          },
          "confidence_basis": {
            "type": "string"
          },
          "contested": {
            "type": "boolean",
            "description": "a tie between hypotheses, or the tracks (rules, review, second reading) disagree"
          },
          "based_on": {
            "type": "object",
            "properties": {
              "evidence": {
                "type": "integer"
              },
              "decisions": {
                "type": "integer"
              },
              "reviews": {
                "type": "integer"
              },
              "latest_input_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "primary_hypothesis": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Hypothesis"
              },
              {
                "type": "null"
              }
            ],
            "description": "null when not assessable"
          },
          "alternative_hypotheses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Hypothesis"
            },
            "description": "every family the evidence or a review touched, best first"
          },
          "untested_hypotheses": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "families nothing on file bears on"
          },
          "supporting_evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relevance"
            }
          },
          "contradicting_evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relevance"
            }
          },
          "neutral_evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relevance"
            }
          },
          "missing_evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MissingEvidence"
            }
          },
          "contradictions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contradiction"
            }
          },
          "conclusion": {
            "type": "string",
            "description": "bounded: names the primary hypothesis, what supports and contradicts it, what is not excluded, the case status, and the disclaimer"
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HistoryEntry"
            }
          },
          "attribution": {
            "type": "object",
            "description": "attribution:read only: vessel_source {support, contradiction, evidence[]}, rule_flags, source_hypotheses (mechanism-level, ranked, with evidence_for / evidence_against), second_reading, reviewer_conflicts, reviewer_missing, triage {band, rule_version, decision_id, decided_at, signals}"
          }
        }
      },
      "Case": {
        "type": "object",
        "description": "With cases:read: identifiers, timestamps, status, position, scores, the newest context per evidence type, rule_flags, verdict, preview and the assessment headline. With ledger:read: gate_reason, status_reason, geometry, detector_features, evidence_counts, decision_count, review_count, the full assessment (on the single-case route, or with expand=assessment) and any expanded arrays. With attribution:read: triage, the latest triage decision of a radar case. stack s1 is a Sentinel-1 radar oil case; s2 is a Sentinel-2 optical material case with material_class set and material_score in scores.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "detection_id": {
            "type": "string",
            "format": "uuid"
          },
          "scene_id": {
            "type": "string"
          },
          "stack": {
            "type": "string",
            "enum": [
              "s1",
              "s2"
            ]
          },
          "material_class": {
            "type": [
              "string",
              "null"
            ],
            "description": "s2 only: macroalgae, floating_debris, organic_material"
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          },
          "ingested_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "NEW",
              "ENRICHING",
              "UNRESOLVED",
              "UNRESOLVED_DATA_GAP",
              "LIKELY_LOOKALIKE",
              "POSSIBLE_OIL",
              "PROBABLE_OIL",
              "INVALID_DETECTION",
              "CONFIRMED_OIL",
              "CONFIRMED_NON_OIL",
              "CONFIRMED_MATERIAL",
              "CONFIRMED_NON_MATERIAL"
            ]
          },
          "area_m2": {
            "type": [
              "number",
              "null"
            ]
          },
          "lon": {
            "type": "number",
            "description": "Decimal degrees; 3 decimals with cases:read, 5 with ledger:read."
          },
          "lat": {
            "type": "number"
          },
          "scores": {
            "$ref": "#/components/schemas/Scores"
          },
          "wind": {
            "type": [
              "object",
              "null"
            ]
          },
          "waves": {
            "type": [
              "object",
              "null"
            ]
          },
          "context": {
            "type": [
              "object",
              "null"
            ]
          },
          "recurrence": {
            "type": [
              "object",
              "null"
            ]
          },
          "cross_detection": {
            "type": [
              "object",
              "null"
            ]
          },
          "optical_coverage": {
            "type": [
              "object",
              "null"
            ]
          },
          "verdict": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "status": {
                "type": "string"
              },
              "at": {
                "type": "string",
                "format": "date-time"
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Reviewer free text. Served at attribution:read; at ledger:read only when it names no vessel or tracking material; null at cases:read."
              }
            }
          },
          "rule_flags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Reason codes that fired in the latest rule decision, filtered by scope. Meanings are listed in /v1/vocabulary under reason_codes."
          },
          "triage": {
            "type": [
              "object",
              "null"
            ],
            "description": "attribution:read. The latest triage decision of a radar case: band, rule_version, decision_id and decided_at. Band meanings are listed in /v1/vocabulary under triage.",
            "properties": {
              "band": {
                "type": "string",
                "enum": [
                  "INVESTIGATE",
                  "WATCH",
                  "LOOKALIKE_SIGNALS"
                ]
              },
              "rule_version": {
                "type": "string"
              },
              "decision_id": {
                "type": "string",
                "format": "uuid"
              },
              "decided_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "assessment": {
            "$ref": "#/components/schemas/Assessment"
          },
          "preview": {
            "type": "object",
            "properties": {
              "available": {
                "type": "boolean"
              }
            }
          },
          "disclaimer": {
            "type": "string"
          },
          "gate_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "operator text; attribution segments removed below attribution:read"
          },
          "geometry": {
            "type": [
              "object",
              "null"
            ]
          },
          "detector_features": {
            "type": [
              "object",
              "null"
            ]
          },
          "evidence_counts": {
            "type": "object"
          },
          "decision_count": {
            "type": "integer"
          },
          "review_count": {
            "type": "integer"
          },
          "evidence": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Evidence"
            }
          },
          "decisions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Decision"
            }
          },
          "reviews": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Review"
            }
          }
        }
      },
      "Evidence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "description": "WIND, WAVES, STATIC_CONTEXT, RECURRENCE, CROSS_DETECTION, OPTICAL_COVERAGE, FIELD_REPORT, MEMORY, GROUND_TRUTH; AIS, AIS_EVENTS and AIS_TRACK at attribution:read"
          },
          "source": {
            "type": "string"
          },
          "source_uri": {
            "type": [
              "string",
              "null"
            ]
          },
          "quality": {
            "type": "string",
            "description": "Quality grade A to D, assigned per source. C marks a provisional row that is re-checked; D marks a source that was queried and failed. Only rows graded A or B can support or contradict a hypothesis; C and D rows are recorded as neutral."
          },
          "observed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "retrieved_at": {
            "type": "string",
            "format": "date-time"
          },
          "facts": {
            "type": "object"
          },
          "relevance": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EvidenceRelevance"
            },
            "description": "what this row means for each hypothesis it bears on"
          }
        }
      },
      "Decision": {
        "type": "object",
        "description": "one rule-set evaluation or human verdict, as a transition",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "rule_version": {
            "type": "string",
            "description": "lookalike-rules-0.x and triage-0.x (s1), material-rules-0.x (s2), human-verdict-1"
          },
          "reasons": {
            "type": [
              "object",
              "null"
            ],
            "description": "Flag to boolean; the flags that fired are listed in reason_codes. Vessel flags at attribution:read. A triage decision carries the lookalike flags it read and a triage object of signals; its band and the vessel derived signals are served at attribution:read. Human verdicts: {human_verdict: true, note}."
          },
          "inputs": {
            "type": [
              "object",
              "null"
            ],
            "description": "the exact facts the rule set read: static_context, recurrence, wind, detector_features, area, score, axis_azimuth_deg, wind_alignment_deg; human verdicts: {prior_status, detector_score}"
          },
          "from_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "changed": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reason_meanings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Review": {
        "type": "object",
        "description": "An independent review of the case. Reviews do not change case status.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "type": "string",
            "enum": [
              "primary",
              "optical_second"
            ]
          },
          "model": {
            "type": "string"
          },
          "prompt_version": {
            "type": "string"
          },
          "concurs": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "optical_second only. Derived by comparison rather than reported by the reviewer: true when the top-ranked mechanism equals that of the primary review."
          },
          "optical_reading": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "SLICK_LIKE_VISIBLE",
              "BLOOM_OR_SCUM_VISIBLE",
              "VESSEL_VISIBLE",
              "CLOUD_OR_GLINT_OBSCURED",
              "NOTHING_DISTINCT",
              "IMAGE_UNUSABLE",
              null
            ]
          },
          "primary_review_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "hypothesis_families": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rank": {
                  "type": "integer"
                },
                "family": {
                  "type": "string"
                }
              }
            },
            "description": "the ranking as nature-level families (ledger:read)"
          },
          "conflict_count": {
            "type": "integer"
          },
          "missing_count": {
            "type": "integer"
          },
          "hypotheses": {
            "type": "array",
            "description": "attribution:read: ranked mechanisms with evidence_for[] and evidence_against[]"
          },
          "image_notes": {
            "type": "string"
          },
          "optical_notes": {
            "type": "string"
          },
          "missing": {
            "type": "array",
            "description": "attribution:read: discriminators the reviewer asked for"
          },
          "conflicts": {
            "type": "array",
            "description": "attribution:read: contradictions the reviewer flagged between image and data"
          },
          "comparison": {
            "type": "object"
          }
        }
      },
      "Change": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "case",
              "evidence",
              "decision",
              "review",
              "report"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "null for report entries"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "detail": {
            "type": "object",
            "description": "case: {status, stack}; evidence: {type, quality, source}; decision: {status, rule_version, from_status, changed, reason_codes}, with band on a triage decision at attribution:read; review: {role, model, prompt_version, top_hypothesis (family at ledger:read, mechanism at attribution:read), concurs}; report: {status, scene, country}"
          }
        }
      },
      "Report": {
        "type": "object",
        "description": "One in-situ report. reports:read fields always; reports:annotations adds annotations and analysis and serves the position at 5 decimals.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reported_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "reported",
              "recovered"
            ]
          },
          "recovered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "recovery_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "app",
              "legacy_flag",
              null
            ],
            "description": "legacy_flag: the previous platform marked the report recovered without a date"
          },
          "source": {
            "type": "string",
            "enum": [
              "app",
              "legacy",
              "analog"
            ]
          },
          "lon": {
            "type": "number",
            "description": "3 decimals with reports:read, 5 with reports:annotations"
          },
          "lat": {
            "type": "number"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "protected_area": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "gfw_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "event": {
            "type": [
              "object",
              "null"
            ],
            "description": "the clean-up event the report belongs to",
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "start_time": {
                "type": "string"
              }
            }
          },
          "scene": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "beach",
              "nature",
              "urban",
              "at_sea",
              null
            ],
            "description": "scene class from the image model"
          },
          "impact": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "low",
              "medium",
              "high",
              null
            ]
          },
          "severity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 to 5"
          },
          "primary_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "the reporter's or model's main class; see /v1/vocabulary item_classes"
          },
          "items": {
            "type": [
              "object",
              "null"
            ],
            "description": "item class → count, from the image model"
          },
          "categories": {
            "type": [
              "object",
              "null"
            ],
            "description": "coarse category → count"
          },
          "materials": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "materials the reporter selected"
          },
          "weight_kg": {
            "type": [
              "number",
              "null"
            ],
            "description": "reported weight; null when only the placeholder value was recorded"
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "primary": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                }
              }
            }
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "disclaimer": {
            "type": "string"
          },
          "annotations": {
            "type": "array",
            "description": "reports:annotations: per-image object detections {class, bbox [x, y, w, h] normalised, confidence} with source and model_version",
            "items": {
              "type": "object"
            }
          },
          "analysis": {
            "type": [
              "object",
              "null"
            ],
            "description": "reports:annotations: the model's scene labels, detected scene, pollution type counts, detected objects, people count, confidence and description"
          }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "generated_at": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    },
    "responses": {
      "400": {
        "description": "malformed parameter or cursor (named in the error)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "401": {
        "description": "missing or unknown key"
      },
      "403": {
        "description": "key revoked or expired, or scope missing (required_scope is named)"
      },
      "404": {
        "description": "unknown endpoint or id"
      },
      "429": {
        "description": "rate limit (per minute, by plan) or the monthly quota; the quota case carries {quota: {limit, used, remaining, resets_at}, plan}. Every authenticated answer carries X-Quota-Limit, X-Quota-Used, X-Quota-Remaining and X-Quota-Reset headers. Every 429 carries Retry-After in seconds."
      }
    }
  },
  "paths": {
    "/v1/me": {
      "get": {
        "summary": "The calling key: partner, plan, effective scopes, rate limit and monthly quota (any active key)",
        "responses": {
          "200": {
            "description": "partner, key {id, label, expires_at}, plan {id, name}, scopes[], tiers {cases, reports}, rate_per_min, quota {limit, used, remaining, resets_at}"
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Liveness, ledger counts and freshness (any active key)",
        "responses": {
          "200": {
            "description": "ok, version, assessment_version, preview_signing, cases_total, cases_updated_24h, evidence_total, decisions_total, reviews_total, last_case_update, ledger_watermark, detections_replicated_to, detections_total, ledger_lag_s, replication_lag_s, last_case_update_age_s, reports_total, reports_recovered, reports_updated_24h, reports_replicated_to"
          }
        }
      }
    },
    "/v1/openapi.json": {
      "get": {
        "summary": "This document (public, no key)",
        "security": []
      }
    },
    "/v1/vocabulary": {
      "get": {
        "summary": "Hypotheses, review mechanisms, reason codes, triage bands and signals, decision sources, rule versions, review roles, confidence levels, effects, statuses, evidence types, history kinds (public, no key)",
        "security": [],
        "responses": {
          "200": {
            "description": "the vocabulary the assessment uses"
          }
        }
      }
    },
    "/v1/cases": {
      "get": {
        "summary": "Cases, newest-updated first, keyset-paged; each carries the assessment headline (full with expand=assessment)",
        "parameters": [
          {
            "$ref": "#/components/parameters/since"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "one status or a comma list"
          },
          {
            "name": "stack",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "s1",
                "s2"
              ]
            },
            "description": "s1 radar oil cases, s2 optical material cases; both when omitted"
          },
          {
            "name": "band",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "attribution:read. One triage band or a comma list of INVESTIGATE, WATCH and LOOKALIKE_SIGNALS; radar cases whose latest triage decision holds that band."
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "$ref": "#/components/parameters/limitCases"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/expand"
          }
        ],
        "responses": {
          "200": {
            "description": "Page + cases[]",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "cases": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Case"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{case_id}": {
      "get": {
        "summary": "One case; the full assessment at ledger:read and above, the headline at cases:read",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          },
          {
            "$ref": "#/components/parameters/expand"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/v1/cases/{case_id}/assessment": {
      "get": {
        "summary": "The assessment alone (ledger:read; attribution block at attribution:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case_id": {
                      "type": "string"
                    },
                    "stack": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "updated_at": {
                      "type": "string"
                    },
                    "assessment": {
                      "$ref": "#/components/schemas/Assessment"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{case_id}/preview": {
      "get": {
        "summary": "Preview PNG (302 to a signed URL valid 1 h; ?redirect=false returns {url, expires_at})",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          }
        ],
        "responses": {
          "302": {
            "description": "redirect to the file"
          },
          "404": {
            "description": "no preview"
          },
          "503": {
            "description": "signing not configured"
          }
        }
      }
    },
    "/v1/cases/{case_id}/evidence": {
      "get": {
        "summary": "Full evidence chain with provenance and per-row relevance (ledger:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case_id": {
                      "type": "string"
                    },
                    "stack": {
                      "type": "string"
                    },
                    "evidence": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Evidence"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{case_id}/decisions": {
      "get": {
        "summary": "Decision trail as transitions, oldest first (ledger:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case_id": {
                      "type": "string"
                    },
                    "stack": {
                      "type": "string"
                    },
                    "decisions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Decision"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{case_id}/reviews": {
      "get": {
        "summary": "Independent reviews (ledger:read: families and counts; attribution:read: mechanisms and prose)",
        "parameters": [
          {
            "$ref": "#/components/parameters/case_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "case_id": {
                      "type": "string"
                    },
                    "reviews": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Review"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/changes": {
      "get": {
        "summary": "Change feed, oldest first: cases, evidence, decisions and reviews with ledger:read; report entries with reports:read.",
        "parameters": [
          {
            "$ref": "#/components/parameters/since"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/limitChanges"
          },
          {
            "name": "kinds",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "comma list of case,evidence,decision,review,report; without it the feed carries every kind the key is entitled to"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "changes": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Change"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/reports": {
      "get": {
        "summary": "In-situ reports, newest-updated first, keyset-paged (reports:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/since"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "reported",
                "recovered"
              ]
            }
          },
          {
            "name": "scene",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "comma list of beach,nature,urban,at_sea"
          },
          {
            "$ref": "#/components/parameters/bbox"
          },
          {
            "$ref": "#/components/parameters/limitReports"
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Page + reports[]",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "reports": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Report"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/reports/{report_id}": {
      "get": {
        "summary": "One report (reports:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/report_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/404"
          }
        }
      }
    },
    "/v1/labels": {
      "get": {
        "summary": "Detection-scoped verified labels with provenance (labels:read)",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "channel",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "ground_truth_link",
                "app_confirmation",
                "community_review"
              ]
            }
          },
          {
            "name": "training_eligible",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 1000
            }
          }
        ],
        "description": "Every label carries channel, quality_tier (1 physical, 2 expert, 3 community agreement, 4 accepted pre-annotation) and training_eligible. Machine output alone never appears here."
      }
    },
    "/v1/image-labels": {
      "get": {
        "summary": "Report-image annotations with provenance (labels:read)",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "schema": {
              "type": "string",
              "description": "device_yolo, gemini, ..."
            }
          },
          {
            "name": "reviewed",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "training_eligible",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 1000
            }
          }
        ],
        "description": "Bounding boxes per report image with source model and review state; unreviewed rows are predictions and say so. Fetch the image via /v1/images/{image_id}."
      }
    },
    "/v1/images/{id}": {
      "get": {
        "summary": "Report image (302 to the file; labels:read)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "redirect",
            "in": "query",
            "schema": {
              "type": "boolean",
              "description": "false returns the URL as JSON instead of 302"
            }
          }
        ]
      }
    }
  },
  "externalDocs": {
    "description": "Partner reference",
    "url": "https://eyesea.org/api"
  }
}
