wiis/wiːs/Write It, I'll Share

EDINET Daily Filings Monitor — Spec & Starter Code

by psind707

A Mac Mini–friendly puller for Japan FSA EDINET disclosures. Default focus: activist 5%-rule filings, tender offers, and material events — the highest-signal flow for active equity trading.

This page is the brief you can hand to Claude Code. Two viable paths:


Why Path A wins

The FSA's official EDINET API requires SMS verification at signup, and their SMS gateway has been throwing "Messages are currently unavailable" for foreign phone numbers — server-side outage, no user-side fix.

EDINET DB is a third-party wrapper (independent project, not FSA-affiliated) that:

This skips the entire XBRL parsing roadmap that was item 4.1 on the original extension queue. They've already done it.


Path A: EDINET DB wrapper

Endpoints that matter

Base URL: https://edinetdb.jp/v1 (note .jp not .com for the API) Auth: X-API-Key: {your-key} header

Endpoint Use case
/events?since=YYYY-MM-DD&until=YYYY-MM-DD&event_type=... Daily monitor — unified EDINET + TDnet feed
/shareholders/activists?category=activist Snapshot: ~918 current activist positions across ~417 filers
/shareholders/history?filer=Oasis Track one activist's full position history with Δratio per filing
/shareholders/history?issuer=... All 5%+ filings on one company over time
/companies/{edinet_code}/shareholders Latest 5%+ holders for a company
/shareholders/search?q=BlackRock Reverse lookup — what does this filer hold?

Event types in /events: large_holding_report, activist_filing, tender_offer, mbo, merger, buyback, earnings_summary, earnings_revision — 25 total. Coverage: 2025-04-28 onward.

Coverage caveats: Large-holding reports go back to March 2021 (EDINET API v2 limit). ~71,000 filings / ~4,330 companies indexed.

Python script (Path A)

#!/usr/bin/env python3
"""
EDINET DB — Daily Activist & Material-Event Monitor
====================================================
Uses the EDINET DB wrapper (https://edinetdb.com), which proxies the FSA
official API and adds: filer normalization, purpose-category tagging,
estimated holding values, and a unified events feed (EDINET + TDnet).

Setup
-----
1. Get a free API key: https://edinetdb.com/developers   (email-only)
2. export EDINETDB_API_KEY='your-key'
3. python edinet_db_monitor.py --help

Common usage
------------
  # Yesterday's high-severity events (activist filings, TOB, MBO, buybacks)
  python edinet_db_monitor.py --days 1

  # Last 7 days, large-holding reports only
  python edinet_db_monitor.py --days 7 --event-types large_holding_report

  # Watchlist (4-digit JP tickers)
  python edinet_db_monitor.py --days 7 --watchlist 6857,6920,7203

  # Snapshot: all current activist positions
  python edinet_db_monitor.py --activists

  # Specific filer's history (full activist track record)
  python edinet_db_monitor.py --filer "Oasis Management"

  # JSON for downstream
  python edinet_db_monitor.py --days 1 --json

Standard library + requests only. Python 3.10+.
"""

from __future__ import annotations

import argparse
import json
import os
import sys
from dataclasses import dataclass, asdict, field
from datetime import date, timedelta
from typing import Any

import requests

API_BASE = "https://edinetdb.jp/v1"
TIMEOUT = 30

# Event types most relevant for active trading (subset of EDINET DB's 25)
HIGH_SIGNAL_EVENTS = [
    "large_holding_report",   # 5%-rule filings (initial + change)
    "activist_filing",        # purpose = engagement / important suggestion
    "tender_offer",
    "mbo",
    "merger",
    "buyback",
    "earnings_revision",      # upward/downward guidance revisions
]


# -------------------------- API client --------------------------

