FastAPI implementation for creating and verifying signed J/D/T/V events using RFC 8785 JCS, algorithm-tagged event hashes, and Ed25519 detached JWS.
Protocol profile: jep-core-0.6; wire version: "1". The 0.7.3 implementation release is versioned separately from the protocol. Live deployment and external database provisioning remain deferred; repository changes do not update hosted services.
With Python 3.10 or newer:
git clone https://github.com/hjs-spec/jep-api.git
cd jep-api
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python -m uvicorn main:app --host 127.0.0.1 --port 8000Open interactive API documentation. The default local configuration persists keys, events, and replay state in .jep-state; keep that directory to verify earlier events after restarting. For an application example, see the local quickstart.
| Endpoint | Purpose |
|---|---|
GET / |
API metadata and current signing public key |
GET /.well-known/jwks.json |
Trusted public keys in JWKS format |
GET /live |
Process liveness |
GET /health |
State and signing-configuration health |
POST /events/create |
Create, sign, and store a Core-0.6 event |
POST /events/verify |
Verify in archival or acceptance mode |
POST /events/verify-legacy |
Explicit historical-format integrity verification |
Production signing and nonce-consuming requests require Bearer authentication. The SDKs/CLI accept API-key options; GitHub Action 0.6.2 uses jep_api_token. See deployment and migration for PostgreSQL shared state, keyring/Vault signing, key rotation, and compatibility formats. The versioned container is ghcr.io/hjs-spec/jep-api:0.7.3.
Submit this body to POST /events/create:
{
"verb": "J",
"who": "did:example:agent-789",
"what": {
"claim": "approve",
"subject": "demo"
},
"aud": "https://api.example.org",
"ttl_minutes": 30,
"digest_only_who": false
}The response contains event, event_hash, and validation. To verify it, send the returned event unchanged as {"event": <returned event>, "mode": "archival"} to POST /events/verify.
A successful archival response has this shape; event_hash below is an illustrative digest:
{
"valid": true,
"level": 1,
"mode": "archival",
"profile": "jep-core-0.6",
"conformance_class": "JEP-Baseline-Ed25519-JWS-JCS-0.6",
"scopes": ["syntax", "cryptographic"],
"event_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"warnings": [
{
"code": "ACCEPTANCE_NOT_CHECKED",
"message": "Historical integrity only; freshness and live authorization were not checked.",
"level": 1,
"recoverable": false
}
],
"errors": []
}Responses follow the validation result schema. Diagnostics include code, message, level, and recoverable. Core diagnostic codes such as ERR_MISSING_REQUIRED_FIELD and ERR_INVALID_TIMESTAMP replace the former generic ERR_SCHEMA_INVALID. Malformed JSON at /events/verify returns HTTP 400 with a structured result and mode unparsed. Legacy results use legacy-integrity-only and do not assert Core-0.6 conformance.
Archival mode checks historical integrity without applying live freshness or TTL rejection. Acceptance additionally requires expected_audience, checks freshness and TTL, and consumes the nonce atomically. Explicit consume_nonce also consumes state in archival mode. These checks do not establish actor identity or authority.
The API reports Level 1 structure and cryptographic checks under its configured trusted keys. It implements TTL and digest-only extensions; unknown critical extensions are rejected. It does not determine external truth, legal liability, regulatory compliance, authorization validity, complete logging, or model correctness.
See hardening notes for regression coverage and historical changes, and deployment and migration for operational requirements. This reference implementation requires deployment-specific trust and security configuration before production use.