Public data & feeds

Everything machine-readable we publish

These are the data files behind the daily digests and the divergence board. No key, no signup, no rate limit worth mentioning — if you want to pull them on a schedule, please do.

Start here

curl -s https://www.insightintochina.com/data/index.json | jq '.datasets[] | {path, row_count}'

That catalog is the contract: it lists every dataset with its schema, row count and update time. It is regenerated on every build, so it cannot go stale quietly.

Datasets · api v1 · catalog built 2026-10-13

Dataset catalog

JSON each deploy

This list, in machine-readable form — every public dataset with its schema, row count and update time. Machines should start here.

/data/index.json

9 datasets · 22.0 KB · updated 2026-10-13

ProvenanceAssembled at build time by pipeline/gen-data-api.mjs from this repository. Row counts and byte sizes are measured from the files on disk as they are published — none of them is estimated.

Limitations
  • Describes the datasets, not the market. This file carries no market figures of its own.
  • An optional dataset with no instance this build (for example the share images on a day the renderer did not run) is removed from datasets[] entirely rather than listed with zero rows, so a missing entry means "none produced yet", not "broken".
  • license.requests[] are non-binding asks, not licence conditions — the licence is CC BY 4.0 and it permits commercial use. Only license.terms[] is stated as a condition, and those terms do not narrow CC BY 4.0 beyond attribution and non-endorsement.
Fields (5)
api_version string
Schema version of the public data namespace.
generated_at string
ISO 8601 timestamp of the build that produced these files.
site string
Canonical origin. All dataset URLs resolve under it.
license object
licence id, summary, license_url, terms[] (conditions) and requests[] (non-binding asks). The dataset licence is CC BY 4.0.
datasets array
One object per dataset: id, title, url, format, cadence, description, row_count, bytes, updated_at, fields, provenance, limitations.

Daily digest index

JSON daily (after each build)

Every trading day for which a sector-grouped digest was produced, with how many briefs it covers and how many sectors they fell into.

/data/daily/manifest.json

16 days · 1.8 KB · updated 2026-10-13

ProvenanceDerived from the same briefing database the site publishes, one entry per trading day that produced a digest. Counts are read back from each day’s own digest file rather than counted a second time.

Limitations
  • Covers only trading days on which a digest was produced. The earliest day held is 2026-09-27; there is no earlier history.
  • otherRatio is our classifier’s failure rate, not a market measure. A high value means the taxonomy did not fit that day, not that the market was unusual.
Fields (3)
generated_at string
ISO 8601 build timestamp.
days object
Map of YYYY-MM-DD → { total, industryCount, otherRatio }. otherRatio is the share of briefs the sector classifier could not place.
order array
Date strings, newest first. Same order as the human index at /daily.

Single-day digest

JSON daily (after each build)

One day’s briefs grouped into sectors, with tone distribution per sector, the representative views, the tickers mentioned most, market breadth and the divergence board.

/data/daily/{YYYY-MM-DD}.json

38 days · 24.1 KB · updated 2026-10-13

ProvenanceBuilt for one trading day from the briefs published that day. Sector assignment is a deterministic keyword classifier over each headline, with the body used only as a fallback. Tone buckets come from the same per-brief scores the site shows, and board is the same divergence computation described at /methodology.

Limitations
  • Figures in the briefs originate from third-party Chinese-language institutional research. We translate and structure them; we do not verify them.
  • Sector labels are a navigation aid, not an analyst judgment. stats.otherRatio is the share the classifier could not place — treat it as the error budget for that day.
  • A day with too few briefs is flagged stats.insufficient rather than padded out to look representative. Several days in the archive are like this: 2026-10-05 carries 5 briefs, 2026-10-02 carries 7.
  • board is empty on days when no scored cross-section was available (for example non-trading days), so an empty array is a normal state, not a failure.
  • Subscriber-only fields are excluded by construction. There is no flag to include them.
Fields (8)
date string
Trading day, YYYY-MM-DD.
headlines array
Up to 3 cross-sector stories: lake_id, title, bottom_line, sentiment, tickers, industryId, industryEn. Restricted to briefs whose sector came from the headline, not from the body.
industries array
Sector sections, sorted by brief count. Each: id, en, zh, noteCount, highConfidenceCount, sentiment{}, tickers[], leaders[], keyDataPoints[].
other object|null
Briefs the classifier could not place. Kept separate from industries[] rather than folded into one.
sentiment object
counts{} per tone bucket, unknownSentiment, bullRatio (null when nothing was rated), netTone, scoredCount, bullCount, bearCount.
stats object
total, classified, other, otherRatio, viaBody, industryCount, insufficient, minRequired. insufficient=true means too few briefs for a representative digest.
regime object|null
Market breadth reading used: fg_score, fg_label, pct_above_ma20, adv_cnt, dec_cnt, median_ret_20d, as_of_date.
board array
Names where research tone and price diverge most: ticker, gap, ret_1d, ret_20d, note_cnt, senti_score, last_close.

