5.1 KiB
API Reference
Base URL (local dev): http://localhost:8080
All request/response bodies are JSON. Authenticated endpoints rely on the
session_id cookie set by /login or the Google OAuth callback - include
it automatically by using a cookie-aware HTTP client (browsers do this
natively; with curl, use -c cookies.txt -b cookies.txt).
GET /health
Liveness check. No authentication, no rate limiting beyond the global limit.
Response 200
{ "status": "ok" }
curl http://localhost:8080/health
POST /register
Creates a new password-based account.
Rate limited to 5 requests/minute per IP (shared with /login).
Request body
{ "email": "hamid@example.com", "password": "secret123" }
email- required, must be unique across all accounts.password- required, minimum 8 characters.
Response 201
{ "id": 1, "email": "hamid@example.com" }
Errors
| Status | Body | Cause |
|---|---|---|
| 400 | {"error":"invalid request body"} |
Malformed JSON |
| 400 | {"error":"email and password are required"} |
Missing field |
| 400 | {"error":"password must be at least 8 characters"} |
Password too short |
| 409 | {"error":"email already registered"} |
Email already taken |
| 429 | (rate limit response) | Too many requests from this IP |
| 500 | {"error":"internal error"} |
Unexpected server/database failure |
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"email":"hamid@example.com","password":"secret123"}'
POST /login
Authenticates with email + password and starts a server-side session.
Rate limited to 5 requests/minute per IP (shared with /register).
Request body
{ "email": "hamid@example.com", "password": "secret123" }
Response 200
{ "id": 1, "email": "hamid@example.com" }
Also sets a session_id cookie (HttpOnly, SameSite=Lax, Secure in
production).
Errors
| Status | Body | Cause |
|---|---|---|
| 400 | {"error":"invalid request body"} |
Malformed JSON |
| 401 | {"error":"invalid email or password"} |
No such email, OR wrong password (identical message for both, deliberately - see Security notes in the README) |
| 429 | (rate limit response) | Too many requests from this IP |
| 500 | {"error":"internal error"} |
Unexpected server/database failure |
curl -c cookies.txt -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"email":"hamid@example.com","password":"secret123"}'
POST /logout
Destroys the current session (deletes it from Redis, expires the cookie).
Not rate-limited beyond the global limit - deliberately excluded from the
strict /login//register limit so a legitimate user can always log out.
Response 200
{ "message": "logged out" }
curl -b cookies.txt -c cookies.txt -X POST http://localhost:8080/logout
GET /me
Requires authentication (a valid session_id cookie from a prior
login). Returns the currently logged-in user.
Response 200
{ "id": 1, "email": "hamid@example.com" }
Errors
| Status | Body | Cause |
|---|---|---|
| 401 | {"error":"unauthorized"} |
No session, expired session, or the session's user no longer exists |
curl -b cookies.txt http://localhost:8080/me
GET /auth/google/login
Redirects the browser to Google's OAuth2 consent screen. Must be opened
in an actual browser - this endpoint returns an HTTP redirect, and the
subsequent Google login page cannot be driven via curl.
Response: 307 Temporary Redirect to accounts.google.com.
open http://localhost:8080/auth/google/login
GET /auth/google/callback
Google redirects here automatically after the user approves access. Not
meant to be called directly - state and code query parameters are
supplied by Google.
On success: creates a new user (or links Google to an existing
email-matched account), starts a session exactly like /login does, and
returns:
Response 200
{ "id": 2, "email": "hamid@gmail.com" }
Errors
| Status | Body | Cause |
|---|---|---|
| 400 | {"error":"invalid oauth state"} |
Missing/mismatched CSRF state - usually means the flow wasn't started via /auth/google/login, or the session expired mid-flow |
| 400 | {"error":"missing code"} |
Google didn't include an authorization code |
| 500 | {"error":"internal error"} |
Token exchange, Google API call, or database failure |
General notes
- CORS: browser-based requests from an origin not listed in
ALLOWED_ORIGINSwill be blocked by the browser itself, before this API's own logic ever runs. Non-browser clients (curl, mobile apps, server-to-server) are unaffected by CORS entirely. - Rate limiting: exceeding a limit returns HTTP
429 Too Many Requests. The global limit (100/min/IP) applies to every route; the strict limit (5/min/IP) applies only to/registerand/login, and stacks with the global limit. - All error responses share the same shape:
{"error": "<message>"}.