class EdinetDBClient:
    def __init__(self, api_key: str | None):
        self.api_key = api_key
        self.session = requests.Session()
        if api_key:
            self.session.headers.update({"X-API-Key": api_key})

    def _get(self, path: str, params: dict | None = None) -> dict:
        url = f"{API_BASE}{path}"
        resp = self.session.get(url, params=params or {}, timeout=TIMEOUT)
        if resp.status_code == 401:
            sys.exit("ERROR: missing/invalid EDINETDB_API_KEY (X-API-Key header).")
        if resp.status_code == 429:
            sys.exit("ERROR: daily rate limit hit. Free tier = 100/day. Wait or upgrade.")
        resp.raise_for_status()
        return resp.json()

    @staticmethod
    def _unwrap(payload: Any) -> list[dict]:
        """EDINET DB wraps lists under varying keys: data / events / results / etc."""
        if isinstance(payload, list):
            return payload
        if isinstance(payload, dict):
            for key in ("data", "events", "results", "activists", "history",
                        "shareholders", "holders", "items"):
                v = payload.get(key)
                if isinstance(v, list):
                    return v
        return []

    def events(
        self,
        since: date,
        until: date,
        event_types: list[str] | None = None,
        sec_code: str | None = None,
        severity: list[str] | None = None,
        limit: int = 1000,
    ) -> list[dict]:
        params: dict[str, Any] = {
            "since": since.isoformat(),
            "until": until.isoformat(),
            "limit": min(limit, 1000),
        }
        if event_types:
            params["event_type"] = ",".join(event_types)
        if sec_code:
            params["sec_code"] = sec_code
        if severity:
            params["severity"] = ",".join(severity)
        return self._unwrap(self._get("/events", params))

    def activists(self, category: str = "activist", limit: int = 500) -> list[dict]:
        return self._unwrap(self._get("/shareholders/activists",
                                      {"category": category, "limit": limit}))

    def shareholder_history(self, filer: str | None = None, issuer: str | None = None) -> list[dict]:
        params: dict[str, Any] = {}
        if filer:
            params["filer"] = filer
        if issuer:
            params["issuer"] = issuer
        if not params:
            sys.exit("ERROR: --filer or --issuer required for history pull.")
        return self._unwrap(self._get("/shareholders/history", params))

    def company_shareholders(self, edinet_code: str) -> list[dict]:
        return self._unwrap(self._get(f"/companies/{edinet_code}/shareholders", {}))


# -------------------------- Output --------------------------

def fmt_pct(v: Any) -> str:
    try:
        return f"{float(v):+.2f}%"
    except (TypeError, ValueError):
        return "—"


def fmt_yen(v: Any) -> str:
    try:
        v = float(v)
    except (TypeError, ValueError):
        return "—"
    if abs(v) >= 1e12:
        return f"¥{v/1e12:.2f}T"
    if abs(v) >= 1e9:
        return f"¥{v/1e9:.2f}B"
    if abs(v) >= 1e6:
        return f"¥{v/1e6:.1f}M"
    return f"¥{v:.0f}"


def render_events_md(events: list[dict]) -> str:
    if not events:
        return "_No matching events._\n"

    by_type: dict[str, list[dict]] = {}
    for e in events:
        by_type.setdefault(e.get("event_type", "other"), []).append(e)

    lines = [f"# EDINET DB — {len(events)} events\n"]
    for et, items in sorted(by_type.items()):
        lines.append(f"\n## {et} ({len(items)})\n")
        for e in items:
            ts = e.get("event_time") or e.get("disclosure_time") or e.get("submitted_at", "")
            sec = e.get("sec_code") or "—"
            name = e.get("filer_name") or e.get("company_name") or e.get("issuer_name") or ""
            title = e.get("title") or e.get("description") or e.get("event_subtype") or ""
            sev = e.get("severity") or ""
            sev_tag = f"[{sev}]" if sev else ""
            line = f"- `{ts}` [{sec}] **{name}** {sev_tag} — {title}"
            if "holding_ratio" in e or "holding_ratio_change" in e:
                line += f"  ratio={fmt_pct(e.get('holding_ratio'))}  Δ={fmt_pct(e.get('holding_ratio_change'))}"
            if "purpose_category" in e:
                line += f"  purpose={e['purpose_category']}"
            if "est_holding_value" in e:
                line += f"  value={fmt_yen(e['est_holding_value'])}"
            lines.append(line)
    return "\n".join(lines) + "\n"


