Skip to content

Latest commit

 

History

History
176 lines (114 loc) · 5.22 KB

File metadata and controls

176 lines (114 loc) · 5.22 KB

API Reference

Base URL: http://127.0.0.1:5000/api/v1 (internal — see docs/ARCHITECTURE.md §2 for why the browser never calls this directly).

All responses are JSON with a consistent envelope:

{ "success": true, "data": { ... } }
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "..." } }

All endpoints except register and login require an X-Session-Token: <token> header. A missing/invalid/expired token returns 401 UNAUTHORIZED.


Auth

POST /register

Request:

{ "username": "alice", "email": "alice@example.com", "password": "correcthorse1" }
  • username: 3–32 chars, letters/numbers/underscore/hyphen only.
  • password: at least 10 characters, containing at least one letter and one number.

Responses: 201 created · 422 validation error · 409 username or email already taken.

POST /login

Request:

{ "identifier": "alice", "password": "correcthorse1" }

identifier may be a username or email.

Responses: 200 with { token, user } · 401 invalid credentials (same generic message whether the account doesn't exist or the password is wrong) · 429 rate-limited after repeated failures (see NOTES_MAX_LOGIN_ATTEMPTS / NOTES_LOGIN_ATTEMPTS_WINDOW_MINUTES).

POST /logout

Requires X-Session-Token. Invalidates the session server-side. Responds 200.

GET /session

Requires X-Session-Token. Returns the current user. Responds 200 with { user }, or 401 if the token is missing/expired.


Notes

All note endpoints are implicitly scoped to the authenticated user — there is no way to pass another user's ID; ownership is derived entirely from the session token.

GET /notes

Query parameters (all optional):

Param Values Default
category_id integer none (all categories)
archived 1/true (archived only), all (both), anything else/absent → active only active only
sort newest, oldest newest
q search text (matches title or body) none

Response: 200 with { notes: [...], count: N }.

POST /notes

Request: { "title": "...", "body": "...", "category_id": 3 } (category_id optional/nullable).

Responses: 201 created · 422 validation error (empty/too-long title, body over 50,000 chars, category_id not owned by the user).

GET /notes/{id}

200 with { note }, or 404 if the note doesn't exist or belongs to another user (no distinction — prevents existence leaks).

PUT /notes/{id}

Request: any of title, body, category_id. Omitted fields keep their current value; "category_id": null explicitly clears the category.

Responses: 200 updated · 404 not found/not owned · 422 validation error.

DELETE /notes/{id}

200 on success, 404 if not found/not owned.

POST /notes/{id}/archive

Sets is_archived = 1. 200 with the updated note, or 404.

POST /notes/{id}/unarchive

Sets is_archived = 0. 200 with the updated note, or 404.


Categories

GET /categories

200 with { categories: [{ id, name, color, created_at, note_count }, ...] }, ordered by name.

POST /categories

Request: { "name": "Work", "color": "#4C6EF5" } (color optional, must be a 6-digit hex code if provided).

Responses: 201 created · 422 validation error · 409 a category with this name already exists for this user.

PUT /categories/{id}

Same body shape as create. 200 updated · 404 not found/not owned · 409 name conflict · 422 validation error.

DELETE /categories/{id}

200 on success, 404 if not found/not owned. Notes referencing the deleted category fall back to uncategorized (category_id = NULL) rather than being deleted.


Search

GET /search

Query parameters:

Param Required Notes
q yes 422 if missing/empty
category_id no filters within results
archived no 1/true archived-only, all/absent → both

200 with { notes: [...], count: N, query: "..." }. LIKE metacharacters (%, _) in q are escaped and matched literally, not as wildcards.


Dashboard

GET /dashboard

200 with:

{
  "total_notes": 12,
  "archived_notes": 3,
  "recent_notes": [ /* last 5 active notes by created_at desc */ ],
  "recently_edited": [ /* last 5 active notes by updated_at desc */ ],
  "categories": [ { "id": 1, "name": "Work", "color": "#4C6EF5", "note_count": 5 }, ... ]
}

Error codes

Code Status Meaning
VALIDATION_ERROR 422 Input failed validation — message lists the specific issue(s)
UNAUTHORIZED 401 Missing, malformed, or expired session token
INVALID_CREDENTIALS 401 Login failed
RATE_LIMITED 429 Too many failed login attempts
NOT_FOUND 404 Resource doesn't exist, or doesn't belong to the caller
CONFLICT 409 Unique constraint violation (username/email/category name)
INVALID_JSON 400 Request body was not valid JSON
METHOD_NOT_ALLOWED 405 Wrong HTTP method for this path
SERVICE_UNAVAILABLE 503 Database temporarily unreachable
INTERNAL_ERROR 500 Unhandled exception — logged server-side, never detailed to the client