Authentication

How MCP clients and API callers authenticate with Savi Finance: OAuth 2.1 with PKCE, dynamic client registration, refresh tokens, scopes and bearer tokens.

Use with an AI agent

Export this page as Markdown or bundle it with a task for your agent.

Try a task
Preview the prompt that will be copied

Which credentials do I need?

You are buildingUseWhat you provide
An AI assistant or any MCP clientThe MCP server with OAuth 2.1Nothing to copy. The client opens a Savi sign-in page and the user approves access.
A script or app that calls the JSON-RPC APIThe JSON-RPC API with a bearer tokenA token from the two-call email sign-in below.

Get an API token

Savi signs you in with an emailed 6-digit code, so there is no password. Two calls give you a bearer token, and a third confirms it works. Each panel below can run against the live API from this page, or you can copy a snippet in any of the ten supported languages.

Treat the token like a password. Anyone who has it can read and change your Savi data. Keep it out of source control and screenshots. This page keeps it in memory for this tab and sends it only to api-v2.financesavi.com. Reloading clears it. MCP OAuth tokens and API tokens are not interchangeable.

Sign in and make your first read

This form calls the live API. It sends an email only after you confirm and press Request code. Your token stays in memory and clears on reload.

  1. 1. Request code
  2. 2. Sign in
  3. 3. Verify token

Raw API sign-in requests and examples

1. Request a sign-in code

Enter your email in the request params. Savi emails you a 6-digit code. Codes can be requested once per minute per address.

POSThttps://api-v2.financesavi.com/rpcPublic
cURL
bash
curl -sS --max-time 30 -X POST 'https://api-v2.financesavi.com/rpc' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"sendLoginCodeEmail","params":{"email":"YOUR_EMAIL_ADDRESS","loginCodeType":"SHORT_LIVED","emailType":"LOGIN_CODE"},"id":1}'
Replace YOUR_EMAIL_ADDRESS with an email address you can access.

Captured response

Local API capture (scrubbed)
$ POST https://api-v2.financesavi.com/rpc  (sendLoginCodeEmail)
HTTP 200

{
  "jsonrpc": "2.0",
  "result": {
    "message": "ok"
  },
  "id": 1
}

Captured from the local sf1/api service and scrubbed. This preview does not send a request.

2. Exchange the code for a token

Enter the code from the email. On success the response contains your token and userId. This page stores both for you; in your own code, save the token.

POSThttps://api-v2.financesavi.com/rpcPublic
cURL
bash
curl -sS --max-time 30 -X POST 'https://api-v2.financesavi.com/rpc' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"loginByEmailVerification","params":{"email":"YOUR_EMAIL_ADDRESS","code":"123456","authenticationMethod":"EMAIL_CODE"},"id":1}'
Replace YOUR_EMAIL_ADDRESS with an email address you can access.

Captured response

Local API capture (scrubbed)
$ POST https://api-v2.financesavi.com/rpc  (loginByEmailVerification)
HTTP 200

{
  "jsonrpc": "2.0",
  "result": {
    "token": "<ACCESS_TOKEN>",
    "userId": "user_8f3a1c2d9b"
  },
  "id": 1
}

Captured from the local sf1/api service and scrubbed. This preview does not send a request.

3. Confirm the token works

Send the token as Authorization: Bearer <token>. A good token returns your profile:

POSThttps://api-v2.financesavi.com/rpcRequires token
cURL
bash
curl -sS --max-time 30 -X POST 'https://api-v2.financesavi.com/rpc' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $SAVI_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"getCurrentUser","params":{},"id":1}'

The snippets read your token from the SAVI_TOKEN environment variable, so it is never pasted into code. Run export SAVI_TOKEN=<your token> first.

No token yet? Get one in two calls. The token stays in this tab and is only sent to api-v2.financesavi.com. Using it sends a read-only getCurrentUser request to find your user id.

Add a token to send this request.

Captured response

Local API capture (scrubbed)
$ POST https://api-v2.financesavi.com/rpc  (getCurrentUser)
HTTP 200

{
  "jsonrpc": "2.0",
  "result": {
    "credentials": {
      "id": "uc_1790000000000000000",
      "email": "[email protected]",
      "username": "u_1790000000000000000",
      "isVerified": false,
      "lastLoginByEmailCodeType": "SHORT_LIVED",
      "createdAt": "2026-10-01T15:48:48.426Z"
    },
    "users": [
      {
        "id": "user_8f3a1c2d9b",
        "email": "[email protected]",
        "userType": "PERSONAL",
        "username": "u_1790000000000000000",
        "createdAt": "2026-10-01T15:48:48.428Z",
        "businessTier": "FREE",
        "referralId": "Ab12Cd34",
        "referralLink": "https://app.financesavi.com/refer/Ab12Cd34",
        "referralCount": 0,
        "hasRedeemedReferral": false,
        "totalStorageUsedInBytes": 0,
        "storageLimitInBytes": 1000000000,
        "numFiles": 0,
        "widgetIds": [],
        "notificationSettings": {
          "insightSummary": {
            "enabled": true
          }
        }
      }
    ]
  },
  "id": 1
}

