{"name":"Saldo – TechTools účetnictví API","version":"1.1","description":"Czech bookkeeping for s.r.o. and OSVČ: invoices, bank matching, double-entry ledger or daňová evidence, VAT return + kontrolní hlášení + souhrnné hlášení with EPO XML, income tax (DPFO/DPPO), OSVČ insurance, income tax returns (DPFO/DPPO) as EPO XML, OSVČ insurance, payroll, travel allowances and depreciation, plus shared invoice links, attachments, quotes and orders, Fio bank feeds and insolvency checks. Public calculators need no account; bookkeeping endpoints need a personal API key or the TechTools session cookie.","base_url":"https://techtools.cz/ucetnictvi-api","ui":"https://saldo.techtools.cz/","documentation":"https://saldo.techtools.cz/dokumentace/api/","openapi":"https://techtools.cz/ucetnictvi-api/openapi.json","conventions":{"money":"JSON numbers in the document currency, 2 decimals; *_czk fields are converted with the document exchange rate","dates":"ISO 8601 (YYYY-MM-DD)","errors":"{ \"error\": \"human readable message (Czech)\" } with 400/401/403/404/422","auth":"Agents: create a key in Saldo → Nastavení → API a integrace and send it as \"X-API-Key: saldo_…\" or \"Authorization: Bearer saldo_…\". A key acts with its owner's rights in every firm they belong to; read-only keys refuse every non-GET request (403 READ_ONLY_KEY). Browsers: the session cookie (_techtools4_session, fetch credentials: \"include\").","csrf":"Every non-GET request that carries the session cookie must also send \"X-Saldo: 1\"; cross-site requests (Origin or Sec-Fetch-Site from another site) are refused with 403 CROSS_SITE. Requests without cookies (API keys, curl to the public calculators) need no extra header.","rate_limits":"API keys: 600 requests per minute (429 RATE_LIMITED). Calculators are CPU-only and stateless; inputs are bounded (numbers ≤ 15 digits, dates 2000–2100)"},"public_endpoints":{"docs":{"method":"GET","path":"/docs"},"rates":{"method":"GET","path":"/rates?year=2026","returns":"every statutory constant used by the engines, with source URLs and reviewed_on (the date they were last checked against the law)"},"holidays":{"method":"GET","path":"/holidays?year=2026","returns":"Czech public holidays (deadline shifting: § 33 odst. 4 daňového řádu, § 23 odst. 3 zákona č. 589/1992 Sb.)"},"deadlines":{"method":"GET","path":"/deadlines?year=2026\u0026legal_form=osvc\u0026vat=quarterly\u0026employees=true","params":{"legal_form":"osvc|sro|as","vat":"none|monthly|quarterly|identified","employees":"bool","advisor":"bool","databox":"bool","flat_tax":"bool","recap_statement":"bool","real_estate":"bool","road_tax":"bool"},"returns":"{ events: [{ date, title, detail, category, basis }] } — every tax and insurance deadline, shifted to business days"},"codebooks":{"method":"GET","path":"/codebooks","returns":"document kinds, VAT modes, account kinds, insurers, EU countries, RC codes, tax offices…"},"qr_platba":{"method":"GET","path":"/qr?iban=CZ6508000000192000145399\u0026amount=1210\u0026vs=20260001\u0026message=Faktura","returns":"{ spayd: \"SPD*1.0*ACC:…\" } — the Czech QR Platba string (render it as a QR code)"},"shared_document":{"method":"GET","path":"/share/:token","returns":"public view of a shared invoice (JSON: document, entity, bank_account, spayd); GET /share/:token/isdoc downloads its ISDOC. The page is https://saldo.techtools.cz/faktura/#\u003ctoken\u003e"},"isdoc_parse":{"method":"POST","path":"/isdoc/parse","body":"multipart field file (.isdoc/.isdocx, or a PDF with embedded ISDOC) or the raw XML as the request body","returns":"normalized invoice: parties, lines, VAT recap, payment details","example":"curl -F file=@faktura.isdoc https://techtools.cz/ucetnictvi-api/isdoc/parse"},"calculators":{"method":"POST","path":"/calc/:calculator","body":"JSON object (Content-Type: application/json) or form fields","available":{"vat":{"input":{"net":1000,"rate":21},"alt":{"gross":1210,"rate":21}},"osvc":{"input":{"year":2026,"income":900000,"flat_rate":60,"children":[{"order":1}],"spouse":false},"returns":"DPFO tax, credits, child bonus, social and health insurance, advances for next year"},"regime":{"input":{"year":2026,"income":900000,"expenses":250000},"returns":"ranked comparison of actual expenses vs výdajové paušály vs paušální daň with the recommendation"},"flat_tax":{"input":{"year":2026,"income":1400000,"income_kind":"services","vat_payer":false}},"corporate":{"input":{"year":2026,"accounting_profit":1250000,"non_deductible":[{"label":"Reprezentace","amount":18000}]},"optional":"gifts (pole částek jednotlivých darů – odečítají se jen dary od 2 000 Kč, § 20 odst. 8 ZDP), loss_carryforward, rd_deduction, paid_advances, last_known_tax"},"payroll":{"input":{"year":2026,"month":9,"contract":"hpp","gross":52000,"children":[{"order":1}]},"optional":"agreed_wage (sjednaná mzda – účast na pojištění u malého rozsahu), ytd_social_base (dosavadní roční vyměřovací základ – maximum § 15a zákona č. 589/1992 Sb.), health_minimum_exempt, pensioner_discount, ztpp, student","returns":"payslip: insurance both sides, tax advance, credits, net pay, employer cost, postings"},"travel":{"input":{"destination":"Brno","departure":"2026-09-21T06:30","arrival":"2026-09-21T19:10","km":420,"vehicle_kind":"car","consumption":6.1,"fuel":"diesel"}},"depreciation":{"input":{"price":850000,"group":2,"method":"straight","start_year":2026}},"late_interest":{"input":{"principal":48400,"due_date":"2026-05-15","paid_on":"2026-09-26"},"optional":"business (výchozí true) – paušál 1 200 Kč náleží jen mezi podnikateli nebo vůči veřejnému zadavateli (§ 3 NV č. 351/2013 Sb.)"}},"example":"curl -X POST -H 'Content-Type: application/json' -d '{\"year\":2026,\"income\":900000,\"flat_rate\":60}' https://techtools.cz/ucetnictvi-api/calc/osvc"}},"account_endpoints":{"entities":{"list":"GET /entities","create":"POST /entities { entity: {...}, bank: {...} }","demo":"POST /entities/demo?variant=sro|osvc","show":"GET /entities/:id","update":"PATCH /entities/:id","lock_period":"POST /entities/:id/lock { locked_until }","invoice_look":"PATCH /entities/:id { entity: { accent_color: #RRGGBB, logo_data, settings: { invoice_style: plain|linka|summary|sidebar|swiss|block|studio|nordic|classic|mono|executive|corporate|modern|elegant|technical|edge|duo|poster|letter|outline|soft|bigtotal|night|glanceink|sideright|sideink|heritage|frame|geometric|atelier|serifclean|ledger|compact|dense|wholesale|guilloche, invoice_font: auto|inter|plex|manrope|dm|franklin|serif|lora, invoice_density: normal|compact|airy, invoice_table: auto|lines|zebra|grid|minimal|headfill|headline, invoice_corners: auto|sharp|round, invoice_logo_size: s|m|l, show_qr, invoice_logo_name, invoice_row_numbers, invoice_paid_stamp, invoice_contacts, invoice_credit (booleans) } } } (owner or accountant; auto = the design's own choice; applies to every document the firm prints and to shared links)","export":"GET /entities/:id/export (full JSON backup)"},"accounting_package":"GET /entities/:entity_id/accounting_package/preview?from=YYYY-MM\u0026to=YYYY-MM (counts, report file names, size, what is missing; reports are not generated), GET …/accounting_package?from=\u0026to= → ZIP with reports (CSV, statements JSON per fiscal year), original attachments and received ISDOC of documents dated in the period (issue date, DUZP or VAT date) or referenced by its entries or payments, attachments of drafts, bank statement files and PDFs, filings with receipts, chybejici-podklady.csv, manifest.json (size and SHA-256 of every file, completeness) and SHA256SUMS; owner or accountant only, at most 800 days and 1 GB of originals, reads only; one build per firm at a time (409 PACKAGE_BUSY), HEAD 405, a browser request from another site 403","work_items":"Accountant work queue across the user's firms: GET /work_items?entity_id=\u0026assignee=me|unassigned|\u003cuser id\u003e\u0026priority=high|normal|low\u0026due=overdue|week\u0026state=open|resolved\u0026refresh=0|1. GET first synchronises the stored steps with the data (it writes queue items, never accounting data): new and returning steps open, an open step resolves only when its own source is done, and people who lost write access to a firm are unassigned; refresh=0 and read-only API keys skip it. Steps: drafts, unmatched bank movements, received documents without an original, every unfiled VAT return (přiznání k DPH) since the firm was added (and the period before when it was still due then; a filed return covering any part of a period counts), and every filing marked as filed without a verified receipt or an approved exception. Kontrolní and souhrnné hlášení obligations are not tracked. At most 1000 rows (truncated: true beyond); resolved steps stay 90 days. PATCH /work_items/:id { assignee_id } (owner or accountant of that firm, assignee must be a writer there).","onboarding":"GET /lookup/:ico (company from ARES + RES + registr plátců DPH with a suggested profile: segment, legal form, bookkeeping, VAT status, tax office, NACE, výdajový paušál; 200 {found:false} when unknown, 502 when ARES is down), GET /lookup/banks, GET /lookup/statement_formats ({ exports[], pdf: { beta, banks[{code, name}] } }: data exports are read exactly, bank PDFs in beta with the banks whose layout is verified), POST /entities with settings.profile {segment, osvc_activity, employees, foreign[], cash, assets, travel, projects, advances, commercial, keeper} and settings.modules {advances, cash, commercial, internal, payroll, assets, trips, projects, closing, multi_currency, forecast} (a missing module key means on)","getting_started":"GET /entities/:entity_id/getting_started, POST …/getting_started/dismiss { key } or { all: true }, DELETE …/getting_started/dismiss (show the first steps again)","members":"GET|POST /entities/:entity_id/members { login: username or account e-mail (case-insensitive), role: accountant|editor|viewer } creates a pending invitation; DELETE …/members/:id (leave or remove)","invitations":"GET /invitations, POST /invitations/:id/accept, DELETE /invitations/:id (decline)","api_keys":"GET|POST /api_keys { name, read_only }, DELETE /api_keys/:id — browser session only; the token is returned once","accounts":"GET|POST|PATCH|DELETE /entities/:entity_id/accounts","partners":"GET|POST|PATCH|DELETE /entities/:entity_id/partners, GET …/partners/lookup?ico= (ARES; 200 {found:false} when unknown)","documents":{"list":"GET /entities/:entity_id/documents?kind=invoice_out\u0026state=unpaid|overdue|paid\u0026q=\u0026year=","template":"GET …/documents/template?kind=invoice_out","create":"POST …/documents { document: { kind, partner_id, issue_date, lines_attributes: [...] }, issue: true }","actions":"POST …/documents/:id/issue|cancel|reopen|remind, GET …/duplicate|credit_note|final_invoice|advance_document|reminder|qr|isdoc","commercial":"kinds quote_out (nabídka), order_out (objednávka), delivery_out (dodací list) are never posted; GET …/documents/:id/convert?kind=invoice_out|proforma_out|delivery_out|invoice_in returns a draft, POST …/documents/:id/outcome { outcome: accepted|rejected }","share":"POST …/documents/:id/share returns { token } for the public page, DELETE …/documents/:id/share revokes it","attachments":"GET|POST …/documents/:id/attachments (multipart file: PDF, JPG, PNG, WebP, HEIC, XML; ≤ 15 MB), GET|DELETE …/documents/:id/attachments/:attachment_id","payments":"POST …/documents/:id/payments { amount, paid_on, bank_account_id }, DELETE …/payments/:payment_id","approval":"Optional approval of received expenses (invoice_in, cash_out, credit_in with a positive total): GET|PATCH /entities/:entity_id/approval_rules { enabled, rules: [{ min_amount (CZK), cost_centers (≤ 80 chars each), accounts (prefixes), approver_id }] } (JSON array or form-encoded rules[0][…]; edit: owner or accountant; the first matching rule applies; rules are kept when approval is switched off). A covered document cannot be issued until approved (422); issue: true on create/update sends it for approval instead. POST …/documents/:id/approval/submit, …/approval/approve { fingerprint } (422 when the document changed since), …/approval/return { reason }; GET …/documents?approval=waiting (sent, returned or changed) or approval=mine (waiting for the caller's approval). The rule's approver, with none named the owner or accountant, approves; the owner always may, and alone when the named approver lost write access. Only an explicit issue (POST …/issue, issue: true, ISDOC and history imports) by an approver approves on the way: bulk issue refuses unapproved documents and recurring runs send them for approval. A later change of amount, CZK amount or exchange rate, supplier (name, IČO, payee account), cost center, accounts or attachments needs a new approval.","import_isdoc":"POST …/documents/import_isdoc (multipart files[]: ISDOC, ISDOCX, ZIP or a PDF carrying ISDOC; issued when the supplier is the firm's IČO, received otherwise; optional direction=sale|purchase refuses the other direction; with the auto_match automation on, payments already in the bank pay the imported documents at once – paid: true on such results and the paid count)","waiting_payments":"GET …/documents/waiting_payments (rows[] { document_id, number, transaction_id, booked_on, amount, currency }: open issued documents whose payment is already among unmatched incoming bank movements, exactly what automatic matching would pair; statements_until = the last bank movement date; beyond_statements = imported overdue invoices due after it, whose payment the bank data cannot show yet)","accountant_export":"GET …/documents/export?format=pohoda|money\u0026from=\u0026to=\u0026kinds[]=\u0026ids[]= (POHODA XML in Windows-1250 or Money S3 XML, period by DUZP / deduction date; 422 { error, problems[] })","payment_order":"GET …/documents/payables (unpaid received invoices with the payee account or the reason they cannot be paid), POST …/documents/payment_order { ids[], bank_account_id, format: auto|abo|sepa, execution_date } → ABO (.kpc, CZK) or SEPA pain.001 (EUR); the order never marks invoices as paid"},"bank":"GET …/bank_accounts, GET …/bank_transactions, POST …/bank_transactions/import (multipart file: a data export ≤ 5 MB or the bank's PDF statement ≤ 15 MB; bank_account_id; optional statement_pdf ≤ 15 MB next to a data export; period_month YYYY-MM only for a statement stating no period or dated movement – the file's own period is used otherwise, a month, a year or any range; password for an encrypted PDF, used once and never stored (422 code PDF_PASSWORD_REQUIRED when missing or wrong); preview=1 returns the period, balances, checks and how many movements are new without writing anything, for a PDF also new_movements[] (up to 5000, for review) and pdf_bank { code, name, verified }. PDF reading is in beta, verified for Air Bank, Česká národní banka, Česká spořitelna, ČSOB, Equa bank, Fio banka, Komerční banka, MONETA Money Bank, Oberbank, Raiffeisenbank, Sberbank CZ, UniCredit Bank; other banks are read too with verified: false. A PDF is imported only when opening + movements = closing and any stated count and totals agree; its movements already in Saldo from another source (same amount, date ±3 days) are skipped. Stores the original data file and PDF with the import batch; reimport skips duplicate movements), GET …/bank_transactions/statements (each archive with reconciliation: status matched|mismatch|gap|needs_manual|reviewed|before_opening, period = the dates the file states or else the chosen month, opening/closing balance and movements from the file, continuity with the previous statement, the books at the period end – account 221xxx in double entry (matched and posted movements in the account currency for a foreign account), the movement balance in daňová evidence, counted from the account opening date – issues in Czech, most important first, and the manual review), PATCH …/bank_transactions/statements/:archive_id/balances { opening_balance, closing_balance, note } (owner/accountant; balances read from the PDF for the sides the data file does not state; refused in a locked period; never posts anything), POST …/bank_transactions/statements/:archive_id/review { note ≤ 250 } and DELETE of the same path (owner/accountant; a statement with a problem counts as checked by hand while its check result stays the same; refused in a locked period), GET …/bank_transactions/statements/:archive_id/data|pdf (authenticated original downloads), POST …/bank_transactions/auto_match (optional transaction_ids[] limits it to those movements), POST …/bank_transactions/:id/match|book|ignore|reset, POST …/bank_accounts/:id/connect { token } (Fio API, owner/accountant), POST …/bank_accounts/:id/sync","document_requests":"Requests for a missing receipt or explanation: POST /entities/:entity_id/document_requests { subject_type: bank_transaction|document, subject_id, title, question, assignee_id (another member with write access), due_on } (one open request per item and none for quotes, orders or delivery notes, 422 otherwise), GET …/document_requests?filter=action|open|closed|all\u0026subject_type=\u0026subject_ids= (rows – open by due date, closed newest first, at most 500 with truncated and limit – plus action_needed and members), GET …/document_requests/counts ({ open, action_needed } for badges), GET …/document_requests/:id (with messages and files), POST …/:id/reply (multipart body and files[] or files[0], files[1] …: ≤ 5 files of PDF, JPG, PNG, WebP, HEIC, GIF or XML, each ≤ 15 MB and ≤ 20 MB together; a reply by anyone but the requester marks it delivered), POST …/:id/verify { attach } (delivered requests only; the requester verifies, the owner or accountant only when the requester can no longer write, and never the member who delivered it; attach copies the files delivered by others to the document), POST …/:id/cancel { reason } (the same people), GET …/:id/files/:file_id. Deleting the movement or document cancels its open request. Nothing is posted.","partners_checks":"GET …/partners/:id/vat_registry, POST …/partners/vat_registry_all (MFČR registr plátců DPH), POST …/partners/:id/insolvency, POST …/partners/insolvency_all (ISIR, 15 per call)","ledger":"GET …/entries?year=\u0026account=\u0026q=\u0026document_id=\u0026source_type=manual,opening\u0026page=\u0026per= (deník, per ≤ 1000), POST …/entries (ruční zápis), DELETE …/entries/:id","reports":"GET …/reports/dashboard|trial_balance|account?code=|saldokonto?side=|statements?year=|cash_book|money|compliance|forecast?days=90|projects?dimension=project|cost_center|insights?year= (Kolik je opravdu vaše, monthly set-aside, runway, Finanční zdraví)","clients":"GET /clients – all firms you are an accepted member of: VAT filing state and next deadline, overdue receivables, unmatched bank movements, drafts, period lock, last activity; most urgent first; max 200","taxes":"GET …/taxes/vat_return|control_statement|recap_statement?period=2026-09, GET …/taxes/dph|kh|sh/xml?period=, GET …/taxes/income_tax|regime?year=, GET …/taxes/income_tax/xml?year=2025 (DPFDP7 for OSVČ, DPPDP9 for companies), POST …/taxes/:report/epo?check=true (validate on mojedane.gov.cz) or without check (opens the pre-filled EPO form), GET …/taxes/filings_check?year= (readiness of the income-tax return and both přehledy: problems, warnings, deadlines, recipients), GET …/taxes/prehled_sp|prehled_zp/xml?year= (přehled OSVČ for ČSSZ, OSVC25, and for the health insurer, SZP-VZP v6), POST …/taxes/prehled_sp/validate?year= (ČSSZ pre-validation, nothing is filed), POST …/taxes/income_tax|prehled_sp|prehled_zp/filing?year= (stores the prepared filing), GET …/taxes/payments?from=2026-08\u0026to=2026-11 (payments due to the FÚ, ČSSZ and health insurers: account, IBAN, VS/KS/SS, due date moved to a business day, SPAYD for QR Platba, paid status from bank movements, missing data and where to find it, missing[].suggestion { value, display, count, last_on } when the OSVČ's own payments to an OSSZ or health insurer account show it; without a tax office the VZP account the firm pays to is used; max 24 months), POST …/taxes/profile_from_bank (owner or accountant: stores those suggestions in the tax profile, only fields still missing; returns applied { field: display })","filings":"GET|POST …/filings, GET|PATCH|DELETE …/filings/:id (filed = označeno jako odeslané, ne doložené doručení), GET|POST …/filings/:filing_id/evidences (originální PDF/XML/ZFO do 15 MB), GET …/filings/:filing_id/evidences/:id/file (stažení originálu), POST …/filings/:filing_id/evidences/:id/verify|void|correct (ověření, vyřazení nebo auditovaná oprava), POST|DELETE …/filings/:id/exception (výslovné schválení bez doručenky a jeho zrušení)","closing":"GET …/closing/month?year=\u0026month= (měsíční uzávěrka checklist), GET …/closing?year= (roční uzávěrka: checklist, preview of kurzové rozdíly, corporate income tax), POST|DELETE …/closing/revaluation?year= (563/663 at the ČNB rate of the balance date, invoices reversed on 1. 1.), POST|DELETE …/closing/income_tax?year= (591/341)","assets":"GET|POST|PATCH|DELETE …/assets, POST …/assets/depreciate?year=","payroll":"GET|POST|PATCH|DELETE …/employees, GET …/payslips?year=\u0026month=, POST …/payslips/generate, POST …/payslips/:id/post_entries, GET …/payslips/jmhz?year=\u0026month= (JMHZ check, totals, deadline, filings), GET …/payslips/jmhz/xml?year=\u0026month=\u0026kind=regular|corrective|cancellation (JMHZ XML 1.4.3.6, stored as a filing), POST …/payslips/jmhz/validation (ČSSZ pre-check, nothing is filed)","trips":"GET|POST|PATCH|DELETE …/trips, POST …/trips/calculate","recurring":"GET|POST|PATCH|DELETE …/recurrences, POST …/recurrences/run","autofill":"GET …/documents/items?q=\u0026kind= (line items used before: description, unit, price, VAT rate, account), GET …/documents/partner_defaults?partner_id=\u0026kind= (due days, account, VAT mode and lines from the partner's last document), GET …/documents/duplicates?kind=invoice_in\u0026partner_id=\u0026original_number=\u0026issue_date=\u0026total=\u0026currency= ({ duplicate_of } when the same received document is already booked)","bulk":"POST …/documents/bulk { operation: issue|pay|remind|delete, ids[], paid_on, bank_account_id }, GET …/documents/reminders?ids= (overdue invoices ready for a reminder with the late-payment interest)","bank_rules":"GET|POST …/bank_rules { bank_rule: { name, active, action: post|match|partner|ignore, conditions: { direction: in|out, bank_account_id, counterparty_account, counterparty_name, message|variable_symbol|constant_symbol|specific_symbol: { op: contains|equals, value }, amount_min, amount_max }, action_data: { account_code, text, partner_id } }, apply: true }, PATCH|DELETE …/bank_rules/:id, POST …/bank_rules/preview (movements the rule would take), POST …/bank_rules/apply { rule_id, transaction_ids[] }, GET …/bank_rules/draft?transaction_id= (a rule proposed from one movement, incl. the official tax office and insurer accounts), GET …/bank_rules/suggestions (repeated movements without a rule), POST …/bank_rules/reorder { ids[] }; the first matching active rule wins","imports":"GET …/imports (history of imports with counts, bookkeeping mode, opening date, money accounts), POST …/imports/preview (multipart: type contacts|documents|trial_balance|open_items|money, file ≤ 20 MB, mapping and options as JSON; nothing is saved; row status create|duplicate|skip|error), POST …/imports (same fields plus batch IMP-XXXXXX, offset, limit; imports one chunk, returns rows created|duplicate|skipped|error and next_offset, null when done), DELETE …/imports/:batch (undo; refused when the period is locked or later payments depend on it). Contacts come from any CSV, POHODA XML adresář or Money S3 firmy, documents from POHODA XML invoices, trial_balance from a předvaha CSV (MD must equal Dal), open_items and money for daňová evidence","automation":"GET …/automation (switches and what each automation did this month), PATCH …/automation { automation: { bank_rules, auto_match, fio_on_open, recurring, reminders, reminder_days: 1–90, partner_checks } } (owner or accountant), POST …/automation/run { only, force } (issues due recurring invoices and checks new contacts in ARES, ISIR and the VAT registry; safe to repeat)","calendar":"GET …/calendar?year= (JSON) or ?format=ics","audit":"GET …/events"},"disclaimer":"Výpočty vycházejí ze zákonů platných pro daný rok a ze zadaných dat. Nejsou daňovým poradenstvím; před podáním si výsledky zkontrolujte."}