/api/v1/compliance/ledger-keysPer-organization evidence-ledger trust anchor (every signing key, active and retired)
Publishes EVERY Ed25519 signing key the organization has ever held — the trust anchor an outside auditor pins to verify that tenant's signed evidence independently. WHY THIS EXISTS. Since per-tenant signing keys landed, every organization signs its evidence with its OWN key held in Supabase Vault. `/api/v1/compliance/ledger-public-key` publishes the single PLATFORM key, so an auditor holding a tenant-signed export finds a `keyId` that endpoint does not contain and has no way to obtain the matching public key except by asking the audited operator for it — which is exactly the trust relationship an independent verification is supposed to remove. Match the record's `keyId` here; fall back to the platform endpoint only for records predating per-tenant keys. RETIRED AND REVOKED KEYS ARE INCLUDED, DELIBERATELY. A record signed under a rotated key must keep verifying: rotation that invalidates history destroys the thing the ledger exists for. `status` and `revocationReason` are ADVISORY — they tell you how much weight to give a valid signature, never whether it is valid. Nothing here narrows a signature to a time window, because record timestamps are supplied by the signer and a window enforced against them would flip historical records from valid to out-of-window on clock skew alone. AUTHENTICATION: an org-scoped credential is required, the same model as `/api/v1/compliance/ledger-checkpoint`. A customer issues their third-party auditor a read-only, revocable API key (`compliance:read` suffices); API keys are pinned to their own organization, so a credential can only ever retrieve that one tenant's anchor. Public keys are not secret, but an anonymous `?orgId=` endpoint would turn the orgId — a widely-copied, unrotatable identifier that appears in every export file — into a permanent, unrevocable feed of that tenant's key-rotation cadence. Gating cannot break the control: the organization can choose whether you reach this endpoint, never what it says, and a verifier that cannot reach it fails closed as an explicit finding rather than a silent pass. NOT PLAN-GATED. Exempt from the /api/v1/compliance feature gate: a customer who can produce signed evidence must be able to obtain the anchor that proves it, whatever they pay.
Authentication
Send Authorization: Bearer YOUR_API_KEY on every request. Generate API keys at /dashboard/settings/api-keys.
Parameters
orgId in queryrequiredOrganization whose signing keys to return, and which your credential must be scoped to. Must be a UUID; 400 INVALID_ORG_ID otherwise.
stringResponse
All status codes
Code samples
cURL
curl -X GET \ https://evalguard.ai/api/v1/compliance/ledger-keys \ -H "Authorization: Bearer $EVALGUARD_API_KEY"
TypeScript
// The TypeScript SDK (@evalguard/sdk) exposes TYPED methods — runEval,
// getEval, runSecurityScan, checkFirewall, … — not a generic request().
// For an arbitrary endpoint, call it directly:
const res = await fetch("https://evalguard.ai/api/v1/compliance/ledger-keys", {
method: "GET",
headers: { Authorization: `Bearer ${process.env.EVALGUARD_API_KEY}` },
});
console.log(res.status, await res.json());Python
# The Python SDK (pip install evalguardai) exposes TYPED methods on
# EvalGuardClient — run_eval, get_eval, … — not a generic request().
# For an arbitrary endpoint, call it directly:
import os
import requests
headers = {"Authorization": f"Bearer {os.environ['EVALGUARD_API_KEY']}"}
response = requests.request("GET", "https://evalguard.ai/api/v1/compliance/ledger-keys", headers=headers)
print(response.status_code, response.json())Go
package main
import (
"context"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequestWithContext(context.Background(), "GET", "https://evalguard.ai/api/v1/compliance/ledger-keys", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("EVALGUARD_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
fmt.Println(resp.Status)
}