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

Current signed evidence-ledger checkpoint (org-scoped, freshness-signed length anchor)

Publishes the organization's CURRENT signed evidence-ledger checkpoint — the length anchor that makes tail truncation detectable. A hash chain commits to ORDER, never LENGTH: lop N records off a signed chain and every survivor still has contiguous seq, intact prevHash linkage and a valid signature. Only a signed commitment to (seq, head recordHash) makes that visible. FRESHNESS IS INSIDE THE SIGNATURE (payload v2). The signed body is `{v:2, orgId, seq, recordHash, keyId, issuedAt, nonce}`. `issuedAt` is the mint instant and `nonce` is the challenge the VERIFIER sent as `?nonce=`, echoed back — both covered by the Ed25519 signature. The verifier enforces a maximum signed age and requires the challenge echo, so a replayed response is refused even when it is a genuine one. This matters concretely: the v1 payload signed only `{orgId, seq, recordHash, keyId}` and carried `issuedAt` alongside it as unsigned transport metadata, which nothing ever compared to anything — so a publisher replaying its own genuine six-year-old checkpoint satisfied the live fetch and the CLI certified an export with 4 of 5 records deleted, exit 0, nothing forged. A checkpoint minted before v2 has no signed timestamp and cannot acquire one retroactively; verifiers report those as `checkpoint-freshness-unsigned` and never certify them. DO NOT SAVE THIS TO A FILE AND PASS IT AS `--checkpoint`. A file anchor can FAIL an export but can never certify one (`completeness.reason: "offline-anchor-only"`, exit 1): a checkpoint is a LOWER bound, a fresh one is minted on every append, a file cannot answer a live challenge, and in practice it reaches the auditor from the party being audited. Have the verifier fetch it instead: `evalguard verify export.json --public-key ledger.pub.pem --checkpoint-url https://evalguard.ai --checkpoint-token <key>`, or `verifyEvidenceExportLive()` from `@evalguard/core/verify`, both of which mint a fresh 128-bit challenge per run. A fetch that fails for any reason is reported as CANNOT-PROVE-COMPLETE and exit 1, never as a pass and never as a fallback to the checkpoint travelling inside the export. AUTHENTICATION: an org-scoped credential is required. 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. This does not weaken the anchor: every response is signed over the LIVE database head, so the organization can choose whether you reach this endpoint but never what it says — and a refusal fails closed in the verifier as an explicit finding rather than a silent pass. (This endpoint was briefly unauthenticated; that posture is withdrawn. It gave anyone holding an orgId — which appears in every export, audit bundle and dashboard URL, and cannot be rotated — a permanent, unrevocable read of any tenant's evidence volume, and bought nothing for the anchor's integrity.) AVAILABLE ON EVERY PLAN TIER. Although it lives under the Team+ feature-gated /api/v1/compliance prefix, this path is exempt from the plan/feature entitlement gate. Being able to prove that evidence we already produced has not been truncated is not a paid add-on: a Free or Pro customer's auditor must be able to obtain the anchor, because a verifier with no anchor fails closed and certifies nothing. The credential requirement is unchanged. The paid compliance product surface (reports, scores, frameworks, controls) remains gated. DISCLOSURE, stated plainly: `length` (= `checkpoint.seq + 1`) IS the organization's exact cumulative evidence-record count, and because the anchor is minted live and never cached, a caller who polls observes both that count and its rate of change — how much governance evidence the organization produces, and when. This is inseparable from the artefact (a length anchor whose length is secret is not an anchor) and `seq` sits inside the signed body, so it cannot be withheld. It is the reason a revocable credential is required rather than optional. Beyond that count and the commitment itself, the response carries no evidence content, no record bodies, no argHash, no prevHash, no non-head recordHash, no metadata, no PII and no key material. Note also that 200/503 versus 404 does distinguish 'this organization holds evidence' from 'empty' — a real distinction, though only visible to a caller who already holds that organization's credential. READ-ONLY: this path never provisions. An organization with no active signing key receives 503 LEDGER_SIGNER_UNPROVISIONED; signing keys are created only on an authenticated evidence append. Rate limit: 10 requests/minute.

Authentication

Send Authorization: Bearer YOUR_API_KEY on every request. Generate API keys at /dashboard/settings/api-keys.

Parameters

orgId in queryrequired

Organization whose ledger head to commit to, and which your credential must be scoped to. Must be a UUID; 400 INVALID_ORG_ID otherwise.

string
nonce in query

Liveness challenge, echoed verbatim INSIDE the signature as `checkpoint.nonce`. Generate 128 bits of fresh randomness per verification and never reuse one; the verifier requires the echo before it will certify completeness, which is what makes a retrieved anchor distinguishable from a recording of one. Omitting it yields a checkpoint that is still signed and still fresh but cannot certify (`checkpoint-unchallenged`). 400 INVALID_NONCE when malformed — a mangled challenge is rejected rather than signed, because a failed echo is indistinguishable from a replay.

string

Response

All status codes

200The current signed checkpoint, minted over the live head.
400`INVALID_ORG_ID` — orgId missing or not a UUID. `INVALID_NONCE` — nonce present but not 8-128 URL-safe characters.
401`UNAUTHORIZED` — no credential supplied. Ask the organization under audit for a read-only API key (`compliance:read`); pass it to the CLI as `--checkpoint-token`.
403`NOT_ORG_MEMBER` / `KEY_ORG_MISMATCH` — the credential is not scoped to the requested organization. Note this endpoint is NOT plan-gated: it is exempt from the /api/v1/compliance feature gate, so a Free or Pro organization's auditor is never refused for tier.
404`EMPTY_LEDGER` — the organization's evidence ledger is empty, so there is no head to commit to.
429Rate limit exceeded (10/min).
500Internal Server Error — LEDGER_HEAD_FAILED.
503`LEDGER_SIGNER_UNPROVISIONED` — the organization has no active evidence-ledger signing key, and this read-only endpoint will never mint one (keys are provisioned on an authenticated evidence append). `LEDGER_SIGNER_UNAVAILABLE` — the key exists but could not be read; fails closed rather than signing an anchor with a key every tenant shares.

Code samples

cURL

curl -X GET \
  https://evalguard.ai/api/v1/compliance/ledger-checkpoint \
  -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-checkpoint", {
  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-checkpoint", 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-checkpoint", 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

400401403404429500503

Other Compliance endpoints