Enterprise API·v1 · stable

Cryptographically signed performance reports

Fetch your portfolio metrics and time series straight from the AuditZK AMD SEV-SNP enclave. Every response is signed with an ECDSA-P256 key whose measurement you can compare against the published GitHub release, letting your investors verify the numbers end-to-end without trusting either your frontend or ours.

Requires the Enterprise plan. See pricing

Quickstart

From zero to a verified report in three steps

1

Mint an API key

Go to /dashboard/api-keys and click Create API key. The full key is shown exactly once. Store it in a secrets manager. Keys carry the reports:read scope and can be given an expiration between 1 and 365 days.

2

Fetch a signed report

Send your key in the x-api-key header and call GET /api/enterprise/reports/signed.

curl -H "x-api-key: azk_your_key_here" \
"https://auditzk.com/api/enterprise/reports/signed\
?startDate=2026-01-01\
&endDate=2026-04-12\
&benchmark=SPY"
3

Verify the signature

The response carries a standard ECDSA-P256 signature over a SHA-256 hash of the financial payload. Verify it client-side with the Web Crypto API or any language that supports ECDSA. See the Verify the signature section below for full examples.

Authentication

API key in the x-api-key header

All Enterprise API requests must include a valid API key in the x-api-key HTTP header. Keys are issued from the dashboard, bound to your Enterprise user, and can be revoked at any time without affecting other keys.

Scoped

Each key grants the reports:read permission, allowing access to signed performance reports.

Rate limited

Each key carries its own per-minute quota, counted server-side on every request. Rate limit headers included in every response.

Revocable

Disable a key without deleting it, re-enable it if a rotation goes wrong, delete it once nothing calls it. Keys are hashed at rest (SHA-256).

Key lifecycle

Rotate without a gap in service

An Enterprise account holds up to five keys at once, which is what makes rotation safe: the replacement can be live before the old key stops working. Deleting a key is irreversible, so it is the last step, not the first.

  1. 1.
    Mint a second key and deploy it to whatever calls the API.
  2. 2.
    Disable the old key. It is refused from the next request onward — the isActive flag is read on every call, there is no cached window — and callers still using it get 401 INVALID_API_KEY.
  3. 3.
    Watch for those 401s. If something was still on the old key, re-enable it and finish the migration. A disabled key keeps its identity, its quota and its creation date.
  4. 4.
    Delete the old key once nothing calls it. The secret is gone at that point and cannot be brought back.

Keys are minted and deleted from the dashboard. To have a key disabled or re-enabled, or its quota raised, write to support.

Endpoint

GET/api/enterprise/reports/signed

Returns a single signed report covering the requested period for the user that owns the API key. The request is scoped to the key's user. There is no userId parameter; users can only read their own data.

Query parameters

ParameterTypeDefaultDescription
startDateISO dateendDate − 365dStart of the reporting period. Format: YYYY-MM-DD.
endDateISO datetodayEnd of the reporting period. Format: YYYY-MM-DD.
benchmark"SPY" | "BTC"SPYBenchmark symbol used to compute alpha, beta, correlation and tracking error.
includeRiskMetricsbooleantrueInclude VaR (95/99), expected shortfall, skewness, kurtosis in the financialData object.
includeDrawdownbooleantrueInclude the full drawdownPeriods array and maxDrawdownDuration.

Response

On success, the API returns a JSON object with a data.financialData field (the signed payload) and a set of cryptographic fields (signature, publicKey, reportHash, measurement) used for verification.

200 OK · application/json
{
"success": true,
"data": {
"financialData": {
"reportId": "cb79acda-5902-4530-a7e3-bbb5559e142c",
"userUid": "user_abc...",
"generatedAt": "2026-04-12T21:47:49.622Z",
"periodStart": "2026-01-06T00:00:00.000Z",
"periodEnd": "2026-04-11T00:00:00.000Z",
"baseCurrency": "USD",
"benchmark": "SPY",
"dataPoints": 90,
"exchanges": ["binance", "ibkr", "mexc"],
"totalReturn": 0.234,
"annualizedReturn": 0.89,
"volatility": 0.18,
"sharpeRatio": 1.82,
"sortinoRatio": 2.4,
"maxDrawdown": 0.07,
"calmarRatio": 3.2,
"var95": 0.0053,
"var99": 0.0238,
"expectedShortfall": 0.0110,
"skewness": 2.44,
"kurtosis": 25.70,
"alpha": 0.12,
"beta": 0.85,
"informationRatio": 0.86,
"trackingError": 0.14,
"correlation": 0.72,
"maxDrawdownDuration": 49,
"currentDrawdown": 0.019,
"drawdownPeriods": [
{
"startDate": "2026-01-18",
"endDate": "2026-03-10",
"depth": 0.0346,
"duration": 49,
"recovered": true
}
],
"dailyReturns": [
{
"date": "2026-01-06",
"netReturn": 0.0,
"benchmarkReturn": 0.0,
"outperformance": 0.0,
"cumulativeReturn": 0.0,
"nav": 1.0
}
],
"monthlyReturns": [
{
"date": "2026-01",
"netReturn": 0.021,
"benchmarkReturn": 0.018,
"outperformance": 0.003,
"aum": 1024000
}
]
},
"signature": "MEQCIDqXBFjwOTf3sx78BJYR8Wpzp0MNBxBelrB0...",
"publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELUTN...",
"signatureAlgorithm": "ECDSA-P256-SHA256",
"reportHash": "0833628eada6be2b20339a852e8f2dcf303ef3f7c77da9e0858368fdfee5d2c2",
"measurement": "12068361369CF9179BB6AC08572B7E15...",
"enclaveVersion": "1.0.0-go",
"enclaveMode": "production"
}
}

