Compare audit fees and auditors across companies
Pull audit, audit related, tax and other fees from proxy statements, plus the auditor of record, and compare non-audit fee share across companies.
Proxy statements disclose what a company paid its independent auditor, split into audit fees, audit related fees, tax fees and all other fees. /v1/audit-fees extracts that table; /v1/auditor returns the auditor of record from the annual report.
Four companies side by side
import os
import requests
BASE = "https://secfeedapi.com"
HEADERS = {"Authorization": f"Bearer {os.environ['SECFEED_KEY']}"}
def latest(values):
"""Values come as printed in the proxy: {year: amount}, or a list with the latest year first."""
if isinstance(values, dict):
y = max(values)
return y, values[y]
return "latest", values[0]
def normalize(fees):
out = {}
for label, values in fees.items():
key = label.lower()
if "total" in key:
out["total"] = latest(values)
elif "related" in key:
out["audit_related"] = latest(values)
elif key.startswith("audit"):
out["audit"] = latest(values)
return out
for ticker in ["NVDA", "MSFT", "AMZN", "JPM"]:
fees = requests.get(f"{BASE}/v1/audit-fees", params={"ticker": ticker}, headers=HEADERS, timeout=120).json()
auditor = requests.get(f"{BASE}/v1/auditor", params={"ticker": ticker}, headers=HEADERS, timeout=120).json()
n = normalize(fees["fees"])
year, audit = n["audit"]
_, total = n["total"]
print(f"{ticker:5} {auditor.get('auditorName', '?')[:26]:26} {year} audit {audit:>14,.1f} total {total:>14,.1f} "
f"unit {fees['unit'] or 'as printed'} non-audit share {1 - audit / total:.1%}")
Output:
NVDA PricewaterhouseCoopers LLP 2026 audit 10,166,400.0 total 13,361,322.0 unit as printed non-audit share 23.9%
MSFT DELOITTE & TOUCHE LLP 2025 audit 53,880,000.0 total 78,465,000.0 unit as printed non-audit share 31.3%
AMZN Ernst & Young LLP 2025 audit 47,260,000.0 total 59,256,965.0 unit as printed non-audit share 20.2%
JPM PricewaterhouseCoopers LLP 2025 audit 91.0 total 135.0 unit millions non-audit share 32.6%
Normalizing the table
The fee table is printed differently by each company, and the API returns it as printed:
- Labels vary. "Audit Fees", "Audit fees" and "Audit" all mean the same thing, as do "Total", "Total Fees" and "TOTAL FEES". The
normalizefunction above maps them by keyword. - Units vary. Some companies print whole dollars, others thousands or millions. When the proxy states the unit, it is in
unit(millionsfor JPM above). Whenunitis null, the values are exactly as printed, so ratios like the non-audit share are safe while absolute amounts need a check against the document. - Years. Most tables come back as
{year: amount}. A few proxies print columns without year headers, and then values come as a list with the latest year first.
Why non-audit share matters
A high share of non-audit fees (tax and consulting work) is a long studied auditor independence signal. Audit committees disclose pre-approval policies for those services in the same section of the proxy.
Auditor of record
/v1/auditor reads AuditorName, AuditorLocation and AuditorFirmId (the PCAOB ID) from the annual report's XBRL, plus whether the auditor attested to internal control over financial reporting.