Captured from the local sf1/api service and scrubbed. This preview does not send a request.

Use the token in your own code

bash
# Save the token once, then reuse it in every request.
export SAVI_TOKEN='<paste the token from step 2>'

curl -sS -X POST https://api-v2.financesavi.com/rpc \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $SAVI_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"getCurrentUser","params":{},"id":1}'

Use the explicit Copy token control after sign-in; live response previews redact it. Store credentials securely in your own application. The browser helper is for manual exploration, and does not persist a session or provide a sandbox. Without a bearer token, production getCurrentUser returned the response below. If your request returns this code, sign in again to get a token:

Production response to getCurrentUser without a bearer token. Captured at .

Request without a token · POST /rpc
{
  "jsonrpc": "2.0",
  "method": "getCurrentUser",
  "params": {},
  "id": 4
}
Production response · HTTP 400
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32002,
    "message": "missing or invalid auth token"
  },
  "id": 4
}

MCP: OAuth 2.1 flow

The MCP server at https://mcp.financesavi.com follows the MCP authorization specification. Compliant clients complete these steps automatically:

  1. Request /.well-known/oauth-protected-resource to find the authorization server.
  2. Request /.well-known/oauth-authorization-server for endpoint metadata.
  3. Register with POST /register (client_name, redirect_uris) to receive a client_id. Clients are public: there is no client secret, so token_endpoint_auth_method must be none.
  4. Open /authorize in a browser with response_type=code, client_id, redirect_uri, state and a PKCE code_challenge with code_challenge_method=S256. Optionally pass scope and resource.
  5. The user enters their email, then the 6-digit code Savi sends to it.
  6. The user sees a consent page that names the app, where they will be sent back to and what it is asking to do, and approves or denies. Approving without Allow changes grants read-only access. For apps that registered themselves through /register, Allow changes starts unticked, so the user has to opt in to write access.
  7. Savi redirects to the redirect_uri with a single-use authorization code, valid for about 60 seconds. If the user denies, it redirects with error=access_denied.
  8. Exchange the code at POST /token with grant_type=authorization_code, code, client_id, redirect_uri and code_verifier. The response contains an access_token of type Bearer, expires_in, a refresh_token and the granted scope.
  9. Call https://mcp.financesavi.com/mcp with Authorization: Bearer <access_token>.

PKCE with S256 is required. A 401 response includes a WWW-Authenticate header with a resource_metadata parameter that points clients at the discovery document.

Registering a client

Redirect URIs must be absolute and must be one of: an https URL, an http URL on localhost or another loopback address, or a custom app scheme such as myapp://callback. URIs with a fragment, embedded credentials or a wildcard are rejected, as are schemes that can run code (javascript:, data:, file:). A client can register up to 10 redirect URIs, and the redirect_uri sent to /authorize must match one exactly. Supported grant types are authorization_code and refresh_token. The consent page notes that Savi has not reviewed clients registered this way.

Token lifetime, refresh and revocation

  • Access tokens last 1 hour. When one expires the server answers 401 and the client should refresh.
  • Refresh tokens last 30 days and rotate. Send POST /token with grant_type=refresh_token, refresh_token and client_id to receive a new access token and a new refresh token. A refresh request can narrow scope but never widen it.
  • Refreshing never extends a sign-in past 90 days from its first token: tokens issued near the end expire at that limit, and after it the user must sign in again.
  • Using a refresh token or an authorization code a second time is treated as theft: every token from that sign-in is revoked and the user must sign in again.
  • POST /revoke with token (an access or refresh token) revokes the sign-in. It always answers 200, whether or not the token existed.
  • If you send the resource parameter it must be https://mcp.financesavi.com or https://mcp.financesavi.com/mcp; anything else is rejected with invalid_target.

MCP scopes

ScopeGrants
readEvery read-only tool.
writeEvery tool that creates, changes or deletes data. These tools also need read, so a request for write alone is granted read write.
read:profile, read:accounts, read:transactions, read:categories, read:tags, read:analyticsA single read tool each.
mcpAll tools. Used when a client requests no scope.

The user can always lower the grant on the consent page by unticking Allow changes. A tool call without the required scope returns a tool result with isError set and an insufficient scope message. See the tool reference for the scope each tool needs.

JSON-RPC API

Methods marked required in the API reference need an Authorization: Bearer <token> header on requests to https://api-v2.financesavi.com/rpc. Methods marked public need no token. MCP access tokens work only on the MCP server and cannot be used with the JSON-RPC API.