For external & API users
API documentation
Everything you need to submit invoices to InvGuard, retrieve fraud decisions, and independently verify that a decision and its compliance record were not altered after the fact.
Overview
The Compliance Toolkit API exposes three capabilities behind one REST surface: score an invoice (InvGuard), verify a decision's cryptographic anchor (rify-AI), and generate EU AI Act artifacts (the Compliance Toolkit). Every response is JSON. All write operations are anchored and persisted, so the decision trail is durable and auditable.
Base URL & environments
All endpoints are versioned under /api/v1.
textProduction https://api.compliant.eu/api/v1
Staging https://staging.api.compliant.eu/api/v1
Local http://127.0.0.1:8000/api/v1Health probes live at the root: GET /health (liveness) andGET /health/ready (readiness, checks the database).
Authentication
Read endpoints are public. Write endpoints (POST /api/v1/decisions) require an API key when the deployment is configured with one. Send it in thex-api-key header. Keys are issued per integrator; treat them as secrets and never expose them in browser code.
bashcurl -X POST https://api.compliant.eu/api/v1/decisions \
-H "x-api-key: $COMPLIANT_API_KEY" \
-H "content-type: application/json" \
-d @invoice.jsonRate limits
Requests are limited per client IP using a fixed window (default 120 requests / 60s). When exceeded you receive 429 Too Many Requests with a Retry-Afterheader (seconds). Health probes are exempt.
Errors
All errors share a stable envelope. Branch on code, not on prose.
json{
"error": {
"code": "not_found",
"message": "Decision 'dec_123' not found.",
"request_id": "9f2c…"
}
}validation_error— 422, request body failed schema validation (seedetails).unauthorized— 401, missing or invalid API key.not_found— 404, unknown decision id.rate_limited— 429, back off and retry afterRetry-After.internal_error— 500, transient; therequest_idties it to our logs.
Every response also returns an x-request-id header — include it in support requests.
Decisions
/api/v1/decisionsjson// request body
{
"invoice_id": "INV-2026-001",
"vendor_id": "vendor-acme",
"buyer_id": "buyer-eu-payments",
"amount": 18450.0,
"currency": "EUR",
"invoice_date": "2026-07-20",
"due_date": "2026-08-19",
"bank_account": "DE89370400440532013000",
"vendor_region": "DE",
"company_size": "sme"
}json// 201 Created (truncated)
{
"decision_id": "dec_a1b2c3…",
"invoice_id": "INV-2026-001",
"model_version": "invguard-heuristic-v1",
"score": 0.35,
"risk_level": "medium",
"decision": "review",
"explanation": {
"summary": "Invoice scored medium risk by InvGuard v1 heuristics.",
"rule_triggers": [
{ "code": "HIGH_VALUE_INVOICE", "description": "…", "weight": 0.35 }
],
"feature_contributions": { "amount": 0.369, "payment_timing": 0.0 }
},
"input_hash": "…",
"proof": {
"commitment_hash": "…",
"payload_signature": "sig-…",
"ipfs_cid": "bafy…",
"chain_network": "local-testnet",
"transaction_hash": "0x…",
"anchored_at": "2026-07-20T10:00:00Z"
}
}Field notes: amount > 0, currency is a 3-letter code, dates are ISO YYYY-MM-DD, company_size ∈ {micro, sme, mid_market, enterprise}.
/api/v1/decisions?limit={1-200}&offset={n}{ items, total, limit, offset }./api/v1/decisions/{decision_id}Verification
/api/v1/decisions/{decision_id}/verificationThe status field is one of:
valid— content hash matches the commitment and the signature verifies.tampered— the anchored content no longer hashes to the recorded commitment.signature_invalid— commitment is intact but the signature does not verify.unanchored— the decision has no anchor proof.
Compliance reports
/api/v1/compliance/model-card/api/v1/compliance/bias-report/api/v1/compliance/audit-reportVerifying a decision independently
You do not have to trust our verification endpoint. The commitment is a SHA-256 over a canonical JSON serialization (sorted keys, no insignificant whitespace) of the committed fields. Given the anchored content, anyone can reproduce the hash:
pythonimport hashlib, json
# 'content' is the anchored canonical JSON string for the decision.
commitment = hashlib.sha256(content.encode("utf-8")).hexdigest()
assert commitment == proof["commitment_hash"] # integrity holdsIn production the anchored content lives on IPFS (addressed by ipfs_cid) and the commitment is recorded on-chain (transaction_hash), so verification does not depend on our database.