{"openapi":"3.1.0","info":{"title":"For-SMS API","version":"1.0.0","description":"White-label, multi-tenant aviation Safety Management System (ICAO Annex 19 / Doc 9859). Flagship capability: confidential / anonymous, non-punitive safety reporting. Authenticate with a per-consumer service key (fsm_<consumer>_…); tenant and RBAC role ride in the key scopes. Anonymous report submission and per-tenant safety bulletins are public."},"servers":[{"url":"/api/v1","description":"For-SMS v1 (relative to app root)"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"For-SMS per-consumer service key \"fsm_<consumer-slug>_<32-base32>\" (or the bootstrap SMS_API_KEY for full/global access). Tenant + RBAC role are carried in the key scopes (e.g. [\"tenant:gna\",\"role:safety_manager\"]). Global keys must name a tenant via ?t=."}}},"paths":{"/reports":{"post":{"operationId":"submitReport","summary":"Submit a safety report (public, anonymous-capable)","description":"Submit a confidential safety report. Public — no key required, so genuine anonymous reporting is possible. When confidentiality=\"anonymous\" the system never stores reporter identity (sanitized on intake). Modes: anonymous | confidential | identified. Returns the assigned reference number ({ICAO}-SR-{YEAR}-{SEQ}).","security":[],"parameters":[{"name":"t","in":"query","required":false,"schema":{"type":"string"},"description":"White-label tenant slug (e.g. \"gna\"). Required for public calls and for global/bootstrap keys; ignored when the key is already scoped to one tenant."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["narrative"],"properties":{"narrative":{"type":"string","description":"What happened (free text)."},"confidentiality":{"type":"string","enum":["anonymous","confidential","identified"],"default":"anonymous"},"category":{"type":"string","description":"Occurrence category / hazard taxonomy."},"location":{"type":"string"},"occurrence_date":{"type":"string","description":"ISO date of the occurrence."},"reporter_name":{"type":"string","description":"Only stored for confidential/identified."},"reporter_contact":{"type":"string"},"severity_initial":{"type":"string","enum":["A","B","C","D","E"]},"likelihood_initial":{"type":"string","enum":["1","2","3","4","5"]}}}}}},"responses":{"201":{"description":"Report accepted; returns ref_number and id."}}},"get":{"operationId":"listReports","summary":"List safety reports (reader+)","description":"List safety reports for the caller's tenant. Reporter identity is masked unless the caller holds safety_manager or accountable_executive; confidential reports show \"«withheld — confidential»\" to lower roles.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["new","under_review","linked","closed"]}}],"responses":{"200":{"description":"Masked report list."}}}},"/reports/{id}":{"get":{"operationId":"getReport","summary":"Get one safety report (reader+)","description":"Fetch a single safety report by id, with identity masking applied per the caller role.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The report (masked)."},"404":{"description":"Not found."}}},"patch":{"operationId":"updateReport","summary":"Update report status / link or escalate to a hazard (investigator+)","description":"Advance a report's workflow status (new → under_review → linked → closed), link it to an existing hazard (`hazard_id`), or escalate it into a NEW hazard seeded from the report (`new_hazard`) — the report transitions to \"linked\". Writes an immutable audit entry.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["new","under_review","linked","closed"]},"hazard_id":{"type":"integer","description":"Existing hazard to link this report to."},"new_hazard":{"description":"Escalate this report into a new hazard. `true` seeds the hazard from the report; an object overrides fields.","oneOf":[{"type":"boolean"},{"type":"object","properties":{"title":{"type":"string"},"taxonomy":{"type":"string"},"description":{"type":"string"}}}]}}}}}},"responses":{"200":{"description":"Updated."}}}},"/hazards":{"get":{"operationId":"listHazards","summary":"List the hazard register (reader+)","description":"List hazards for the caller's tenant (ICAO Component 2 — safety risk management).","responses":{"200":{"description":"Hazard register."}}},"post":{"operationId":"createHazard","summary":"Register a hazard (investigator+)","description":"Add a hazard to the register. Returns its reference number ({ICAO}-HAZ-{YEAR}-{SEQ}).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"taxonomy":{"type":"string","description":"ADREP-like hazard category."}}}}}},"responses":{"201":{"description":"Created; returns ref_number and id."}}}},"/hazards/{id}":{"get":{"operationId":"getHazard","summary":"Get a hazard with its risk assessments + mitigations (reader+)","description":"Fetch one hazard and its 5×5 risk assessments and corrective actions.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Hazard detail."},"404":{"description":"Not found."}}}},"/hazards/{id}/assess":{"post":{"operationId":"assessHazard","summary":"Record a 5×5 ICAO risk assessment (investigator+)","description":"Score a hazard on the ICAO Doc 9859 5×5 matrix. severity is A–E (A=catastrophic), likelihood is 1–5 (5=frequent). The server computes risk_index (e.g. \"5A\") and tolerability (intolerable | tolerable | acceptable). kind = initial | residual.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["severity","likelihood"],"properties":{"severity":{"type":"string","enum":["A","B","C","D","E"]},"likelihood":{"type":"string","enum":["1","2","3","4","5"]},"kind":{"type":"string","enum":["initial","residual"],"default":"initial"},"assessed_by":{"type":"string"},"notes":{"type":"string"}}}}}},"responses":{"201":{"description":"Assessment recorded with computed tolerability."}}}},"/hazards/{id}/mitigations":{"get":{"operationId":"listMitigations","summary":"List corrective actions for a hazard (reader+)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Mitigations / CAPA list."}}},"post":{"operationId":"addMitigation","summary":"Add a corrective action to a hazard (investigator+)","description":"Record a mitigation (corrective/preventive action) with optional owner and due date.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string"},"owner":{"type":"string"},"due_date":{"type":"string","description":"ISO date."}}}}}},"responses":{"201":{"description":"Mitigation added."}}}},"/spis":{"get":{"operationId":"listSpis","summary":"List Safety Performance Indicators (reader+)","description":"List SPIs for the caller's tenant with breach flags (current_value vs alert_level). ICAO Component 3 — safety assurance.","responses":{"200":{"description":"SPIs with breach count."}}},"post":{"operationId":"createSpi","summary":"Define or update a Safety Performance Indicator (safety_manager+)","description":"Create or update an SPI for the caller's tenant. Keyed on (tenant, name): re-posting the same name adjusts target/alert/period in place rather than duplicating, and resets the breach flag so the next evaluation re-notifies against the new threshold. current_value is optional — indicators that follow the evaluator's naming convention are kept fresh automatically. Writes an immutable audit entry.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Indicator name; unique per tenant."},"unit":{"type":"string"},"target":{"type":"number","description":"Desired performance target."},"alert_level":{"type":"number","description":"Value at or above which the SPI is breached."},"current_value":{"type":"number","description":"Optional manual value; omit to let the evaluator derive it."},"period":{"type":"string","description":"weekly | monthly | quarterly | yearly | rolling-<n>d."}}}}}},"responses":{"200":{"description":"Updated existing SPI."},"201":{"description":"Created new SPI."}}}},"/audit":{"get":{"operationId":"listAuditLog","summary":"Read the immutable audit trail (safety_manager+)","description":"Read the append-only audit trail for the caller's tenant, newest first. ICAO Component 3 — safety assurance / traceability: every state change is recorded and cannot be altered (DB-tier DENY UPDATE/DELETE). Optional entity/action filters and a limit (default 100, max 500).","parameters":[{"name":"entity","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by entity (e.g. report, hazard, spi, bulletin, tenant)."},{"name":"action","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by action (e.g. bulletin.publish, spi.create)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"Audit entries (newest first) with count."}}}},"/digest":{"get":{"operationId":"getSafetyDigest","summary":"Get the safety-performance digest (safety_manager+)","description":"Consolidated safety-promotion roll-up for the caller's tenant over a trailing window: new + open reports, open hazards, SPIs at or above alert level, and bulletins published. ICAO Component 4 — safety promotion. This is the pull side of the same digest the weekly safety-digest cron publishes as a bulletin.","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":366,"default":7},"description":"Trailing window in days (clamped 1–366)."}],"responses":{"200":{"description":"Safety-performance digest with a human-readable summary."}}}},"/bulletins":{"get":{"operationId":"listBulletins","summary":"List safety bulletins (public per tenant)","description":"Public safety communication for a tenant (ICAO Component 4). Pass ?t=<tenant>.","security":[],"parameters":[{"name":"t","in":"query","required":false,"schema":{"type":"string"},"description":"White-label tenant slug (e.g. \"gna\"). Required for public calls and for global/bootstrap keys; ignored when the key is already scoped to one tenant."}],"responses":{"200":{"description":"Published bulletins."}}},"post":{"operationId":"publishBulletin","summary":"Publish a safety bulletin (safety_manager+)","description":"Broadcast a safety bulletin to a tenant. Uses a confirmation guard: the first call returns a preview + confirmation_token; re-send the same body including that token to execute.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","body"],"properties":{"title":{"type":"string"},"body":{"type":"string"},"confirmation_token":{"type":"string","description":"HMAC token from the preview call. Omit on the first call."}}}}}},"responses":{"200":{"description":"Preview + confirmation_token (first call)."},"201":{"description":"Published (with token)."}}}},"/tenants":{"get":{"operationId":"listTenants","summary":"List white-label tenants (admin)","description":"List all provisioned tenants. Requires a global/admin key (bootstrap or `admin` scope).","responses":{"200":{"description":"Tenant list."},"403":{"description":"Admin key required."}}},"post":{"operationId":"createTenant","summary":"Provision a white-label tenant (admin)","description":"Onboard a new airline / AOC holder as a tenant, including brand colours and the just-culture policy shown on its report form. Confirmation guard: first call previews + returns a token, re-send the same body with the token to execute.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","description":"Tenant slug (lowercase)."},"name":{"type":"string"},"icao_code":{"type":"string"},"iata_code":{"type":"string"},"aad_tenant_id":{"type":"string"},"brand_primary":{"type":"string","description":"Hex colour."},"brand_secondary":{"type":"string"},"brand_accent":{"type":"string"},"brand_logo_url":{"type":"string"},"just_culture_policy":{"type":"string"},"confirmation_token":{"type":"string","description":"HMAC token from preview. Omit on first call."}}}}}},"responses":{"200":{"description":"Preview + confirmation_token (first call)."},"201":{"description":"Tenant provisioned (with token)."}}}},"/keys":{"post":{"operationId":"issueApiKey","summary":"Issue a per-tenant API key scoped to one role (admin)","description":"Mint a new per-consumer service key for the acting tenant, scoped to a single RBAC role (ICAO element 1.2 — safety accountability). Requires a global/admin key; a global key selects the tenant via ?t=. Confirmation guard: the first call previews + returns a confirmation_token, re-send the same body with the token to execute. The raw key (fsm_<consumer>_…) is returned exactly once and is not recoverable — only its SHA-256 digest is stored.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["reporter","reader","investigator","safety_manager","accountable_executive"]},"consumer_slug":{"type":"string","description":"Identifier for the consuming principal. Defaults to \"<tenant>-<role>\"."},"note":{"type":"string","description":"Free-text note stored with the key (never the key material)."},"confirmation_token":{"type":"string","description":"HMAC token from the preview. Omit on the first call."}}}}}},"responses":{"200":{"description":"Preview + confirmation_token (first call)."},"201":{"description":"Key issued; returns key_id, prefix, scopes and the raw api_key (shown once)."},"403":{"description":"Admin key required."}}}},"/risk":{"get":{"operationId":"assessRisk","summary":"Compute ICAO 5×5 risk tolerability (public reference)","description":"Pure ICAO Doc 9859 5×5 lookup — no tenant data. Given severity (A–E) and likelihood (1–5), returns the risk index (e.g. \"5A\") and tolerability (intolerable | tolerable | acceptable).","security":[],"parameters":[{"name":"severity","in":"query","required":true,"schema":{"type":"string","enum":["A","B","C","D","E"]}},{"name":"likelihood","in":"query","required":true,"schema":{"type":"string","enum":["1","2","3","4","5"]}}],"responses":{"200":{"description":"Risk index + tolerability."}}}},"/icao":{"get":{"operationId":"icaoModel","summary":"ICAO SMS reference model (public reference)","description":"Returns the ICAO Annex 19 reference data this system encodes: severity scale (A–E), likelihood scale (1–5), the 5×5 tolerability matrix, the four SMS components, and the hazard taxonomy. No tenant data.","security":[],"responses":{"200":{"description":"ICAO reference model."}}}}}}