SsAIvify
ProductPricingLive demoContact
Sign In
Sign In
MCPContract 2026-09-01

Agent API reference

Saivify exposes your household ledger to the AI agent you already use through one Model Context Protocol endpoint. Every call runs as you, sees only what you can see in the app, and is recorded in the group's audit log. This page is generated from the same contract the server uses, so it lists exactly the tools the endpoint has.

EndpointQuickstartToolsScopesTransportsError codesVersioning and deprecation

Endpoint

One endpoint, POST /api/mcp, speaking JSON-RPC 2.0 over Streamable HTTP: initialize, tools/list and tools/call. Present your credential as Authorization: Bearer <token>. The handshake below returns the contract version beside the protocol version.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18"
  }
}

Quickstart

Connect Claude Desktop or Claude Code to https://saivify.shiftclawco.com/api/mcp, or try the endpoint with three curl calls. Configuration and curl only: there is no SDK to install.

Get a credential

  1. OAuth 2.1. Connect from an MCP client through the consent screen: OAuth 2.1 with PKCE through WorkOS AuthKit. The client starts at /api/mcp/authorize and receives its access token at /api/mcp/callback.
  2. Personal token. In Settings › Agents, create a connection with at least read:accounts. The token is shown exactly once, right after you create it: copy it then, it cannot be shown again. Below, replace <your-personal-token> with it.
Claude Desktop
Add this entry to claude_desktop_config.json, then restart Claude Desktop.
{
  "mcpServers": {
    "saivify": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://saivify.shiftclawco.com/api/mcp",
        "--header",
        "Authorization:${SAIVIFY_TOKEN}"
      ],
      "env": {
        "SAIVIFY_TOKEN": "Bearer <your-personal-token>"
      }
    }
  }
}
Claude Code
Run this once in a terminal; the server is then available in every session.
claude mcp add --transport http saivify https://saivify.shiftclawco.com/api/mcp \
  --header "Authorization: Bearer <your-personal-token>"

One curl round trip

The handshake, the tool list, then one list_accounts call. Each answers 200 with the JSON-RPC result shown under it.

export SAIVIFY_TOKEN='<your-personal-token>'

# 1. initialize
curl -sS https://saivify.shiftclawco.com/api/mcp \
  -H "Authorization: Bearer $SAIVIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'

# 2. tools/list
curl -sS https://saivify.shiftclawco.com/api/mcp \
  -H "Authorization: Bearer $SAIVIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. tools/call list_accounts
curl -sS https://saivify.shiftclawco.com/api/mcp \
  -H "Authorization: Bearer $SAIVIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_accounts","arguments":{}}}'

Expected response: 1. initialize

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {
        "listChanged": false
      }
    },
    "serverInfo": {
      "name": "saivify",
      "version": "2026-09-01"
    },
    "contractVersion": "2026-09-01"
  }
}

Expected response: 2. tools/list

Abridged: every entry also carries its description, inputSchema and outputSchema.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "list_accounts",
        "requiredScope": "read:accounts"
      },
      {
        "name": "search_transactions",
        "requiredScope": "read:transactions"
      },
      {
        "name": "get_forecast",
        "requiredScope": "read:transactions"
      },
      {
        "name": "create_transaction",
        "requiredScope": "write:transactions"
      },
      {
        "name": "set_tags",
        "requiredScope": "write:tags"
      }
    ]
  }
}

Expected response: 3. tools/call list_accounts

One entry per account you can see in the active group; ended and unshared accounts are absent.

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"accounts\":[{\"publicRef\":\"<account-ref>\",\"label\":\"<account label>\",\"kind\":\"checking\",\"currency\":\"EUR\",\"balance\":\"<balance>\"}]}"
      }
    ],
    "structuredContent": {
      "accounts": [
        {
          "publicRef": "<account-ref>",
          "label": "<account label>",
          "kind": "checking",
          "currency": "EUR",
          "balance": "<balance>"
        }
      ]
    },
    "isError": false
  }
}

Leave optional arguments out

Every nullable argument may be omitted and then means null. A search needs only limit, and a forecast needs no argument at all. A wrong type or an unknown argument is still refused with validation_failed, naming the argument.

