This document summarizes the server routes in app/api that are used by the frontend for reminders, authentication, feedback, and notification lookups.
Saves or updates the reminder opt-in preference for a wallet address.
- Request body:
{ "address": "G...", "email": "payer@example.com", "enabled": true } - Response:
{ "success": true } - Auth requirement: none. The route uses the Supabase admin client on the server.
- Validation:
addressmust be a well-formed Ed25519 Stellar public key (StrKey.isValidEd25519PublicKey);emailmust match a basic email shape and stay under 320 characters;enabled, if present, must be a boolean. - Rate limit: 5 requests per minute per client IP (see Rate limits and operational notes).
- Error responses:
400whenaddressoremailis missing or malformed.429when the rate limit is exceeded.500when the Supabase upsert fails.
Triggers reminder emails for active preferences. This is intended for a cron job or manual admin run.
- Auth requirement:
Authorization: Bearer <CRON_SECRET>. - Rate limit: 10 requests per minute per client IP, applied after the
CRON_SECRETcheck. - Response:
{ "success": true, "sentCount": 1, "details": [ { "invoiceId": "42", "milestone": 24, "email": "payer@example.com" } ] } - Error responses:
401when the bearer token does not matchCRON_SECRET.429when the rate limit is exceeded.200with{ "message": "No active preferences" }when no reminder preferences are enabled.500on internal failures.
Example curl:
curl -X GET "http://localhost:3000/api/reminders" \
-H "Authorization: Bearer $CRON_SECRET"Disables reminder delivery for a wallet address.
- Query string:
?address=<wallet-address> - Response: HTML page that confirms the unsubscribe.
- Auth requirement: none.
- Error responses:
400when theaddressquery parameter is missing.500when the update fails.
Example curl:
curl "http://localhost:3000/api/reminders/unsubscribe?address=G..."Accepts feedback from the UI and forwards it to GitHub issues when GitHub credentials are configured.
- Request body:
{ "rating": 5, "category": "Bug", "feedback": "The dashboard feels slow", "email": "user@example.com" } - Response:
- Success without GitHub config:
{ "success": true } - Success with GitHub config:
{ "success": true, "issueUrl": "https://github.com/..." }
- Success without GitHub config:
- Auth requirement: none.
- Validation:
ratingmust be an integer 1-5,categorymust be one ofBug,Feature,UX,Other,feedbackmust be a non-empty string under 5,000 characters, andemail(if provided) must be a valid email under 320 characters. - Rate limit: 5 requests per minute per client IP.
- Error responses:
400when a required field is missing or fails validation.429when the local rate limit is exceeded, or when GitHub returns a rate-limit response; the body includeserror: "rate_limit"andretryAfter.500for unexpected failures.
Example curl:
curl -X POST "http://localhost:3000/api/feedback" \
-H "Content-Type: application/json" \
-d '{"rating":5,"category":"Bug","feedback":"The dashboard feels slow","email":"user@example.com"}'Returns the notification list for a given wallet address.
- Path parameter:
/api/notifications/<address> - Response: a JSON array of notification objects with the shape:
[ { "id": "1", "category": "invoice", "type": "overdue", "title": "Invoice overdue", "message": "A funded invoice is now overdue", "href": "/pay/42", "createdAt": "2026-07-26T00:00:00.000Z", "read": false } ] - Auth requirement: none.
- Validation: the
addresspath segment must be a well-formed Ed25519 Stellar public key; requests with a malformed address are rejected with400before reaching the notification backend. - Rate limit: 30 requests per minute per client IP.
- Error responses:
400whenaddressis not a valid Stellar public key.429when the rate limit is exceeded.- The route returns an empty array
[]if notification fetching fails or the upstream service is not configured.
Example curl:
curl "http://localhost:3000/api/notifications/G..."Returns leaderboard entries for LPs, payers, or freelancers over a given period. This route lives at
app/api/leaderboard/route.ts and backs TopFundersWidget.
- Query string:
?type=<lp|payer|freelancer>&period=<7d|30d|90d|all>&limit=<1-100> - Response: a JSON array of leaderboard entries, or
[]if the upstream indexer request fails. - Auth requirement: none.
- Validation:
typeandperiodare checked against fixed allow-lists;limit, if provided, must be an integer between 1 and 100. - Rate limit: 30 requests per minute per client IP.
- Error responses:
400whentype,period, orlimitis invalid.429when the rate limit is exceeded.
Example curl:
curl "http://localhost:3000/api/leaderboard?type=lp&period=30d&limit=10"- All routes above (
reminders,feedback,notifications/[address],leaderboard) apply an in-memory, per-client-IP rate limit via src/lib/rate-limit.ts. This is a best-effort, per-instance fixed window - it does not share state across serverless instances or regions, so it should be treated as defense-in-depth rather than a hard guarantee. If stricter enforcement becomes necessary (e.g. under active abuse), replace it with a shared store such as Upstash Redis. - The reminders
GETroute additionally requiresCRON_SECRET; treat it as a privileged endpoint and keep it behind a scheduler rather than exposing it to end users. - The feedback route inherits GitHub API rate limits and returns a
429response if the upstream API refuses the request. - All routes return generic, non-identifying error messages to callers; detailed errors are only logged server-side via
console.error, never included in the response body.