Verification

Verify the signature with one of your existing libraries

The signature is computed as ECDSA-P256-SHA256(sha256(reportHashString)) where reportHashString is the hex-encoded SHA-256 of the signed financial payload. The snippets below reproduce this computation and call the standard ECDSA verify primitive from crypto.subtle or the Python cryptography package.

// Verifies an AuditZK signed report using the Web Crypto API.
// Works in the browser and in Node.js 18+ (globalThis.crypto.subtle).
interface SignedReport {
financialData: unknown
signature: string // base64 DER-encoded ECDSA signature
publicKey: string // base64 DER-encoded SPKI
reportHash: string // hex-encoded SHA-256 of financialData
signatureAlgorithm: 'ECDSA-P256-SHA256'
measurement: string // hex-encoded SEV-SNP launch measurement
}
function base64ToBytes(b64: string): Uint8Array {
const bin = atob(b64)
const out = new Uint8Array(bin.length)
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)
return out
}
export async function verifySignedReport(
report: SignedReport,
expectedMeasurement?: string
): Promise<boolean> {
// 1. Optional: bind the report to a specific audited enclave build
if (expectedMeasurement && report.measurement !== expectedMeasurement) {
console.warn('Measurement mismatch', {
got: report.measurement,
want: expectedMeasurement,
})
return false
}
// 2. Import the enclave's public key
const key = await crypto.subtle.importKey(
'spki',
base64ToBytes(report.publicKey),
{ name: 'ECDSA', namedCurve: 'P-256' },
false,
['verify']
)
// 3. The enclave signs SHA-256(reportHashString), so we need to
// feed the hex-encoded report hash STRING through SHA-256 again
// to match the signed payload.
const reportHashDigest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(report.reportHash)
)
// 4. Verify ECDSA-P256 over the double hash
return crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
key,
base64ToBytes(report.signature),
reportHashDigest
)
}

Hardware attestation

Bind the report to the audited enclave build

A valid ECDSA signature only proves some key signed this data. To bind the report to the specific AuditZK enclave build that produced it, compare the measurement field against the SEV-SNP launch measurement published on the matching GitHub release. If both match, the data was generated by the audited source code running inside a confidential VM, end to end.

Transitional state

The production enclave currently runs the legacy TypeScript build and does not yet emit the measurement field. Responses contain an empty measurement string. Signature verification still works (the ECDSA chain is intact); hardware binding will be enabled when the Go enclave is promoted to production. Follow the roadmap on GitHub.

Error codes

What each status code means

StatusCodeMeaning
400INVALID_PARAMETERSQuery parameters failed validation (bad date format, unknown benchmark, etc.).
401MISSING_API_KEYNo x-api-key header provided.
401INVALID_API_KEYAPI key is unknown, revoked, or past its expiration date.
403PLAN_REQUIREDThe user backing this key is no longer on the Enterprise plan.
403INSUFFICIENT_SCOPESKey does not have the reports:read scope.
404NO_DATAUser has no snapshots in the requested period: cannot build a report.
429RATE_LIMIT_EXCEEDEDPer-key quota exceeded. Check the Retry-After header for seconds until the window resets.
502UPSTREAM_ERRORReport service or enclave unreachable. Retry with exponential backoff.

Rate limiting

Per-key quota, soft limits

Each API key has its own quota, counted per key in a fixed one-minute window by the API route that serves the request. Disabling or deleting one key never affects the counter of another. When a request is rate-limited, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait. Every successful response also includes:

  • X-RateLimit-Remaining: number of requests remaining in the current window.
  • X-RateLimit-Reset: Unix epoch (seconds) at which the window resets.
  • X-RateLimit-Limit: the quota configured on the key (default: 2 requests per minute).

The default suits a scheduled pull. If your integration needs more, tell support which key and what rate: the quota is raised on that key alone, and X-RateLimit-Limit reflects the new value on the next response.

Need a feature that isn't listed here? Reach out to support. We read every message.