Skip to content

API documentation

Getting started

From an API key to your first downloaded report in a few minutes. The examples use curl and Python.

API version 2.4.0 · Updated 2 October 2026

Before you start

Put the key in an environment variable so it never appears in your scripts or shell history:

Shell
read -rs SRC_API_KEY && export SRC_API_KEY   # paste the key, press Enter

Check your key

GET /entitlements confirms the key works and shows what it can see.

Shell
curl -s https://api.sustainabilityreports.com/api/v2/entitlements \
  -H "Authorization: Bearer $SRC_API_KEY"
Response
{
  "client_name": "Example Research Ltd",
  "full_access": false,
  "fcyear_start": 2023,
  "fcyear_end": null,
  "report_types": null,
  "watchlist_size": 250
}

fcyear_start and fcyear_end are the fiscal years in your licence; a null end means open-ended. report_types: null means every report type in your licence. watchlist_size is the number of companies your key can see. A 401 means the key is missing, mistyped, revoked or expired.

The objects

  • Company, identified by company_key: the exchange MIC and ticker of its primary listing, for example XMIL_G.
  • Report, identified by report_id (an integer, used in API calls) and report_key (readable and stable, for example XMIL_G_2025_AR2). Each report carries its metadata and a download_path.
  • Download link: GET /reports/{report_id}/download returns a signed URL, valid for up to 15 minutes. Fetch the PDF from that URL with a plain GET, without your key.

The data model guide explains identifiers, report types and every field.

First requests in curl

Shell
AUTH="Authorization: Bearer $SRC_API_KEY"
BASE=https://api.sustainabilityreports.com/api/v2

# 1. Which companies are in my licence? (up to 500 per page)
curl -s "$BASE/watchlist?limit=500" -H "$AUTH"

# 2. Map my own identifiers to company keys (up to 1,000 per call)
curl -s -X POST "$BASE/companies/resolve" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"isins": ["IT0000062072", "DE0008404005"], "tickers": ["XLON:HSBA"]}'

# 3. List reports, 200 per page; repeat with cursor=<page_info.next_cursor>
curl -s "$BASE/reports?limit=200" -H "$AUTH"

# 4. Narrow: sustainability and integrated reports of one company for fiscal 2025
curl -s "$BASE/reports?company_key=XMIL_G&type=SR&type=IR&fcyear=2025" -H "$AUTH"

# 5. Get a signed link to one PDF, then fetch the PDF from that link (no key)
curl -s "$BASE/reports/529769/download" -H "$AUTH"
curl -s -o XMIL_G_2025_AR2.pdf "<url from the previous response>"

The download call returns the link and its expiry. The url value is a credential in its own right: do not log it or share it.

Response
{
  "report_id": 529769,
  "format": "pdf",
  "url": "https://<signed link>",
  "expires_at": "2026-10-08T03:27:44Z",
  "expires_in_seconds": 900,
  "content_type": "application/pdf"
}

The identifiers and report ids in these examples are illustrative. Your key only sees the companies in your licence, so use values from your own /watchlist and /reports responses.

The same in Python

A short script that lists your reports and downloads the first PDF. It uses one session, follows the cursor and checks the file against content_sha256.

first_steps.py
import hashlib
import os

import requests

BASE = "https://api.sustainabilityreports.com/api/v2"
session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {os.environ['SRC_API_KEY']}",
    "User-Agent": "example-sync/1.0",   # name your integration; it helps us help you
})


def get(path, **params):
    r = session.get(BASE + path, params=params, timeout=60)
    r.raise_for_status()
    return r.json()


def all_reports(**filters):
    """Yield every report in your licence that matches the filters."""
    params = {"limit": 200, **filters}
    while True:
        page = get("/reports", **params)
        yield from page["data"]
        if not page["page_info"]["has_more"]:
            return
        params["cursor"] = page["page_info"]["next_cursor"]


print(get("/entitlements"))

reports = list(all_reports(type=["SR", "IR"], fcyear=2025))
print(len(reports), "reports")

first = next((r for r in reports if r["has_file"]), None)
if first:
    link = get(f"/reports/{first['report_id']}/download")
    pdf = requests.get(link["url"], timeout=300)   # no API key on this request
    pdf.raise_for_status()
    expected = first.get("content_sha256")
    if expected and hashlib.sha256(pdf.content).hexdigest() != expected:
        raise ValueError("checksum mismatch, download again")
    with open(first["filename"], "wb") as f:
        f.write(pdf.content)
    print("saved", first["filename"])

requests repeats a list parameter, so type=["SR", "IR"] becomes ?type=SR&type=IR.

Next steps

  • Loading a whole collection, or keeping one in sync? Read bulk PDF downloads. It has a complete sync client with retries and the change feed.
  • Mapping our data to yours? Read the data model.
  • Generating a client? Use the API reference or download the OpenAPI file into Postman, Insomnia or a code generator.
  • Stuck? Email [email protected] with your key prefix, the request path and the time (UTC).