npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jofotara-kit

v0.3.0

Published

Document builder, safe submission client, XML validator, local mock server and AI agent skill for JoFotara (Jordan ISTD e-invoicing) integrations.

Readme

jofotara-kit

اقرأ الدليل بالعربية

The JoFotara sandbox that doesn't exist — plus a document builder, a safe submission client, a validator and an AI agent skill.

JoFotara (فوترة, Jordan's ISTD e-invoicing system) has no test environment. The only way to see if your XML is right is to send a real invoice with real credentials and create a real tax record. jofotara-kit lets you get the shape right locally first:

  • build — turns plain sale data into a valid invoice or return: tax-inclusive prices, line and order discounts, exempt and zero-rated lines, buyer rules, and partial returns that add up to the invoice exactly. From TypeScript/JavaScript, or from any language through JSON.
  • send — submits documents and sorts every answer into accepted, rejected, unknown or not sent, so a timeout is never mistaken for a rejection and resent as a duplicate tax record.
  • validate — lints JoFotara UBL 2.1 XML against the rules of the official ISTD technical guide (v1.4): type names, tax categories S/Z/O and allowed VAT rates, buyer IDs, totals formulas, return references and the extra fields sales returns need. Every finding cites the manual page and says how to fix it.
  • serve — a local mock of POST /core/invoices/: same path, same Client-Id / Secret-Key headers, same {"invoice": base64} body, EINV_STATUS / EINV_RESULTS / EINV_QR-shaped responses. It remembers what it accepted, so duplicates, returns against unknown invoices and over-returns across several partial returns are rejected, line by line.
  • template — prints sales and income invoices and (partial) returns, cash and receivable, in the manual's shape.
  • Agent skill — teaches Claude Code, Cursor, Devin, Codex & co. how JoFotara works and makes them validate their own output instead of guessing.

Not affiliated with the Income and Sales Tax Department. Passing jofotara-kit means "passes known rules", not "accepted by ISTD".

Quick start

npx jofotara-kit template invoice > invoice.xml
npx jofotara-kit validate invoice.xml
npx jofotara-kit serve --port 8080

Then point your integration's base URL at http://127.0.0.1:8080 instead of https://backend.jofotara.gov.jo. Nothing else changes.

curl -s http://127.0.0.1:8080/core/invoices/ \
  -H "Client-Id: test" -H "Secret-Key: test" -H "Content-Type: application/json" \
  -d "$(npx jofotara-kit template invoice --body)"

To use it from your code, tests or CI, install it (Node.js 22.18 or later):

npm install jofotara-kit

Build documents

Describe the sale; the builder does the JoFotara math and writes XML that passes every rule.

import { buildInvoice, buildCreditNote } from 'jofotara-kit';

const invoice = buildInvoice({
  id: 'INV-1001', icv: 57,                       // your number and your invoice counter
  seller: { taxNumber: '…', name: '…', incomeSourceSequence: '…' },
  pricesIncludeTax: true,                        // shelf prices; VAT is taken out exactly
  lines: [
    { name: 'Coffee', quantity: 3, unitPrice: 2.5, taxRate: 16, discount: { percent: 10 } },
    { name: 'Bread', quantity: 2, unitPrice: 0.75, taxRate: 0, taxCategory: 'Z' },
  ],
  orderDiscount: { amount: 1 },                  // spread over the lines for you
});
// invoice.xml, invoice.body (the request body to POST), invoice.document (every computed amount)

const ret = buildCreditNote({
  original: storedInvoiceXml,                    // the XML you sent and stored
  previousReturns: [storedReturnXml],            // earlier returns against it
  lines: [{ lineId: 1, quantity: 1 }],           // or 'all' for everything not yet returned
  reason: 'Customer return', id: 'RET-1001', icv: 58,
});

What it takes care of:

  • Money — exact arithmetic in micro-JOD, amounts written with six decimals. With pricesIncludeTax, each line's amount plus VAT equals what the customer paid, exactly.
  • Tax categories — S at an allowed rate; a 0% line must say Z (exempt) or O (zero-rated). Rates outside the manual's list are refused before any XML exists.
  • Buyers — a walk-in buyer (TN 0, "Cash customer") by default; a named buyer is required for receivables and cash sales above 10,000 JOD.
  • Returns — built from the original XML alone: original line numbers, names, prices, categories, buyer and type name are mirrored, sales returns get their extra fields, returned quantities are tracked across returns (over-returns are refused), and the last return of a line takes exactly what is left, so the returns always add up to the invoice.
  • Errors — a BuildError whose code is the rule the input would break, e.g. JOF-STA-005 for an over-return.

Keep the uuid of every document you send: a retry after a timeout must reuse the same XML. readDocument(xml) reads any stored JoFotara document back into plain amounts.

Special sales (013/023) follow the manual's formulas (p.56-80) but have not been verified against the live API yet; builds report this in warnings.

From any language

jofotara-kit build takes the same fields as JSON, so PHP, .NET, Python or Java code can shell out to it:

npx jofotara-kit build invoice.json > INV-1001.xml
npx jofotara-kit build invoice.json --body     # the request body instead of XML
npx jofotara-kit build invoice.json --json     # {xml, body, document, warnings}

A return names its original (and earlier returns) by path, relative to the JSON file:

{
  "type": "credit-note",
  "original": "INV-1001.xml",
  "previousReturns": ["RET-1000.xml"],
  "lines": [{ "lineId": 1, "quantity": 1 }],
  "reason": "Customer return",
  "id": "RET-1001",
  "icv": 58
}

A build error exits with status 1 and prints the rule id and the reason.

Send documents

The dangerous part of going live is not the XML — it is the answer. A timeout does not mean the invoice was rejected; resending a new document for the same sale can create a duplicate tax record. The client sorts every submission into one of four outcomes:

import { createClient, JOFOTARA_PRODUCTION_URL } from 'jofotara-kit';

const jofotara = createClient({
  baseUrl: 'http://127.0.0.1:8080',   // required: the local mock, or JOFOTARA_PRODUCTION_URL on purpose
  clientId: process.env.JOFOTARA_CLIENT_ID!,
  secretKey: process.env.JOFOTARA_SECRET_KEY!,
});

const outcome = await jofotara.submit(invoice);   // XML, request body, or a build result

| outcome.status | When | What to do | |---|---|---| | accepted | HTTP success and an explicit success status and a QR code | Store outcome.qr and outcome.raw; print the QR on the invoice. | | rejected | Explicit rejection (NOT_SUBMITTED, ERROR, an error list) and no sign of acceptance — also when it arrives with HTTP 200 | Show outcome.errors, fix, send again. | | unknown | Timeout, dropped connection, unreadable or HTML response, 5xx, success without a QR, contradictory answers | Do not send a new document for this sale. Check the portal; to retry, send this exact XML again (same UUID). | | not-sent | The document failed validation, or the server could not be reached at all (DNS, connection refused) | Nothing was recorded; fix and send. |

The client validates before sending (validateBeforeSending: false turns that off), never retries on its own, and never puts the secret key in an outcome. classifyResponse(status, body) applies the same rules to a response you received with your own HTTP code.

From the command line, documents are sent in order and sending stops at the first one that is not accepted, so a return is never sent after its invoice failed:

npx jofotara-kit serve &                                  # the local mock
npx jofotara-kit send INV-1001.xml RET-1001.xml           # goes to the mock by default

send --production goes to https://backend.jofotara.gov.jo and takes the credentials only from the JOFOTARA_CLIENT_ID and JOFOTARA_SECRET_KEY environment variables. Exit status: 0 accepted, 1 rejected or not sent, 3 unknown.

MCP server

Give any MCP client (Claude Code, Cursor, Devin, Windsurf, …) direct access to the validator:

{
  "mcpServers": {
    "jofotara": { "command": "npx", "args": ["-y", "jofotara-kit", "mcp"] }
  }
}

Tools: build_invoice, build_credit_note, validate_invoice, get_template, list_rules, explain_rule.

Install the agent skill

npx skills add balwahidi/jofotara-kit

Or copy skills/jofotara into your agent's skills folder (.claude/skills/, .devin/skills/, .cursor/skills/, …).

Commands

| Command | What it does | |---|---| | build [input.json] [--body \| --json] | Build an invoice or return from JSON (see Build documents). Reads stdin if no file. Exit 1 on build errors. | | send <files...> [--production \| --base-url URL] [--json] [--timeout MS] [--no-validate] | Send documents in order and classify each answer (see Send documents). Mock by default; credentials from the environment. | | validate [files...] [--json] | Validate XML, a {"invoice": base64} request body, or bare base64. Reads stdin if no files. Exit 1 on errors. | | serve [--port] [--host] [--client-id] [--secret-key] [--reject-status] | Run the mock. With --client-id/--secret-key only those credentials pass. --reject-status 200 tests "HTTP 200 but rejected" handling. | | template <name> [--body] | Print a sample XML, or its JSON request body. Names below. | | rules [--json] | List every rule. | | mcp | MCP server over stdio. |

Templates:

| Name | Type | Document | |---|---|---| | invoice | 388 / 012 | Sales invoice, cash (S, Z and O lines) | | credit-note | 381 / 012 | Partial sales return | | receivable-invoice | 388 / 022 | Sales invoice, receivable (named buyer) | | receivable-credit-note | 381 / 022 | Partial return of a receivable sales invoice | | income-invoice | 388 / 011 | Income invoice, cash (no VAT) | | income-credit-note | 381 / 011 | Partial income return | | income-receivable-invoice | 388 / 021 | Income invoice, receivable (named buyer) | | income-receivable-credit-note | 381 / 021 | Partial return of a receivable income invoice |

Mock extras: GET /_kit/invoices lists accepted documents, DELETE /_kit/invoices resets. Mock responses add a JOFOTARA_KIT key with full findings and fixes; everything else mirrors the real response shape. The mock QR decodes to JOFOTARA-KIT MOCK|NOT A TAX DOCUMENT|….

Use in CI

# have your test suite write the XML your integration generates to out/, then:
npx jofotara-kit validate out/*.xml

Or as a library:

import { validate } from 'jofotara-kit';

const report = validate(xml);
if (!report.ok) throw new Error(report.findings.map((f) => `${f.rule}: ${f.message}`).join('\n'));

Where the rules come from

Every rule states its source:

| Source | Meaning | |---|---| | manual p.N | Stated in the ISTD technical guide for the e-invoicing API, v1.4 (2023-12-01), page N. | | verified | Observed on the live API and contributed back with a redacted response. | | inferred | Follows from UBL 2.1 or accounting common sense; not in the manual. |

Run npx jofotara-kit rules for the full list.

Scope

The builder, the validator and the mock cover all six document families in the manual, in JOD: income (011/021), general sales (012/022) and special sales (013/023), each as new invoice (388) and return (381) — full, partial and multiple returns. Special sales are not yet verified against the live API. Templates cover income and general sales.

Contributing

The most valuable contribution is a real JoFotara response — especially a rejection the kit didn't predict — with the XML (redacted: tax numbers, names, income source sequence) and the response body. That is how inferred rules become verified. Never share your Client-Id or Secret-Key.

npm install
npm test
npm run build

License

MIT