Nodea — logo

Account profile — API v1 and MCP | Nodea Documentation

The Account profile module covers reading and partially updating the token user's profile: identity, company and billing address, the VAT profile (country, company status, VAT number with VIES validation), tax number and timezone. This document describes the REST API v1 layer (/api/v1/profile) and the MCP tools profile-get and profile-update.

Authentication and scopes

API v1 authenticates with a Passport personal access token via the Authorization: Bearer <token> header. The profile operates exclusively on the token user — it accepts no ids, so there is no cross-tenant surface.

  • account:read — read the profile (GET /profile).
  • account:write — partial update (PATCH /profile).

The profile is not gated by any Pennant flag — account access is never feature-flagged (same as the web UI). Rate limit: throttle:api-v1 = 240/min per token user.

Deliberately omitted (web-only)

Sensitive, confirmation-bound operations stay in the web UI and are not exposed over API v1:

  • email change,
  • password change,
  • 2FA (two-factor) management,
  • OAuth provider disconnect,
  • avatar upload,
  • billing currency change (locked in the web by the zero-wallet-balance guard).

The email field is returned on read, but PATCH /profile has no rule for it — submitting it is ignored (the field is not part of validated()). By contrast the MCP profile-update tool explicitly rejects email and password with a validation error (see the MCP section) and — unlike the API — can change the currency.

API v1 — endpoints

MethodPathScopeDescription
GET/api/v1/profileaccount:readThe token user's profile (details, company, address, VAT, timezone, currency).
PATCH/api/v1/profileaccount:writePartial update of any subset of general and VAT fields. Returns the updated profile.

GET /profile — response

Returns a ProfileResource — fields: id, name, email, company_name, address, country, is_company (bool), vat_number, vat_validated (bool), vat_validated_at (ISO 8601 or null), tax_number, timezone, currency (PLN|EUR), created_at. It never returns secrets, 2FA state, password/remember hashes, OAuth provider rows or admin flags.

PATCH /profile — body parameters

Every rule carries sometimes — a client may send any subset of fields; omitted fields are left untouched (partial update).

  • name (string, sometimes|required, max 255) — a submitted name may not be blank.
  • company_name (string|null, max 255).
  • address (string|null, max 1000).
  • tax_number (string|null, max 64).
  • timezone (string|null, timezone rule — IANA identifier).
  • country (string|null, size:2 — ISO 3166-1 alpha-2).
  • is_company (boolean).
  • vat_number (string|null, max 32).

Currency is not accepted by the API — changing currency is web-only (and available in MCP). General fields (name, company_name, address, tax_number, timezone) are persisted only when present in the payload.

VAT profile and VIES re-validation

Submitting any of country / is_company / vat_number invalidates the previous VIES validation: vat_validated is set to false and vat_validated_at to null. Then, when the effective profile (submitted values merged over stored ones) is a company (is_company = true) with both a country and a VAT number set, a round-trip to VIES (ViesValidator) is performed. The country and VAT number are normalised (upper-cased, trimmed). Like the web flow, a failed VIES lookup still persists the submitted values — vat_validated simply stays false (no reverse-charge branch). VIES uses SOAP (not the Http facade) and is sandboxed by default outside production (billing.vies.sandbox).

Error codes

  • 401 — missing/invalid token.
  • 403 — missing required scope (account:read for GET, account:write for PATCH).
  • 422 — validation (e.g. blank name, country not size:2, invalid timezone).

Examples — API

Read the profile

curl -s https://app.nodea.io/api/v1/profile \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

Update general details and timezone

curl -s -X PATCH https://app.nodea.io/api/v1/profile \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Smith",
    "company_name": "Smith Ltd.",
    "address": "1 Example St, London",
    "timezone": "Europe/London"
  }'

Set the VAT profile (triggers VIES re-validation)

curl -s -X PATCH https://app.nodea.io/api/v1/profile \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "is_company": true,
    "country": "PL",
    "vat_number": "PL1234567890"
  }'

MCP — tools

MCP tools share the auth and scope model with API v1 (Bearer + scope checked in ensureScope). The profile is ungated, so both tools are always present in tools/list. They operate on the token user only.

ToolScopeTypeDescription
profile-getaccount:readread-onlyReturns the profile: name, email, company_name, address, country, company/VAT status, tax_number, timezone and currency. No credentials or 2FA state.
profile-updateaccount:writeidempotentPartial update; only provided fields change. Additionally supports currency. Rejects email/password.

profile-get

No parameters. The response is a superset of ProfileResource — the same fields plus effective_timezone (the user's resolved timezone when timezone is null).

profile-update — parameters

All optional; only provided fields change. Empty strings / null clear the field (blank → null), country and VAT number are upper-cased.

  • name (string, max 255).
  • company_name (string, max 255; null/empty clears).
  • address (string, max 1000; null/empty clears).
  • tax_number (string, max 64; null/empty clears).
  • timezone (string — IANA identifier; null clears).
  • country (string — ISO 3166-1 alpha-2; null clears).
  • is_company (boolean).
  • vat_number (string, max 32; null/empty clears).
  • currency (enum PLN|EUR) — locked while any wallet holds a non-zero balance (returns a tool error); the change is written to the activity log.

Differences from the API: changing a VAT field (country/is_company/vat_number) to a value different from the stored one resets vat_validated (and vat_validated_at), but this tool does not perform the VIES round-trip — re-validation is done in the web UI. Submitting email or password fails validation (the prohibited rule: "The email address / password can only be changed in the web UI.") rather than being silently ignored as in the API.

Example — MCP (JSON-RPC)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "profile-get",
    "arguments": {}
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "profile-update",
    "arguments": {
      "is_company": true,
      "country": "PL",
      "vat_number": "PL1234567890",
      "timezone": "Europe/Warsaw"
    }
  }
}