Pe această pagină

fiscalm API

openapi.json is the public HTTP contract: routes, fields, request schemas and refusal codes. The same schema is available without authentication at GET /api/v1/openapi.json. It is generated from finrec.api.schema.openapi(); change descriptions there and regenerate the JSON, rather than editing it by hand:

uv run python -c 'import json; from finrec.api.schema import openapi; print(json.dumps(openapi(), ensure_ascii=False, indent=2))' > docs/openapi.json

Authentication and transport

Use HTTPS through the installation's reverse proxy. Create a scoped credential:

finrec token create --user admin --name 'Reporting script' --scope read
finrec token list
finrec token revoke <id>

Creation reveals the secret once; only its digest is stored. A token covers every company in the installation; write includes read. Every token belongs to an enabled account. Its effective scope is intersected with that account's current role: a write token becomes read-only immediately when its owner is demoted to Doar citire (writes return 403 insufficient_scope). A disabled or missing owner returns 401 unauthorized; enabling the owner restores access without reissuing the token. Listing adds user_id and owner: {id, display_name}. Invoicing, commerce and settlement writes recheck the token and owner together under the write lock. No ownerless token can authenticate, and CLI creation requires an explicit --user. Send it in Authorization: Bearer <token> to /api/v1/. Never put it in a URL, source control or logs. Revocation is checked again under the write lock on each engine transaction, including generic HTTP/MCP actions and multi-stage imports. A transaction already authorized under that lock may finish; parsing a request does not reserve future write authority. Invoicing also rechecks token authority at its post-commit SPV claim. App links, cookies and Tailscale identity do not authenticate the public API. The browser lists owned keys in Setări › Acces and Setări › Cont, including before the first company. Token creation sends {name, scope} and revocation sends {id}, with no company or claimed owner. Administrators see all owners; other normal accounts see only their own keys, and Doar citire offers only read.

POST requests require application/json, one Content-Length, no transfer encoding, and at most 4 KB (order intake permits 256 KB). If Origin is present it must match the server origin; the server also checks it for MCP. All routes, including API v1, upload capabilities and hooks, pin Host and port to FINREC_PUBLIC_URL (only /healthz is exempt). Preserve Host through the proxy. Forwarded headers are trusted only from FINREC_TRUST_PROXY; see self-hosting.

Reading state

Start with /api/v1/companies, then a company's state or resource lists. Treat identifiers as opaque, including payment IDs. Entity links contain related IDs, not URLs; null/empty means unavailable. Money is a two-decimal string, dates ISO. ContaHub-API-Version: 1 is retained as a protocol identifier.

Lists use data and next_cursor: pass the returned cursor unchanged and URL-encoded with the same filters. Default limit is 50, maximum 200. Pagination is not a snapshot. The invoicing workspace's embedded lists are not paginated. Files are served through guarded resource routes, never arbitrary storage paths.

Actions and safety

POST /api/v1/companies/{co}/actions/{action} with the fields described in the schema. The engine action registry supplies the action names, argument fields and app-only metadata to the generated contract. Every action refuses unknown keys; API v1 clients must send only fields declared for that action. If co is also supplied in the body it must equal the URL company. Invoicing has explicit /invoicing/{operation} routes with separate request schemas. Refresh state after an action and use returned capability flags; a flag is a preview, and the engine rechecks every precondition when writing.

Errors have {"error":{"code":"…","message":"…"}}; successful actions retain the Romanian message. Use codes and schemas, not message matching, in clients. A user marking an invoice paid is not bank proof. fiscalm prepares payment files and documents to download; a human imports the file or sends the documents. The mail account is read-only.

Without <FINREC_HOME>/state/LIVE, all local work is available with the same evidence and authorization guards. SPV uploads, nudges, webhooks, marketplace writes and payment-file generation/downloads are refused or skipped; check runs a tick that respects Sandbox. Administrator-only company, integration and automation settings use the app/CLI, not bearer access. Receipt-upload links are separate upload-only capabilities, not API credentials.

Issue and storno support Idempotency-Key, scoped to token and company for 24 hours. Retry an uncertain result with the same key and body; a changed body conflicts. Other operations reject this header. After issuance, read SPV status separately; a committed invoice does not prove successful submission.

App and MCP

The browser uses /web/api/… with a __Host-session cookie (Secure, HttpOnly, SameSite=Strict, Path=/). Configure an HTTPS FINREC_PUBLIC_URL before starting the listener. Every host/port is checked against it, except /healthz. The app shell at / and /setup is public; financial reads/actions require an enabled account's session. URL-token links and Tailscale identity headers never authenticate.

