From scribo-skill
Generates EN 16931-compliant e-invoices (XRechnung, ZUGFeRD) or plain PDF via Scribo API. Uses curl and jq; no signup needed.
How this skill is triggered — by the user, by Claude, or both
Slash command
/scribo-skill:scriboThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use Scribo when a user asks to generate, draft, or "create" an invoice. Scribo emits structured machine-readable invoices that satisfy EU mandates (German B2B / Federal B2G) and can also produce a plain US PDF when no XML is needed. French Factur-X, Spanish Facturae, and Belgian/cross-border Peppol BIS UBL are **Phase 2 — coming soon**.
Use Scribo when a user asks to generate, draft, or "create" an invoice. Scribo emits structured machine-readable invoices that satisfy EU mandates (German B2B / Federal B2G) and can also produce a plain US PDF when no XML is needed. French Factur-X, Spanish Facturae, and Belgian/cross-border Peppol BIS UBL are Phase 2 — coming soon.
This skill talks to the public Scribo HTTP API at https://scribo.causaprima.ai via small curl + jq helper scripts. No MCP server or npm install required. Override the base URL with SCRIBO_BASE_URL for dev/staging.
references/tax-codes.md only if the user is unsure.recipient.contact_email at issue time and returns a download URL, but portal/Peppol submission is manual — the user uploads the XML themselves.unsupported_jurisdiction — don't attempt to draft.unsupported_jurisdiction.discount to apply a rebate.Detect the invoice type early. If the user mentions a German public-sector recipient, a Leitweg-ID, or specifically asks for XRechnung, immediately tell them: "Scribo generates the XRechnung XML (plus a PDF preview) but doesn't submit it for you yet — you'll upload the XML manually to xrechnung.bund.de (federal) or your Land's OZG-RE portal, keyed by the Leitweg-ID. Direct Peppol submission is on the roadmap." Confirm they're OK with this before proceeding.
Collect the required fields from the user in a brief conversational pass:
address_line1), postcode, city, country_code (ISO 3166 alpha-2 — reserved codes like XX / ZZ are rejected).DE123456788). The email doubles as the user's login for return visits. German Kleinunternehmer § 19 UStG senders don't have a VAT ID — pass the Steuernummer as sender.tax_registration_id (e.g. 9999/999/9999) instead so the EN 16931 BR-CO-26 constraint is still satisfied. Both fields can also be supplied side-by-side when both are issued (BT-31 + BT-32).leitweg_id if it's a German federal B2G recipient — that field alone auto-selects XRechnung UBL (broadest Peppol AccessPoint compatibility; force xrechnung_cii via format_override only if a portal specifically requires the CII syntax). Reminder: Scribo generates the XML and a PDF preview but does not submit — the response includes a submission hint pointing at the right manual-upload portal.recipient.contact_email. Required — Scribo refuses to draft without it. This is the accounts-payable / billing email. Recipient tax_id is optional in general but required for intra-EU reverse charge (AE).[0, 100]; quantity capped at 999,999.999), tax category code. Optional line-level discount ({ type: 'percent' | 'amount', value, reason }; reason is required by EN 16931 BR-41). Currency is ISO 4217.tax_exemption_code (per line) — REQUIRED when tax_category_code === "E" per EN 16931 BR-E-10. Pass a VATEX code matching the legal basis (VATEX-EU-79-C for Kleinunternehmer § 19 UStG, VATEX-EU-132 for Art. 132 health/education, etc.). AE / K / G / O auto-emit their well-known VATEX codes server-side (VATEX-EU-AE / -IC / -G / -O) — only include this for category E unless you want to override a default.payment_means (top-level) — REQUIRED for XRechnung (any invoice with recipient.leitweg_id, or an explicit format_override of xrechnung_ubl / xrechnung_cii). Shape: { "type": "credit_transfer", "iban": "DE89…", "bic"?: "…", "account_name"?: "…" }. XRechnung BR-DE-1 enforces this. Ask the user "on which account?" if you don't have the IBAN yet — never invent one.Optional extras: jurisdiction override, format_override, notes (≤ 1000 chars), idempotency_key (if not supplied, the script auto-mints one from a SHA-256 of the payload so accidental retries don't double-bill), tax_exemption_reason (per-line free-form BT-120 note to the buyer).
If the user is unsure of the tax category code, read references/tax-codes.md once and offer the right pick. Never guess.
Build the JSON payload and invoke scripts/create_invoice.sh (passes payload on stdin).
Verify email ownership the first time — see the Verification section below. The first create_invoice.sh for a sender email prints status: "verification_required" with a challenge_id (exit 10); Scribo emails a 6-digit code to sender.contact_email. Ask the user for the code, redeem it for a verification_token with scripts/redeem_verification.sh, then re-run the create with the token. One token is good for ~30 minutes of invoices for the same sender.
Hand the user the result — download_url (durable; re-fetchable any time), the resolved format, and what was emailed: when recipient_email_sent is true, tell the user the invoice was emailed straight to recipient.contact_email; when it is false, warn them the recipient email could not be sent and they should deliver the download link or PDF themselves; when the field is absent (idempotent replay — no email is re-sent), say nothing about it. Also mention that a magic link was emailed to the sender so they can come back later. For XRechnung output the legally binding file is the UBL XML (or CII XML if you forced xrechnung_cii); for ZUGFeRD it's a PDF/A-3 with factur-x.xml embedded; for US it's a plain PDF. For XRechnung the response also carries a preview_url to the PDF visualisation (same content, human-readable) and a submission object describing how the user should deliver the XML (manual portal upload today). Surface both URLs and the submission hint to the user.
On a 4xx/5xx response, surface the error.code and error.message from the response envelope. Read references/troubleshooting.md if the error is one of: email_verification_required, verification_email_mismatch, verification_invalid, rate_limited, idempotency_key_mismatch, validator_failed. A bare HTTP 403 with no JSON error envelope is the exception — it was blocked upstream (a sandbox network-egress proxy or a WAF), not by Scribo; see references/troubleshooting.md. Common gotchas: missing tax_exemption_code for an E line, missing payment_means on an XRechnung path, reserved country_code, tax_rate > 100.
Scribo proves the caller owns sender.contact_email before it generates anything (Scribo-03). You orchestrate it: call create, get told to verify, ask the user for the code, redeem, retry.
Email language. The verification email (and the confirmation page its link opens) is English by default. To send it in the language you're speaking with the user, set
SCRIBO_LOCALEto the BCP-47 tag before callingcreate_invoice.sh/request_verification.sh— e.g.export SCRIBO_LOCALE=de-DE. This also localizes the later "invoice ready" notification email. Unsupported values fall back to English, so it's safe to set whenever the language is clear. UI-language only — it does not affect the invoice content, currency, or jurisdiction.
Canonical sequence:
scripts/create_invoice.sh. With no token it does not create the invoice; it requests a challenge (Scribo emails a 6-digit code to the sender) and prints:
{
"status": "verification_required",
"challenge_id": "9b1d…",
"email_hint": "a***@e***.com",
"expires_at": "…",
"next_step": "Ask the user for the 6-digit code emailed to a***@e***.com, then run: redeem_verification.sh 9b1d… <code> …"
}
The script exits with code 10.{2,3,4,5,6,7,8,9} (no 0/1, no letters).scripts/redeem_verification.sh <challenge_id> <code>. It prints { "verification_token": "…", "expires_at": "…" }.SCRIBO_VERIFICATION_TOKEN=<verification_token> \
cat payload.json | scripts/create_invoice.sh
The token is reusable for ~30 minutes, so one export SCRIBO_VERIFICATION_TOKEN=… covers several invoices for the same sender. The --verification-token <token> flag does the same thing if you prefer it.If the code is wrong, redeem_verification.sh exits 11 with verification_invalid. Ask the user to re-check and try again. After 5 wrong attempts the challenge is revoked — re-run create_invoice.sh (no token) to mint a fresh one.
Unattended / batch use: every tokenless
create_invoice.shcall sends a verification email and exits10. For scripted runs, verify once andexport SCRIBO_VERIFICATION_TOKEN=…so subsequent calls go straight through. (The server caps sends to 1 per 30 s and 5 per hour per email, so an accidental retry loop can't flood an inbox.)
User: "Issue an invoice to Acme GmbH for 1 day of consulting at €1000, my email is [email protected]."
cat payload.json | scripts/create_invoice.sh → { "status": "verification_required", "challenge_id": "9b1d…", "email_hint": "a***@e***.com" } (exit 10).scripts/redeem_verification.sh 9b1d… 234567 → { "verification_token": "vtok…" }.SCRIBO_VERIFICATION_TOKEN=vtok… ; cat payload.json | scripts/create_invoice.sh → { invoice_id, download_url, format, … }.scripts/create_invoice.sh — POST /api/v1/invoicesReads a JSON payload on stdin (or via --from FILE).
Example — DE B2B ZUGFeRD COMFORT (the simplest happy path):
cat <<'JSON' | skills/scribo/scripts/create_invoice.sh
{
"sender": {
"legal_name": "Causa Prima Germany GmbH",
"country_code": "DE",
"address_line1": "Example Allee 1",
"postcode": "10115",
"city": "Berlin",
"tax_id": "DE123456788",
"contact_email": "[email protected]"
},
"recipient": {
"legal_name": "Acme GmbH",
"country_code": "DE",
"address_line1": "Hauptstrasse 1",
"postcode": "10117",
"city": "Berlin",
"tax_id": "DE136695976",
"contact_email": "[email protected]"
},
"line_items": [
{
"description": "Consulting, 3 days",
"quantity": "3",
"unit_code": "DAY",
"unit_price": "1200.00",
"tax_rate": "19",
"tax_category_code": "S"
}
],
"currency": "EUR"
}
JSON
Example — DE B2G XRechnung UBL (Leitweg-ID + payment_means + supplier contact phone for BR-DE-5/6):
{
"sender": {
"legal_name": "Acme GmbH", "country_code": "DE",
"address_line1": "Musterstr. 1", "postcode": "10115", "city": "Berlin",
"tax_id": "DE123456788", "contact_email": "[email protected]",
"contact_name": "Erika Beispiel", "contact_phone": "+49 30 1234567"
},
"recipient": {
"legal_name": "Bundesamt für Beispiele", "country_code": "DE",
"address_line1": "Wilhelmstr. 1", "postcode": "10117", "city": "Berlin",
"contact_email": "[email protected]",
"leitweg_id": "991-12345-67"
},
"line_items": [
{ "description": "Consulting", "quantity": "3", "unit_code": "DAY",
"unit_price": "1200.00", "tax_rate": "19", "tax_category_code": "S" }
],
"currency": "EUR",
"payment_means": {
"type": "credit_transfer",
"iban": "DE89370400440532013000",
"account_name": "Acme GmbH"
}
}
Example — Kleinunternehmer § 19 UStG (category E + VATEX-EU-79-C; pass Steuernummer as tax_registration_id since there's no VAT ID — BR-CO-26 needs one or the other):
{
"sender": {
"legal_name": "Friedrich Beratung", "country_code": "DE",
"address_line1": "Musterstr. 1", "postcode": "10115", "city": "Berlin",
"tax_registration_id": "26/750/12345",
"contact_email": "[email protected]"
},
"recipient": { /* ... */ },
"line_items": [
{
"description": "Beratung", "quantity": "5", "unit_code": "HUR",
"unit_price": "80.00", "tax_rate": "0",
"tax_category_code": "E",
"tax_exemption_code": "VATEX-EU-79-C"
}
],
"currency": "EUR"
}
Returns { invoice_id, document_id, format, download_url, download_url_expires_at, validator_summary, magic_link_sent, recipient_email_sent }. The download URL is durable — re-fetchable any time from any device. The magic link arrives at the sender's email and lets them sign back in. recipient_email_sent reports the recipient-facing email: true = the invoice was emailed to recipient.contact_email, false = the best-effort send failed (tell the user to deliver the PDF themselves), absent = idempotent replay of an older response (no email re-sent — claim nothing).
Format is picked from the priority chain (format_override → Leitweg-ID → jurisdiction → sender country → sender tax-ID prefix → recipient country → recipient tax-ID prefix). See references/jurisdictions.md for the full table.
Pass the email verification_token via the SCRIBO_VERIFICATION_TOKEN env var (preferred — keeps it out of the process list and shell history) or the --verification-token <token> flag; it is sent as the X-Email-Verification-Token header. Without a token the script requests a verification challenge and prints status: "verification_required" (exit 10) instead of creating the invoice — see the Verification section above.
scripts/request_verification.sh EMAIL — POST /api/v1/scribo/email-verificationsRequests an email-ownership challenge; Scribo emails a magic link and a 6-digit code to EMAIL. Returns { challenge_id, expires_at, next_request_allowed_at }. You normally don't call this directly — create_invoice.sh requests the challenge for you when no token is present. Set SCRIBO_LOCALE (e.g. de-DE) to control the email + confirmation-page language — see the Verification section.
scripts/redeem_verification.sh CHALLENGE_ID CODE — POST /api/v1/scribo/email-verifications/:id/redeemExchanges the 6-digit code the user copied from the email for a verification_token (reusable ~30 min). All failures (wrong/expired/revoked code) return a uniform verification_invalid (exit 11).
scripts/get_invoice.sh INVOICE_ID — GET /api/v1/invoices/:idFetch metadata + a fresh signed download URL for a previously generated invoice. Tenant-scoped (the caller's session must own it).
scripts/download_invoice.sh INVOICE_ID [-o FILE] — GET /api/v1/invoices/:id/downloadStreams the PDF bytes to -o FILE (default invoice-<id>.pdf).
scripts/list_jurisdictions.sh — GET /api/v1/jurisdictionsReturns [{ jurisdiction, formats, default_format }]. Useful to confirm a country is supported before collecting recipient data.
User picks; Scribo never infers. One-liners only — read references/tax-codes.md for when each applies and which schematron rules it triggers.
S — Standard ratedZ — Zero ratedE — Exempt (statutory) — requires tax_exemption_code (VATEX-EU-79-C for § 19 UStG Kleinunternehmer, VATEX-EU-132 for Art. 132 health/education, etc.)AE — Reverse charge (intra-EU B2B services AND § 13b UStG domestic — construction, scrap, mobile-phone wholesale, etc.). Auto-emits VATEX-EU-AE.K — Intra-community supply of goods. Auto-emits VATEX-EU-IC.G — Free export. Auto-emits VATEX-EU-G.O — Services outside the scope of VAT. Auto-emits VATEX-EU-O. (All-O invoices are rejected for XRechnung — BR-DE-14 unrepresentable; use AE or G instead.)| Sender→Recipient | Default format |
|---|---|
| DE → DE B2B | ZUGFeRD COMFORT |
| DE → DE B2G (Leitweg-ID present) | XRechnung UBL (auto-selected; force xrechnung_cii via format_override if the recipient portal specifically requires CII) |
| FR → * | Factur-X (Phase 2 — coming soon) |
| ES → * | Facturae (Phase 2 — coming soon) |
| BE → * | Peppol BIS UBL (Phase 2 — coming soon) |
| US → * | Plain PDF |
Full table and priority chain in references/jurisdictions.md.
For any XRechnung-resolved invoice (Leitweg-ID present or format_override=xrechnung_*), scripts/download_invoice.sh streams the legally binding UBL/CII XML, not the PDF preview the workflow also generates. KoSIT, Peppol AccessPoints, and the German federal procurement portals all consume the XML. For ZUGFeRD output you get a PDF/A-3 with factur-x.xml embedded inside (the PDF is the legal artifact in that case).
discount instead.verification_token is reusable for ~30 min; after that, the next create re-runs the challenge./api/v1/* endpoints these scripts call are never CAPTCHA-gated. A bare HTTP 403 with no JSON error envelope is not Turnstile and not a verification problem — it means the request was blocked upstream (a sandbox network-egress proxy, or a WAF in front of the API), so re-verifying or visiting the web UI won't help. See references/troubleshooting.md.End-user install instructions live in README.md. Three methods: /plugin install (Claude Code), a .zip upload via Settings → Customize → Skills (Claude.ai, Claude Desktop, Cowork), and git clone + copy of the inner skills/scribo directory (Codex CLI, manual Claude Code, Cowork repo-baked).
npx claudepluginhub causa-prima-ai/scribo-skill --plugin scribo-skillCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.