def render_activists_md(positions: list[dict]) -> str:
    if not positions:
        return "_No activist positions._\n"

    by_group: dict[str, list[dict]] = {}
    for p in positions:
        g = p.get("parent_group_id") or p.get("filer_name") or "—"
        by_group.setdefault(g, []).append(p)

    lines = [f"# Active activist positions — {len(positions)} positions across {len(by_group)} groups\n"]
    for group, items in sorted(by_group.items(), key=lambda x: -len(x[1])):
        total_value = sum(float(p.get("est_holding_value") or 0) for p in items)
        lines.append(f"\n## {group} — {len(items)} positions, total {fmt_yen(total_value)}\n")
        for p in sorted(items, key=lambda x: -float(x.get("est_holding_value") or 0)):
            sec = p.get("sec_code") or "—"
            issuer = p.get("issuer_name") or ""
            ratio = fmt_pct(p.get("holding_ratio"))
            value = fmt_yen(p.get("est_holding_value"))
            acq = fmt_yen(p.get("est_acquisition_price"))
            last = p.get("latest_filing_date") or ""
            lines.append(f"- [{sec}] **{issuer}** — {ratio}  value={value}  cost~{acq}  last={last}")
    return "\n".join(lines) + "\n"


def render_history_md(history: list[dict]) -> str:
    if not history:
        return "_No history._\n"
    lines = [f"# Shareholding history — {len(history)} filings\n"]
    for h in sorted(history, key=lambda x: x.get("filing_date", ""), reverse=True):
        date_s = h.get("filing_date", "")
        filer = h.get("filer_name", "")
        issuer = h.get("issuer_name", "")
        sec = h.get("sec_code", "—")
        ratio = fmt_pct(h.get("holding_ratio"))
        delta = fmt_pct(h.get("holding_ratio_change"))
        purpose = h.get("purpose_category", "")
        value = fmt_yen(h.get("est_holding_value"))
        lines.append(
            f"- `{date_s}` [{sec}] **{filer}** → {issuer}: ratio={ratio} Δ={delta} value={value} purpose={purpose}"
        )
    return "\n".join(lines) + "\n"


# -------------------------- CLI --------------------------

def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="EDINET DB activist & material-event monitor")
    p.add_argument("--days", type=int, default=1,
                   help="Days back to scan for events (default 1)")
    p.add_argument("--end-date", type=str, default=None,
                   help="End date YYYY-MM-DD (default = today)")
    p.add_argument("--event-types", type=str, default=None,
                   help=f"Comma-separated. Default: {','.join(HIGH_SIGNAL_EVENTS)}")
    p.add_argument("--severity", type=str, default=None,
                   help="Comma-separated: critical,high,medium,low")
    p.add_argument("--watchlist", type=str, default=None,
                   help="Comma-separated 4-digit sec codes")
    p.add_argument("--activists", action="store_true",
                   help="Snapshot mode: show all current activist positions")
    p.add_argument("--category", type=str, default="activist",
                   help="Activist filter category (with --activists)")
    p.add_argument("--filer", type=str, default=None,
                   help="History mode: pull all filings by this filer name")
    p.add_argument("--issuer", type=str, default=None,
                   help="History mode: pull all 5%%+ filings on this issuer")
    p.add_argument("--company", type=str, default=None,
                   help="EDINET code (e.g. E02144) — show all 5%%+ holders for this company")
    p.add_argument("--json", action="store_true", help="Emit JSON instead of Markdown")
    return p.parse_args()


def main() -> int:
    args = parse_args()
    api_key = os.environ.get("EDINETDB_API_KEY", "").strip()
    if not api_key:
        print("WARNING: EDINETDB_API_KEY not set. Anonymous access is 100/day.", file=sys.stderr)
    client = EdinetDBClient(api_key or None)

    if args.activists:
        positions = client.activists(category=args.category, limit=500)
        out = json.dumps(positions, ensure_ascii=False, indent=2) if args.json else render_activists_md(positions)
        print(out)
        return 0

    if args.filer or args.issuer:
        history = client.shareholder_history(filer=args.filer, issuer=args.issuer)
        out = json.dumps(history, ensure_ascii=False, indent=2) if args.json else render_history_md(history)
        print(out)
        return 0

    if args.company:
        holders = client.company_shareholders(args.company)
        out = json.dumps(holders, ensure_ascii=False, indent=2) if args.json else render_history_md(holders)
        print(out)
        return 0

    # Default: events feed
    end = date.fromisoformat(args.end_date) if args.end_date else date.today()
    since = end - timedelta(days=args.days)
    event_types = args.event_types.split(",") if args.event_types else HIGH_SIGNAL_EVENTS
    severity = args.severity.split(",") if args.severity else None

    if args.watchlist:
        watchlist = {w.strip() for w in args.watchlist.split(",") if w.strip()}
        all_events: list[dict] = []
        for sec in watchlist:
            evs = client.events(since=since, until=end, event_types=event_types, sec_code=sec, severity=severity)
            all_events.extend(evs)
        events = all_events
    else:
        events = client.events(since=since, until=end, event_types=event_types, severity=severity)

    events.sort(key=lambda e: e.get("event_time") or e.get("submitted_at") or "", reverse=True)

    out = json.dumps(events, ensure_ascii=False, indent=2) if args.json else render_events_md(events)
    print(out)
    print(f"\n# {len(events)} events  range={since}..{end}  types={event_types}", file=sys.stderr)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Quick sanity check

