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 /pricingStart 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

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"}}}
  1. validate_vat_number with BE0123456749valid: format + checksum only, no network.
  2. verify_vat_vies with the same number → live status invalid, 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 see valid with provenance (source, checkedAt). unavailable means VIES itself could not answer: truthful, retry later, never a guess.
  3. lookup_peppol_participant with {"countryCode":"BE","vatId":"BE0671516647"} → status registered with supportsBis30Invoice: 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 returns registered or not_registered (both definitive answers, not errors); unavailable is truthful and never a hidden "no".
  4. get_einvoice_readiness with {} → one plain state + what is missing + the next step. In this validation pilot expect not_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": "…"
}

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

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-26initialize 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.