# tools/call search_transactions {"limit":5}
curl -sS https://saivify.shiftclawco.com/api/mcp \
  -H "Authorization: Bearer $SAIVIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"search_transactions","arguments":{"limit":5}}}'

# tools/call get_forecast {}
curl -sS https://saivify.shiftclawco.com/api/mcp \
  -H "Authorization: Bearer $SAIVIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"get_forecast","arguments":{}}}'

Expected response: tools/call search_transactions {"limit":5}

Abridged: up to five movements, newest first. Follow nextCursor until it is null.

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"movements\":[{\"movementId\":\"<movement-ref>\",\"accountRef\":\"<account-ref>\",\"date\":\"<YYYY-MM-DD>\",\"amount\":-12.5,\"currency\":\"EUR\",\"merchant\":\"<merchant>\",\"description\":null,\"tagIds\":[\"<tag-ref>\"],\"tags\":[{\"ref\":\"<tag-ref>\",\"name\":\"<tag name>\"}],\"forMember\":null}],\"nextCursor\":null}"
      }
    ],
    "structuredContent": {
      "movements": [
        {
          "movementId": "<movement-ref>",
          "accountRef": "<account-ref>",
          "date": "<YYYY-MM-DD>",
          "amount": -12.5,
          "currency": "EUR",
          "merchant": "<merchant>",
          "description": null,
          "tagIds": [
            "<tag-ref>"
          ],
          "tags": [
            {
              "ref": "<tag-ref>",
              "name": "<tag name>"
            }
          ],
          "forMember": null
        }
      ],
      "nextCursor": null
    },
    "isError": false
  }
}

Expected response: tools/call get_forecast {}

Abridged: one band per projected month over the default horizon.

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"currency\":\"EUR\",\"minimumBalance\":0,\"minimumMonth\":\"<YYYY-MM>\",\"monthsUnderBuffer\":0,\"bands\":[{\"month\":\"<YYYY-MM>\",\"low\":0,\"expected\":0,\"high\":0}],\"basis\":\"<how the projection was computed>\"}"
      }
    ],
    "structuredContent": {
      "currency": "EUR",
      "minimumBalance": 0,
      "minimumMonth": "<YYYY-MM>",
      "monthsUnderBuffer": 0,
      "bands": [
        {
          "month": "<YYYY-MM>",
          "low": 0,
          "expected": 0,
          "high": 0
        }
      ],
      "basis": "<how the projection was computed>"
    },
    "isError": false
  }
}

What a failure looks like

Every failure is the same envelope with a stable code. Quote the requestId when you contact support.

Bad credential401
The token is absent, malformed, expired or revoked. The response also carries a WWW-Authenticate challenge.
HTTP/1.1 401
Content-Type: application/json
WWW-Authenticate: Bearer realm="saivify", error="invalid_token", ...

{
  "code": "unauthorized",
  "message": "Missing, malformed, expired or revoked credential.",
  "requestId": "req_example0001"
}
Missing scope403
The credential is valid but was not granted read:accounts. The reason names the scope it lacks; the tool did not run.
HTTP/1.1 403
Content-Type: application/json

{
  "code": "forbidden",
  "message": "You do not have permission to do that.",
  "requestId": "req_example0001",
  "reason": "missing_scope:read:accounts"
}
Allowance exhausted402
The group has used today's agent call allowance. resetsAt is the next UTC midnight; retry after it.
HTTP/1.1 402
Content-Type: application/json
Retry-After: <seconds until the reset>

{
  "resetsAt": "2026-10-12T00:00:00.000Z",
  "code": "quota_exceeded:agent_calls_per_day",
  "message": "You have reached the daily agent call allowance for this group.",
  "requestId": "req_example0001"
}

Tools

5 tools. Read tools never change data; write tools run the same checks as the app. No tool can delete an account or a group, remove a member, revoke a share or export raw statements.

