English | Русский | Українська | Srpski | Српски
Usage
Quick Start
If you want to quickly create a specific declaration:
- Configure settings (config) ↗ — once on first run
- Fetch latest data (fetch) ↗
- Generate report (report) ↗
- Upload the generated XML to the ePorezi portal (PPDG-3R section)
To automatically receive all declarations and track their statuses — use sync instead of steps 2–3.
Configuration (config)
ibkr-porez config
Creating or modifying personal data and IBKR access settings.
You will be prompted for:
- IBKR Flex Token: Get Token ↗
- IBKR Query ID: Create Flex Query ↗
- Personal ID: JMBG / EBS
- Full Name: First and Last Name
- Address: Registered Address
- City Code: 3-digit municipality code. Example:
223(Novi Sad). Code can be found in the list (see column “Šifra”). Also available in the dropdown on the ePorezi portal. - Phone: Phone
- Email: Email
- Data Directory: Absolute path to folder with data files (
transactions.json,declarations.json,rates.json, etc.). Default:ibkr-porez-datain the application folder. - Output Folder: Absolute path to folder for saving files from
sync,export,export-flex,reportcommands. Default: your system’s Downloads folder.
Fetch Data (fetch)
ibkr-porez fetch
Downloads latest data from IBKR and syncs exchange rates from NBS (National Bank of Serbia).
Saves them to local storage.
Import Historical Data (import)
ibkr-porez import /path/to/activity_statement.csv
Loading transaction history older than 365 days, which cannot be retrieved via Flex Query (fetch).
To create a file with transactions on the Interactive Brokers portal, see Export Full History ↗
⚠️ Do not forget to run
fetchafterimportso the application adds maximum details at least for the last year into the less detailed data loaded from CSV.
Synchronization Logic (import + fetch)
When loading data from CSV (import) and Flex Query (fetch), the system prioritizes more complete Flex Query data:
- Flex Query (
fetch) data is the source of truth. It overwrites CSV data for any matching dates. - If a Flex Query record matches a CSV record semantically (Date, Ticker, Price, Quantity), it counts as an update (replacing with official ID).
- If data structure differs (e.g. split orders in Flex Query vs “bundled” record in CSV), the old CSV record is removed, and new Flex Query records are added.
- Completely identical records are skipped.
Sync Data and Create Declarations (sync)
ibkr-porez sync
Does the same as fetch:
- Downloads latest transactions from IBKR via Flex Query
- Syncs exchange rates from NBS
Then creates all necessary declarations for the last 45 days (if they haven’t been created already).
💡 If the connection to IBKR fails,
syncstill creates declarations from the transactions already stored locally and prints a warning; it exits successfully, and in the GUI keeps retrying for fresh data on the next cycle.
You can then Manage created declarations.
💡 If you ran
syncfor the first time and it created declarations that you already submitted before starting to use the application, you can quickly mark them all as paid and remove them from list output:ibkr-porez list --status submitted -1 | ibkr-porez pay
Sync from a Downloaded XML File (sync --file)
If the IBKR API is temporarily unavailable, you can download the Flex Query XML manually from the IBKR website and use it instead:
ibkr-porez sync --file /path/to/report.xml
Does everything sync does — saves transactions, generates all necessary declarations — but reads data from a local file instead of calling the IBKR API.
See how to download the Flex Query XML ↗.
In the GUI, the same option is available in the ☰ hamburger menu as Sync from Flex Query XML….
View Statistics (stat)
ibkr-porez stat --year 2025
ibkr-porez stat --ticker AAPL
ibkr-porez stat --month 2025-01
Shows:
- Dividends received (in RSD)
- Number of sales (taxable events)
- Estimated Realized P/L (Capital Gains) (in RSD)
- Detailed breakdown by tickers or months (when using filters)
Generate Tax Report (report)
ibkr-porez report
If you don’t specify report type and period, PPDG-3R for the last complete half-year is generated by default
- Creates
ppdg3r_XXXX_HY.xmlin Output Folder - Import this file into the Serbian Tax Administration portal (ePorezi)
- Manually upload the file from Confirmation Document to Item 8
To select a different declaration type or time period, see the documentation
ibkr-porez report --help
Declaration Management
After creating declarations through the sync command, you can view them, change their status, and export them for upload to the tax portal.
List Declarations (list)
Shows a list of all declarations with the ability to filter by status.
# Show active declarations (default):
# draft + submitted + pending
ibkr-porez list
# Show all declarations
ibkr-porez list --all
# Filter by status
ibkr-porez list --status draft
ibkr-porez list --status submitted
ibkr-porez list --status pending
ibkr-porez list --status finalized
# Only declaration IDs (for use in pipes)
ibkr-porez list --ids-only
ibkr-porez list --status draft -1
Example usage in Linux-style:
# Submit all drafts
ibkr-porez list --status draft -1 | ibkr-porez submit
View Declaration Details (show)
Shows detailed information about a specific declaration.
ibkr-porez show <declaration_id>
Displays:
- Declaration type (PPDG-3R or PP OPO)
- Declaration period
- Status (draft, submitted, pending, finalized)
- Transaction details and calculations
- For PPDG-3R: tax-authority-recognized gain/loss alongside the calculated values, the capital loss carryforward applied (opening/used/adjusted/ closing balance), and which vintages it was drawn from
- Attached files
Submit Declaration (submit)
ibkr-porez submit <id> [<id> ...]
Marks the declaration as submitted (imported to the tax portal).
Behavior depends on declaration type:
PPDG-3Rmoves topendingaftersubmit(waiting for tax authority assessment).PP OPOaftersubmit:- moves to
submittedif tax is due; - moves directly to
finalizedif tax due is0.
- moves to
Pay Declaration (pay)
ibkr-porez pay <id> [<id> ...]
ibkr-porez pay <id> --tax 1234.56
Marks the declaration as finalized and stores payment date.
Option --tax lets you record tax amount during payment, without a separate assess step.
After this, it will disappear from the list shown by list (without --all)
Record Assessed Tax (assess)
# Record the official tax amount from the assessment
ibkr-porez assess <declaration_id> --tax 1234.56
# Record the amount and immediately mark it as already paid
ibkr-porez assess <declaration_id> --tax 1234.56 --paid
# Record a capital loss recognized by the tax authority (PPDG-3R only)
ibkr-porez assess <declaration_id> --loss 50000.00 \
--reference "RES-123/2025" --date 2025-09-01
# Record a capital gain recognized by the tax authority (PPDG-3R only)
ibkr-porez assess <declaration_id> --gain 12000.00
This command is mainly for PPDG-3R, where the tax amount as well as the
recognized capital gain/loss are determined by the tax authority after
submission.
What it does:
- stores the official tax amount in the declaration metadata (
--tax); - with
--paid, immediately moves the declaration tofinalized; - without
--paid:- if the amount is greater than zero, keeps the declaration active (
submitted) for later payment; - if the amount is zero, moves the declaration to
finalized.
- if the amount is greater than zero, keeps the declaration active (
At least one of --tax, --gain, or --loss must be provided.
--gain and --loss are only available for PPDG-3R and record the capital
gain/loss recognized by the tax authority — they are stored alongside the
application’s calculated values and may differ from them (due to CPI
adjustments or the tax authority’s methodology). A single assessment cannot
recognize both a gain and a loss.
--reference, --date, and --notes are details about the assessment
decision (reference number, date, notes). They are shown in
show, and for a recognized loss the ruling
number and date are also filled into part 7 of future PPDG-3R declarations
(see capital loss carryforward),
so it is worth recording them.
If the assessment recognizes a loss (--loss greater than zero), an entry is
created (or updated) in the
capital loss carryforward registry.
The carryforward is always based on the loss recognized by the tax authority,
not the calculated one.
⚠️ Once a carried-forward loss has been at least partially used by a subsequent declaration, the recognized loss can no longer be changed via
assess— the command will return an error.
Capital Loss Carryforward (carryforward)
ibkr-porez carryforward
Shows all vintages of capital losses recognized by the tax authority that are available for carryforward to future periods:
- the source declaration and the period for which the loss was recognized;
- the recognized and remaining (unused) amount;
- the tax year after which the carryforward expires (a loss can be carried forward for 5 years);
- status:
Active(usable),Exhausted(fully used),Expired(past its expiration).
The same list is available in the GUI from the ☰ menu → Capital loss carryforward….
Every PPDG-3R declaration created via
sync automatically reduces the
calculated tax base using available carryforward (from older periods to
newer), until the base reaches zero or the carryforward is exhausted. The
report preview shows the carryforward amount
used and remaining afterwards. The amount is deducted from the registry once
— when the declaration is saved; re-running sync for the same period does
not deduct it again.
Carried-forward losses are also declared in the declaration itself: the
PPDG-3R XML fills in form part 7 (“Kapitalni gubici”) — one row per active
carryforward, with the tax authority’s ruling number and date (7.2/7.3) and
the remaining loss amount (7.4). The final Osnovica and PorezZaUplatu in
the XML also reflect the applied carryforward. Declaring the loss in part 7
is the taxpayer’s responsibility — without it the tax authority will not
apply it in the assessment.
The ruling number and date come from
assess (--reference and --date). If they
are not recorded, fields 7.2/7.3 stay empty in the XML and report prints a
warning — record them with assess and regenerate the report, or fill in
those fields manually on the portal.
Export Declaration (export)
ibkr-porez export <declaration_id>
ibkr-porez export <declaration_id> -o /path/to/output
Copies XML and all attached files (attach) to Output Folder or to the directory specified in parameters.
Revert Declaration Status (revert)
# Revert to draft (default)
ibkr-porez revert <id> [<id> ...]
# Revert to submitted
ibkr-porez revert <id> [<id> ...] --to submitted
Reverts declaration status.
Delete Declaration (delete)
# Preview the plan (changes nothing)
ibkr-porez delete <id>
# Delete the declaration
ibkr-porez delete <id> --yes
# Allow deleting a non-draft declaration
ibkr-porez delete <id> --yes --force
Deletes a declaration and undoes its carryforward-ledger effects: any carryforward it consumed is returned to the source vintages, and its own recognized-loss vintage (created by assess) is removed. If you deleted it to fix an error, run sync afterwards to rebuild the period from stored transactions.
Without --yes it only prints what would be deleted. --force is required to delete a declaration that is not a draft. Only the most recent PPDG-3R can be deleted — deleting an earlier one would leave later declarations’ carryforward dangling; PP OPO declarations can be deleted at any time.
This is also how you correct a prior declaration’s assessment that a later one already consumed: delete the later declaration (freeing the carryforward), re-run assess on the earlier one, then sync to rebuild.
In the GUI the same action is available as the Delete button in the declaration’s row: it opens a confirmation dialog (replacing --yes) with a Force checkbox for non-draft declarations.
Attach File to Declaration (attach)
# Attach file
ibkr-porez attach <declaration_id> /path/to/file.pdf
# Delete attached file
ibkr-porez attach <declaration_id> <file_id> --delete
ibkr-porez attach <declaration_id> --delete --file-id <file_id>
Attaches a file to a declaration or removes an attached file from declaration storage.
For saving to declaration storage, only the file name is used (path is discarded), so names must be unique - otherwise a file with the same name will overwrite a previously uploaded file with the same name even if from a different path
💡 Attached files are copied along with the declaration XML during export
Export Flex Query (export-flex)
ibkr-porez export-flex 2025-01-15
ibkr-porez export-flex 2025-01-15 -o /path/to/output.xml
ibkr-porez export-flex 2025-01-15 -o - # Output to stdout (for pipes)
Exports Flex Query XML file obtained during fetch or sync on the specified date.
Example usage in Linux-style:
ibkr-porez export-flex 2025-01-15 | ibkr-porez sync --file -