Before you start
- An API key. Keys come with an Enterprise Licence; see getting access.
- curl, or Python 3.9 or later with the
requestspackage.
Put the key in an environment variable so it never appears in your scripts or shell history:
read -rs SRC_API_KEY && export SRC_API_KEY # paste the key, press EnterCheck your key
GET /entitlements confirms the key works and shows what it can see.
curl -s https://api.sustainabilityreports.com/api/v2/entitlements \
-H "Authorization: Bearer $SRC_API_KEY"{
"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 exampleXMIL_G. - Report, identified by
report_id(an integer, used in API calls) andreport_key(readable and stable, for exampleXMIL_G_2025_AR2). Each report carries its metadata and adownload_path. - Download link:
GET /reports/{report_id}/downloadreturns 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
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.
{
"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.
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).
