Verification API

Public lookup over the signed model identity registry. Records are returned as canonical JWS values; clients verify locally against the published JWKS. No accounts. No keys to obtain. No request-time signing.

The API is a propagation layer. The signed static registry at attest.fallrisk.ai/registry.json and the JWKS at attest.fallrisk.ai/.well-known/jwks.json are the trust root.

The API does not create trust. It serves signed claims that already exist on the authority surface, and it fails closed when the manifest signature cannot be verified.

Implementation status
Status
live
Base URL
https://api.attest.fallrisk.ai/v1/
Registry schema
v0.2.3
Registry coverage
current signed registry
Signature algorithm
RS256 (RSA 2048)
Issuer
https://attest.fallrisk.ai
Issuer kid
fallrisk-96cd5e6a01e1
JWKS
attest.fallrisk.ai/.well-known/jwks.json
Hash logging
redacted at the web tier (see Privacy)
Runtime structural verification
not exposed through this API

The API is not the authority. It is the window.

Trust chain
  Local artifact
SHA-256
  Trustfall Lite
lookup · this API serves the records
  Signed Registry Record
JWKS verification
  Verdict · verified · unknown variant · not enrolled · pilot available
when artifact verification is not enough
  Trustfall Deep · runtime structural identity
Artifact verification stops at the file. Runtime structural identity begins with Trustfall Deep.

Endpoints

GET /v1/verify/hash/{sha256} Look up a single artifact hash.
POST /v1/verify/manifest Look up up to 1,000 artifact hashes in one request.
GET /v1/health Liveness probe. Returns {"status": "ok"}.
GET /v1/registry/status Loaded registry state, including JWKS source, manifest digest, kid, and the public-key fingerprint.
GET /v1/registry/manifest_digest Returns the canonical registry manifest digest and its signature, so any client can verify that the registry contents the API is serving have not drifted from the static signed registry.

Single hash lookup

Compute the SHA-256 of an artifact (a model shard, typically a .safetensors or .gguf file) and look it up:

$ curl -s "https://api.attest.fallrisk.ai/v1/verify/hash/\
b477be7572f0ab3ae3cbba38d508cc33e70600b2045669c4ad848051c3432094" | jq
{
  "sha256": "b477be7572f0ab3ae3cbba38d508cc33e70600b2045669c4ad848051c3432094",
  "status": "verified",
  "record_jws": "eyJraWQiOiJmYWxscmlzay05NmNkNWU2YTAxZTEi...",
  "record": { ... decoded JWS payload ... },
  "registry_snapshot_at": "2026-05-02T05:43:28+00:00",
  "registry_manifest_digest": "0568fe38fc3fb4801b016450d23d2fce963f523204eb105db59fa4755ff13846",
  "registry_kid": "fallrisk-96cd5e6a01e1"
}

Example value as of May 3, 2026 (registry v0.2.3, 211 records). The live value is the response of GET /v1/registry/manifest_digest. The format is locked at 64-character lowercase raw hex with no sha256: prefix.

The decoded record field is convenience data. The authoritative value is record_jws. Always verify the JWS locally against the published JWKS before trusting the result.

Status values

ValueMeaning
verifiedHash matches a signed enrollment record.
not_enrolledHash is not in the registry (HTTP 404).
revokedHash matched a record that has since been revoked (HTTP 410).

Batch hash lookup

For verifying an entire model directory in one request:

$ curl -s -X POST https://api.attest.fallrisk.ai/v1/verify/manifest \
  -H "Content-Type: application/json" \
  -d '{
    "hashes": [
      {"sha256": "b477be7572f0ab3ae3cbba38d508cc33e70600b2045669c4ad848051c3432094"},
      {"sha256": "eb356aacae443e30f52712b1e98fadf206976365e2f5ee886321b0bb38c7cea8"}
    ],
    "client": {"name": "my-tool", "version": "1.0"}
  }' | jq

Per-hash results are returned in the same order as the input. Each result carries the same status and record_jws fields as the single-hash endpoint. Maximum 1,000 hashes per request.

Verifying signatures

The published JWKS contains a single RSA public key. Pin the kid in operational configuration to prevent silent issuer rotation.

FieldValue
JWKS URLattest.fallrisk.ai/.well-known/jwks.json
kidfallrisk-96cd5e6a01e1
ktyRSA
algRS256
Key fingerprintsha256:FlqonYOsEwXi5eaLuhjMKmHzbKxtM0MrM7yGg2xW-2M
Fingerprint methodrfc7638-sha256 (JWK Thumbprint, RFC 7638)

The fingerprint is reproducible by any compliant JWK library. Reference implementation:

import json, hashlib, base64, urllib.request

with urllib.request.urlopen('https://attest.fallrisk.ai/.well-known/jwks.json') as r:
    keys = json.load(r)['keys']

key = next(k for k in keys if k['kid'] == 'fallrisk-96cd5e6a01e1')
canonical = json.dumps(
    {'e': key['e'], 'kty': 'RSA', 'n': key['n']},
    sort_keys=True, separators=(',', ':')
)
fp = base64.urlsafe_b64encode(hashlib.sha256(canonical.encode()).digest()).rstrip(b'=')
print(f"sha256:{fp.decode()}")
# sha256:FlqonYOsEwXi5eaLuhjMKmHzbKxtM0MrM7yGg2xW-2M

End-to-end verification of a returned record:

import json, urllib.request
from jose import jws

# 1. Fetch a verified record from the API.
with urllib.request.urlopen(
    'https://api.attest.fallrisk.ai/v1/verify/hash/'
    'b477be7572f0ab3ae3cbba38d508cc33e70600b2045669c4ad848051c3432094'
) as r:
    response = json.load(r)
token = response['record_jws']

# 2. Fetch the published JWKS.
with urllib.request.urlopen('https://attest.fallrisk.ai/.well-known/jwks.json') as r:
    jwks = json.load(r)

# 3. Verify the JWS. Raises on signature mismatch or algorithm substitution.
payload = jws.verify(token, jwks, algorithms=['RS256'])
print(json.loads(payload))

Rate limits

Per source IP. All limits use a one-minute rolling window.

EndpointLimit
GET /v1/verify/hash/{sha256}300 req/min
POST /v1/verify/manifest60 req/min
Batch size1,000 hashes per request
Global circuit breaker5,000 req/min across all sources

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Retry-After is added on 429.

Privacy

Individual hash queries are not retained. Specifically:

Standard nginx access logs (with redaction applied) are retained 30 days.

How to verify the API is honest

Every response carries the registry_manifest_digest of the signed registry the API is serving. Pin that digest in your client. Compare against the value at:

GET /v1/registry/manifest_digest

That endpoint returns both the digest and a manifest_signature. Verify the signature against the published JWKS — do not trust the digest alone. For the full static-registry check, compare against registry.json → manifest.manifest_digest at attest.fallrisk.ai/registry.json; the static registry is the authority, the manifest endpoint is the convenience proof surface.

If any of the three values disagree, the API has drifted. Treat any manifest digest mismatch as fail-closed.

For independent verification of the registry itself — reproducing the trust path from the published JWKS to a single signed enrollment record without trusting Fall Risk's word for it — see the verification guide.

What this API does not do

This API answers one question: is this artifact known and signed against the public registry. That is artifact verification.

It does not perform runtime structural verification. Runtime structural verification — measuring a running model against an enrolled identity anchor and producing a fresh signed JWT — is Trustfall Deep, an enrollment-based service.

Trustfall Lite verifies what model artifact you have.
Trustfall Deep verifies which model is actually computing.

For Deep: integrations@fallrisk.ai.