API Documentation

This backend exposes a JWT-protected REST API for authenticating with a wallet, managing expenses, and updating the user profile.

Base URL example: https://backend.example.com. In the app, this is configured via NEXT_PUBLIC_API_URL.

Health check

Simple unauthenticated endpoint to verify that the backend is running.

Health check – GET /health

GET /health

Successful response (200):
{
  "status": "ok"
}

Authentication

Authentication is password-less and based on wallet signatures. The flow is:

  1. Request a nonce with your wallet address via POST /api/auth/nonce.
  2. Sign the nonce in your wallet and send the signature to POST /api/auth/verify.
  3. Receive a short-lived JWT and send it as Authorization: Bearer <token> to all protected endpoints.

Request nonce – POST /api/auth/nonce

POST /api/auth/nonce
Content-Type: application/json

Request body:
{
  "walletAddress": "0xYourWalletAddress"
}

Successful response (200):
{
  "nonce": "random-string-to-sign"
}

Error responses:
- 400: { "error": "walletAddress is required" }

Verify signature – POST /api/auth/verify

POST /api/auth/verify
Content-Type: application/json

Request body:
{
  "walletAddress": "0xYourWalletAddress",
  "signature": "0xSignedNonce"
}

Successful response (200):
{
  "token": "jwt-token"
}

Error responses:
- 400: { "error": "walletAddress and signature are required" }
- 401: { "error": "Signature verification failed" }

Logout – POST /api/auth/logout

Stateless logout endpoint. On the backend this simply returns a confirmation message; the client is responsible for clearing the JWT locally.

POST /api/auth/logout

Successful response (200):
{
  "message": "Logged out"
}

Expenses

All expense endpoints require a valid JWT in the Authorization: Bearer <token> header. Each expense belongs to the authenticated user.

List expenses – GET /api/expenses

Supports optional filtering and pagination.

GET /api/expenses?category=Food&from=2024-01-01&to=2024-12-31&limit=50&offset=0
Authorization: Bearer <token>

Query parameters (all optional):
- category: one of the allowed categories (e.g. "Food", "Bills", ...)
- from: ISO date or yyyy-mm-dd (inclusive start)
- to: ISO date or yyyy-mm-dd (inclusive end)
- limit: integer 1–200 (default depends on backend)
- offset: integer >= 0

Successful response (200):
{
  "expenses": [
    {
      "id": 1,
      "amount": "12.34",
      "category": "Food",
      "note": "Lunch",
      "date": "2024-05-01T12:00:00.000Z"
    }
    // ...
  ]
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "..." } // invalid filters

Create expense – POST /api/expenses

POST /api/expenses
Authorization: Bearer <token>
Content-Type: application/json

Request body:
{
  "amount": "12.34",          // or number, will be normalized
  "category": "Food",         // must be a valid category
  "date": "2024-05-01",       // or full ISO date
  "note": "optional note"     // optional, max 255 chars
}

Successful response (201):
{
  "expense": {
    "id": 1,
    "amount": "12.34",
    "category": "Food",
    "note": "optional note",
    "date": "2024-05-01T12:00:00.000Z"
  }
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "..." } // validation failure

Get single expense – GET /api/expenses/:id

GET /api/expenses/1
Authorization: Bearer <token>

Successful response (200):
{
  "expense": {
    "id": 1,
    "amount": "12.34",
    "category": "Food",
    "note": "optional note",
    "date": "2024-05-01T12:00:00.000Z"
  }
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "Invalid expense id" }
- 404: { "error": "Expense not found" }
- 500: { "error": "Failed to fetch expense" }

Update expense – PUT /api/expenses/:id

At least one field is required in the request body.

PUT /api/expenses/1
Authorization: Bearer <token>
Content-Type: application/json

Request body (any subset of):
{
  "amount": "15.00",
  "category": "Bills",
  "date": "2024-06-01",
  "note": "updated note"
}

Successful response (200):
{
  "expense": {
    "id": 1,
    "amount": "15.00",
    "category": "Bills",
    "note": "updated note",
    "date": "2024-06-01T12:00:00.000Z"
  }
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "Invalid expense id" } or { "error": "At least one field is required" }
- 404: { "error": "Expense not found" }

Delete expense – DELETE /api/expenses/:id

DELETE /api/expenses/1
Authorization: Bearer <token>

Successful response (200):
{
  "message": "Expense deleted"
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "Invalid expense id" }
- 404: { "error": "Expense not found" }
- 500: { "error": "Failed to delete expense" }

User profile

User endpoints also require a valid JWT in the Authorization header.

Get profile – GET /api/user/profile

GET /api/user/profile
Authorization: Bearer <token>

Successful response (200):
{
  "profile": {
    "id": 1,
    "walletAddress": "0xYourWalletAddress",
    "name": "Your Name",
    // ...other fields depending on schema
  }
}

Error responses:
- 401: { "error": "Unauthorized" }
- 404: { "error": "User not found" }
- 500: { "error": "Failed to load profile" }

Update profile – PUT /api/user/profile

Currently, only the name field is supported.

PUT /api/user/profile
Authorization: Bearer <token>
Content-Type: application/json

Request body:
{
  "name": "New Name"
}

Successful response (200):
{
  "profile": {
    "id": 1,
    "walletAddress": "0xYourWalletAddress",
    "name": "New Name"
  }
}

Error responses:
- 401: { "error": "Unauthorized" }
- 400: { "error": "..." } // validation failures
- 404: { "error": "User not found" }

Delete account – DELETE /api/user/account

DELETE /api/user/account
Authorization: Bearer <token>

Successful response (200):
{
  "message": "Account deleted"
}

Error responses:
- 401: { "error": "Unauthorized" }
- 404: { "error": "User not found" }
- 500: { "error": "Failed to delete account" }