The public search endpoint (no auth needed) confirms connectivity:

curl -s "https://edinetdb.jp/v1/search?q=Toyota" | jq '.data[:2]'

Should return Toyota Motor (E02144) and Toyota Boshoku (E00540).

Telegram hook (matches OpenClaw setup)

Drop in after the final print(out) line in main():

def telegram_alert(text: str) -> None:
    import os, requests
    token = os.environ.get("TELEGRAM_BOT_TOKEN")
    chat_id = os.environ.get("TELEGRAM_CHAT_ID")
    if not (token and chat_id):
        return
    for chunk in [text[i:i+4000] for i in range(0, len(text), 4000)]:
        requests.post(
            f"https://api.telegram.org/bot{token}/sendMessage",
            json={"chat_id": chat_id, "text": chunk, "parse_mode": "Markdown",
                  "disable_web_page_preview": True},
            timeout=10,
        )

# After the final print:
if events and not args.json:
    telegram_alert(out)

Cron on Mac Mini

# Every weekday at 19:00 JST after most filings settle
0 19 * * 1-5 cd ~/edinet && EDINETDB_API_KEY=xxxxx /usr/bin/python3 edinet_db_monitor.py --days 1 >> logs/$(date +\%Y\%m).md 2>&1

Bonus: their MCP server

EDINET DB ships an MCP server. If you'd rather skip the script entirely for interactive use, connect it directly to Claude Code or Claude Desktop — see https://edinetdb.com/docs/mcp-guide. The Python script is still useful for cron-driven Telegram alerts, but the MCP path is faster for ad hoc digging.


Path B: FSA direct API (only if you can sign up)

Use this only if you successfully get past SMS verification, OR if EDINET DB's coverage gap (March 2021 onwards) is a problem and you need 2015–2020 data.

Sign-up gotchas

  1. Popup/redirect blocker. Add https://api.edinet-fsa.go.jp to allowed pop-ups in Chrome.
  2. Phone format. Country code +66 Thailand, number without leading 0. [PHONE] → 81234-5678.
  3. Email confirmation expires in ~30 min. Don't walk away mid-signup.
  4. SMS gateway outages. Error message "Messages are currently unavailable" — server-side. Try again later or fall back to Path A.

API model

Base URL: https://api.edinet-fsa.go.jp/api/v2 Auth: Subscription-Key=KEY (query param) Rate limit: 3-second floor between calls.

GET /documents.json?date=YYYY-MM-DD&type=2&Subscription-Key={KEY}
GET /documents/{docID}?type={1|2|4|5}&Subscription-Key={KEY}

Document types worth filtering:

Code Meaning
340 大量保有報告書 (initial 5%+)
350 変更報告書 (change in 5%+ holding)
360 Amended large shareholding/change
240 TOB
290 TOB Opinion Statement
180 臨時報告書 (extraordinary report)
220 Treasury buyback status
120 有価証券報告書 (annual report)

Python script (Path B)