list_accountssearch_transactionsget_forecastcreate_transactionset_tags
list_accounts
Readread:accounts
List the accounts of the active group that the calling member may see, with label, kind, currency and balance. Ended and unshared accounts are absent.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {},
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "accounts": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "publicRef": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Opaque account reference. No bank identifier is ever returned."
          },
          "label": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "description": "Account kind, e.g. checking, savings, card, cash, broker."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$",
            "description": "ISO 4217 currency code."
          },
          "balance": {
            "type": "string",
            "description": "Amount formatted for display (it-IT)."
          }
        },
        "required": [
          "publicRef",
          "label",
          "kind",
          "currency",
          "balance"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "accounts"
  ],
  "additionalProperties": false
}
search_transactions
Readread:transactions
Search the movements the calling member may see, by date range, account, tag and text. Paged; follow `nextCursor` until it is null.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "from": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD."
    },
    "to": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD."
    },
    "accountRef": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 128
    },
    "tagId": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 128
    },
    "query": {
      "type": [
        "string",
        "null"
      ],
      "maxLength": 200
    },
    "cursor": {
      "type": [
        "string",
        "null"
      ],
      "maxLength": 512
    },
    "limit": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 1,
      "maximum": 200
    }
  },
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "movements": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "movementId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "accountRef": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Calendar date, YYYY-MM-DD."
          },
          "amount": {
            "type": "number",
            "description": "Amount in major units of `currency`; negative = outflow."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$",
            "description": "ISO 4217 currency code."
          },
          "merchant": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ref": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "ref",
                "name"
              ],
              "additionalProperties": false
            },
            "description": "The tags on the movement as {ref, name}, in `tagIds` order (added 2026-10-19). A tag id that no longer resolves (merged or deleted) is omitted."
          },
          "forMember": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128
              },
              {
                "type": "null"
              }
            ],
            "description": "Member the movement is attributed to; null = shared."
          }
        },
        "required": [
          "movementId",
          "accountRef",
          "date",
          "amount",
          "currency",
          "merchant",
          "description",
          "tagIds",
          "tags",
          "forMember"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "movements",
    "nextCursor"
  ],
  "additionalProperties": false
}
get_forecast
Readread:transactions
Project the group balance over the next months: minimum balance, its month, months under the safety buffer and a three-band range. Deterministic arithmetic over your movements, not a model.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "months": {
      "type": [
        "integer",
        "null"
      ],
      "minimum": 1,
      "maximum": 12
    },
    "scenarioId": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 128
    }
  },
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$",
      "description": "ISO 4217 currency code."
    },
    "minimumBalance": {
      "type": "number",
      "description": "Amount in major units of `currency`; negative = outflow."
    },
    "minimumMonth": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}$",
      "description": "Calendar month, YYYY-MM."
    },
    "monthsUnderBuffer": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "bands": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}$",
            "description": "Calendar month, YYYY-MM."
          },
          "low": {
            "type": "number",
            "description": "Amount in major units of `currency`; negative = outflow."
          },
          "expected": {
            "type": "number",
            "description": "Amount in major units of `currency`; negative = outflow."
          },
          "high": {
            "type": "number",
            "description": "Amount in major units of `currency`; negative = outflow."
          }
        },
        "required": [
          "month",
          "low",
          "expected",
          "high"
        ],
        "additionalProperties": false
      }
    },
    "basis": {
      "type": "string"
    }
  },
  "required": [
    "currency",
    "minimumBalance",
    "minimumMonth",
    "monthsUnderBuffer",
    "bands",
    "basis"
  ],
  "additionalProperties": false
}
create_transaction
Writewrite:transactions
Record one movement on an account the calling member may see. Runs the same checks as a manual entry: account window, bank-identifier guard, tag vocabulary and the monthly transactions allowance.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "accountRef": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD."
    },
    "amount": {
      "type": "number",
      "description": "Amount in major units of `currency`; negative = outflow."
    },
    "currency": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[A-Z]{3}$",
      "description": "ISO 4217 currency code."
    },
    "merchant": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "description": {
      "type": [
        "string",
        "null"
      ],
      "maxLength": 500
    },
    "tagIds": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      }
    },
    "forMember": {
      "type": [
        "string",
        "null"
      ],
      "minLength": 1,
      "maxLength": 128
    }
  },
  "required": [
    "accountRef",
    "date",
    "amount",
    "merchant"
  ],
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "movementId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "provenance": {
      "type": "string",
      "const": "agent"
    }
  },
  "required": [
    "movementId",
    "provenance"
  ],
  "additionalProperties": false
}
set_tags
Writewrite:tags
Add or remove tags on one movement. Never overwrites: `add` keeps the tags already on the movement, `remove` drops only the named ones.
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "movementId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "tagIds": {
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      }
    },
    "mode": {
      "type": "string",
      "enum": [
        "add",
        "remove"
      ]
    }
  },
  "required": [
    "movementId",
    "tagIds",
    "mode"
  ],
  "additionalProperties": false
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "movementId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128
    },
    "tagIds": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 128
      }
    }
  },
  "required": [
    "movementId",
    "tagIds"
  ],
  "additionalProperties": false
}

