Security and limits
What a personal access token is
A token looks like fpat_ plus 43 characters plus a 6-character checksum — 54 characters total. It is not an app JWT: it only works on the /v1/mcp endpoint and nowhere else — Fealthy's GraphQL API, WebSocket, and chat all refuse it. With no token, or an invalid one, the server answers 401 with the header WWW-Authenticate: Bearer realm="fealthy", error="invalid_token".
Scope — your data only
A token is bound to one user, and that's checked on the server for every single call. Reaching for someone else's record (for example, substituting a foreign uid) returns the exact same not_found error as a request for a record that doesn't exist — the server never confirms, by the mere fact of its answer, that a foreign record even exists.
Premium-only access
MCP is available only on Fealthy's Premium plan. Demo accounts are always blocked, regardless of any other setting. On a Free plan, a call returns a JSON-RPC forbidden error with reason plan_restricted — and deliberately on HTTP 200: the reference MCP client only looks for a JSON-RPC error inside a 2xx response, so a non-2xx status here would read to the agent as an opaque transport failure rather than an explanation.
Token access levels
A token is minted at one of two levels:
- Write — all 70 tools;
- Read — only
list_*/get_*, 29 tools; calling a write tool is rejected, andtools/listfor a token at this level won't show anything but reads.
Rate limit
50 operations per 60 seconds, per user account, shared across all of that user's tokens — a second token doesn't add budget. A batch call with N items (for example, create_transaction with several rows at once) counts as N operations; but create_transfer and create_exchange, even though each creates two transactions, count as one operation. Remaining budget rides back in the _meta["ua.fealthy/rateLimit"] field of every response. Exceeding the limit returns a rate_limited error with a retryAfterSeconds field — exactly how long to wait before retrying.
uid, never a numeric id
Every record is identified only by uid (a UUID string); numeric ids don't exist in this API. The server generates the uid of a new record itself — a create call that supplies its own uid is rejected with validation_error.
Global (system) records
Built-in categories and merchants are shared across all users and read-only: trying to delete one returns forbidden with reason global_read_only. Renaming a system category or changing its icon is still possible — it creates a personal override just for you: other users and the shared record itself stay unchanged, and the category's English name never changes. Hiding a system category (the same "eye" toggle as in the app's settings) is also possible, and reversible.
Error shape
A tool error comes back not as an HTTP error but inside an ordinary, successful JSON-RPC response: isError: true and a code in structuredContent.error.code. Real HTTP errors are reserved for authentication, request method, and body format (401, 405, and the like).
| Code | When it happens |
|---|---|
unauthorized | The token is missing or invalid |
forbidden | The action is not allowed; the reason is in the reason field (global_read_only, plan_restricted, demo_user, write_access_required for a Read token, feature_disabled) |
not_found | The uid doesn't exist, or belongs to another user |
validation_error | Invalid call parameters; the field name is in error.field |
conflict | The action conflicts with the record's current state |
rate_limited | The rate limit was exceeded; the wait time is in retryAfterSeconds |
internal | An internal server error; retrying the call later may help |
Timezone and language
Timezone resolves in this order: your Fealthy profile setting → else the app-timezone request header → else Europe/Kyiv. A date or time you send with no explicit offset is interpreted in that timezone; every date the server returns always carries an offset.
The response language is your Fealthy profile's interface language: the server directly instructs the agent to answer in it.
What the server tells the agent
Along with the tool list, the server sends the agent explicit instructions — rules it's expected to follow:
- Records have only a
uid; numeric ids don't exist — never invent or ask for one. - Look for an existing record first via
list_*/get_*and use it — create only what's actually missing. - The server generates the
uidof a new record; supplying your own is an error. - Before any
delete_*call or other irreversible change, tell you exactly what will be deleted or changed and wait for your explicit confirmation. - Global records can be read and referenced, but never changed or deleted.
- Dates with no offset are in your timezone; every date in a response carries an offset.
- Answer in your Fealthy profile's language.
- Don't exceed the rate limit, and don't retry immediately after a
rate_limitederror — wait forretryAfterSeconds. - Don't repeat, unchanged, a call that failed with
validation_errororforbidden. - Remember its own token's level (Write or Read) and never offer an action that level doesn't allow.
Revoking a token
There's no self-service revocation in the app yet — ask the Fealthy team to revoke the token. A revoked token stops working within 60 seconds at the latest (the lifetime of the internal token-verification cache).
Where your data goes
The MCP endpoint hands your own data directly to whichever client you connected — one HTTP call, no intermediary services. Fealthy logs the fact of the call itself (which tool, whether it succeeded, what it cost against the rate limit, how long it took) — not the call's arguments and not the content of the response. What the client or agent does with the data afterward is outside Fealthy's control: choose a client you trust, and guard the token as carefully as a password.