#!/usr/bin/env python3
"""
FSA direct EDINET monitor — fallback path.
Requires API key from https://api.edinet-fsa.go.jp/api/auth/index.aspx?mode=1
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import time
from dataclasses import dataclass, asdict
from datetime import date, datetime, timedelta
from pathlib import Path
from typing import Iterable

import requests

API_BASE = "https://api.edinet-fsa.go.jp/api/v2"
RATE_LIMIT_SLEEP = 3.0
MAX_RETRIES = 5
RETRY_BACKOFF = 10.0

DOC_TYPES = {
    "120": "Annual Securities Report",
    "130": "Amended Annual Securities Report",
    "140": "Quarterly Report",
    "150": "Amended Quarterly Report",
    "160": "Semi-Annual Report",
    "170": "Amended Semi-Annual Report",
    "180": "Extraordinary Report",
    "190": "Amended Extraordinary Report",
    "220": "Treasury Stock Buyback Status",
    "230": "Amended Treasury Stock Buyback Status",
    "235": "Internal Control Report",
    "240": "Tender Offer Statement (TOB)",
    "250": "Amended Tender Offer Statement",
    "260": "Tender Offer Withdrawal",
    "270": "Tender Offer Report",
    "290": "TOB Opinion Statement",
    "340": "Large Shareholding Report (initial)",
    "350": "Change Report (5%+ holding change)",
    "360": "Amended Large Shareholding / Change",
}

DEFAULT_TYPES = {"340", "350", "360", "240", "250", "270", "290", "180"}


class EdinetClient:
    def __init__(self, api_key: str, sleep: float = RATE_LIMIT_SLEEP):
        if not api_key:
            sys.exit("ERROR: EDINET_API_KEY is not set.")
        self.api_key = api_key
        self.sleep = sleep
        self.session = requests.Session()
        self._last_call = 0.0

    def _throttle(self) -> None:
        elapsed = time.time() - self._last_call
        if elapsed < self.sleep:
            time.sleep(self.sleep - elapsed)
        self._last_call = time.time()

    def _get(self, url: str, params: dict, *, stream: bool = False):
        params = {**params, "Subscription-Key": self.api_key}
        for attempt in range(1, MAX_RETRIES + 1):
            self._throttle()
            try:
                resp = self.session.get(url, params=params, stream=stream, timeout=30)
            except requests.RequestException as e:
                if attempt == MAX_RETRIES:
                    raise
                print(f"  ! request error ({e}); retry {attempt}/{MAX_RETRIES}", file=sys.stderr)
                time.sleep(RETRY_BACKOFF)
                continue

            if resp.status_code == 403 and attempt < MAX_RETRIES:
                print(f"  ! 403 from EDINET; retry {attempt}/{MAX_RETRIES}", file=sys.stderr)
                time.sleep(RETRY_BACKOFF)
                continue
            resp.raise_for_status()
            return resp
        raise RuntimeError("EDINET API: exhausted retries")

    def list_documents(self, on_date: date) -> list[dict]:
        url = f"{API_BASE}/documents.json"
        resp = self._get(url, {"date": on_date.isoformat(), "type": 2})
        return resp.json().get("results", []) or []

    def fetch_document(self, doc_id: str, doc_type: int = 2) -> bytes:
        url = f"{API_BASE}/documents/{doc_id}"
        resp = self._get(url, {"type": doc_type}, stream=True)
        return resp.content


@dataclass
class Filing:
    doc_id: str
    submitted_at: str
    doc_type_code: str
    doc_type_label: str
    description: str
    filer: str
    issuer_edinet_code: str | None
    sec_code: str | None
    period_start: str | None
    period_end: str | None
    has_pdf: bool
    has_english: bool

    @property
    def ticker4(self) -> str | None:
        return self.sec_code[:4] if self.sec_code and len(self.sec_code) >= 4 else None

    @property
    def edinet_url(self) -> str:
        return f"https://disclosure2.edinet-fsa.go.jp/WEEK0030.aspx?docId={self.doc_id}"


def parse_filing(d: dict) -> Filing:
    code = d.get("docTypeCode") or ""
    return Filing(
        doc_id=d.get("docID") or "",
        submitted_at=d.get("submitDateTime") or "",
        doc_type_code=code,
        doc_type_label=DOC_TYPES.get(code, "Other"),
        description=d.get("docDescription") or "",
        filer=d.get("filerName") or "",
        issuer_edinet_code=d.get("issuerEdinetCode"),
        sec_code=d.get("secCode"),
        period_start=d.get("periodStart"),
        period_end=d.get("periodEnd"),
        has_pdf=d.get("pdfFlag") == "1",
        has_english=d.get("englishDocFlag") == "1",
    )


def filter_filings(filings, types, watchlist, filer_substring):
    out = []
    for f in filings:
        if types and f.doc_type_code not in types:
            continue
        if watchlist:
            if not (f.sec_code in watchlist or (f.ticker4 and f.ticker4 in watchlist)):
                continue
        if filer_substring and filer_substring.lower() not in f.filer.lower():
            continue
        out.append(f)
    return out


def render_markdown(filings):
    if not filings:
        return "_No matching filings._\n"
    by_type = {}
    for f in filings:
        by_type.setdefault(f.doc_type_label, []).append(f)
    lines = [f"# EDINET filings — {len(filings)} hits\n"]
    for label, items in sorted(by_type.items()):
        lines.append(f"\n## {label} ({len(items)})\n")
        for f in items:
            tk = f"[{f.ticker4}]" if f.ticker4 else "[—]"
            lines.append(f"- `{f.submitted_at}` {tk} **{f.filer}** — {f.description}")
            lines.append(f"  - {f.edinet_url}")
    return "\n".join(lines) + "\n"


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--days", type=int, default=1)
    p.add_argument("--end-date", type=str, default=None)
    p.add_argument("--types", type=str, default=None)
    p.add_argument("--watchlist", type=str, default=None)
    p.add_argument("--filer", type=str, default=None)
    p.add_argument("--json", action="store_true")
    args = p.parse_args()

    api_key = os.environ.get("EDINET_API_KEY", "").strip()
    client = EdinetClient(api_key)
    end = date.fromisoformat(args.end_date) if args.end_date else date.today()
    days = [end - timedelta(days=i) for i in range(args.days)]
    types = set(args.types.split(",")) if args.types else DEFAULT_TYPES
    watchlist = set(args.watchlist.split(",")) if args.watchlist else None

    all_filings = []
    for d in days:
        if d.weekday() >= 5:
            continue
        print(f"  · scanning {d.isoformat()}…", file=sys.stderr)
        try:
            raw = client.list_documents(d)
        except requests.HTTPError as e:
            print(f"    ! {e}", file=sys.stderr)
            continue
        all_filings.extend(parse_filing(r) for r in raw if r.get("docID"))

    filtered = filter_filings(all_filings, types, watchlist, args.filer)
    filtered.sort(key=lambda x: x.submitted_at, reverse=True)
    out = json.dumps([asdict(f) for f in filtered], ensure_ascii=False, indent=2) if args.json else render_markdown(filtered)
    print(out)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Next-step extensions (priority order)

For Claude Code to work through. Builds on Path A.

1. SQLite persistence + dedup

Cron will re-fetch overlapping days. Add a SQLite store keyed on a stable event ID:

2. Watchlist driven by live portfolio

Pull tickers from wherever you keep your live book and pipe into --watchlist. Then alerts only fire for names you actually trade.

3. Activist signal backtest

Walk-forward eval: "buy on filer X entry, hold N days, exit on Y." Sharpe × hit-rate on Murakami / Oasis / Effissimo / Dalton entries 2021–2026. Same evaluation framework as the autoresearch system. Probably the single most valuable downstream use.

EDINET DB's parent_group_id already groups the alias variants, and est_acquisition_price gives an entry price proxy — both pre-built. The work is just: cross-reference filing dates against price data (J-Quants free tier for JP daily bars), compute forward returns, aggregate by filer group.

4. Cross-reference EDINET filings with same-day TDnet announcements

The /events endpoint already merges these. But for activist plays specifically, you want: did a 5%+ filing on company X arrive within N days of company X disclosing a buyback / earnings revision / governance change? That co-incidence is the highest-confidence read on intent.

5. Filer outreach pattern detection

Some activists file series (Murakami group hits ~20 names per year). Detect: "filer X just initiated 3 new 5% positions in the last 5 trading days" → potential start of a campaign cycle worth front-running.

6. FSA direct fallback for pre-2021 history

If FSA SMS ever clears, run the Path B script as a one-off backfill 2015–2021, dump to your SQLite. EDINET DB picks up from there.


Things to know / gotchas

Discussion

No comments yet.

No redactions applied.

Published via wiis. Originals are encrypted at rest; this page reflects the redacted version. report abuse.