What you can get
Eight things, each a plain JSON answer:
| What | Endpoint |
|---|---|
| A company's place in the Sustainability Reporting Index, and every stored report with its rank | /api/v1/companies/{company_id}/index, /reports |
| The full breakdown of a report: readability, emphasis, theme weights and alignment, each ranked in its sector and industry | /api/v1/reports/{report_id} |
| Rating-agency and investor theme weights for any sector or industry, or a blend of the two | /api/v1/benchmarks |
| How sustainability news has moved share prices in a sector or industry, per theme | /api/v1/alpha-benchmarks |
| Our keyword dictionary: every theme, its keywords and their framework tags | /api/v1/keywords |
| Scoring on demand for a report (PDF or link) or a sustainability web page | /api/v1/report-assessments, /api/v1/website-assessments |
| Our sentiment scores for a company's news coverage, with the trend | /api/v1/companies/{company_id}/sentiment |
| How sustainability news has moved one company's share price, per theme | /api/v1/companies/{company_id}/sustainability-alpha |
You get our scores and statistics. You never get the text, articles or prices they're computed from.
Get a key
Keys are issued by Eunoic to companies with an Eunoic account. Contact us and we'll switch the API on for your account and send you a key. It's shown to you once, so keep it somewhere safe. If it's ever exposed, tell us and we'll revoke it and issue a new one.
Send the key with every call:
Authorization: Bearer eun_live_...
Your first call
Find a company's company_id, then ask for its Index entry:
curl -H "Authorization: Bearer $EUNOIC_API_KEY" "https://eunoic.com/api/v1/companies?q=Fortum"
curl -H "Authorization: Bearer $EUNOIC_API_KEY" "https://eunoic.com/api/v1/companies/123/index"
Every answer has the same shape. The data you asked for sits under data, and meta says how current it is:
{
"data": {"company_id": 123, "rankings_updated": "2026-09-24T02:00:00+00:00", "entry": {"rank": 1, "sector_ranked": 1386, "index_score": 88.4, ...}},
"meta": {"request_id": "2de6fff1...", "as_of": "2026-09-24T02:00:00+00:00", "methodology_version": "4.1"}
}
How ranks work
Every ranked score comes back the same way: its value, then its rank in the company's sector and in its industry.
"rai_real": {"value": 62.5, "sector": {"rank": 12, "of": 214, "percentile": 95}, "industry": {"rank": 3, "of": 41, "percentile": 95}}
Rank 1 is always the best, whichever way the score runs. of counts the peers, this report included. percentile is the share of peers it matches or beats. Peers are each company's newest report by default. You can change that on any breakdown:
per_company=allranks against every report, not just each company's newest.year=2024ranks against reports from one year.country=DEranks against companies in one country.sectors=,industries=orcompanies=(comma-separatedcompany_ids) add a third rank,peers, against a group you choose.
Our clients' own reports never count as peers.
Endpoints
Find a company
GET /api/v1/companies?q=Fortum returns up to 20 companies whose name or ticker matches, each with its company_id, ticker, sector, industry and country. Use the company_id everywhere else; names change and tickers repeat.
GET /api/v1/industries lists every sector and the industries in it, named exactly as rankings and benchmarks name them.
Index entry and reports
GET /api/v1/companies/{company_id}/index returns the company's latest ranked report in the Index: its score, its rank in its sector, the five measures behind it, and the report's name, year and link.
GET /api/v1/companies/{company_id}/reports lists every report we hold for the company, newest first, each with its report_id, its Index rank, and the reason when it isn't ranked.
A report's breakdown
GET /api/v1/reports/{report_id} returns everything we measure in one report:
- Accessibility: how much of the text survives cleaning.
- Readability for people (the full text) and for AI (its summary): eight formulas, the grade most of them agree on, our Readability Aggregate Index (RAI) raw and real, prose quality and reading time.
- Emphasis: the share of the text that is sustainability language.
- Theme weights: the share of that language on each of 20 themes.
- Alignment: how closely those weights match the industry's rating-agency benchmark, with the gap on each theme.
- Index: its score and rank in the Sustainability Reporting Index.
Benchmarks
GET /api/v1/benchmarks?industry=Renewable Utilities returns the weight rating agencies give each theme in that industry. Add basis=investor for the weights investors reward, or basis=blend&investor_share=0.3 for a mix. Use sector= for a whole sector.
GET /api/v1/alpha-benchmarks?sector=Utilities shows, per theme, how often news on the theme moved the sector's share prices, which way, and how strongly. windows gives the dates the evidence covers. An industry with too few windows of its own shows its sector's and says so with from_sector.
Keyword dictionary
GET /api/v1/keywords returns every pillar and theme with its description and keywords, each keyword with the frameworks it comes from (ESRS, GRI, TCFD, CDP, SASB and others).
Scoring a report or web page
Send a report as a PDF (up to 100 MB):
curl -H "Authorization: Bearer $EUNOIC_API_KEY" \
-F "file=@report.pdf" -F "name=Fortum 2025" -F "company_id=123" \
"https://eunoic.com/api/v1/report-assessments"
Or as a link, with the same fields in JSON: {"url": "https://...", "name": "Fortum 2025", "company_id": 123}. For a company we don't list, send company_name and an industry from /api/v1/industries instead of company_id, so we can rank it against the right peers.
You get a job_id straight away while the scoring runs. Ask for the result with it:
curl -H "Authorization: Bearer $EUNOIC_API_KEY" "https://eunoic.com/api/v1/report-assessments/report_assessment_..."
status is pending, running, succeeded or failed. Once it has succeeded, the answer holds the same breakdown as a stored report, including where it would sit in the Index. If it failed, error says why.
A web page works the same way: send {"url", "name", "company_id"} to /api/v1/website-assessments, then read /api/v1/website-assessments/{job_id}. Its scores are ranked against other companies' web pages. GET /api/v1/companies/{company_id}/website returns a company's page we've already scored in the last year.
Each submission counts as one scoring job. Sending exactly the same request again returns the same job. Everything you score is saved to your account, visible only to you, and never enters the Index or anyone's rankings.
Media sentiment
GET /api/v1/companies/{company_id}/sentiment scores the company's news coverage over the last 90 days: the average sentiment, the split of positive, neutral and negative articles, a score per pillar, a timeline, and the trend. Choose the range with from and to (dates, up to two years apart), a theme with theme=Climate Change, and the trend's period with period=day, week or month. The trend compares the latest complete period with the one before it. The trend's thin is true when either of the two periods compared holds fewer than 10 scored articles: read that trend with care. The overall thin is true when the range holds between 1 and 9 scored articles.
Sustainability alpha
GET /api/v1/companies/{company_id}/sustainability-alpha shows, per theme, how often news on the theme moved the company's share price, which way and how strongly, with the statistics of the most recent window: impact, p-value, R², correlation and the number of trading days behind it. We keep only windows with a p-value under 0.05.
Your usage
GET /api/v1/usage shows today's calls and scoring jobs, used and left.
Limits
Each account gets 5,000 calls and 50 scoring jobs a day, reset at midnight UTC. Each key can also make at most 60 calls a minute. Over either limit, you'll get a 429 with a Retry-After header saying how many seconds to wait. If you need more, talk to us.
Errors
An error comes back with its own status and a message that says what to fix:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request |
A parameter is missing or wrong |
| 401 | unauthorised |
No key, or a key that isn't valid |
| 403 | forbidden |
API access isn't switched on, or the subscription isn't active |
| 404 | not_found |
No such company, report or job |
| 429 | limit_reached |
Today's calls or jobs are used |
| 503 | unavailable |
A fault on our side. Try again in a minute |
Quote the request_id from meta if you contact us about a call.
How current it is
meta.as_of tells you. For Index entries, it's when the rankings were last rebuilt (every night). For a stored web page, when we captured it. For sentiment, the newest article. For alpha, the end of the latest window. Otherwise, the time of your call.
What we never serve
Article headlines, summaries or text. Share prices or company profiles. The text of any report or web page, or our summaries of it. Anything a client has uploaded, except your own. Contact details of any kind.
Worked example
This short Python script finds a company, reads its Index entry and its report's breakdown, and prints where it ranks.
"""
Find a company, read its Sustainability Reporting Index entry and its report's breakdown, and print where it ranks.
Run:
EUNOIC_API_KEY=eun_live_... python example.py "Fortum"
"""
import os
import sys
import requests
BASE = os.environ.get("EUNOIC_API", "https://eunoic.com")
KEY = os.environ["EUNOIC_API_KEY"]
def get(path, **params):
"""Call one endpoint and return its data, or stop with the error it gave."""
answer = requests.get(f"{BASE}{path}", params=params, headers={"Authorization": f"Bearer {KEY}"}, timeout=60)
body = answer.json()
if "error" in body:
sys.exit(f"{body['error']['code']}: {body['error']['message']}")
return body["data"]
def ordinal(number):
"""1st, 2nd, 3rd, 4th, 11th, 12th, 21st..."""
suffix = "th" if 10 <= number % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(number % 10, "th")
return f"{number}{suffix}"
company = get("/api/v1/companies", q=sys.argv[1])[0]
entry = get(f"/api/v1/companies/{company['company_id']}/index")["entry"]
print(f"{company['name']}, {entry['report_name']}: Sustainability Reporting Index score {entry['index_score']}, "
f"{ordinal(entry['rank'])} of {entry['sector_ranked']} in {entry['sector']}")
report = get(f"/api/v1/reports/{entry['report_id']}")
readability = report["readability_people"]["rai_real"]["industry"]
print(f"{ordinal(readability['rank'])} of {readability['of']} in its industry ({company['industry']}) for readability")
Run it with your key:
EUNOIC_API_KEY=eun_live_... python example.py "Fortum"
For developers and low-code tools
The full description of every endpoint, parameter and field is at /static/api/openapi.json (OpenAPI 3.1). ChatGPT actions take an OpenAPI file, so you can add it to a custom GPT as an action.