Scopes

Every credential carries the scopes chosen when it is created. A call to a tool outside them is refused with 403 before its arguments are read.

ScopeTools
read:accounts
list_accounts
read:transactions
search_transactionsget_forecast
write:transactions
create_transaction
write:tags
set_tags

Transports

Two ways to obtain a credential, one credential underneath: whichever you use, the agent presents it as a Bearer header to POST /api/mcp over Streamable HTTP.

OAuth 2.1oauth
Connect from an MCP client through the consent screen: OAuth 2.1 with PKCE through WorkOS AuthKit. The client starts at /api/mcp/authorize and receives its access token at /api/mcp/callback.
Personal tokentoken
Mint a scoped personal token in Settings, Agents, and present it as a Bearer header. It is shown once and stored only as a hash.

Error codes

Every refusal is the same envelope: a stable code to branch on, a human message and a request id. Branch on the code, never on the message.

{
  "code": "validation_failed",
  "message": "That request could not be processed.",
  "requestId": "req_example0001"
}

Present on validation_failed when a tool argument fails its schema: which argument, and why. The argument value is never echoed.

{
  "code": "validation_failed",
  "message": "to has an invalid format.",
  "requestId": "req_example0001",
  "details": {
    "field": "to",
    "reason": "format"
  }
}
  • details.field — JSON path of the offending argument, dot-joined (e.g. `to`, `tagIds.2`); `arguments` for the whole object.
  • details.reason — Stable reason token: one of type, required, format, range, enum.
details.reasonMeaning
typeThe argument has the wrong type, or the arguments object carries a key the tool does not declare.
requiredA required argument is missing. Nullable arguments must still be present; send null.
formatThe argument does not match its declared format (for example YYYY-MM-DD).
rangeThe argument is outside its allowed range or length.
enumThe argument is not one of the allowed values.
CodeStatusMessage
unauthorizedMissing, malformed, expired or revoked credential.401Missing, malformed, expired or revoked credential.
forbiddenYou do not have permission to do that.403You do not have permission to do that.
forbidden:not_a_memberYou are not a member of this group.403You are not a member of this group.
forbidden:account_not_sharedThat account is not shared with you.403That account is not shared with you.
quota_exceeded:agent_calls_per_dayYou have reached the daily agent call allowance for this group.402You have reached the daily agent call allowance for this group.
quota_exceeded:transactions_per_monthYou have reached your plan limit for transactions_per_month.402You have reached your plan limit for transactions_per_month.
not_foundThat item no longer exists.404That item no longer exists.
validation_failedThat request could not be processed.422That request could not be processed.
internal_errorSomething went wrong. Please try again.500Something went wrong. Please try again.

Connect your agent

Create a scoped, revocable connection for your agent in your group's settings, then point it at the endpoint above.

Open Settings › Agents

Versioning and deprecation

The contract is versioned by date. The current version is 2026-09-01, and every initialize response carries it as contractVersion, so your agent can tell which contract it is talking to.

At least 90 days of notice
A tool, a field or a scope is only removed or renamed in a new contract version, and the previous version keeps being served for at least 90 days after the new one is announced.
Removals are announced before they ship
Every removal is announced in the published reference before the version that drops it ships: on this page and in /openapi.json, where the affected tool carries the OpenAPI deprecated flag, the date it is removed after and its replacement. A deprecated tool keeps answering normally until then.
New fields are never breaking
Within a version fields are only ever added, and every absent value is sent as null rather than left out, so a client that ignores fields it does not know keeps working unchanged.

The household money, in one private place

Start free on your own, then bring the household in when you are ready. Saivify never asks for a bank password.

See pricing
SsAIvify

Navigate

  • Product
  • Pricing
  • Live demo
  • Contact
  • Docs

Account

  • Sign In
  • Dashboard

Company

  • About
  • Contact

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Saivify. All rights reserved.

Privacy PolicyTerms