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.

Implementation status
Status
live
Base URL
https://api.attest.fallrisk.ai/v1/
Registry schema
v0.2.1
Records served
75
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

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.

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-04-26T22:45:45+00:00",
  "registry_manifest_digest": "sha256:5f159f7f6408e476...",
  "registry_kid": "fallrisk-96cd5e6a01e1"
}

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.

What this API does not do

This is a lookup surface over signed enrollment records. It answers the question is this artifact known and signed. It does not perform the runtime structural verification that the signed records describe.

Runtime structural verification — measuring a model under contract and producing a fresh signed JWT bound to that measurement — is a separate enrollment service.

For the runtime side: integrations@fallrisk.ai.