edf-tool
Your monthly export-of-services paperwork in one command, entirely on your own computer.
If you're an Indian freelancer or sole proprietor paid from abroad (directly or through Deel, Remote, an EOR and so on), your bank wants an EDF (Export Declaration Form) for each foreign payment. edf generates, every month:
| File | What it is |
|---|---|
…_INV-2026-10.pdf |
Your invoice to the client: GSTIN, PAN, SAC, IGST line, place of supply, amount in words |
…_EDF_INV-2026-10.xlsx |
The bank's own EDF workbook, filled in. The bank's formatting, dropdowns and logos are left untouched. |
…_EDF_Form_INV-2026-10.pdf |
Two pages ready to sign and send: the Request Letter and the Invoice Details |
Generated from the fake data in config.example.json.
🔒 Privacy: nothing leaves your machine
- No network access, ever. The code has no networking, sends no telemetry and doesn't check for updates. The test suite fails if any networking module is even imported.
- Your details (PAN, GSTIN, bank account, client) are stored only in
~/.config/edf-tool/config.json, readable only by you (0600). - Your signature is stored only in
~/.config/edf-tool/signature.png. - Generated files go to
~/Documents/EDF/<invoice no>/, or wherever you choose. - No Python packages are needed: it's pure standard library, so there's no third-party code to trust.
- PDFs are made locally. The invoice is drawn by edf itself, and the bank form PDF uses your own LibreOffice, run offline.
Install
Homebrew (macOS / Linux)
brew install krushiraj/tap/edf-tool
uv: run it without installing, or install it as a tool
uvx edf-tool --help # one-off run
uv tool install edf-tool # then just: edf
pipx / pip
pipx install edf-tool
# or: python3 -m pip install --user edf-tool
Straight from GitHub (no PyPI needed)
uvx --from git+https://github.com/krushiraj/edf-tool edf --help
Optional: LibreOffice, used only to turn the bank workbook into a PDF. The invoice PDF and the Excel file work without it.
brew install --cask libreoffice # macOS
sudo apt install libreoffice-calc # Debian/Ubuntu
You need Python 3.9 or newer; macOS already includes it. Check everything with edf doctor.
First run (one-time setup)
edf init
Setup asks for everything the bank form and the invoice need. Every required field must be answered and is checked as you type: PAN format, GSTIN format and check digit, GSTIN-matches-PAN, IFSC, SAC code, phone and email. It covers:
- You: business name, proprietor, address (as on your GST certificate), phone, email, PAN, GSTIN, IEC (or "Not applicable")
- Your bank account: branch, branch address, IFSC, account number
- Your client: legal name, address, country, registration/VAT number (optional)
- Who sends the money: the client directly, or a third party like Deel (name, address, relationship)
- The service: RBI purpose code (e.g. P0802), SAC code (e.g. 998314), description, scope
- Monthly defaults: usual amount, usual transfer fee, invoice number prefix
- (optional) A picture of your signature
If you run edf generate before setting up, it starts setup for you.
Prefer a file? Copy config.example.json, fill it in, then:
edf init --from my-details.json --signature ~/Desktop/signature.jpg
rm my-details.json # the details now live in ~/.config/edf-tool/
Every month
edf generate
Monthly invoice + EDF (Enter accepts the [suggested] value)
Invoice date (DD-MM-YYYY) [30-09-2026]:
Invoice number [INV-2026-10]:
Work period start [31-08-2026]:
Work period end [30-09-2026]:
Due date [30-09-2026]:
Invoice amount (USD) [2400.00]:
Fee deducted before it reaches the bank [6.00]:
Date on the bank request letter [07-10-2026]:
Invoice INV-2026-10 dated 30-09-2026, due 30-09-2026
Client Example Corp LLC
Period August 31, 2026 to September 30, 2026
Amount USD 2,400.00 fee USD 6.00 net to bank USD 2,394.00
Generate these files? (yes/no) [yes]:
✓ Files in ~/Documents/EDF/INV-2026-10
Press Enter to accept each suggestion:
- Invoice number: the next number after the last one you made.
- Invoice date: the last day of last month (or of this month, if it's after the 15th).
- Work period: from the last day of the previous month to the invoice date.
edf won't let you reuse an invoice number unless you add --force.
About the fee: if Deel, or a correspondent bank, takes a fee before the money reaches you, enter it here. The bank form then declares the full export value (the invoice amount) and the lower Net Realisable Value, and explains the difference in the remarks. That's what lets your bank close the EDF cleanly when slightly less money arrives.
Signing
Pick whichever suits you:
- macOS Preview (or any PDF app): open the two PDFs and use Tools → Annotate → Signature.
- No PDF signing app? Save your signature once, and edf will stamp it for you:
edf signature set ~/Desktop/my-signature.jpg # background removed automatically edf generate --sign # also writes *_signed.pdf copies edf sign INV-2026-10 # or sign one you've already generated
Use a photo or scan of your signature on white paper. PNG works everywhere; on a Mac, JPG and HEIC work too. - In the Excel file: once a signature is saved, it's placed on the signatory line automatically. You can turn this off with
edf config set output.embed_signature_in_excel false.
Then send the bank form PDF and the invoice PDF to your branch, along with the .xlsx if they ask for it. Many branches also want your firm's stamp/seal next to the signature.
Commands
| Command | What it does |
|---|---|
edf init |
First-run setup. --from FILE reads a JSON file, --force redoes setup, --signature IMG saves a signature |
edf generate (gen) |
This month's files. Flags: --invoice-date, --invoice-no, --amount, --fee, --period-start/--period-end, --due-date, --letter-date, --sign, --dry-run, -y, --json |
edf sign INV-… |
Signed PDFs for an invoice already generated |
edf signature set IMG | show | remove |
Manage your saved signature |
edf config show | path | edit | validate |
Look at, open or check the config |
edf config set FIELD VALUE |
Change one value, e.g. edf config set defaults.amount 2500 |
edf history |
Invoices generated so far |
edf doctor |
Checks the config, bank template, signature, LibreOffice and output folder |
edf banks |
Supported bank forms |
edf man |
The full manual (also man edf when installed with Homebrew) |
Every command accepts --json for machine-readable output, and has --help with examples.
Exit codes:
0OK2config missing or invalid3bad input (date, amount, or an invoice number already used)4bank form doesn't match the known version5files written, but an optional step was skipped (e.g. no LibreOffice)130cancelled
Configuration reference
The config lives in ~/.config/edf-tool/config.json; run edf config path to see the exact location. Edit it with edf config edit or edf config set.
| Field | Example | Notes |
|---|---|---|
exporter.firm_name |
ACME DEV STUDIO |
As on your GST certificate |
exporter.proprietor |
Asha Kumar |
Signs the forms |
exporter.constitution |
Sole Proprietorship |
|
exporter.address_lines |
["12 MG Road", "Bengaluru, KA 560038", "India"] |
Your GST-registered address |
exporter.phone, exporter.email |
Printed on the invoice and the bank form | |
exporter.pan, exporter.gstin |
ABCDE1234F, 29ABCDE1234F1ZW |
Validated, including the GSTIN check digit |
exporter.iec |
Not applicable |
Most service exporters don't have one |
bank.branch_name, bank.branch_address, bank.ifsc |
From your bank statement | |
bank.account_no, bank.fee_account_no |
The account credited, and the account charges are debited from | |
client.name, client.address_lines, client.country |
The service recipient | |
client.short_address |
Example Corp LLC, San Francisco, CA, USA |
Used in narrow boxes |
client.registration_number, client.vat_id |
Optional | |
third_party.enabled |
true |
Set to true if Deel, Remote or similar sends the money |
third_party.name, .address, .short_address, .country, .relationship |
Deel, Inc. |
The bank must know about third-party payers (FEMA) |
service.purpose_code, .purpose_description |
P0802, Software consulting / implementation |
RBI purpose code |
service.sac |
998314 |
998314 IT design & development; 998313 IT consulting & support |
service.description, .short_description, .line_item, .scope |
Wording on the invoice and the form | |
service.currency |
USD |
Any 3-letter code |
defaults.amount, defaults.payment_fee |
2400, 6 |
Used as the suggestions each month |
defaults.invoice_prefix |
INV- |
|
output.folder |
~/Documents/EDF |
|
output.file_prefix |
Acme_Dev |
|
output.embed_signature_in_excel |
true |
|
bank_format |
hdfc_services |
See edf banks |
bank_template |
(unset) | Optional path to a newer copy of the same bank form |
More than one client? Keep a separate profile for each one with EDF_TOOL_HOME:
EDF_TOOL_HOME=~/.config/edf-tool-clientB edf init
EDF_TOOL_HOME=~/.config/edf-tool-clientB edf generate
Using it with AI agents
edf is built to be driven by coding agents (Claude Code, Codex, Cursor and so on) without anything leaving your machine:
- Everything works without prompts:
-y,--from, and--jsonon every command, with documented exit codes. AGENTS.mdhas step-by-step setup and monthly instructions, plus guardrails.skills/edf-tool/SKILL.mdis a ready-made agent skill. Copy it into your agent's skills folder, e.g.~/.claude/skills/edf-tool/.
Tell your agent: "Set up edf-tool from github.com/krushiraj/edf-tool and generate this month's EDF."
Good to know
- GST / LUT: the invoice shows
IGST (0%). Exporting services without charging IGST requires a Letter of Undertaking (LUT), filed for free on the GST portal (form RFD-11). Without an LUT, IGST is payable and then refunded. Check with your CA. You can change the tax line's wording withedf config set defaults.igst_label "…". - IEC: usually not required for exporting services; the form says "Not applicable".
- The bank form version: before filling anything, edf checks that the form's labels match the version it knows. If HDFC changes the form, edf stops with exit status 4 rather than writing into the wrong cells. Please open an issue, and remove your personal data from anything you attach.
- Fees and realisation: keep Deel's (or your bank's) statement showing the fee. Banks sometimes ask for it when they close the EDF.
Troubleshooting
| Problem | Fix |
|---|---|
No config found |
Run edf init |
EDF form PDF skipped (exit status 5) |
Install LibreOffice, or open the .xlsx and export the REQUEST LETTER and Invoice Details 1 sheets as PDF |
| LibreOffice installed but not found | export SOFFICE_PATH=/path/to/soffice |
| Signature has a grey box around it | edf signature set img.jpg --threshold 170 (a lower value removes more background) |
| An invoice number was already used | Use the next number, or add --force to regenerate it |
| Text cut off in a box on the bank form | Shorten client.short_address, service.short_description or third_party.short_address |
Contributing
Bug reports and new bank formats are welcome; see CONTRIBUTING.md. The tests run with only fake data: python3 -m unittest discover -s tests.
License
MIT, © Krushi Raj Tula. The bundled HDFC form belongs to HDFC Bank. It's included unchanged apart from removed document metadata, and isn't covered by the MIT license. This project is not affiliated with or endorsed by HDFC Bank.
Metadata
Release files for edf-tool 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| edf_tool-0.1.0.tar.gz | 327.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| edf_tool-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 414.5 kB
Release files / edf_tool-0.1.0.tar.gz
| Download URL | edf_tool-0.1.0.tar.gz |
|---|---|
| Size | 327.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a3adbeb5c8baef16161980e2d69b346644831612293ffcaaddfe1d81a05a123
|
|
BLAKE2b-256 checksum How to use checksums |
6a2c15c3260e5f869d5423c8f4f55a7bc44c65f133ff130ab1536506779f85d7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / edf_tool-0.1.0-py3-none-any.whl
| Download URL | edf_tool-0.1.0-py3-none-any.whl |
|---|---|
| Size | 87.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
45de389fbb4d3a49d2e0673a181279e0fbdccb3a368479999a6ec16bb360311c
|
|
BLAKE2b-256 checksum How to use checksums |
21ac785f7ba0284d68db51d2c91b8548d8d90d8825d40c703dcd6a9e1b81ef92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log