Divergence board — latest

JSON daily

The names where the tone of published research and the price move disagree most, as of the most recent scored trading day. Gap = tone percentile minus 20-day return percentile.

/data/divergence/latest.json

50 tickers · 9.3 KB · updated 2026-10-13

ProvenanceComputed from the same scored rows as the divergence board page: gap = tone percentile minus 20-day return percentile, rounded to four decimals. The percentile basis is the scored cross-section of that single trading day, so a gap is comparable within a date and not across dates.

Limitations
  • A description of disagreement between two measurements. It is not a prediction, a rating, a price target or a recommendation.
  • The tone input derives from third-party institutional research; only the comparison between tone and return is ours.
  • Cross-sections are small. The widest day we have scored carries about 600 names. This file holds a single day — for the accumulated history use /data/divergence/history.json.
  • Rows are deduplicated by ticker keeping the larger absolute gap, so a name that qualified on both sides appears once. sides{} reports how many rows each side contributed before deduplication.
Fields (3)
as_of_date string
Trading day the board was computed for.
score_note string
Plain-language definition of gap, repeated so a consumer does not have to fetch /methodology.
rows array
ticker, gap, ret_1d, ret_20d, note_cnt, senti_score, last_close. Sorted by gap, descending.

Divergence board — accumulated panel

JSON daily (appends one cross-section per scored trading day)

Every scored trading day the board has covered, one row per ticker — the accumulated history behind the single-day board. It is appended to, not mirrored, so a day stays in the file even after the source view stops serving it.

/data/divergence/history.json

2416 rows · 452.8 KB · updated 2026-10-13

ProvenanceAccumulated from the same divergence_daily_view the board pages read, one cross-section per scored trading day. Each day is re-fetched on every run and replaced whole — but only after its row count is reconciled against the count the source view declares for itself (divergence_dates_view.rows_cnt). A day that fails reconciliation keeps its previous rows rather than being half-replaced. Days the source no longer serves are kept. The file carries no timestamp, so it is byte-for-byte reproducible from the source view.

Limitations
  • Three complete cross-sections so far (2026-09-24 with 580 rows, 2026-10-08 with 626, 2026-10-09 with 555). 2026-09-18 (5 rows) and 2026-10-07 (6 rows) are functional test runs, not full cross-sections — check rows_per_day before using a date as a panel observation.
  • gap is computed against that day’s own cross-section, so it is comparable within a date and not across dates. A gap of +0.8 on one day does not mean the same thing as +0.8 on another.
  • Units are mixed, because they are the units the source uses: gap and senti_score are 0–1, ret_1d and ret_20d are already percentages, and last_close is unadjusted for splits or dividends.
  • The tone input is our scoring of third-party research; only the comparison between tone and return is ours.
  • Depth grows one cross-section per scored trading day. Days the pipeline did not score cannot be back-filled, so a gap in days[] is permanent.
  • Nothing here is a buy or sell call, a rating, a price target or investment advice.
Fields (9)
schema string
Format version of this file.
source_view string
The view the rows are read from.
primary_key array
["as_of_date", "ticker"] — unique across the whole file.
units object
The unit of each measure. gap and senti_score are 0–1; ret_1d and ret_20d are already multiplied by 100.
rows_per_day object
Map of YYYY-MM-DD → row count. Read this before treating a date as a panel observation: a day with a handful of rows is a test run, not a cross-section.
days array
Every date present in rows[], ascending.
days_no_longer_in_source array
Dates still in this file that the source view no longer serves. Kept deliberately — that is what "accumulated" means.
count number
Total rows; equals rows.length.
rows array
as_of_date, ticker, gap, ret_1d, ret_20d, note_cnt, senti_score, last_close.

Latest institutional briefs

JSON daily

The most recently published briefs with their headline, one-line bottom line, tone and mentioned tickers — the same public fields the site shows, in the order they were published.

/data/notes/latest.json

50 briefs · 25.2 KB · updated 2026-10-13

ProvenanceThe public fields of the most recently published briefs, in publication order, read from the same masked database view that serves logged-out visitors. The industry value is added by our classifier and is labelled as such inside the file, because who assigned a label decides how it may be cited.

Limitations
  • Headlines and bottom lines are our English renderings of third-party Chinese-language research. The argument belongs to the named source institution; cite that institution too.
  • sentiment_score is our scoring of the source text, not the source’s own rating. Do not restate it as the research house’s view.
  • key_data_points are figures taken from the source material. They are not independently verified by us.
  • Only the newest 50 briefs are present, so this file is a window and not an archive. Use the per-day digests for history.
  • excluded_fields lists every subscriber-only field by name and reason. A reader can tell what was withheld rather than having to guess.
