MCP (Model Context Protocol) server for POP — enabling LLMs to generate, submit, and manage Italian e-invoices (FatturaPA/SdI), Peppol, KSeF, ZUGFeRD/Factur-X, and PDF invoices directly from AI assistants.
npm:
@getpopapi/pop-mcp· Remote:https://mcp.popapi.io/mcp
Don't want to install anything? pop-mcp runs as a hosted, multi-tenant MCP server at:
https://mcp.popapi.io/mcp
Head to popapi.io to grab a license key, then point any MCP-speaking client at
that URL with your key as a Bearer token. No local install, no POP_API_KEY env var, no build step
— this is the recommended way to try pop-mcp for most people. Use the local stdio setup below only
if you specifically need a Claude Desktop config running a process on your own machine.
This endpoint speaks MCP 2026-07-28, which is fully stateless: there is no initialize
handshake and no session to open or track. Every request is self-contained — it names its own
protocol version and capabilities — and the server answers it independently. Because of that,
this is a multi-tenant endpoint: it never reads a fixed POP_API_KEY from its own environment.
Every request must carry your own POP license key as a Bearer token:
Authorization: Bearer <your_license_key>
A missing or malformed Authorization header returns a 401 with error_code: "unauthorized_user"
before any POP API call is made. An invalid-but-well-formed key is passed straight through to POP's
API and surfaces whatever error POP returns (unauthorized_user, insufficient_level, etc.) — the
server does not re-validate keys itself.
Any modern MCP HTTP client can connect: Claude (remote connector), the OpenAI Responses API, n8n,
MCP Inspector, or a custom integration — not
just Claude Desktop. All invoice, status, advanced, and onboarding tools are available; onboarding
tools use their own onboarding_token per call and don't require the Bearer key.
Discover the server's supported protocol versions and capabilities (optional — clients can also
just call tools/list or tools/call directly and handle a version-negotiation error inline):
curl -X POST https://mcp.popapi.io/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_license_key_here" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: server/discover" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } }
}'List the available tools — every request is self-contained, so _meta (protocol version + client
capabilities) travels on every call, not just the first one:
curl -X POST https://mcp.popapi.io/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_license_key_here" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/list" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": { "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {} } }
}'The tool catalog is identical for every license key, so tools/list and server/discover
responses carry a one-hour public cache hint (ttlMs: 3600000, cacheScope: "public") — clients and
gateways may cache them across tenants.
MCP-Protocol-VersionandMcp-Methodare required on every request (per SEP-2243), and must match the body's_meta.protocolVersionandmethodexactly, or the server rejects the request with a400and JSON-RPC error-32020(HeaderMismatch).tools/callrequests additionally require anMcp-Nameheader matchingparams.name.
npx @modelcontextprotocol/inspectorConfigure it to connect to https://mcp.popapi.io/mcp with header
Authorization: Bearer <your_license_key>.
This endpoint runs as a Vercel serverless function (api/mcp.ts → src/mcpHandler.ts). To run it
locally: npx vercel dev (requires vercel link to the project first).
POP is a cloud service for electronic invoice generation and delivery, supporting:
- 🇮🇹 Italian e-invoicing (FatturaPA/SdI) — compliant with D.Lgs. 127/2015
- 🇪🇺 Peppol — pan-European cross-border B2B invoicing (UBL 2.1)
- 📄 PDF invoices — branded, with email delivery
- ✅ Validation — fiscal codes, VAT numbers, document pre-submission checks
- 🗄️ Preservation — Italian legal archival (conservazione sostitutiva)
| Tool | Endpoint | Plan |
|---|---|---|
pop_create_sdi_invoice |
POST /create-xml |
Any |
pop_create_peppol_invoice |
POST /create-ubl |
Any (Basic+ to submit) |
pop_create_pdf_invoice |
POST /create-pdf |
Any (Basic+ for email) |
pop_create_ksef_invoice |
POST /create-ksef-xml |
Any (KSeF setup for provider submission) |
pop_create_zugferd_invoice |
POST /create-zugferd |
Any |
pop_sync_zoho_document |
POST /integration/zoho/sync |
Zoho connector required |
| Tool | Endpoint | Plan |
|---|---|---|
pop_get_invoice_status |
POST /sdi/document-notifications |
Any |
pop_get_peppol_document |
POST /peppol/document-get |
Basic+ |
pop_get_sdi_document |
POST /sdi/document-get |
Basic+ |
| Tool | Endpoint | Plan |
|---|---|---|
pop_verify_sdi_document |
POST /sdi/document-verify |
Basic+ |
pop_preserve_document |
POST /sdi/document-preserve |
Basic+ |
- Node.js >= 20
- A POP license key
- For SdI/Peppol submission: active integration on your POP account (Basic/Growth plan)
New to POP? Visit popapi.io to create your account and get your license key.
API-only users can activate their account and obtain a license_key with this flow:
- Open https://popapi.io/otp-login/
- Enter your email address
- Receive a one-time password (OTP) by email and enter it
- Complete the configuration wizard
- Open https://popapi.io/ → Account > API
- Copy the default generated
license_key
- Your account includes one default
license_key, visible under Account > API - You can generate additional keys linked to the same account from that same page
- Every
license_keymust be treated as a secret credential — do not commit it to source control
- Get your
license_key - Test it with
GET /account-profile - Send one document-generation request with a real payload
- Add optional delivery integrations only after local generation works
npm install -g @getpopapi/pop-mcpgit clone https://github.com/getpopapi/pop-mcp
cd pop-mcp
npm install
npm run buildSet your POP license key as an environment variable:
export POP_API_KEY=your_license_key_hereOptional — use the staging environment:
export POP_ENVIRONMENT=stagingAdd to your claude_desktop_config.json:
If installed from npm:
{
"mcpServers": {
"pop": {
"command": "pop-mcp",
"env": {
"POP_API_KEY": "your_license_key_here"
}
}
}
}If running from source:
{
"mcpServers": {
"pop": {
"command": "node",
"args": ["/path/to/pop-mcp/dist/cli.js"],
"env": {
"POP_API_KEY": "your_license_key_here"
}
}
}
}Config file locations:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
The license_key is always injected automatically from POP_API_KEY — never pass it manually.
Generate an Italian FatturaPA XML document. Optionally submit it to the SdI (Sistema di Interscambio).
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
object | ✅ | Full invoice data (see Invoice Data Structure) |
submit_to_sdi |
boolean | — | Set true to submit to SdI. Requires Basic+ plan with active SdI integration. Default: false |
integration |
object | — | Override integration config. Overrides submit_to_sdi if set. |
environment |
string | — | Target environment (e.g. "sandbox") |
Integration options for integration.use:
"sdi-via-pop"or"sdi"— Submit via POP SdI"pop-to-webhook"— Deliver to a webhook (requiresid)"fatture-in-cloud"— Deliver to Fatture in Cloud
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"user_agent": "pop-mcp",
"user_agent_version": "1.0.0",
"data": { "...invoice fields..." },
"integration": { "use": "sdi-via-pop", "action": "create" }
}
integrationis omitted whensubmit_to_sdiisfalseand no override is provided (XML-only generation).
Generate a Peppol UBL 2.1 document. Optionally submit it to the Peppol network.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
object | ✅ | Full invoice data. customer_type must be "company" or "freelance" |
submit_to_peppol |
boolean | — | Set true to submit to the Peppol network. Requires Basic+ plan. Default: false |
integration |
object | — | Override integration config |
environment |
string | — | Target environment |
Integration options for integration.use:
"peppol-via-pop"or"peppol"— Submit via POP Peppol"pop-to-webhook"— Deliver to a webhook (requiresid)
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"user_agent": "pop-mcp",
"user_agent_version": "1.0.0",
"data": { "...invoice fields..." },
"integration": { "use": "peppol-via-pop", "action": "create" }
}Generate a branded PDF invoice. Optionally email it to up to 3 recipients.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
object | ✅ | Invoice data. Must include data.pdf for PDF-specific settings |
send_email |
boolean | — | Set true to email the PDF (requires data.pdf.email_invoice, Basic+ plan). Default: false |
environment |
string | — | Target environment |
data.pdf fields:
| Field | Description |
|---|---|
doc_type_title |
Title shown on document (e.g. "Invoice", "Receipt") |
logo_url |
Company logo URL (HTTPS) |
head.store_info_address |
Supplier address string in header |
head.billing[] |
Customer billing address array |
head.shipping[] |
Shipping address array (optional) |
email_invoice.to |
Up to 3 recipient email addresses |
email_invoice.from |
Reply-to address |
footer_text |
Custom footer message |
total_tax |
Total tax amount as string |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"user_agent": "pop-mcp",
"user_agent_version": "1.0.0",
"data": {
"...invoice fields...",
"pdf": {
"doc_type_title": "Invoice",
"logo_url": "https://example.com/logo.png",
"head": { "store_info_address": "Via Roma 1, 00100 Roma IT", "billing": [] },
"total_tax": "22.00",
"email_invoice": { "to": ["customer@example.com"] }
}
}
}Generate a Polish KSeF FA(3) XML invoice or credit note. Optionally submit it through a configured KSeF provider integration.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
object | ✅ | Full invoice data for KSeF FA(3) generation |
integration |
object | — | Optional KSeF provider submission config: { use: "ksef" | "ksef-via-pop", action } |
environment |
string | — | Target environment (e.g. "sandbox") |
Domain rules specific to KSeF:
- Poland only —
transfer_lender.personal_data.tax_id_vat.country_idmust be"PL"with a 10-digit NIP asid_code customer_typemust be"company"or"freelance"(no private individuals)natureis always required at the top level for KSeF (unlike SdI/Peppol, where it's only required at 0% VAT) — reuses the same SdI nature codes (N1,N2.1,N2.2,N3.1,N3.2,N4, ...) to derive KSeF's internal fiscal varianttransmitter_datais not used (SdI-only concept)payment_data.payment_detailsonly acceptsMP01,MP02/MP03,MP05,MP08— other payment method codes are rejected at generation time- Base XML generation is available on any plan; provider submission via
integration.use: "ksef"requires a Basic+ plan and the supplier already enrolled as a KSeF legal entity in the POP dashboard
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"user_agent": "pop-mcp",
"user_agent_version": "1.0.0",
"data": { "...invoice fields...", "nature": "N1" },
"integration": { "use": "ksef", "action": "create" }
}
integrationis omitted entirely for local XML-only generation (no provider submission).
Returns: raw FA(3) XML (application/xml) for local generation, or JSON (with a UUID) when submitted through a provider integration.
Generate a ZUGFeRD/Factur-X document package: a visual PDF, an EN16931 CII XML, and a hybrid PDF/A-3 with the XML embedded.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
data |
object | ✅ | Full invoice data for ZUGFeRD/Factur-X generation |
environment |
string | — | Target environment (e.g. "sandbox") |
This tool has no integration parameter — ZUGFeRD generation is local only, with no submit/delivery step.
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"user_agent": "pop-mcp",
"user_agent_version": "1.0.0",
"data": { "...invoice fields..." }
}Returns: JSON with generation metadata and three Base64-encoded attachments:
{
"success": true,
"data": {
"valid": true,
"profile": "EN16931",
"attachments": {
"pdf": { "filename": "...", "mime": "application/pdf", "content_base64": "..." },
"xml": { "filename": "...", "mime": "application/xml", "content_base64": "..." },
"hybrid_pdf": { "filename": "...", "mime": "application/pdf", "content_base64": "..." }
},
"validation": { "...": "..." },
"errors": [],
"warnings": []
}
}Retrieve the SdI processing status and notifications for a submitted invoice.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string (UUID) | ✅ | Invoice UUID returned by pop_create_sdi_invoice when submit_to_sdi=true |
response_format |
"markdown" | "json" |
— | Output format. Default: "markdown" |
environment |
string | — | Target environment |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"integration": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
}SdI notification statuses: pending · accepted · rejected · delivery
SdI processing is asynchronous and can take minutes to hours. Retry if no notifications are returned yet.
Retrieve a Peppol document from the network by UUID.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string (UUID) | ✅ | Peppol document UUID from pop_create_peppol_invoice |
zone |
string (2 chars) | — | Country code of the Peppol access point (e.g. "BE" for Belgium). Required for some regions. |
response_format |
"markdown" | "json" |
— | Output format. Default: "markdown" |
environment |
string | — | Target environment |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"integration": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "zone": "IT" }
}
zoneis omitted from the payload if not provided.
Retrieve an archived SdI (FatturaPA) document from POP storage by UUID.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string (UUID) | ✅ | SdI document UUID |
response_format |
"markdown" | "json" |
— | Output format. Default: "markdown" |
environment |
string | — | Target environment |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"integration": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
}Requires: Basic+ plan with active SdI integration.
Validate an SdI XML document for compliance before submission. Does not submit the document.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
xml_base64 |
string | ✅ | The SdI XML document encoded as a Base64 string |
environment |
string | — | Target environment |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"skip_business_check": true,
"integration": { "xml": "<base64-encoded-xml-string>" }
}Validation checks performed: XML schema conformance · fiscal code format · VAT number validity · required field presence · amount consistency
Requires: Basic+ plan with active SdI integration and registered business.
Archive an SdI document in certified long-term digital storage (conservazione sostitutiva). Italian law requires invoices to be preserved for 10 years.
MCP inputs:
| Parameter | Type | Required | Description |
|---|---|---|---|
uuid |
string (UUID) | ✅ | UUID of the SdI document to archive |
environment |
string | — | Target environment |
API payload sent:
{
"license_key": "YOUR_LICENSE_KEY",
"integration": { "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
}Important: Only call this tool when
pop_get_invoice_statusreturns statusRC(Ricevuta di Consegna) orMC(Mancata Consegna). Do not call for statusesNS,EC,SE, orDT.
Requires: Basic+ plan with active SdI integration.
Ask your AI assistant:
"Create a FatturaPA invoice for 1000€ + 22% VAT to Rossi SRL (VAT IT12345678901, Milan). My company is Bianchi SRL (VAT IT98765432109, Rome), using payment method bank transfer to IBAN IT60X0542811101000000123456."
"Create and submit to SdI an invoice #45 for consulting services, 500€ + 22% VAT to customer Mario Rossi (fiscal code RSSMRA80A01H501U) in Rome."
"What's the status of SdI invoice with UUID abc123-def456-...?"
"Create a PDF invoice for order #123 and email it to customer@example.com."
"Verify SdI document with UUID abc123-... for compliance before submission."
| Feature | Free | Basic/Growth | Pro |
|---|---|---|---|
| XML generation (local) | ✅ | ✅ | ✅ |
| PDF generation | ✅ | ✅ | ✅ |
| SdI submission | ❌ | ✅ | ✅ |
| Peppol submission | ❌ | ✅ | ✅ |
| PDF email delivery | ❌ | ✅ | ✅ |
| SdI document verification | ❌ | ✅ | ✅ |
| Document preservation | ❌ | ✅ | ✅ |
npm run inspector
# or
npx @modelcontextprotocol/inspector dist/cli.jsPOP_API_KEY=your_key node -e "
import('./dist/cli.js').catch(e => {
if (e.message.includes('stdin')) process.exit(0);
console.error(e); process.exit(1);
});
"echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | POP_API_KEY=test node dist/cli.js# Run with auto-reload
npm run dev
# Build
npm run build
# Clean build artifacts
npm run cleanThe data parameter for invoice creation follows the FatturaPA structure:
data
├── id Invoice/order ID (numeric)
├── filename Output filename without extension (e.g. 'IT99900088876_00009')
├── type "invoice" | "credit_note"
├── version "FPR12" | "FPA12"
├── sdi_type 7-char SDI code ('0000000' for private individuals)
├── customer_type "private" | "company" | "freelance" | "pa"
├── nature VAT exemption code (required when rate is 0%, e.g. 'N2.1', 'N6.1')
├── transmitter_data
│ ├── transmitter_id { country_id, id_code }
│ ├── progressive Transmission progressive ID (e.g. '00001')
│ ├── transmitter_format "FPR12" | "FPA12"
│ ├── sdi_code 7-char code
│ ├── transmitter_contact { phone, email }
│ └── recipient_pec PEC email (alternative to sdi_code)
├── transfer_lender Supplier/seller
│ ├── personal_data { tax_id_vat: { country_id, id_code, tax_regime }, company_name }
│ ├── place { address, zip_code, city, province_id, country_id }
│ └── contact { phone, email }
├── transferee_client Customer/buyer
│ ├── personal_data { tax_id_vat, tax_id_code (fiscal code for IT private), company_name }
│ └── place { address, zip_code, city, province_id, country_id }
├── invoice_body
│ ├── general_data { doc_type (TD01|TD04), date (YYYY-MM-DD), invoice_number, currency }
│ └── total_document_amount
├── order_items[]
│ ├── description, quantity, unit
│ ├── unit_price, total_price
│ ├── rate VAT rate as string (e.g. '22.00')
│ ├── total_tax VAT amount (number)
│ └── item_type "product" | "shipping" | "fee"
├── payment_data
│ ├── terms_payment TP01 (instalment) | TP02 (full) | TP03 (advance)
│ ├── payment_details MP01 (Cash) | MP02 (Check) | MP05 (Bank Transfer) | MP08 (Credit Card) | ...
│ ├── payment_amount
│ ├── beneficiary Required for MP05 (bank transfer)
│ ├── financial_institution Required for MP05
│ └── iban Required for MP05
├── purchase_order_data (optional) { id, date }
├── connected_invoice_data[] (required for credit notes) { id, date }
├── overrides (optional) { language, bollo_force_apply }
└── pdf (only for pop_create_pdf_invoice)
├── doc_type_title
├── logo_url
├── head { store_info_address, billing[], shipping[] }
├── total_tax
├── email_invoice { to[] (max 3), from }
└── footer_text
| Error Code | Meaning | Solution |
|---|---|---|
unauthorized_user |
Invalid license key | Check POP_API_KEY |
insufficient_level |
Plan too low | Upgrade POP plan |
business_not_registered |
No business profile | Register on popapi.io |
integration_inactive |
SdI/Peppol not enabled | Activate on popapi.io |
pop_api_email_limit |
>3 email recipients | Reduce to max 3 |
pop_api_email_not_allowed |
Plan doesn't allow email | Upgrade to Basic+ |
- n8n-nodes-pop — n8n community nodes for POP
- POP — Official website
- API Documentation — Postman docs
MIT © getpopapi