Jithox — developer guide
Five read-only tools for VAT / VIES / Peppol readiness over MCP. Endpoint: https://sanctions.mcp.jithox.com/mcp. Validation only — no sending, no compliance guarantee. A competent developer goes from zero to a verified first check in about 15 minutes on this page.
New here? See the decision workflow first — three concrete decisions (ready / needs_review / unavailable) and a verifiable demo receipt, fixture data, no signup.
1. Get access (€39, 30 days)
Open /pricing → Start for €39 → pay with a card. After the payment settles, the success page shows your one-time developer access key — copy it now; it is shown only once. Access is granted only after a settled payment (never a redirect alone). Lost it before saving? Contact info.jithox@gmail.com with your payment reference — never buy twice.
2. Connect your MCP client
The endpoint is the same for every client: https://sanctions.mcp.jithox.com/mcp (Streamable HTTP). Your client discovers the OAuth server automatically; you enter your developer access key once on the consent screen; it then keeps a refreshing connection — no copying tokens by hand.
Recommended path — Claude Code (one command, then /mcp to authenticate):
claude mcp add --transport http jithox-einvoice https://sanctions.mcp.jithox.com/mcp
That is the whole setup: approve the OAuth consent, paste your access key once, and continue to step 3.
Other clients — Cursor, VS Code, Claude web/desktop, or anything else
Cursor — add to ~/.cursor/mcp.json, then approve the consent Cursor opens:
{ "mcpServers": { "jithox-einvoice": { "url": "https://sanctions.mcp.jithox.com/mcp" } } }
VS Code — add to .vscode/mcp.json:
{ "servers": { "jithox-einvoice": { "type": "http", "url": "https://sanctions.mcp.jithox.com/mcp" } } }
Claude (web/desktop): Settings → Connectors → Add custom connector → paste the URL. Any other MCP client: add a remote server (Streamable HTTP) with the URL and approve OAuth.
PKCE is required, scopes are least-privilege per tool, tokens refresh automatically, and you can revoke anytime from your client. Connecting before you paid is fine — the consent screen links you to /pricing and tool calls answer 402 until access is active.
3. The five tools
validate_invoice— structural checks on an e-invoice payload.validate_vat_number— VAT format + checksum, local (no network).verify_vat_vies— live check against the official EU VIES service.lookup_peppol_participant— Peppol network discovery via the official SML/SMP route when enabled; otherwise an honest mock that says so. Onlyregistered/not_registeredare definitive —unavailable/not_configuredare never a hidden "no".get_einvoice_readiness— a composite readiness snapshot + next step.
There is no sixth tool and no send; send_peppol_invoice is absent. A tool that can't reach its provider returns a truthful unavailable rather than a guess.
4. Your first workflow (what to expect, tool by tool)
Run these over the connected session — in an agent chat you can simply ask ("verify VAT BE0123456749 in VIES"); raw JSON-RPC shown for direct calls:
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"validate_vat_number","arguments":{"vatNumber":"BE0123456749"}}}
validate_vat_numberwithBE0123456749→valid: format + checksum only, no network.verify_vat_vieswith the same number → live statusinvalid, and that is correct: this docs example is checksum-valid but not an active EU VAT registration — exactly the gap the live check exists to catch. Use your own VAT number to seevalidwith provenance (source, checkedAt).unavailablemeans VIES itself could not answer: truthful, retry later, never a guess.lookup_peppol_participantwith{"countryCode":"BE","vatId":"BE0671516647"}→ statusregisteredwithsupportsBis30Invoice: true— that is FPS BOSA, a Belgian public service whose Peppol registration is public record (it is listed in the public Peppol Directory), so this example is deterministic. Your own number returnsregisteredornot_registered(both definitive answers, not errors);unavailableis truthful and never a hidden "no".get_einvoice_readinesswith{}→ one plain state + what is missing + the next step. In this validation pilot expectnot_configured: your workspace has no send/receive provider bound — truthful, because sending is not part of this product. The four tools above work regardless.
Every accepted execution returns its result, counts one against your plan, and carries a signed receipt in _meta.receipt — verify it offline in §7.
"Accepted" is not a verdict. A 200 that counts one check only means Jithox ran the check and returned a truthful answer — it is never itself an approval of your invoice or counterparty. The verdict is decision.status below, and an answer of not_ready, needs_review or unavailable is a fully successful, billed call whose content is "not yet / can't tell". Read the status, not the HTTP code.
4b. Reading the result — the decision envelope
Every accepted result carries a uniform decision object (added alongside the tool's native fields — nothing was removed). One shape, whichever tool you call, so software and agents can act without learning five vocabularies. Full contract: docs/DECISION_GRADE_EINVOICING.md.
"decision": {
"status": "ready | not_ready | needs_review | unavailable",
"summary": "one-line conclusion",
"decision": { "canProceed": bool, "requiresHumanReview": bool,
"blockingReasons": [], "warnings": [], "nextActions": [] },
"evidence": [ { "source": "vies", "statement": "…", "observedAt": "ISO" } ],
"limitations": [ "Technical readiness only: not a legal/fiscal/compliance guarantee…" ],
"provenance": { "source": "…", "checkedAt": "ISO" },
"requestId": "…", "receiptId": "…"
}
- ready — proceed within the technical scope (
canProceed:true). Not a legal/fiscal guarantee, not a payment or delivery guarantee. - not_ready — a concrete blocker; read
blockingReasonsand donextActions. - needs_review — valid but incomplete/ambiguous (e.g. registered on Peppol but BIS 3.0 not advertised, or a sandbox-only result); a human should judge
warnings. - unavailable — a provider could not answer (VIES/Peppol timeout, not configured). This is an unknown, never a "no" — never treat it as invalid / not registered.
The decision is committed by the signed receipt (resultHash covers it), so the whole decision — not just the raw result — is verifiable offline (§7). Worked outcomes:
validate_vat_number "BE0123456749" → decision.status "ready" (format valid; warns: run VIES next)
verify_vat_vies "BE0123456749" → decision.status "not_ready" (checksum-valid but not VIES-active — by design)
lookup_peppol_participant BOSA BE0671516647 → decision.status "ready" (registered, BIS 3.0)
verify_vat_vies (VIES timeout) → decision.status "unavailable" (retry; NOT "invalid")
get_einvoice_readiness {} → decision.status "needs_review" (validation-only: no send provider bound)
5. Limits & errors
- Plan: 30 days · 10,000 accepted checks · 60/minute · 5,000/day · hard stop (no overage, no auto-renewal).
- See your usage inline: every accepted call returns
_meta.usage.entitlementwithused,remaining,perMinute,perDayandexpiresAt— your live entitlement, no dashboard needed. 402 payment_required/402 execution_budget_exhausted— no active entitlement, or the 10,000-check budget is used up.403 entitlement_expired/403 entitlement_suspended— the 30 days ended, or the payment was refunded/disputed.429 rate_limited/429 daily_cap_reached— per-minute or daily window hit; honorretry-after. A rejected call consumes nothing.403 insufficient_scope— the tool needs a scope your grant lacks.
Entitlement errors carry error.data.pricing and error.data.docs URLs, so an agent hitting 402 can hand its human the exact next step.
6. Privacy
verify_vat_vies sends only the VAT number to the EU VIES service — never an invoice, name, or email. Full VAT numbers are not logged; request logs hold only coded errors and privacy-safe timing.
7. Verify your receipts (offline, no account needed)
Every accepted execution returns a signed receipt in _meta.receipt (Ed25519). Anyone can verify one offline with our public key — no account, no payment, no API call to us at verification time. Try it in two minutes, before you even buy access:
curl -sO https://sanctions.mcp.jithox.com/receipts/public-key.pem curl -sO https://sanctions.mcp.jithox.com/receipts/verify-receipt.mjs curl -s https://sanctions.mcp.jithox.com/receipts/sample.json -o receipt.json node verify-receipt.mjs receipt.json public-key.pem # → VALID
The sample is a REAL receipt signed by the live key; its kind is "sample", which marks it a dry-run artifact — it is never proof of an execution or a payment. To verify one of your receipts, save the _meta.receipt object as receipt.json and re-run the last command. Machine-readable key metadata (JWK, key ids, canonicalization rules): /receipts/verification.
The downloadable script is exactly this, in full — audit it; it is self-contained (Node 18+, no dependencies) and never phones home:
// verify-receipt.mjs — node verify-receipt.mjs receipt.json public-key.pem
import { readFileSync } from "node:fs";
import { createPublicKey, createHash, verify } from "node:crypto";
const receipt = JSON.parse(readFileSync(process.argv[2], "utf8"));
const key = createPublicKey(readFileSync(process.argv[3], "utf8"));
const canon = (v) => v === null || typeof v !== "object" ? JSON.stringify(v)
: Array.isArray(v) ? "[" + v.map(canon).join(",") + "]"
: "{" + Object.entries(v).filter(([,x]) => x !== undefined).sort(([a],[b]) => a < b ? -1 : 1)
.map(([k,x]) => JSON.stringify(k) + ":" + canon(x)).join(",") + "}";
const spki = key.export({ type: "spki", format: "der" });
const ids = new Set(["canary-ed25519-" + createHash("sha256").update(spki).digest("hex").slice(0,16),
"canary-ed25519-" + Buffer.from(spki).toString("hex").slice(0,16)]); // legacy alias
if (receipt.alg !== "Ed25519") throw new Error("unsupported algorithm");
if (!ids.has(receipt.keyId)) throw new Error("unknown key id (not this public key)");
let input;
if (receipt.version === "2") { const { signature, ...rest } = receipt; input = "jithox.receipt.v2\n" + canon(rest); }
else if (receipt.version === "1") input = ["jithox.receipt.v1", receipt.keyId, receipt.alg, receipt.executionId,
receipt.requestHash, receipt.resultHash, receipt.tool, receipt.identityClass, receipt.usageRef,
receipt.outcome, String(receipt.billable), receipt.ts].join("\n");
else throw new Error("unsupported receipt version");
const ok = verify(null, Buffer.from(input, "utf8"), key, Buffer.from(receipt.signature, "base64"));
// JX-MCP-065: a kind:"demo" receipt (signed by the SEPARATE demo key) is labelled
// so fixture evidence is never mistaken for a live/production verification.
console.log(ok ? (receipt.kind === "demo" ? "VALID DEMO RECEIPT (fixture — not a live company verification)" : "VALID") : "INVALID (signature does not match payload)");
Error meanings: unknown key id — the receipt was not signed by this key (wrong key file, or a forged receipt); INVALID signature — the payload was altered after signing (any field, even one character); unsupported receipt version — versions other than 1/2 do not exist today; a malformed/re-ordered file is safe — canonicalization sorts keys, so formatting never changes the outcome, only content does.
What a valid signature proves: this exact receipt was issued by Jithox at the stated time and is unaltered. What it does not prove: it is not a legal/fiscal/compliance guarantee, not proof the underlying VAT/VIES status is still current, and not a payment instrument.
Receipts reference inputs/results only as SHA-256 commitments (requestHash/resultHash) — your data is never inside a receipt.
8. Versioning & deprecation
MCP protocol. Supported protocol versions today: 2025-11-25, 2025-06-18, 2025-03-26 — initialize echoes your requested version when supported. Dropping support for a protocol version is a breaking change (see notice policy below).
Tools. The five tool names are stable and will not be renamed or removed. Input/output schemas evolve additively only: new optional fields may appear; existing fields never silently change meaning or type. A field we intend to remove is first marked deprecated in its description here for at least 30 days. Stable error codes (payment_required, rate_limited, insufficient_scope, …) are additive-only. Provider statuses stay truthful: unavailable / not_configured mean exactly that — never a guessed answer.
Breaking changes. Announced on this page at least 30 days in advance. One explicit exception: security emergencies may change behaviour immediately; we disclose what changed here afterwards.
Receipts. Receipt versions currently issued: v2 (v1 was issued before 2026-07-14). Every receipt ever issued stays verifiable: old versions remain supported by the published verification rules, rotated-out public keys stay listed at /receipts/verification for at least 24 months, the legacy key-id alias is documented there, and historical receipts are never re-signed or rewritten — including after an entitlement expires or is revoked.
No uptime SLA is offered during the pilot phase; health is observable at /healthz and /readyz.
9. Support & status
Founder-direct: info.jithox@gmail.com. Health: /healthz and /readyz. Pricing: /pricing.