Eunoic
Book a demo

API

Our scores, in your own tools

The Eunoic API gives you the sustainability reporting scores, benchmarks, media sentiment and sustainability alpha behind our platform as plain JSON, every score ranked against its sector and industry.

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=all ranks against every report, not just each company's newest.
  • year=2024 ranks against reports from one year.
  • country=DE ranks against companies in one country.
  • sectors=, industries= or companies= (comma-separated company_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.

Want to try it on your own companies?

Tell us what you'd build, and we'll switch the API on for your account.

Ask for a key