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.
Simple unauthenticated endpoint to verify that the backend is running.
GET /healthGET /health
Successful response (200):
{
"status": "ok"
}Authentication is password-less and based on wallet signatures. The flow is:
POST /api/auth/nonce.POST /api/auth/verify.Authorization: Bearer <token> to all protected endpoints.POST /api/auth/noncePOST /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" }POST /api/auth/verifyPOST /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" }POST /api/auth/logoutStateless 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"
}All expense endpoints require a valid JWT in the Authorization: Bearer <token> header. Each expense belongs to the authenticated user.
GET /api/expensesSupports 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 filtersPOST /api/expensesPOST /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 failureGET /api/expenses/:idGET /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" }PUT /api/expenses/:idAt 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 /api/expenses/:idDELETE /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 endpoints also require a valid JWT in the Authorization header.
GET /api/user/profileGET /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" }PUT /api/user/profileCurrently, 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 /api/user/accountDELETE /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" }