GET /web/api/me returns safe account metadata, role capabilities and recovery state; 401 starts sign-in. POST /auth/password with username/password returns only a five-minute ticket. POST /auth/totp with ticket/code sets a new cookie after TOTP proof. /auth/recovery takes the password ticket and one recovery code and opens a restricted session. /auth/recover/begin and /auth/recover/finish replace TOTP and codes, then close every session; sign in again afterward. Setup links use /auth/setup/inspect, /auth/setup/begin and /auth/setup/finish; link is only the URL fragment, never a request path. The generated x-browser-auth contract lists exact bodies and permitted session types. Password alone never signs in. Codes and TOTP secrets appear only in the intended one-time enrollment/recovery responses.

Passkeys use the generated x-browser-auth.passkeys contract and the /auth/passkey/…, /auth/step-up/passkey/…, /auth/setup/passkey/… and /auth/me/passkey/… route families. Options return ticket and WebAuthn publicKey JSON; binary proof fields use canonical unpadded base64url and transports is a JSON-array string. A DNS HTTPS origin, discoverable credentials and user verification are required. api/me includes safe owned-key metadata, availability and the session's actual authentication method. The app offers native passkey sign-in when advertised by the server and supported by the browser. Same-account confirmation can also use an owned key. Leaving the source cancels its native request; a lost finish response is never replayed automatically. The setup page can create a discoverable passkey from the one-time setup link, then shows recovery codes once and requires acknowledgement before entering the app. An uncertain finish offers an explicit access check; restarting obtains fresh options.

For an account whose only complete sign-in method is a passkey, /auth/passkey/recovery accepts username and one 80-bit recovery code with no existing cookie. Its recovery_code session can only use /auth/recover/passkey/… to verify a replacement, retire all old factors and replace codes; it cannot perform financial actions or step-up. Password-and-code recovery sessions stay confined to the existing TOTP flow. Both finalizations close every session and require fresh sign-in. Own-key rename/removal requires recent authentication and cannot remove the last complete sign-in method. The app exposes code-only recovery separately from password/TOTP recovery and selects the replacement form from the restricted session's actual method. Recovery is never offered as sensitive-action confirmation. A lost recovery response can be checked by reading the current identity; the code is not resubmitted automatically. Account and administrator screens reflect configured key counts and actual session methods. Password/TOTP change controls appear for an existing pair; administrator reset applies to key-only accounts too and removes their keys. Cont › Chei de acces lists owned key names, creation and last-use dates. Every normal role can add, rename and remove its own keys after recent authentication; native creation requires a compatible browser. Unreadable write responses refresh metadata and retain an uncertainty warning without automatically repeating a mutation.

Every browser POST, including sign-in, requires one matching Origin, JSON up to 4 KB (explicit document/invoice upload limits are declared separately), and Sec-Fetch-Site: same-origin if supplied. Plain HTTP cookies are refused even on loopback. No CORS is enabled. Sensitive actions return 401 {ok:false, reauth:true}; POST /auth/step-up/password then /auth/step-up/totp confirms the cookie's own identity and rotates it. Normal sign-in and setup also revoke the browser’s previously presented session, including when switching accounts. Failure preserves that session, and replacement commits atomically. Other devices remain signed in. The app opens a confirmation sheet without losing the current form, verifies that the same account is still active, and repeats the refused request once. Cancellation, failed proof or an account change never repeats it. Network errors never trigger an automatic retry. POST /auth/signout with {} revokes it and clears it.

Administrator, Membru and Doar citire apply to every company. Setări › Utilizatori uses the administrator account endpoints without a company field; role changes refresh api/me, and one-time setup links remain only in component memory. Resetting the current account hands its link directly to /setup. The generated x-app-action-roles map publishes the same registry capability used by the server; app controls combine it with api/me capabilities, never a separate role table. Read-only accounts cannot mutate state or download payment files. Integrations require an administrator (except member access when no enabled administrator exists). Bank files use Origin-checked POST /web/api/download {co,v} and a CSV blob response; their former GET route is refused. The app saves the returned CSV, then refreshes once; it never repeats the POST automatically after an interrupted response. A changed file returns 409. The session and current role are checked again under the writer lock before reading the snapshot or recording a download; a late disable, demotion or revocation returns 403 without CSV bytes. Account IDs are recorded in audit notes and action logs; no credential is logged.

