# FundWise HTTP API

Public API contract for FundWise, the Group money app for on-chain settlement. Fundy and other agent clients should use this page as the endpoint reference and fetch `https://fundwise.pages.dev/skill.md` first for capability and safety rules.

Base URL: `https://fundwise.pages.dev`

## Current product state for agents

- Mobile-native FundWise/Seeker readiness gates the Split Mode mainnet beta.
- Split Mode mainnet is the first active milestone, followed by a narrow June 15 LI.FI/CCTP multichain funding beta.
- Fund Mode mainnet invite-only closed beta follows mobile-native Split Mode, multichain funding, and Fund Mode devnet proof; devnet remains the rehearsal and hardening environment.
- Fundy is live on Railway as the Telegram-native FundLabs agent and MCP server. It uses FundWise HTTP routes for linking and ledger access, and Zerion CLI is live in Fundy for wallet analysis, readiness, and verification.
- LI.FI is the current top-up support path for `Route funds for Settlement` and `Route funds for Contribution`. June 15 scope is supported EVM USDC funding into Solana USDC before normal verified Settlement; full multi-chain wallet identity is later.
- Non-crypto onboarding through embedded wallets, Bridge.xyz, and Visa/card payments is a later phase, not current FundWise app behavior.
- Agents must treat money movement as wallet-confirmed unless a future Payable Settlement Request flow explicitly grants narrow, verified payment authority.

## Auth

### Browser wallet session

Most routes accept the same wallet-signed browser session used by the web app:

1. `POST /api/auth/wallet/challenge` with `{ "wallet": "<solana-pubkey>" }`.
2. Sign the returned `message` with the wallet.
3. `POST /api/auth/wallet/verify` with `{ "wallet": "<solana-pubkey>", "signature": "<base64-signature>" }`.
4. Send the returned HTTP-only cookie on protected API calls.

### Fundy Telegram linking

Fundy links a Telegram account to a FundWise wallet with a short-lived web-generated code:

1. The Member signs into FundWise with the browser wallet session.
2. The web app calls `POST /api/telegram/link-code` and shows a code like `FW-7X9K2M`.
3. The code expires after 5 minutes and can be used once.
4. The user DMs Fundy with `/link FW-7X9K2M`.
5. Fundy calls `POST /api/telegram/link` with service auth to bind that Telegram account to the wallet.

Fundy service-auth headers:

```http
Authorization: Bearer <FUNDWISE_SERVICE_API_KEY>
Content-Type: application/json
```

Rules:

