Public API v1.0.0

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.

BASH
curl https://esrsalign.app/public-api/health

A real analysis call looks like this. Add --max-time 900 so cURL doesn't time out before the 3–8 min analysis finishes.

BASH
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.json

Authentication

All analysis endpoints require two API key headers. Keys are used only for the duration of your request and are never stored or logged.

BASH
# Required headers for every analysis request
X-Gemini-Key: your-google-gemini-api-key
X-OpenAI-Key: your-openai-api-key

Base URL

BASH
https://esrsalign.app/public-api

Endpoints

Error Codes

CodeMeaning
400Bad request — invalid parameters or non-PDF file
401Unauthorized — missing or invalid API key header
413File too large (max 100 MB)
422Validation error — malformed JSON body or missing fields
429Rate limited — exceeded 10 requests/second
500Internal server error — check the error.detail field
504Gateway timeout — analysis exceeded 15-minute server limit

Full Tutorial

One Python script: Double Materiality + ESRS E1–E5, with CSV exports and markdown summaries.

PYTHON
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, 38 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.md
  • e1_evidence.csve5_evidence.csv
  • e1_summary.mde5_summary.md
  • all.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.

Try It Live

Open the interactive Swagger UI to test endpoints directly in your browser.