Normal browser accounts can list their own tokens at /web/api/tokens; an administrator sees all metadata, including revoked historical records. Create with POST /web/api/token-create {name,scope} after step-up; the cookie determines the owner. Administrator and Membru may choose read/write, Doar citire only read. POST /web/api/token-revoke {id} revokes an own token; administrators may revoke any. Ownership and live administrator status are rechecked under the writer lock. No body owner or actor override is accepted, and bearer credentials cannot manage tokens. The generated x-app-token-management describes these app operations.

Own-account reads use /web/api/me, including active sessions and the remaining recovery-code count (never code hashes). Normal accounts of every role can POST /auth/me/recovery-codes {}, /auth/me/session-close {id} and /auth/me/sessions-close-others {} after step-up. The owner always comes from the cookie; another user's session and any submitted owner override are refused. Closing the current session clears its cookie. Codes are shown once and replace the entire old set. Recovery-only sessions cannot use these routes.

Password replacement uses POST /auth/me/password-change {password} after step-up. Hashing runs outside the write lock; live authority and the old credentials are rechecked before saving. Other sessions and pending tickets close, while the current session stays valid. TOTP replacement uses /auth/me/totp/begin {} followed by /auth/me/totp/finish {ticket,code}. The ticket belongs to that user and session; only a verified new seed replaces the old one. Success keeps the current session, closes others and reveals a replacement set of recovery codes. Wrong/replayed codes or stale/swapped tickets change no factor. No endpoint removes the last sign-in pair.

Administrator account management uses GET /web/api/users, /web/api/user-sessions?id=<user id> and /web/api/auth-events (latest 100). The generated x-app-user-administration lists exact POST fields for user-create, user-update, user-disable, user-enable, user-unlock, user-setup-link, user-reset and session-close. Every mutation requires fresh authentication, rechecked together with the live session/role under the writer lock. Create and setup/reset return a one-time done.url with the setup fragment; neither links nor secrets appear in audit metadata. The last working administrator is protected. Session IDs in these lists are one-way digests and cannot be used as cookies.

The app rechecks identity when a tab becomes visible or focused and after a session change in another tab. Only a random notification nonce is stored in localStorage. Private content and portalled sheets remain suspended until identity is confirmed; errors leave retry available. Same-account forms survive; a different owner clears them. Generation checks discard older successes and refusals. Setup/recovery code acknowledgement survives returning to the tab; an external session change clears it.

Search and custom financial controls combine the current identity capabilities with business-state flags. Role names or stale company metadata never grant access.

The schema's x-app-actions metadata records app-only action fields. App actions return ok, message, next, done, and refreshed state (possibly null). A refused engine action may return HTTP 200 with ok:false; authentication and transport refusals use HTTP status codes. App fixtures are generated with npm run fixtures in app/ and excluded from the production export.

MCP uses JSON-RPC 2.0 at POST /api/v1/mcp, or finrec mcp over stdio with FINREC_MCP_TOKEN injected privately. HTTP uses the same bearer permissions and guards as REST. HTTP requests with an id require Accept: application/json, text/event-stream. For the legacy 2025-11-25 protocol, initialize with protocolVersion and capabilities, then send MCP-Protocol-Version: 2025-11-25 on later requests. The modern protocol and matching headers are specified in the generated schema. Tool results are JSON text in content blocks, no resource capability is advertised, and notifications return HTTP 202. See the schema for the MCP protocol surface.

Signed shop webhooks

Configure webhook_url and webhook_secret together in the shop integration. Events are live-only and delivered at least once, so receivers must verify signatures and freshness, then deduplicate by event ID. The generated contract's x-shop-webhooks section specifies all header names, the exact signature calculation, payload fields, event types and retry behavior. Orders cannot choose webhook destinations.

Accountant packs use local actions pack-prepare (month) and pack-sent (month, exact version). Read the report's version after preparing, then download GET /api/v1/companies/{co}/reports/{month}/pack?version=SHA256. Confirmation marks only the frozen document set, never later arrivals. Issued invoice PDF/XML downloads remain available. Reminder tasks contain subject and body for a human to copy. No mail-draft action or outgoing IMAP route exists.

Product metadata: GET /api/v1/meta is public and reports the installed fiscalm version from the Python package metadata, shared with finrec --version and the app’s Setări footer.

Official AI CLI login is an app-only administrator configuration flow, including in Sandbox. Its route, operations and sanitized responses are described by x-app-ai-cli in the generated OpenAPI contract. POST requires the install's Origin; credentials and raw CLI output are never returned. AI integration mutations are admin-only; other integrations retain their existing permissions. api/integrations supplies can_manage_ai so the app shows AI settings read-only to other users.