- `FUNDWISE_SERVICE_API_KEY` must be configured on both the FundWise web app deployment and Fundy service.
- Fundy service auth applies only to the Telegram linking endpoints: `/api/telegram/link` (link codes are minted under the Member's browser wallet session at `/api/telegram/link-code`). No other route accepts the service key.
- There is no acting-wallet header. The linked Member wallet is derived server-side from the active Telegram link record, never from a request header.
- Server-side membership and ownership checks still apply.
- Fundy must not execute money movement. Settlement and Contribution transfers still require wallet confirmation in the web app; API routes only record verified transaction signatures after the user signs.
- Third-party agents should not use the Fundy service key. Scoped Agent Access tokens are the planned external-agent auth path.

## Error shape

All JSON errors use:

```json
{ "error": "Human readable message" }
```

Common statuses: `400` validation error, `401` missing or invalid auth, `403` not a Group Member or not allowed, `404` not found.

## Endpoints

### Agent discovery

#### GET /skill.md

Public Agent Skill Endpoint. Returns machine-readable markdown describing FundWise capabilities, allowed and forbidden actions, auth, limits, and safety rules.

Auth: none.

#### GET /auth.md

Auth.md discovery document for agents. Describes the current wallet-signature registration flow, OAuth Protected Resource Metadata, Authorization Server metadata, and the boundary between current browser sessions and planned Scoped Agent Access.

Auth: none.

#### GET /.well-known/agent-card.json

A2A Agent Card for agent-to-agent discovery. Describes the FundWise agent identity, HTTP+JSON interface, discovery and ledger-reading capabilities, and wallet-confirmation safety boundaries.

Auth: none.

#### GET /api/docs

This document.

Auth: none.

#### GET /sitemap.xml

Public XML sitemap listing canonical public FundWise URLs.

Auth: none.

#### GET /robots.txt

Crawler policy with a `Sitemap:` reference to `/sitemap.xml`.

Auth: none.

#### GET /.well-known/api-catalog

RFC 9727 API catalog in `application/linkset+json` format. Links to the OpenAPI service description, service documentation, Agent Skill Endpoint, and public health endpoint.

Auth: none.

#### GET /api/openapi.json

OpenAPI 3.1 service description for the current FundWise HTTP API.

Auth: none.

#### GET /openapi.json

Root-level alias for agents that discover OpenAPI documents outside the `/api` path.

Auth: none.

#### GET /.well-known/agent-skills/index.json

Agent Skills discovery index with a SHA-256 digest for `/skill.md`.

Auth: none.

#### GET /.well-known/oauth-authorization-server

OAuth-style discovery metadata for FundWise's current wallet-signature auth endpoints, Auth.md `agent_auth` block, and planned Scoped Agent Access scopes.

Auth: none.

#### GET /.well-known/oauth-protected-resource

Protected Resource Metadata for the FundWise API resource.

Auth: none.

#### GET /.well-known/mcp/server-card.json

MCP Server Card describing FundWise's agent discovery resources and browser-provided WebMCP tools.

Auth: none.

### Wallet auth

#### POST /api/auth/wallet/challenge

Create a short-lived wallet challenge.

Auth: none.

Request:

```json
{ "wallet": "<solana-pubkey>" }
```

Response:

```json
{ "message": "...", "expiresAt": 1770000000000 }
```

#### POST /api/auth/wallet/verify

Verify a signed challenge and set the protected wallet-session cookie.

Auth: challenge cookie from `/api/auth/wallet/challenge`.

Request:

```json
{ "wallet": "<solana-pubkey>", "signature": "<base64-signature>" }
```

Response:

```json
{ "wallet": "<solana-pubkey>" }
```

#### GET /api/auth/wallet/session

Return the current browser wallet-session status.

Auth: optional browser wallet session.

Response:

```json
{ "authenticated": true, "wallet": "<solana-pubkey>" }
```

### Mobile handoff

#### GET /.well-known/assetlinks.json

Android App Links metadata for the FundWise Seeker package `fun.fundwise.seeker`.

Auth: none.

Production env: `FUNDWISE_SEEKER_ANDROID_CERT_SHA256_FINGERPRINTS` must contain the release signing cert SHA-256 fingerprint.

#### POST /api/mobile/settlement-requests

Create a signed, seven-day mobile Settlement Request handoff URL for `/settle/r/{requestId}`.

Auth: browser wallet session; caller must be a Group Member.

Request:

```json
{ "groupId": "<group-id>", "fromWallet": "<payer-wallet>", "toWallet": "<payee-wallet>" }
```

Response:

```json
{ "requestId": "...", "url": "https://fundwise.fun/settle/r/...", "fallbackUrl": "https://fundwise.fun/groups/...", "expiresAt": "2026-06-01T00:00:00.000Z" }
```

#### GET /api/mobile/settlement-requests/{requestId}/preview

Preview redacted role, live amount when settleable, payer/payee, mint, expiry, and fallback URL for Seeker.

Auth: browser wallet session; the authenticated wallet is used as the viewer.

#### GET /settle/r/{requestId}

Mobile-safe Settlement Request link. Redirects to the current web Group fallback state.

Auth: none for redirect; Group details still require the normal wallet session after landing.

#### GET /receipts/{id}

Stable Receipt handoff route. Receipt details require wallet verification and Group membership.

### Fundy Telegram auth

#### POST /api/telegram/link-code

Create a one-time Telegram link code for the authenticated wallet. The code is shown in the web app and pasted into Fundy's DM as `/link FW-XXXXXX`.

Auth: browser wallet session.

Response:

```json
{ "code": "FW-7X9K2M", "expiresAt": "2026-05-14T14:35:00.000Z" }
```

#### POST /api/telegram/link

Consume a one-time Telegram link code and bind one Telegram user id to the wallet that generated the code.

Auth: Fundy service auth.

Request:

```json
{
  "code": "FW-7X9K2M",
  "telegramId": "123456789",
  "telegramUsername": "sarthi",
  "telegramFirstName": "Sarthi",
  "telegramLastName": "Borkar"
}
```

Response:

```json
{ "linked": true, "wallet": "<solana-pubkey>", "link": { "telegramId": "123456789", "wallet": "<solana-pubkey>" } }
```

#### GET /api/telegram/link?telegramId=<telegramId>

Return the active wallet link for a Telegram user.

Auth: Fundy service auth.

#### DELETE /api/telegram/link

Deactivate the active Telegram wallet link for a Telegram user.

Auth: Fundy service auth.

Request:

```json
{ "telegramId": "123456789" }
```

### Groups

#### GET /api/groups?code=<inviteCode>

Legacy local/dev invite-code fallback. Disabled in production; production joins use tokenized invite URLs.

Auth: none.

#### GET /api/groups?wallet=<wallet>

List Groups for the authenticated wallet.

Auth: browser wallet session. If `wallet` is provided, it must match the authenticated wallet.

#### POST /api/groups

Create a Group.

Auth: browser wallet session. `createdBy` must match the authenticated wallet.

Request:

```json
{
  "name": "Lisbon Trip",
  "mode": "split",
  "stablecoinMint": "<usdc-mint>",
  "createdBy": "<creator-wallet>",
  "fundingGoal": 1000,
  "approvalThreshold": 2
}
```

#### GET /api/groups/{groupId}?wallet=<wallet>

Load a Group dashboard snapshot. Private ledger fields are included only when the authenticated wallet matches the requested Member wallet.

Auth: optional for public Group shell; browser wallet session for Member-specific view.

#### GET /api/groups/{groupId}/ledger

Load protected Split Mode ledger data, including Balances, suggested Settlements, Activity Feed, and total settled volume.

Auth: browser wallet session. Authenticated wallet must be a Group Member.

#### POST /api/groups/{groupId}/members

Join a Group as a Member with a valid invite token.

Auth: browser wallet session. `wallet` must match the authenticated wallet. New Members must provide an unexpired invite token for the target Group.

Request:

```json
{ "wallet": "<member-wallet>", "displayName": "Sarthi", "inviteToken": "FWI-..." }
```

#### POST /api/groups/{groupId}/invites

Create a tokenized Group invite link credential. The response returns the raw token once; the database stores only its hash.

Auth: browser wallet session. Split Mode requires the acting wallet to be a Member. Fund Mode requires the acting wallet to have `invite_member`.

Response:

```json
{ "inviteToken": "FWI-...", "expiresAt": "2026-06-23T12:00:00.000Z" }
```

#### PATCH /api/groups/{groupId}/treasury

Persist Fund Mode Treasury addresses after Treasury initialization.

Auth: browser wallet session. `creatorWallet` must match the authenticated wallet.

Request:

```json
{
  "creatorWallet": "<creator-wallet>",
  "multisigAddress": "<squads-multisig>",
  "treasuryAddress": "<treasury-token-account>"
}
```

### Expenses

#### GET /api/expenses?groupId=<groupId>

List Expenses for a Group.

Auth: browser wallet session. Authenticated wallet must be allowed to read the Group.

#### POST /api/expenses

Create a real Expense record.

Auth: browser wallet session. `createdBy` must match the authenticated wallet.

Fundy note: after service auth ships, use this only when the Member explicitly wants to create a real Expense. A separate draft Expense API is still planned for `/draft`-style commands.

Amounts are integer token units, not decimal USDC display amounts. For a 6-decimal USDC mint, `42500000` means 42.5 USDC.

Request:

```json
{
  "groupId": "<group-id>",
  "payer": "<payer-wallet>",
  "createdBy": "<creator-wallet>",
  "amount": 42500000,
  "mint": "<usdc-mint>",
  "memo": "Dinner",
  "category": "food",
  "splitMethod": "equal",
  "splits": [
    { "wallet": "<member-a>", "share": 21250000 },
    { "wallet": "<member-b>", "share": 21250000 }
  ],
  "sourceCurrency": "USD",
  "sourceAmount": 42500000,
  "exchangeRate": 1,
  "exchangeRateSource": "manual",
  "exchangeRateAt": "2026-05-04T00:00:00.000Z"
}
```

#### GET /api/expenses/{expenseId}

Load one Expense.

Auth: browser wallet session.

#### PATCH /api/expenses/{expenseId}

Update an Expense before later Settlements make the ledger unsafe.

Auth: browser wallet session. `actorWallet` must match the authenticated wallet and the Expense creator.

#### DELETE /api/expenses/{expenseId}

Delete an Expense before later Settlements make the ledger unsafe.

Auth: browser wallet session. `actorWallet` must match the authenticated wallet and the Expense creator.

Request:

```json
{ "actorWallet": "<creator-wallet>" }
```

### Settlements

#### POST /api/settlements

Record a Settlement receipt after the debtor signs an on-chain USDC transfer.

Auth: browser wallet session. `fromWallet` must match the authenticated wallet.

Important: this route does not initiate an on-chain transfer. It verifies and persists the transaction signature. Fundy should deep-link the debtor back to the app for wallet confirmation instead of trying to settle in Telegram.

Request:

```json
{
  "groupId": "<group-id>",
  "fromWallet": "<debtor-wallet>",
  "toWallet": "<creditor-wallet>",
  "amount": 23500000,
  "mint": "<usdc-mint>",
  "txSig": "<solana-signature>"
}
```

#### GET /api/settlements/{settlementId}

Load a protected Receipt view.

Auth: browser wallet session. Authenticated wallet must be allowed to read the Receipt.

### Provider Rail Intents

Provider rail endpoints are the audit/recovery layer for LI.FI/CCTP route-then-settle funding and future external rails. They never create Group Balance, Settlement, Contribution, Proposal, or Receipt rows.

#### POST /api/provider-rail-intents

Create a service-role-only provider route intent plus quote snapshot for a LI.FI/CCTP route. The authenticated wallet must match `memberWallet` and be a Group Member.

#### GET /api/provider-rail-intents/{intentId}

Recover the authenticated Member's provider route status, latest quote hash, latest provider event, and reconciliation state.

#### POST /api/provider-rail-intents/{intentId}/events

Record client-observed LI.FI route execution status, tx hash, explorer link, and route message. Duplicate event ids are treated idempotently.

#### POST /api/provider-rail-intents/{intentId}/refresh

Refresh LI.FI provider status by `txHash` or `taskId` and record provider event / reconciliation evidence. This remains external route evidence; normal FundWise Receipts still require verified Solana USDC Settlement or Contribution.

### Planned Agent Payment Endpoints

These endpoints are not live yet. They are listed so agents and developers do not confuse current FundWise APIs with planned payable settlement support.

Planned payment protocols to evaluate:

- x402 HTTP 402 payment challenges for payable agent routes.
- Machine Payment Protocol (MPP) discovery through OpenAPI `x-payment-info` extensions for payable operations.
- Agentic Commerce Protocol (ACP) discovery at `/.well-known/acp.json` only if FundWise exposes a commerce API.

#### POST /api/agent/spending-policies

Planned. Create a Member-granted Spending Policy after direct wallet confirmation. Policy fields must include agent identity, scopes, Group scope, USDC asset scope, per-Settlement cap, daily cap, counterparty policy, expiry, and revocation support.

#### GET /api/agent/spending-policies

Planned. List active Spending Policies for the authenticated Member.

#### PATCH /api/agent/spending-policies/{policyId}

Planned. Lower limits, renew, or revoke a Spending Policy. Raising limits must require fresh direct wallet confirmation.

#### POST /api/agent/settlement-requests

Planned. Create or fetch a Payable Settlement Request for one exact live debtor-to-creditor Settlement intent. Must resolve the live Group Balance and expose a human Settlement Request Link fallback.

#### GET /api/agent/settlement-requests/{requestId}

Planned. Inspect amount, expiry, selected payment rail, and status for a Payable Settlement Request.

#### POST /api/agent/settlement-requests/{requestId}/pay

Planned. Pay only when the authenticated agent has a valid Spending Policy. If the amount is above policy, return the human Settlement Request Link instead of attempting payment.

#### POST /api/agent/settlement-requests/{requestId}/verify

Planned. Verify x402 / MPP / on-chain proof, then create the normal Settlement and Receipt only if the proof matches the request.

### Contributions

#### POST /api/contributions

Record a Fund Mode Contribution receipt after a Member signs an on-chain USDC transfer into the Treasury.

Auth: browser wallet session. `memberWallet` must match the authenticated wallet.

Important: this route does not initiate an on-chain transfer. Fundy should deep-link the Member back to the app for wallet confirmation.

Request:

```json
{
  "groupId": "<group-id>",
  "memberWallet": "<member-wallet>",
  "amount": 100,
  "mint": "<usdc-mint>",
  "txSig": "<solana-signature>"
}
```

### Proposals

#### POST /api/proposals

Create a Fund Mode reimbursement Proposal for a current Group Member.

Auth: browser wallet session. `proposerWallet` must match the authenticated wallet. The Group must be a Fund Mode Group with an initialized Treasury. The recipient must be a current Group Member.

Important: the browser creates the Squads vault transaction and Squads Proposal first. This route verifies and stores the Squads Proposal mapping for FundWise UX/history; the database is not the approval authority and this route does not execute Treasury movement.

Request:

```json
{
  "groupId": "<group-id>",
  "proposerWallet": "<member-wallet>",
  "recipientWallet": "<member-wallet>",
  "amount": 100,
  "mint": "<usdc-mint>",
  "squadsTransactionIndex": 1,
  "squadsProposalAddress": "<squads-proposal-pda>",
  "squadsTransactionAddress": "<squads-transaction-pda>",
  "squadsCreateTxSig": "<solana-signature>",
  "proofUrl": "https://example.com/receipt.pdf",
  "memo": "Hotel deposit reimbursement"
}
```

#### PATCH /api/proposals/{proposalId}

Update editable off-chain Proposal metadata.

Auth: browser wallet session. `editorWallet` must match the authenticated wallet, be the Proposal creator, and still be a current Group Member.

Important: only `memo` and `proofUrl` are editable. Recipient, amount, mint, and Squads transaction details are immutable because they are anchored in the Squads transaction. Edits are blocked after the first approval.

Request:

```json
{
  "editorWallet": "<member-wallet>",
  "memo": "Updated memo",
  "proofUrl": "https://example.com/receipt.pdf"
}
```

#### POST /api/proposals/{proposalId}/comments

Add a Proposal-scoped comment.

Auth: browser wallet session. `memberWallet` must match the authenticated wallet and be a current Group Member.

Request:

```json
{
  "memberWallet": "<member-wallet>",
  "body": "Looks good to me."
}
```

#### POST /api/proposals/{proposalId}/review

Approve or reject a pending Fund Mode reimbursement Proposal.

Auth: browser wallet session. `memberWallet` must match the authenticated wallet. Each Member can approve or reject at most once.

Important: the browser signs the Squads approve/reject action first. This route verifies the Squads Proposal state and records the review signature; threshold approval comes from Squads status and does not execute Treasury movement.

Request:

```json
{
  "memberWallet": "<member-wallet>",
  "decision": "approved",
  "txSig": "<solana-signature>"
}
```

#### POST /api/proposals/{proposalId}/execute

Record execution of an approved Fund Mode reimbursement Proposal.

Auth: browser wallet session. `executorWallet` must match the authenticated wallet and be a current Group Member.

Important: the browser executes the approved Squads vault transaction first. This route verifies Squads status is executed and verifies the stablecoin transfer from the Treasury ATA to the approved recipient before marking the FundWise Proposal executed.

Request:

```json
{
  "executorWallet": "<member-wallet>",
  "txSig": "<solana-signature>"
}
```

### Profile

#### POST /api/profile/display-name

Update a Member's global Profile Display Name.

Auth: browser wallet session. `wallet` must match the authenticated wallet.

Request:

```json
{ "wallet": "<member-wallet>", "displayName": "Sarthi" }
```

## Current browser-session calls

Current protected calls require a browser wallet session:

- Read Group list: `GET /api/groups?wallet=<wallet>`
- Read Group ledger: `GET /api/groups/{groupId}/ledger`
- Read Expenses: `GET /api/expenses?groupId=<groupId>`
- Read Receipts: `GET /api/settlements/{settlementId}`
- Generate Settlement links in the client using the existing web URL: `https://fundwise.pages.dev/groups/{groupId}?settle=<debtor-wallet>`

Use mutation routes only for explicit Member-directed actions. Money-moving actions still deep-link to the web app for wallet signing. Fundy service auth is currently available for Telegram link management; other protected routes require browser wallet sessions unless their docs explicitly say they support Fundy service auth.
