API Documentation
Integrate ESRS compliance analysis directly into your workflow. Upload sustainability PDFs and receive structured results via a simple REST API.
API Keys
Use your own Gemini & OpenAI keys. We never store them.
Upload
Send a PDF via multipart form data — get JSON or CSV back.
CSV Export
Append ?format=csv to any analysis endpoint for a download.
Try it in one line
A health check returns immediately and needs no API keys — useful for verifying your network can reach the API.
curl https://esrsalign.app/public-api/healthA real analysis call looks like this. Add --max-time 900 so cURL doesn't time out before the 3–8 min analysis finishes.
curl -X POST "https://esrsalign.app/public-api/analyze/esrs-evidence?standard_code=E1" \
-H "X-Gemini-Key: YOUR_GEMINI_KEY" \
-H "X-OpenAI-Key: YOUR_OPENAI_KEY" \
-F "file=@report.pdf" \
--max-time 900 \
-o e1.jsonAuthentication
All analysis endpoints require two API key headers. Keys are used only for the duration of your request and are never stored or logged.
# Required headers for every analysis request
X-Gemini-Key: your-google-gemini-api-key
X-OpenAI-Key: your-openai-api-keyBase URL
https://esrsalign.app/public-apiEndpoints
Error Codes
| Code | Meaning |
|---|---|
| 400 | Bad request — invalid parameters or non-PDF file |
| 401 | Unauthorized — missing or invalid API key header |
| 413 | File too large (max 100 MB) |
| 422 | Validation error — malformed JSON body or missing fields |
| 429 | Rate limited — exceeded 10 requests/second |
| 500 | Internal server error — check the error.detail field |
| 504 | Gateway timeout — analysis exceeded 15-minute server limit |
Full Tutorial
One Python script: Double Materiality + ESRS E1–E5, with CSV exports and markdown summaries.
import csv, json, requests
from pathlib import Path
BASE = "https://esrsalign.app/public-api"
HEADERS = {
"X-Gemini-Key": "YOUR_GEMINI_KEY",
"X-OpenAI-Key": "YOUR_OPENAI_KEY",
}
TIMEOUT = 900 # 15 min — analyses are synchronous, 3–8 min each
PDF = "report.pdf"
OUT = Path("esrs_results")
OUT.mkdir(exist_ok=True)
def analyze(path, params=None):
with open(PDF, "rb") as f:
r = requests.post(f"{BASE}{path}", headers=HEADERS,
files={"file": f}, params=params, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
def summarize(path, data):
r = requests.post(f"{BASE}{path}", headers=HEADERS,
json={"json_data": data}, timeout=TIMEOUT)
r.raise_for_status()
return r.json()["summary"]
def evidence_rows(analysis, category=None):
rows = []
for gm in analysis.get("guideline_matches") or []:
n = (gm.get("evidence_index") or 0) + 1
for m in (gm.get("matches") or [{}]):
sim = m.get("similarity_score")
row = [n, gm.get("page_number", ""), gm.get("evidence_text", ""),
m.get("guideline_label", ""), m.get("guideline_name", ""),
m.get("area_of_reporting", ""), m.get("data_type", ""),
f"{sim*100:.1f}" if isinstance(sim, (int, float)) else "",
m.get("rank", "")]
rows.append([category, *row] if category else row)
return rows
def write_csv(path, header, rows):
with open(path, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f); w.writerow(header); w.writerows(rows)
HEAD = ["Evidence #", "Page", "Evidence Text",
"Guideline Label", "Guideline Name", "Area", "Data Type",
"Similarity %", "Rank"]
# 1) Double Materiality: analyze, save CSV, summarize, save .md
dm = analyze("/analyze/double-materiality")
imp = evidence_rows(dm.get("impact_materiality") or {}, "Impact")
fin = evidence_rows(dm.get("financial_materiality") or {}, "Financial")
write_csv(OUT / "double_materiality.csv", ["Category"] + HEAD, imp + fin)
(OUT / "double_materiality_summary.md").write_text(
summarize("/summarize/double-materiality", dm), encoding="utf-8")
# 2) ESRS E1–E5: analyze each, save CSV, summarize, save .md
all_evidence = {}
for code in ["E1", "E2", "E3", "E4", "E5"]:
ev = analyze("/analyze/esrs-evidence", params={"standard_code": code})
all_evidence[code] = ev
write_csv(OUT / f"{code.lower()}_evidence.csv", HEAD, evidence_rows(ev))
(OUT / f"{code.lower()}_summary.md").write_text(
summarize("/summarize/esrs-evidence", ev), encoding="utf-8")
# 3) Save consolidated JSON
(OUT / "all.json").write_text(
json.dumps({"double_materiality": dm, "evidence_by_standard": all_evidence},
indent=2, default=str),
encoding="utf-8")
print(f"Done. Outputs in {OUT}/")What you get when this finishes — in esrs_results/:
double_materiality.csv+double_materiality_summary.mde1_evidence.csv…e5_evidence.csve1_summary.md…e5_summary.mdall.json— consolidated raw responses
Runtime — roughly 25–55 minutes total (each analysis is 3–8 min, plus a short summary call after each).
Rate Limits
10 requests/second per IP address with a burst of 10.
All analysis endpoints are synchronous — your request stays open for the full duration. Each call typically takes 3–8 minutes depending on PDF size, model latency, and the standard being analysed. Always set your HTTP client's read timeout to at least 15 minutes (900s) — Python's requests.post(..., timeout=900). Without an explicit timeout, the connection may hang or your client's default (often 5 minutes) will cut the analysis off mid-flight.