Taxomat logo

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

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.

GET /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
GET /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
POST /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.
POST /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
GET /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.

GET /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
GET /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
}
POST /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": {} }
  }
}