Fields (4)
generated_at string
ISO 8601 build timestamp.
count number
Number of briefs included.
rows array
published_at, lake_id, title_en, bottom_line, sentiment_score, tickers[], key_data_points[], industry (our classifier’s sector id).
excluded_fields array
Names of the subscriber-only fields deliberately not included, with the reason.

Ticker index

JSON daily (after each build)

Every ticker with at least one published brief, with its note count. Keys are abbreviated because this file is also loaded by the site’s own /tickers page — the schema below is the contract.

/data/tickers-index.json

2481 tickers · 110.6 KB · updated 2026-10-13

ProvenanceEvery ticker appearing in at least one published brief, with the count of briefs that mention it. Rebuilt from the same briefing database as the rest of the site; the symbol list is derived from tickers[] on the briefs themselves.

Limitations
  • A coverage count, not a ranking. A high count means our sources wrote about it often, not that it is more important, more liquid or better regarded.
  • Company names are best-effort and may be empty for thinly covered symbols.
  • Absence means "not mentioned in any brief we hold", not "not listed on the exchange". The index covers only names our sources named.
  • Symbol suffixes follow the market of listing (.SZ / .SH / .SS / .HK) as they appear in the source material; they are not normalised across vendors.
Fields (3)
[].s string
Ticker symbol, e.g. 300308.SZ. Sorted ascending by code unit order.
[].n string
Company name (may be empty).
[].c number
Number of published briefs whose tickers[] contains this symbol. Always > 0 here.

Daily share images

PNG daily on trading days (best-effort)

A ready-to-post picture of each day’s sector breakdown — the same content as the digest page, laid out for social feeds. Only exists for trading days where a digest was produced; days without one have no image, and the digest page simply shows no share link.

/daily/{YYYY-MM-DD}.png

10 images · 582.6 KB · updated 2026-10-13

ProvenanceA screenshot of that day’s digest page: rendered headlessly at 1080×1350 logical pixels, captured at 2× device scale, written next to the page it depicts.

Limitations
  • Best-effort by design. Production depends on a browser rendering channel, so some trading days have no image and the digest page simply shows no share link. A missing day is not a failed digest.
  • The image follows the digest page exactly. It adds no figure that is not already in the JSON, so it is the wrong thing to parse — use the dataset files for anything computed.
  • The updated_at on this entry is the PNG’s real file mtime, not the build time, because the image can legitimately lag the build by a day.
Fields (3)
(binary) png
PNG, 1080×1350 logical pixels captured at 2× device scale (2160×2700 px).
content string
Date, brief and sector counts, market breadth, and up to 5 sector cards each with tone distribution and a one-line representative view.
coverage string
Not every calendar day. Follow the <enclosure> in /feed.xml or the links listed at /daily for the days that have one.

RSS feed — daily digests

RSS 2.0 daily (after each build)

RSS 2.0 feed of the daily digests, newest first. Each item links to the full digest and carries a plain-text summary of that day’s sector breakdown.

/feed.xml

16 items · 38.6 KB · updated 2026-10-13

ProvenanceRSS 2.0 built from the same digest files the site publishes, newest first. The guid is the digest URL, which is stable and never reused, so subscribers do not see the same day twice.

Limitations
  • The description carries the same public text as the digest page and nothing more. Subscriber-only fields never appear in the feed.
  • The <enclosure> is present only on days that have a share image, so a subscriber cannot rely on a picture every day.
  • The feed window is bounded — it is a rolling list, not an archive. Use the per-day files at /data/daily for the full history.
Fields (6)
channel/item/title string
China Institutional Research Digest — YYYY-MM-DD
channel/item/link string
Absolute URL of the digest page.
channel/item/pubDate string
RFC 822 date.
channel/item/guid string
Stable, never reused: the digest URL.
channel/item/description string
Plain-text digest: brief count, sector breakdown with tone, and the top stories.
channel/item/enclosure string
Present only on days that have a share image: url, length (real byte count), type=image/png.

Terms of use

IIC-Data-1.0 (CC BY 4.0 compatible) — Free to use, quote and redistribute with attribution and a link back to the source URL. Released under Creative Commons Attribution 4.0 (CC BY 4.0); the requests below are not conditions of the licence.

Licence text: https://creativecommons.org/licenses/by/4.0/

Requests — not conditions of the licence:

What is not here, and why. Subscriber-only fields — the full translation of the original Chinese, the analyst's hidden reasoning, the extracted key numbers and the risk analysis — are deliberately absent from every file on this page. They are what a subscription pays for, so publishing them here would make the subscription meaningless. The briefs above carry only the public fields the site itself shows.

How sectors and scores are produced. Sector assignment comes from a deterministic keyword classifier over each headline, with the body used only as a fallback. It is a navigation aid, not an analyst judgment, and it is imperfect by design — which is why every digest reports how many briefs it could not place. The divergence score is a percentile comparison, not a prediction; the methodology page has the full definition.