eleata-einvoice-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/index.js +266 -0
- package/error-fixes.json +268 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# eleata e-invoice MCP server
|
|
2
|
+
|
|
3
|
+
Validate **EU electronic invoices** and explain validation **error codes** directly from your AI
|
|
4
|
+
coding agent (Claude, Cursor, Copilot, …). It wraps the hosted [eleata](https://eleata.io) validation
|
|
5
|
+
API plus a bundled offline error-code reference.
|
|
6
|
+
|
|
7
|
+
> Never ship a broken invoice. When you ask your agent "is this FatturaPA / XRechnung / Peppol file
|
|
8
|
+
> valid?", it runs the official Schematron rules and gets back the rule id **and the fix** — before a
|
|
9
|
+
> rejection (an SdI *scarto*, a Chorus Pro refusal, a KSeF error) ever happens.
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | What it does | API key |
|
|
14
|
+
|------|--------------|---------|
|
|
15
|
+
| `validate_einvoice` | Validate one invoice (Peppol BIS 3.0, EN 16931 UBL/CII, XRechnung 3.0.x, Factur-X/ZUGFeRD, UBL, CII). Returns valid/invalid + each rule id, explanation and fix. | required |
|
|
16
|
+
| `list_formats` | List the formats eleata validates today + roadmap. | none |
|
|
17
|
+
| `explain_error_code` | Explain one error code (e.g. `00400`, `BR-DE-21`) in plain English, with the fix. Works offline. | none |
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
1. Get a free API key (200 validations/month, no card) at <https://eleata.io/signup/>.
|
|
22
|
+
2. Add the server to your agent's MCP config.
|
|
23
|
+
|
|
24
|
+
### Claude Desktop / Claude Code (`claude_desktop_config.json` or `.mcp.json`)
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"eleata-einvoice": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "eleata-einvoice-mcp"],
|
|
32
|
+
"env": { "EINVOICE_API_KEY": "your_free_key_here" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Cursor (`~/.cursor/mcp.json`)
|
|
39
|
+
|
|
40
|
+
Same block as above.
|
|
41
|
+
|
|
42
|
+
## Example prompts
|
|
43
|
+
|
|
44
|
+
- "Validate this XRechnung file and tell me what to fix" (paste the XML)
|
|
45
|
+
- "What does FatturaPA error 00400 mean and how do I fix it?"
|
|
46
|
+
- "Which e-invoice formats can you validate today?"
|
|
47
|
+
|
|
48
|
+
## Privacy
|
|
49
|
+
|
|
50
|
+
`validate_einvoice` sends the invoice you pass to the hosted eleata API for validation. `list_formats`
|
|
51
|
+
and `explain_error_code` are local/offline (the error-code reference is bundled). See
|
|
52
|
+
<https://eleata.io/privacy/>.
|
|
53
|
+
|
|
54
|
+
## Links
|
|
55
|
+
|
|
56
|
+
- Web validator, CLI and GitHub Action: <https://eleata.io>
|
|
57
|
+
- Error-code reference: <https://eleata.io/error/>
|
|
58
|
+
- CLI (`npx @eleata/validate-einvoice`): same API, for CI/CD
|
|
59
|
+
|
|
60
|
+
MIT licensed. Schematron engines: [Mustang](https://www.mustangproject.org/) /
|
|
61
|
+
[phive](https://github.com/phax/phive). Schematron rules from CEN, OpenPeppol, KoSIT.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* eleata e-invoice MCP server
|
|
4
|
+
* ---------------------------
|
|
5
|
+
* Lets an AI coding agent (Claude, Cursor, Copilot, …) validate EU e-invoices
|
|
6
|
+
* and explain validation error codes, by wrapping the hosted eleata API
|
|
7
|
+
* (https://api.eleata.io) plus a bundled offline error-code reference.
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* - validate_einvoice(content, format?, is_pdf?) -> POST /v1/validate
|
|
11
|
+
* - list_formats() -> GET /v1/formats
|
|
12
|
+
* - explain_error_code(rule_id) -> offline error-fixes.json
|
|
13
|
+
*
|
|
14
|
+
* Auth: set EINVOICE_API_KEY (a free key from https://eleata.io/signup/).
|
|
15
|
+
* validate_einvoice needs it; list_formats and explain_error_code do not.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
const API_BASE = (process.env.EINVOICE_API_BASE || "https://api.eleata.io").replace(/\/+$/, "");
|
|
24
|
+
const API_KEY = process.env.EINVOICE_API_KEY || "";
|
|
25
|
+
const USER_AGENT = "eleata-einvoice-mcp/0.1.0";
|
|
26
|
+
const TIMEOUT_MS = 25_000;
|
|
27
|
+
// Cap input so a runaway agent can't OOM the local process or send a huge payload.
|
|
28
|
+
const MAX_INPUT_CHARS = 8_000_000;
|
|
29
|
+
const FORMAT_VALUES = [
|
|
30
|
+
"auto",
|
|
31
|
+
"peppol-bis-3",
|
|
32
|
+
"en16931-ubl",
|
|
33
|
+
"en16931-cii",
|
|
34
|
+
"xrechnung-ubl",
|
|
35
|
+
"xrechnung-cii",
|
|
36
|
+
"factur-x",
|
|
37
|
+
"ubl",
|
|
38
|
+
"cii",
|
|
39
|
+
];
|
|
40
|
+
let ERROR_FIXES = {};
|
|
41
|
+
try {
|
|
42
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
// error-fixes.json sits at the package root, next to dist/.
|
|
44
|
+
const raw = JSON.parse(readFileSync(join(here, "..", "error-fixes.json"), "utf8"));
|
|
45
|
+
ERROR_FIXES = (raw.rules ?? {});
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
// explain_error_code is a core offline feature — make the failure visible (stderr, not the MCP channel).
|
|
49
|
+
process.stderr.write(`warning: could not load bundled error-fixes.json: ${e.message}\n`);
|
|
50
|
+
ERROR_FIXES = {};
|
|
51
|
+
}
|
|
52
|
+
// ---- tool definitions (explicit JSON Schema) ------------------------------
|
|
53
|
+
const TOOLS = [
|
|
54
|
+
{
|
|
55
|
+
name: "validate_einvoice",
|
|
56
|
+
description: "Validate an EU electronic invoice against the official Schematron rules " +
|
|
57
|
+
"(Peppol BIS 3.0, EN 16931 UBL/CII, XRechnung 3.0.x, Factur-X/ZUGFeRD, UBL, CII). " +
|
|
58
|
+
"Returns whether it is valid and, for each violation, the rule id, a plain-English " +
|
|
59
|
+
"explanation and a suggested fix. Use this before a developer ships or transmits an " +
|
|
60
|
+
"invoice so a rejection (an SdI scarto, a Chorus Pro refusal, a KSeF error) is caught early. " +
|
|
61
|
+
"Requires EINVOICE_API_KEY (a free key from https://eleata.io/signup/).",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
properties: {
|
|
66
|
+
content: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "The invoice to validate. For XML formats, the raw XML text. " +
|
|
69
|
+
"For a Factur-X / ZUGFeRD PDF, the base64-encoded PDF bytes (set is_pdf=true).",
|
|
70
|
+
maxLength: MAX_INPUT_CHARS,
|
|
71
|
+
},
|
|
72
|
+
format: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Format hint. Default: auto (the server sniffs XML vs PDF and the profile).",
|
|
75
|
+
enum: FORMAT_VALUES,
|
|
76
|
+
default: "auto",
|
|
77
|
+
},
|
|
78
|
+
is_pdf: {
|
|
79
|
+
type: "boolean",
|
|
80
|
+
description: "Set true if `content` is a base64-encoded PDF (Factur-X/ZUGFeRD). Default false.",
|
|
81
|
+
default: false,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ["content"],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "list_formats",
|
|
89
|
+
description: "List the EU e-invoice formats eleata can validate today, plus what is on the roadmap " +
|
|
90
|
+
"(e.g. FatturaPA, KSeF). No API key required.",
|
|
91
|
+
inputSchema: { type: "object", additionalProperties: false, properties: {} },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "explain_error_code",
|
|
95
|
+
description: "Explain a single e-invoice validation error code (e.g. a FatturaPA SdI control like 00400, " +
|
|
96
|
+
"or an XRechnung rule like BR-DE-21) in plain English, with the suggested fix and an example. " +
|
|
97
|
+
"Works offline; no API key required.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
additionalProperties: false,
|
|
101
|
+
properties: {
|
|
102
|
+
rule_id: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "The rule id / error code, e.g. '00400', 'BR-DE-21', 'PEPPOL-EN16931-R053'.",
|
|
105
|
+
maxLength: 128,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
required: ["rule_id"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
async function httpRequest(url, init) {
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
115
|
+
const text = await res.text();
|
|
116
|
+
return { ok: true, status: res.status, text };
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
const err = e;
|
|
120
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
121
|
+
return { ok: false, message: `the eleata API did not respond within ${TIMEOUT_MS / 1000}s` };
|
|
122
|
+
}
|
|
123
|
+
return { ok: false, message: `could not reach the eleata API at ${API_BASE}` };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ---- handlers --------------------------------------------------------------
|
|
127
|
+
async function validateEinvoice(args) {
|
|
128
|
+
if (!API_KEY) {
|
|
129
|
+
return ("No API key configured. Set the EINVOICE_API_KEY environment variable for this MCP server. " +
|
|
130
|
+
"Get a free key (200 validations/month, no card) at https://eleata.io/signup/.");
|
|
131
|
+
}
|
|
132
|
+
const content = typeof args.content === "string" ? args.content : "";
|
|
133
|
+
if (!content)
|
|
134
|
+
return "No invoice content provided.";
|
|
135
|
+
if (content.length > MAX_INPUT_CHARS) {
|
|
136
|
+
return (`Input too large (${content.length} chars; max ${MAX_INPUT_CHARS}). ` +
|
|
137
|
+
"For large or many files, use the CLI (npx @eleata/validate-einvoice) or the batch endpoint.");
|
|
138
|
+
}
|
|
139
|
+
const isPdf = args.is_pdf === true;
|
|
140
|
+
let format = typeof args.format === "string" ? args.format.trim() : "auto";
|
|
141
|
+
if (!FORMAT_VALUES.includes(format))
|
|
142
|
+
format = "auto";
|
|
143
|
+
const body = isPdf ? Buffer.from(content, "base64") : Buffer.from(content, "utf8");
|
|
144
|
+
const contentType = isPdf ? "application/pdf" : "application/xml";
|
|
145
|
+
const url = `${API_BASE}/v1/validate?format=${encodeURIComponent(format)}`;
|
|
146
|
+
const r = await httpRequest(url, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": contentType, "User-Agent": USER_AGENT },
|
|
149
|
+
body,
|
|
150
|
+
});
|
|
151
|
+
if (!r.ok)
|
|
152
|
+
return `Validation request failed: ${r.message}.`;
|
|
153
|
+
let data;
|
|
154
|
+
try {
|
|
155
|
+
data = JSON.parse(r.text);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Don't echo the raw body (could be an HTML error page / proxy trace).
|
|
159
|
+
return `The eleata API returned an unexpected (non-JSON) response (HTTP ${r.status}). It may be down or rate-limiting; try again shortly.`;
|
|
160
|
+
}
|
|
161
|
+
if (r.status < 200 || r.status >= 300) {
|
|
162
|
+
// Prefer the structured error message; never dump the raw body.
|
|
163
|
+
const msg = (data && (data.error?.message || data.detail || data.message)) ||
|
|
164
|
+
`HTTP ${r.status}`;
|
|
165
|
+
return `Validation request failed: ${String(msg).slice(0, 300)}`;
|
|
166
|
+
}
|
|
167
|
+
const valid = data.valid === true;
|
|
168
|
+
const detected = (typeof data.format === "string" && data.format) || format;
|
|
169
|
+
const ruleset = data.ruleset || data.applied_ruleset || "";
|
|
170
|
+
const rawErrors = (Array.isArray(data.errors) && data.errors) ||
|
|
171
|
+
(Array.isArray(data.issues) && data.issues) ||
|
|
172
|
+
(Array.isArray(data.violations) && data.violations) ||
|
|
173
|
+
[];
|
|
174
|
+
const errors = rawErrors.filter((e) => e && typeof e === "object");
|
|
175
|
+
const lines = [];
|
|
176
|
+
lines.push(valid
|
|
177
|
+
? `✅ VALID — ${detected}`
|
|
178
|
+
: `❌ INVALID — ${detected} (${errors.length} issue${errors.length === 1 ? "" : "s"})`);
|
|
179
|
+
if (ruleset)
|
|
180
|
+
lines.push(`ruleset: ${String(ruleset)}`);
|
|
181
|
+
for (const err of errors) {
|
|
182
|
+
const id = err.rule_id || err.id || "?";
|
|
183
|
+
const sev = err.severity ? `[${String(err.severity)}] ` : "";
|
|
184
|
+
const loc = err.location ? ` (at ${String(err.location)})` : "";
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push(`• ${sev}${id}${loc}`);
|
|
187
|
+
if (err.message)
|
|
188
|
+
lines.push(` ${String(err.message)}`);
|
|
189
|
+
if (err.fix_hint)
|
|
190
|
+
lines.push(` fix: ${String(err.fix_hint)}`);
|
|
191
|
+
}
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
async function listFormats() {
|
|
195
|
+
const r = await httpRequest(`${API_BASE}/v1/formats`, { headers: { "User-Agent": USER_AGENT } });
|
|
196
|
+
if (!r.ok)
|
|
197
|
+
return `Could not list formats: ${r.message}.`;
|
|
198
|
+
if (r.status < 200 || r.status >= 300) {
|
|
199
|
+
return `Could not list formats (HTTP ${r.status}). The service may be temporarily unavailable.`;
|
|
200
|
+
}
|
|
201
|
+
return r.text;
|
|
202
|
+
}
|
|
203
|
+
function explainErrorCode(args) {
|
|
204
|
+
const id = typeof args.rule_id === "string" ? args.rule_id.trim() : "";
|
|
205
|
+
if (!id)
|
|
206
|
+
return "No rule_id provided.";
|
|
207
|
+
const fix = ERROR_FIXES[id];
|
|
208
|
+
if (!fix) {
|
|
209
|
+
const known = Object.keys(ERROR_FIXES);
|
|
210
|
+
const sample = known.slice(0, 10).join(", ");
|
|
211
|
+
return (`No bundled explanation for '${id}'. ` +
|
|
212
|
+
(known.length
|
|
213
|
+
? `Known codes include: ${sample}${known.length > 10 ? ", …" : ""}. ` +
|
|
214
|
+
`See the full list at https://eleata.io/error/.`
|
|
215
|
+
: `The offline error-code reference is unavailable; see https://eleata.io/error/.`));
|
|
216
|
+
}
|
|
217
|
+
const out = [];
|
|
218
|
+
out.push(`${id}${fix.title ? ` — ${fix.title}` : ""} (${fix.format})`);
|
|
219
|
+
out.push("");
|
|
220
|
+
out.push(`What it means: ${fix.explanation}`);
|
|
221
|
+
out.push("");
|
|
222
|
+
out.push(`How to fix it: ${fix.suggested_fix}`);
|
|
223
|
+
if (fix.example) {
|
|
224
|
+
out.push("");
|
|
225
|
+
out.push("Example:");
|
|
226
|
+
out.push(fix.example);
|
|
227
|
+
}
|
|
228
|
+
out.push("");
|
|
229
|
+
out.push(`Reference: https://eleata.io/error/${id}/`);
|
|
230
|
+
return out.join("\n");
|
|
231
|
+
}
|
|
232
|
+
// ---- server wiring ---------------------------------------------------------
|
|
233
|
+
const server = new Server({ name: "eleata-einvoice", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
234
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
235
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
236
|
+
const { name, arguments: args } = req.params;
|
|
237
|
+
try {
|
|
238
|
+
let textOut;
|
|
239
|
+
if (name === "validate_einvoice") {
|
|
240
|
+
textOut = await validateEinvoice((args ?? {}));
|
|
241
|
+
}
|
|
242
|
+
else if (name === "list_formats") {
|
|
243
|
+
textOut = await listFormats();
|
|
244
|
+
}
|
|
245
|
+
else if (name === "explain_error_code") {
|
|
246
|
+
textOut = explainErrorCode((args ?? {}));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
250
|
+
}
|
|
251
|
+
return { content: [{ type: "text", text: textOut }] };
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
return { isError: true, content: [{ type: "text", text: `Tool ${name} failed: ${e.message}` }] };
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
async function main() {
|
|
258
|
+
const transport = new StdioServerTransport();
|
|
259
|
+
await server.connect(transport);
|
|
260
|
+
// stderr is safe for logs; stdout is the MCP channel.
|
|
261
|
+
process.stderr.write("eleata-einvoice MCP server running on stdio\n");
|
|
262
|
+
}
|
|
263
|
+
main().catch((e) => {
|
|
264
|
+
process.stderr.write(`fatal: ${e.message}\n`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|
package/error-fixes.json
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Error -> fix mapping, first wave: FatturaPA (SdI 00xxx) + XRechnung (BR-DE-*). Edit freely; surfaced by error_fixes.py and by the /error/{rule-id} content pages. Keep entries factual and version-agnostic where possible.",
|
|
3
|
+
"docs_base": "https://eleata.io/error/",
|
|
4
|
+
"rules": {
|
|
5
|
+
"00200": {
|
|
6
|
+
"format": "fatturapa",
|
|
7
|
+
"title": "File non conforme al formato (schema XSD)",
|
|
8
|
+
"explanation": "The FatturaPA XML does not conform to the official AgID schema (Schema_del_file_xml_FatturaPA v1.2.x): a required element is missing, an element is in the wrong order, or a value does not match the type/pattern/enumeration the schema allows.",
|
|
9
|
+
"suggested_fix": "Read the schema-violation message that follows: it names the offending element. Fix the element order (the FatturaPA schema sequences are strict), add the missing mandatory element, or correct the value to match the allowed type/codelist. Re-validate against Schema_del_file_xml_FatturaPA_v1.2.2.xsd before re-submitting to the SdI.",
|
|
10
|
+
"example": "<!-- WRONG: PECDestinatario before ContattiTrasmittente --> ... <CodiceDestinatario>0000000</CodiceDestinatario><PECDestinatario>x@pec.it</PECDestinatario><ContattiTrasmittente/> <!-- RIGHT --> <CodiceDestinatario>0000000</CodiceDestinatario><ContattiTrasmittente/><PECDestinatario>x@pec.it</PECDestinatario>"
|
|
11
|
+
},
|
|
12
|
+
"00300": {
|
|
13
|
+
"format": "fatturapa",
|
|
14
|
+
"title": "IdFiscaleIVA del CedentePrestatore non valido",
|
|
15
|
+
"explanation": "The supplier's VAT identifier (CedentePrestatore/DatiAnagrafici/IdFiscaleIVA) is not well-formed. For an Italian supplier IdPaese must be 'IT' and IdCodice must be exactly 11 digits.",
|
|
16
|
+
"suggested_fix": "Set IdPaese to a valid ISO 3166-1 alpha-2 country code; for IT, make IdCodice the 11-digit partita IVA (no spaces, no 'IT' prefix inside IdCodice).",
|
|
17
|
+
"example": "<IdFiscaleIVA><IdPaese>IT</IdPaese><IdCodice>01234567890</IdCodice></IdFiscaleIVA>"
|
|
18
|
+
},
|
|
19
|
+
"00301": {
|
|
20
|
+
"format": "fatturapa",
|
|
21
|
+
"title": "IdFiscaleIVA del CessionarioCommittente non valido",
|
|
22
|
+
"explanation": "The buyer's VAT identifier (CessionarioCommittente/DatiAnagrafici/IdFiscaleIVA) is not well-formed. For an Italian buyer IdPaese must be 'IT' and IdCodice must be exactly 11 digits.",
|
|
23
|
+
"suggested_fix": "Set IdPaese to a valid ISO country code; for IT, make IdCodice the 11-digit partita IVA. If the buyer is a private individual or has no VAT number, omit IdFiscaleIVA and use CodiceFiscale instead.",
|
|
24
|
+
"example": "<IdFiscaleIVA><IdPaese>IT</IdPaese><IdCodice>09876543210</IdCodice></IdFiscaleIVA>"
|
|
25
|
+
},
|
|
26
|
+
"00305": {
|
|
27
|
+
"format": "fatturapa",
|
|
28
|
+
"title": "CodiceFiscale del CessionarioCommittente non valido",
|
|
29
|
+
"explanation": "The buyer's Italian tax code (CessionarioCommittente/DatiAnagrafici/CodiceFiscale) is not a valid codice fiscale: it must be 16 alphanumeric characters (natural person) or 11 digits (entity).",
|
|
30
|
+
"suggested_fix": "Use the 16-character codice fiscale for individuals (e.g. RSSMRA80A01H501U) or the 11-digit code for organisations. Remove spaces and check the character/checksum pattern.",
|
|
31
|
+
"example": "<CodiceFiscale>RSSMRA80A01H501U</CodiceFiscale>"
|
|
32
|
+
},
|
|
33
|
+
"00311": {
|
|
34
|
+
"format": "fatturapa",
|
|
35
|
+
"title": "CodiceDestinatario non valido",
|
|
36
|
+
"explanation": "The recipient code (DatiTrasmissione/CodiceDestinatario) is not valid. It must be 6 alphanumeric characters for transmissions to a PA (FormatoTrasmissione=FPA12) or 7 for private recipients (FPR12). '0000000' (7 zeros) is the wildcard used when delivery is via PEC.",
|
|
37
|
+
"suggested_fix": "Use the 6-char IPA code for a PA recipient, or the 7-char SDI channel code the buyer provided. If you don't have it and the buyer has a PEC, set CodiceDestinatario to 0000000 and fill PECDestinatario (FPR12 only).",
|
|
38
|
+
"example": "<CodiceDestinatario>ABC1234</CodiceDestinatario> <!-- or, with PEC --> <CodiceDestinatario>0000000</CodiceDestinatario>"
|
|
39
|
+
},
|
|
40
|
+
"00312": {
|
|
41
|
+
"format": "fatturapa",
|
|
42
|
+
"title": "CodiceDestinatario non coerente con FormatoTrasmissione",
|
|
43
|
+
"explanation": "The length of CodiceDestinatario does not match FormatoTrasmissione: FPA12 (PA) requires 6 characters, FPR12 (private) requires 7.",
|
|
44
|
+
"suggested_fix": "If FormatoTrasmissione is FPR12 use a 7-character code (or 0000000 with a PEC); if FPA12 use the 6-character IPA office code."
|
|
45
|
+
},
|
|
46
|
+
"00320": {
|
|
47
|
+
"format": "fatturapa",
|
|
48
|
+
"title": "CodiceDestinatario errato per FPA12",
|
|
49
|
+
"explanation": "For a Pubblica Amministrazione invoice (FormatoTrasmissione=FPA12) the CodiceDestinatario must be the 6-character codice univoco ufficio from the IPA index.",
|
|
50
|
+
"suggested_fix": "Look up the recipient office in https://indicepa.gov.it and use its 6-character code; do not pad to 7 characters and do not use 0000000 for a PA."
|
|
51
|
+
},
|
|
52
|
+
"00321": {
|
|
53
|
+
"format": "fatturapa",
|
|
54
|
+
"title": "CodiceDestinatario '0000000' ammesso solo con FPR12",
|
|
55
|
+
"explanation": "The wildcard recipient code 0000000 is only allowed for private-recipient transmissions (FormatoTrasmissione=FPR12), not for FPA12.",
|
|
56
|
+
"suggested_fix": "For FPR12 keep 0000000 and supply PECDestinatario. For a PA, use the real 6-character IPA office code instead."
|
|
57
|
+
},
|
|
58
|
+
"00400": {
|
|
59
|
+
"format": "fatturapa",
|
|
60
|
+
"title": "Natura presente con AliquotaIVA diversa da zero (riga di dettaglio)",
|
|
61
|
+
"explanation": "On a detail line (DettaglioLinee) you put a Natura code (exemption/reverse-charge/etc.) together with a non-zero AliquotaIVA. SdI rule 00400: when Natura is present on a line, AliquotaIVA must be 0.00.",
|
|
62
|
+
"suggested_fix": "If the line really is taxable, remove the Natura element and keep the real VAT rate. If it is exempt/non-taxable/reverse-charge, set AliquotaIVA to 0.00 and keep the correct Natura code (N1..N7 with sub-codes).",
|
|
63
|
+
"example": "<AliquotaIVA>0.00</AliquotaIVA><Natura>N2.2</Natura>"
|
|
64
|
+
},
|
|
65
|
+
"00401": {
|
|
66
|
+
"format": "fatturapa",
|
|
67
|
+
"title": "AliquotaIVA pari a zero senza Natura (riga di dettaglio)",
|
|
68
|
+
"explanation": "On a detail line you set AliquotaIVA to 0.00 but did not specify a Natura code. SdI rule 00401: a 0% line must carry a Natura code that explains why VAT is not applied.",
|
|
69
|
+
"suggested_fix": "Add the appropriate Natura code (N1 escluse, N2.x non soggette, N3.x non imponibili, N4 esenti, N5 regime del margine, N6.x inversione contabile, N7 IVA assolta in altro Stato UE). If VAT should actually apply, set the real rate instead of 0.00."
|
|
70
|
+
},
|
|
71
|
+
"00413": {
|
|
72
|
+
"format": "fatturapa",
|
|
73
|
+
"title": "Natura presente con AliquotaIVA diversa da zero (riepilogo)",
|
|
74
|
+
"explanation": "In a VAT summary line (DatiRiepilogo) Natura is present together with a non-zero AliquotaIVA. SdI rule 00413: a summary line with a Natura code must have AliquotaIVA = 0.00 (and vice-versa).",
|
|
75
|
+
"suggested_fix": "Split your summary so that taxable amounts go into a DatiRiepilogo with a real rate and no Natura, and non-taxable amounts go into a separate DatiRiepilogo with AliquotaIVA 0.00 and the matching Natura."
|
|
76
|
+
},
|
|
77
|
+
"00414": {
|
|
78
|
+
"format": "fatturapa",
|
|
79
|
+
"title": "AliquotaIVA pari a zero senza Natura (riepilogo)",
|
|
80
|
+
"explanation": "A VAT summary line (DatiRiepilogo) has AliquotaIVA 0.00 but no Natura code. SdI rule 00414: a 0% summary line must explain the reason via a Natura code.",
|
|
81
|
+
"suggested_fix": "Add the correct Natura code to that DatiRiepilogo block, or use the real VAT rate if the amount is in fact taxable."
|
|
82
|
+
},
|
|
83
|
+
"00415": {
|
|
84
|
+
"format": "fatturapa",
|
|
85
|
+
"title": "Natura N6 (inversione contabile) con EsigibilitaIVA diversa da 'S'",
|
|
86
|
+
"explanation": "For reverse-charge transactions (Natura N6 / N6.x) the SdI expects EsigibilitaIVA = 'S' (split/scissione is not applicable; immediate exigibility is the convention used here).",
|
|
87
|
+
"suggested_fix": "When Natura starts with N6, set EsigibilitaIVA to 'S'. Use 'I' (immediata) / 'D' (differita) only for ordinary taxable summaries; 'S' (scissione dei pagamenti) only for split-payment to a PA."
|
|
88
|
+
},
|
|
89
|
+
"00417": {
|
|
90
|
+
"format": "fatturapa",
|
|
91
|
+
"title": "CessionarioCommittente senza IdFiscaleIVA né CodiceFiscale",
|
|
92
|
+
"explanation": "The buyer block must identify the buyer with at least one of IdFiscaleIVA (VAT number) or CodiceFiscale (tax code). SdI rule 00417: both were omitted.",
|
|
93
|
+
"suggested_fix": "Add the buyer's partita IVA in IdFiscaleIVA (IdPaese + IdCodice) for a business, or the codice fiscale in CodiceFiscale for an individual / entity without VAT. For a foreign buyer without an Italian id, use IdFiscaleIVA with the foreign country code."
|
|
94
|
+
},
|
|
95
|
+
"00423": {
|
|
96
|
+
"format": "fatturapa",
|
|
97
|
+
"title": "Importo non coerente / valore negativo non ammesso",
|
|
98
|
+
"explanation": "An amount is not coherent with the document totals, or a negative line/total amount was used on a document type that does not allow it (e.g. TD01 invoice). Credit notes (TD04) use positive amounts on a TD04 document, not negatives on an invoice.",
|
|
99
|
+
"suggested_fix": "Recompute: ImportoTotaleDocumento should equal the sum over DatiRiepilogo of (ImponibileImporto + Imposta), adjusted for Arrotondamento, ScontoMaggiorazione, bollo, cassa and ritenuta as applicable. To reverse a previous invoice, issue a TD04 credit note with positive amounts rather than a negative invoice."
|
|
100
|
+
},
|
|
101
|
+
"00424": {
|
|
102
|
+
"format": "fatturapa",
|
|
103
|
+
"title": "Imposta non calcolata correttamente (riepilogo)",
|
|
104
|
+
"explanation": "In a DatiRiepilogo block the declared Imposta does not match ImponibileImporto x AliquotaIVA / 100 within the rounding tolerance (the SdI allows the difference declared in Arrotondamento, typically a cent or two).",
|
|
105
|
+
"suggested_fix": "Set Imposta = round(ImponibileImporto * AliquotaIVA / 100, 2). If you intentionally round differently, declare the delta in the Arrotondamento element of the same DatiRiepilogo block.",
|
|
106
|
+
"example": "<AliquotaIVA>22.00</AliquotaIVA><ImponibileImporto>5.00</ImponibileImporto><Imposta>1.10</Imposta>"
|
|
107
|
+
},
|
|
108
|
+
"00425": {
|
|
109
|
+
"format": "fatturapa",
|
|
110
|
+
"title": "Numero documento non valido",
|
|
111
|
+
"explanation": "The invoice number (DatiGeneraliDocumento/Numero) must contain at least one numeric digit. SdI rule 00425 rejects purely alphabetic numbers.",
|
|
112
|
+
"suggested_fix": "Include the running number in the Numero value, e.g. '2026/123' or 'INV-000123'."
|
|
113
|
+
},
|
|
114
|
+
"00427": {
|
|
115
|
+
"format": "fatturapa",
|
|
116
|
+
"title": "TipoRitenuta mancante con blocco DatiRitenuta presente",
|
|
117
|
+
"explanation": "When a DatiRitenuta block is present, TipoRitenuta is mandatory (RT01 ritenuta persone fisiche, RT02 persone giuridiche, RT03 INPS, RT04 ENASARCO, RT05 ENPAM, RT06 altro).",
|
|
118
|
+
"suggested_fix": "Add the TipoRitenuta element with the correct code, plus ImportoRitenuta, AliquotaRitenuta and CausalePagamento."
|
|
119
|
+
},
|
|
120
|
+
"00428": {
|
|
121
|
+
"format": "fatturapa",
|
|
122
|
+
"title": "TipoCassa o AlCassa non valido",
|
|
123
|
+
"explanation": "In DatiCassaPrevidenziale, TipoCassa must be one of the TC01..TC22 codes and the rates/amounts must be present and consistent.",
|
|
124
|
+
"suggested_fix": "Use the correct TipoCassa code for the professional fund, and fill AlCassa (rate), ImportoContributoCassa, ImponibileCassa and AliquotaIVA accordingly."
|
|
125
|
+
},
|
|
126
|
+
"00429": {
|
|
127
|
+
"format": "fatturapa",
|
|
128
|
+
"title": "Natura non valida",
|
|
129
|
+
"explanation": "The Natura code is not one of the allowed values. Since spec v1.6 (2021-01-01) the generic N2, N3 and N6 are no longer accepted at detail level — you must use the sub-codes (N2.1, N2.2, N3.1..N3.6, N6.1..N6.9).",
|
|
130
|
+
"suggested_fix": "Replace the generic code with the correct sub-code: N2.1 (non soggette art. 7-7septies), N2.2 (altre non soggette), N3.1..N3.6 (non imponibili: esportazioni, cessioni intra, ecc.), N6.1..N6.9 (inversione contabile per tipologia), N1/N4/N5/N7 as before."
|
|
131
|
+
},
|
|
132
|
+
"00430": {
|
|
133
|
+
"format": "fatturapa",
|
|
134
|
+
"title": "TipoDocumento non valido o incoerente",
|
|
135
|
+
"explanation": "TipoDocumento is not one of the allowed codes (TD01 fattura, TD02 acconto/anticipo, TD03 acconto su parcella, TD04 nota di credito, TD05 nota di debito, TD06 parcella, TD16..TD28 autofattura/reverse-charge/esterometro), or it is inconsistent with the rest of the document.",
|
|
136
|
+
"suggested_fix": "Pick the code that matches the operation. Use TD04 for a credit note (not a negative TD01), TD16/TD17/TD18/TD19 for the various reverse-charge self-invoices, TD24/TD25 for deferred invoices, TD20 for omitted-invoice self-billing."
|
|
137
|
+
},
|
|
138
|
+
"00437": {
|
|
139
|
+
"format": "fatturapa",
|
|
140
|
+
"title": "Natura/AliquotaIVA incoerente con il riepilogo (riga vs riepilogo)",
|
|
141
|
+
"explanation": "A detail line declares a Natura/AliquotaIVA combination for which there is no matching DatiRiepilogo block (or vice-versa). Every distinct (AliquotaIVA, Natura) pair used on the lines must have its own summary block.",
|
|
142
|
+
"suggested_fix": "For each distinct (AliquotaIVA, Natura) used across DettaglioLinee, add a DatiRiepilogo block with the same AliquotaIVA and Natura, the summed ImponibileImporto and the computed Imposta."
|
|
143
|
+
},
|
|
144
|
+
"00443": {
|
|
145
|
+
"format": "fatturapa",
|
|
146
|
+
"title": "Riepilogo con Natura N6 e Imposta diversa da zero",
|
|
147
|
+
"explanation": "For reverse-charge summaries (Natura N6.x) the supplier does not charge VAT, so Imposta must be 0.00 in that DatiRiepilogo block (the buyer self-assesses).",
|
|
148
|
+
"suggested_fix": "Set Imposta to 0.00 and AliquotaIVA to 0.00 in the N6.x summary block; the buyer issues the corresponding integration/self-invoice (TD16) with the VAT."
|
|
149
|
+
},
|
|
150
|
+
"00444": {
|
|
151
|
+
"format": "fatturapa",
|
|
152
|
+
"title": "RegimeFiscale non valido",
|
|
153
|
+
"explanation": "RegimeFiscale (CedentePrestatore/DatiAnagrafici/RegimeFiscale) is not one of the allowed codes RF01..RF19.",
|
|
154
|
+
"suggested_fix": "Use the code for your tax regime: RF01 ordinario, RF02/RF04/RF05/RF06 special schemes, RF18 altro, RF19 forfettario (regime forfettario L.190/2014). Remove any free text."
|
|
155
|
+
},
|
|
156
|
+
"00471": {
|
|
157
|
+
"format": "fatturapa",
|
|
158
|
+
"title": "TipoDocumento TD01/TD02/TD03/TD06 incompatibile con la presenza del CedentePrestatore = CessionarioCommittente",
|
|
159
|
+
"explanation": "For ordinary invoice document types the supplier (CedentePrestatore) and the buyer (CessionarioCommittente) must be different parties; a self-invoice must use the autofattura document types (TD16..TD27) instead.",
|
|
160
|
+
"suggested_fix": "If you are issuing a self-invoice (reverse charge / esterometro), change TipoDocumento to the matching TD1x..TD2x code; otherwise correct the party that was filled in by mistake."
|
|
161
|
+
},
|
|
162
|
+
"00472": {
|
|
163
|
+
"format": "fatturapa",
|
|
164
|
+
"title": "TipoDocumento di autofattura con CedentePrestatore diverso dal CessionarioCommittente",
|
|
165
|
+
"explanation": "For autofattura document types where the law requires it (e.g. TD20), the CessionarioCommittente that issues the document must coincide with the party indicated; mismatches are rejected.",
|
|
166
|
+
"suggested_fix": "Align the CedentePrestatore / CessionarioCommittente blocks with the rule for the chosen TDxx code (see the SdI 'controlli' table), or pick a different TipoDocumento."
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
"BR-DE-1": {
|
|
170
|
+
"format": "xrechnung",
|
|
171
|
+
"title": "Payment instructions (BG-16) required",
|
|
172
|
+
"explanation": "XRechnung requires the document to include payment instructions: at least one PAYMENT MEANS group (BG-16) describing how the invoice is to be paid.",
|
|
173
|
+
"suggested_fix": "Add a cac:PaymentMeans (UBL) / ram:SpecifiedTradeSettlementPaymentMeans (CII) with the payment means type code (BT-81) and the relevant account details (e.g. IBAN in BT-84 for a credit transfer)."
|
|
174
|
+
},
|
|
175
|
+
"BR-DE-2": {
|
|
176
|
+
"format": "xrechnung",
|
|
177
|
+
"title": "Seller contact (BG-6) required",
|
|
178
|
+
"explanation": "XRechnung requires the SELLER CONTACT group (BG-6) — a contact point at the seller — to be present.",
|
|
179
|
+
"suggested_fix": "Add cac:Contact under the seller party with a contact name (BT-41), a telephone number (BT-42) and an email address (BT-43)."
|
|
180
|
+
},
|
|
181
|
+
"BR-DE-3": {
|
|
182
|
+
"format": "xrechnung",
|
|
183
|
+
"title": "Seller city (BT-37) required",
|
|
184
|
+
"explanation": "XRechnung requires the seller's city name (BT-37) in the seller postal address.",
|
|
185
|
+
"suggested_fix": "Fill cac:PostalAddress/cbc:CityName for the AccountingSupplierParty (e.g. <cbc:CityName>Berlin</cbc:CityName>)."
|
|
186
|
+
},
|
|
187
|
+
"BR-DE-4": {
|
|
188
|
+
"format": "xrechnung",
|
|
189
|
+
"title": "Seller post code (BT-38) required",
|
|
190
|
+
"explanation": "XRechnung requires the seller's postal code (BT-38) in the seller postal address.",
|
|
191
|
+
"suggested_fix": "Fill cac:PostalAddress/cbc:PostalZone for the AccountingSupplierParty (e.g. <cbc:PostalZone>10115</cbc:PostalZone>)."
|
|
192
|
+
},
|
|
193
|
+
"BR-DE-5": {
|
|
194
|
+
"format": "xrechnung",
|
|
195
|
+
"title": "Seller contact point name (BT-41) required",
|
|
196
|
+
"explanation": "Within the SELLER CONTACT group, the contact point name (BT-41) must be present.",
|
|
197
|
+
"suggested_fix": "Add cbc:Name inside cac:Contact for the seller party (e.g. <cbc:Name>Buchhaltung</cbc:Name>)."
|
|
198
|
+
},
|
|
199
|
+
"BR-DE-6": {
|
|
200
|
+
"format": "xrechnung",
|
|
201
|
+
"title": "Seller contact telephone (BT-42) required",
|
|
202
|
+
"explanation": "Within the SELLER CONTACT group, the telephone number (BT-42) must be present.",
|
|
203
|
+
"suggested_fix": "Add cac:Contact/cbc:Telephone for the seller party (e.g. <cbc:Telephone>+49 30 1234567</cbc:Telephone>)."
|
|
204
|
+
},
|
|
205
|
+
"BR-DE-7": {
|
|
206
|
+
"format": "xrechnung",
|
|
207
|
+
"title": "Seller contact email (BT-43) required",
|
|
208
|
+
"explanation": "Within the SELLER CONTACT group, the email address (BT-43) must be present.",
|
|
209
|
+
"suggested_fix": "Add cac:Contact/cbc:ElectronicMail for the seller party (e.g. <cbc:ElectronicMail>rechnung@example.de</cbc:ElectronicMail>)."
|
|
210
|
+
},
|
|
211
|
+
"BR-DE-8": {
|
|
212
|
+
"format": "xrechnung",
|
|
213
|
+
"title": "Buyer city (BT-52) required",
|
|
214
|
+
"explanation": "XRechnung requires the buyer's city name (BT-52) in the buyer postal address.",
|
|
215
|
+
"suggested_fix": "Fill cac:PostalAddress/cbc:CityName for the AccountingCustomerParty."
|
|
216
|
+
},
|
|
217
|
+
"BR-DE-9": {
|
|
218
|
+
"format": "xrechnung",
|
|
219
|
+
"title": "Buyer post code (BT-53) required",
|
|
220
|
+
"explanation": "XRechnung requires the buyer's postal code (BT-53) in the buyer postal address.",
|
|
221
|
+
"suggested_fix": "Fill cac:PostalAddress/cbc:PostalZone for the AccountingCustomerParty."
|
|
222
|
+
},
|
|
223
|
+
"BR-DE-15": {
|
|
224
|
+
"format": "xrechnung",
|
|
225
|
+
"title": "Buyer reference / Leitweg-ID (BT-10) required",
|
|
226
|
+
"explanation": "XRechnung requires the Buyer reference (BT-10) — for public-sector invoices in Germany this is the Leitweg-ID that routes the invoice to the right authority.",
|
|
227
|
+
"suggested_fix": "Set cbc:BuyerReference (UBL) / ram:BuyerReference (CII) to the Leitweg-ID the public buyer gave you (format like '991-12345-67'). Don't leave it empty.",
|
|
228
|
+
"example": "<cbc:BuyerReference>991-12345-67</cbc:BuyerReference>"
|
|
229
|
+
},
|
|
230
|
+
"BR-DE-16": {
|
|
231
|
+
"format": "xrechnung",
|
|
232
|
+
"title": "Payment account identifier (BT-84) required for credit transfer",
|
|
233
|
+
"explanation": "When the payment means is a credit transfer / SEPA credit transfer (UNTDID 4461 code 30 or 58), the payee's account identifier (BT-84, the IBAN) must be present.",
|
|
234
|
+
"suggested_fix": "Add cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID with the IBAN (and BT-85 the account holder name; BT-86 the BIC if needed)."
|
|
235
|
+
},
|
|
236
|
+
"BR-DE-17": {
|
|
237
|
+
"format": "xrechnung",
|
|
238
|
+
"title": "Invoice type code (BT-3) restricted",
|
|
239
|
+
"explanation": "XRechnung restricts the document type code (BT-3): allowed values are 326 (partial invoice), 380 (commercial invoice), 384 (corrected invoice), 389 (self-billed invoice), 381 (credit note), and 875/876/877 (construction-specific).",
|
|
240
|
+
"suggested_fix": "Set cbc:InvoiceTypeCode (or CreditNoteTypeCode 381) to one of the allowed values; 380 for a normal invoice, 384 for a correction (then add the preceding-invoice reference), 381 for a credit note."
|
|
241
|
+
},
|
|
242
|
+
"BR-DE-21": {
|
|
243
|
+
"format": "xrechnung",
|
|
244
|
+
"title": "Specification identifier (BT-24) must be the XRechnung CustomizationID",
|
|
245
|
+
"explanation": "Exactly one Specification identifier (BT-24) must carry the XRechnung CustomizationID for the version you target, e.g. 'urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0'. A plain EN 16931 CustomizationID is not an XRechnung.",
|
|
246
|
+
"suggested_fix": "Set cbc:CustomizationID to the XRechnung 3.0 URN (or the standard:extension URN if you use the extension). Keep cbc:ProfileID as 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0' if you also send via Peppol.",
|
|
247
|
+
"example": "<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0</cbc:CustomizationID>"
|
|
248
|
+
},
|
|
249
|
+
"BR-DE-23": {
|
|
250
|
+
"format": "xrechnung",
|
|
251
|
+
"title": "One PAYMENT MEANS must use exactly one of account / card / direct debit",
|
|
252
|
+
"explanation": "In a PAYMENT MEANS group XRechnung requires you to specify the Payment account identifier (BT-84) OR Payment card information (BG-18) OR Direct debit (BG-19) — exactly one of the three, not zero and not more than one.",
|
|
253
|
+
"suggested_fix": "Pick one settlement channel per cac:PaymentMeans: either a PayeeFinancialAccount (IBAN), or CardAccount details, or the direct-debit group (mandate reference + debited account); split into multiple PaymentMeans if you offer several."
|
|
254
|
+
},
|
|
255
|
+
"BR-DE-26": {
|
|
256
|
+
"format": "xrechnung",
|
|
257
|
+
"title": "Corrected invoice requires preceding-invoice reference",
|
|
258
|
+
"explanation": "When the document type code (BT-3) is 384 (corrected invoice), the document must reference the invoice it corrects (BG-3 PRECEDING INVOICE REFERENCE).",
|
|
259
|
+
"suggested_fix": "Add cac:BillingReference/cac:InvoiceDocumentReference with the original invoice number (BT-25) and, if available, its issue date (BT-26)."
|
|
260
|
+
},
|
|
261
|
+
"BR-DEX-01": {
|
|
262
|
+
"format": "xrechnung",
|
|
263
|
+
"title": "XRechnung extension structure error",
|
|
264
|
+
"explanation": "The document declares the XRechnung 'extension' CustomizationID (urn:...:xrechnung_3.0#conformant#urn:...:xrechnung_3.0_extension) but the use of extension elements is not consistent with the rules for the extension profile.",
|
|
265
|
+
"suggested_fix": "Either remove the extension elements and use the plain XRechnung CustomizationID, or correct the extension usage per the XRechnung extension specification (e.g. allowed sub-invoice lines, additional document references)."
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eleata-einvoice-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server to validate EU e-invoices (Peppol, XRechnung, Factur-X, UBL/CII) and explain validation error codes — for AI coding agents (Claude, Cursor, Copilot).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"eleata-einvoice-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"error-fixes.json",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"e-invoice",
|
|
23
|
+
"einvoice",
|
|
24
|
+
"peppol",
|
|
25
|
+
"xrechnung",
|
|
26
|
+
"fatturapa",
|
|
27
|
+
"factur-x",
|
|
28
|
+
"zugferd",
|
|
29
|
+
"en16931",
|
|
30
|
+
"ubl",
|
|
31
|
+
"validation",
|
|
32
|
+
"eu-invoicing"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": "eleata",
|
|
36
|
+
"homepage": "https://eleata.io",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/hernaninverso/eleata-einvoice-mcp.git"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"@types/node": "^22.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|