Skip to content
GET/api/v1/compliance/ledger-keys

Per-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 queryrequired

Organization whose signing keys to return, and which your credential must be scoped to. Must be a UUID; 400 INVALID_ORG_ID otherwise.

string

Response

All status codes

200The organization's complete signing-key history.
400`INVALID_ORG_ID` — orgId missing or not a UUID.
401`UNAUTHORIZED` — no credential supplied. Ask the organization under audit for a read-only API key (`compliance:read`).
403`NOT_ORG_MEMBER` / `KEY_ORG_MISMATCH` — the credential is not scoped to the requested organization.
404`LEDGER_KEYS_UNPROVISIONED` — the organization has no evidence-ledger signing key yet. A key is provisioned on the org's first authenticated evidence append; this endpoint never mints one. Distinguished from an empty list on purpose: 'no key exists' and 'this key is not ours' mean very different things to an auditor.
429Rate limit exceeded (30/min).
500`LEDGER_KEYS_UNAVAILABLE` — the directory could not be read. Fails rather than publishing an empty anchor, which a verifier could misread as 'this organization has no keys'.

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)
}

Errors

400401403404429500

Other Compliance endpoints