Taxomat API
REST over the same data and scopes as MCP. The Bearer is an OAuth access token or a personal token from AI and API.
Import
GET /api/v1/accountsGET /api/v1/imports/schemaPOST /api/v1/importsPOST /api/v1/import_filesGET /api/v1/imports/:id
Export
Authentication
Authorization Bearer header. A personal token starts with tm_ and lasts 90 days. OAuth sign-in is refreshed by the client; after 30 days Authenticate again in the client. The target is a granted account alias, never a UUID.
Authorization: Bearer tm_…
Scopes and licenses
JSON import (scope imports:write) works without a license, including on the Basic plan. Exporting holdings (scope holdings:read) needs the Optimal or Maximal plan — Basic is not enough. On Optimal the connection is time-limited; on Maximal there is no limit. A connection with only holdings:read cannot import.
Import
Into one account. No license or subscription is required. Scope imports:write. Read granted account aliases from GET /api/v1/accounts. The JSON Schema is public (GET /api/v1/imports/schema, no token). JSON trades via POST /imports. Broker statements (XTB, IB, …) the same as Nahrání dat via POST /import_files. Read status by import_id.
/api/v1/accounts
imports:write
Portfolios and accounts checked on this connection. Tree portfolios → accounts, aliases only.
Request
No body. No filters.
Response
JSON tree with aliases. The same aliases import expects in account.alias.
Errors
401 without a token. 429 when the read limit is hit — retry_after in the body and the Retry-After header.
MCP: list_accounts (no arguments).
Example:
curl -H "Authorization: Bearer tm_…" https://app.taxomat.cz/api/v1/accounts
/api/v1/imports/schema
public
Public JSON Schema for POST /api/v1/imports. No token, no scope.
Request
No body and no Authorization header.
Response
JSON Schema.
Errors
No 401. The endpoint is open.
MCP: describe_import_schema (in an MCP session, same body).
Example:
curl https://app.taxomat.cz/api/v1/imports/schema
/api/v1/imports
imports:write
Asynchronous JSON import into one account. Nothing is calculated in the request.
Request
JSON body per the schema. Required account.alias and broker (key, or name + address). At least one row in flows or corporate_actions.
Response
202 { ok, import_id, filename, state, received_count }. Same body and 200 when client_request_id is replayed.
Errors
401 / 403 without imports:write. 404 for an unknown alias. 422 for invalid JSON. 429 after more than 30 uploads per hour — retry_after in the body and the Retry-After header. No dry_run.
MCP: import_flows (same body).
Same Bearer as the other endpoints. One account per request (alias from the grant). Commit is asynchronous. No dry_run, and no PDF or XLSX on this endpoint.
unit_price is the per-unit price including the trading fee. Optional trade_fee is only a breakdown (total commission in the trade currency) — it is not added again. Buy 10 shares at 180 USD + 5 USD commission → unit_price 180.5, trade_fee 5. Sell 2 shares at 190 USD − 1 USD commission → unit_price 189.5, trade_fee 1.
{
"account": { "alias": "account_1" },
"broker": {
"key": "ib",
"name": "Interactive Brokers",
"address": "91/92 N Wall Quay, North Wall, Dublin, Irsko"
},
"flows": [
{
"operation_type": "stock_buy",
"date": "2026-01-15",
"symbol": "AAPL",
"isin": "US0378331005",
"name": "Apple Inc.",
"units": 10,
"currency": "USD",
"unit_price": 180.5,
"trade_fee": 5.0
}
]
}
Upload import.json:
curl -X POST -H "Authorization: Bearer tm_…" -H "Content-Type: application/json" --data @import.json https://app.taxomat.cz/api/v1/imports
- An unknown account alias or another user's import_id returns 404. Portfolio UUIDs do not belong in the body.
- Shorts, futures, and forex are out of the first wave. Corporate actions go in corporate_actions, not flows. A broker statement (XLSX, CSV, PDF, ZIP) does not belong here — use POST /api/v1/import_files.
- The upload appears under Nahrání dat with an API, MCP, JSON, API vstup, or MCP vstup badge. Delete it there, not through the API.
/api/v1/import_files
imports:write
A broker statement into one account. Same importers as Nahrání dat (XTB, IB, Fio, Degiro, …).
Request
multipart/form-data. Required alias (granted account alias) and file. ZIP is unpacked.
Response
202 { ok, import_id, state, imports[{ import_id, state, filename }] }.
Errors
401 / 403 without imports:write. 404 for an unknown alias. 422 without a file or when the file is empty / too large. 429 after more than 10 files per minute or 30 uploads per hour — retry_after in the body and the Retry-After header.
MCP: import_file (alias, filename, content_base64) or describe_file_import.
Same Bearer. One account. Commit is asynchronous, same probe as the website. Keep JSON trades on POST /api/v1/imports.
Upload an XTB statement:
curl -X POST -H "Authorization: Bearer tm_…" -F "alias=account_1" -F "[email protected]" https://app.taxomat.cz/api/v1/import_files
/api/v1/imports/:id
imports:write
Import status, warnings, and errors. JSON or a broker file. Not a Flow dump.
Request
id in the URL is the import_id from POST /imports or POST /import_files.
Response
{ ok, import_id, filename, state, state_label, received_count, created_count, warnings[{ type, message, items }], errors }
Errors
404 for another user's import_id. 403 without access.
MCP: get_import.
import_id is the upload UUID (a poll handle, not a secret). filename is the file name. received_count = rows taken in (set immediately for JSON; after processing for a broker file). created_count = newly created flows; a duplicate stays 0. state: waiting_for_processing, processing, processed, processed_with_warnings (done, some rows skipped, usually duplicates), empty_file, unknown_file / unknown_file_final, conflict_with_final_tax_return, brokers_limit_exceeded, subscription_expired, error.
Export
Open positions, instrument performance, and sale simulation for the checked accounts. Optimal or Maximal plan only. The Basic plan and an account without a license cannot export. Scope holdings:read.
/api/v1/holdings
holdings:read
Open positions for the checked accounts. Tree portfolios → accounts → stocks / bonds / crypto / options.
Request
No body. No filters.
Response
JSON tree with aliases. status ready uses the same gate as the Taxomat overview.
Errors
401 without a token. 403 without holdings:read or without the Optimal / Maximal plan. 429 when the read limit is hit — retry_after in the body and the Retry-After header.
MCP: list_holdings (no arguments).
Example:
curl -H "Authorization: Bearer tm_…" https://app.taxomat.cz/api/v1/holdings
Holdings sample
{
"portfolios": [
{
"alias": "portfolio_1",
"accounts": [
{
"alias": "account_1",
"status": "ready",
"stocks": [
{
"isin": "US0378331005",
"symbol": "AAPL",
"units": 15,
"average_price": 172.4,
"currency": "USD",
"last_price": 189.5,
"exempt_units": 10,
"next_exemption_on": "2027-06-01",
"next_exemption_units": 5
}
],
"bonds": [],
"crypto": [],
"options": [
{
"name": "AAPL 20MAR26 180 C",
"symbol": "AAPL",
"option_type": "call",
"strike": 180,
"expires_on": "2026-03-20",
"units": -2,
"price": 640,
"currency": "USD"
}
]
}
]
}
]
}
status ready uses the same gate as the Taxomat overview. tax_calculation_incomplete / no_data means positions are not computed (tax year not ready, or no data).
What the fields mean
- portfolios → accounts → stocks / bonds / crypto / options. portfolio is a Taxomat group of accounts (family, client), not a broker login. account is one investment account (IBKR, Fio…). alias is the label the user kept or anonymized — never a UUID.
- stocks are open stock and ETF positions, not trades. bonds and crypto use the same fields. Market value = units × last_price (last_price may be omitted). isin is often omitted for crypto.
- options are open contracts only, not fills. Identical contracts (underlying, call/put, strike, expiry) are summed. units: long positive, short negative. name is the broker contract label. price is remaining premium (Cena on the open-options list), not a live quote.
- exempt_units — units that already passed the Czech 3-year holding test (exempt if sold today). next_exemption_on — date the next remaining units become exempt (ISO YYYY-MM-DD). next_exemption_units — how many units become exempt that day. next_* is omitted when the whole position is already exempt.
What is not returned
- Database UUIDs; portfolio and account names only if you leave them in the alias
- Trade history
- Tax returns and email
/api/v1/instrument_performance
holdings:read
Celkem performance for one instrument (stock, ETF, bond, or crypto) by ISIN on one account. Same numbers as the Zhodnocení card on the symbol detail page.
Request
Query: alias (required), isin (required), currency (optional — omit for the instrument currency, or CZK).
Response
JSON: invested_capital, market_value, averages, realized profit (sales / dividends / options), unrealized and total profit. No held units, exemption, or strategies.
Errors
401 without a token. 403 without holdings:read or without Optimal / Maximal. 404 for an unknown alias or an ISIN not on that account. 422 when alias/isin is missing or currency is not allowed. 429 when the read limit is hit — retry_after in the body and the Retry-After header.
MCP: get_instrument_performance (alias, isin, optional currency). Expensive — one ISIN per call.
Example:
curl -H "Authorization: Bearer tm_…" "https://app.taxomat.cz/api/v1/instrument_performance?alias=account_1&isin=US0378331005"
Instrument = stock, ETF, bond, or crypto by ISIN — not a company name. Amounts are in currency. Per-share averages stay in average_currency (the instrument currency). Do not loop this over every row from list_holdings.
{
"account": "account_1",
"isin": "US0378331005",
"symbol": "AAPL",
"currency": "USD",
"average_currency": "USD",
"invested_capital": -1977,
"market_value": 7774,
"economic_average_price": -3.97,
"economic_average_to_market_percent": null,
"purchase_average_price": 9.57,
"purchase_average_to_market_percent": 63.1,
"realized": {
"sales": 6744,
"dividends": 0,
"options": 0
},
"unrealized_profit": 3007,
"unrealized_profit_percent": 63.1,
"realized_profit": 6744,
"realized_profit_percent": null,
"total_profit": 9751
}
/api/v1/sale_simulation
holdings:read
Sale simulation for one stock/ETF on one account. Same numbers as Simulace prodeje in the tracker. Does not write a trade.
Request
JSON body: alias, isin, units, unit_price (required). Optional date (ISO; default today; never before today).
Response
JSON: sale_proceeds, prefer_exempted true/false, each method with taxable/exempt profit and units plus open_after_sale (remaining average price, units/value, next time-test). Daily FX only.
Errors
401 without a token. 403 without holdings:read or without Optimal / Maximal. 404 for an unknown alias or no open lots. 422 for missing / invalid inputs. 429 when the read limit is hit — retry_after in the body and the Retry-After header.
MCP: simulate_sale (alias, isin, units, unit_price, optional date). Expensive — one scenario per call.
Example:
curl -X POST -H "Authorization: Bearer tm_…" -H "Content-Type: application/json" \
-d '{"alias":"account_1","isin":"US0378331005","units":76,"unit_price":15.61,"date":"2027-10-09"}' \
https://app.taxomat.cz/api/v1/sale_simulation
Daily FX only. Both prefer-exempted variants. No recommended method and no lot dump. Does not write a trade.
{
"account": "account_1",
"isin": "US0378331005",
"symbol": "SOFI.US",
"date": "2027-10-09",
"units": 76,
"unit_price": 15.61,
"currency": "USD",
"sale_proceeds": 1186.36,
"exchange_method": "daily_rates",
"prefer_exempted": {
"true": {
"methods": {
"fifo": {
"taxable_profit": 0,
"taxable_units": 0,
"exempt_profit": 13053,
"exempt_units": 76,
"open_after_sale": {
"average_price": 7.71,
"exempt_units": 400,
"exempt_value": 136188,
"taxable_units": 22,
"taxable_value": 7490,
"next_time_test_on": "2029-05-07",
"next_time_test_units": 22
}
}
}
},
"false": { "methods": {} }
}
}