api-design.md
1 # crash api design 2 3 **base url:** `https://crash.app/api/v1` 4 5 **authentication:** magic link tokens in http-only cookies or authorization header 6 7 --- 8 9 ## endpoints 10 11 ### **authentication** 12 13 **POST /auth/request-magic-link** 14 - request: `{ "email": "user@example.com" }` 15 - response: `{ "message": "magic link sent to email" }` 16 - sends email with unique token link 17 - rate limit: 3 requests per email per hour 18 19 **GET /auth/verify?token={token}** 20 - validates magic link token 21 - sets auth cookie or returns jwt 22 - redirects to pwa (or returns token for spa) 23 - response: `{ "token": "jwt...", "user": {...} }` 24 25 **POST /auth/logout** 26 - clears auth cookie 27 - response: `{ "message": "logged out" }` 28 29 **GET /auth/me** 30 - returns current user info 31 - response: `{ "user": {...} }` 32 33 --- 34 35 ### **users** 36 37 **POST /users** (signup) 38 - request: 39 ```json 40 { 41 "email": "user@example.com", 42 "name": "alex", 43 "city": "berlin", 44 "pronouns": "they/them", 45 "birth_year": 1995, 46 "bio": "touring musician, love cats", 47 "telegram_chat_id": "123456789", 48 "contact_info": "@alex_pdx on ig/tg", 49 "invited_by_id": "uuid-of-inviter" 50 } 51 ``` 52 - response: `{ "user": {...}, "message": "account created, check email for magic link" }` 53 - sends magic link email automatically 54 55 **GET /users/me** 56 - returns current user's full profile 57 - response: `{ "user": {...} }` 58 59 **PATCH /users/me** 60 - update current user's profile 61 - request: partial user object (any editable fields) 62 - response: `{ "user": {...} }` 63 64 **GET /users/search?city={city}&query={name}** 65 - search users for "invited by" dropdown 66 - filters by city, searches name/contact_info 67 - response: `{ "users": [{id, name, pronouns, birth_year, city, contact_info}] }` 68 - limit: 20 results 69 70 --- 71 72 ### **housing posts** (seeking only for mvp) 73 74 **POST /posts** 75 - create new seeking post 76 - request: 77 ```json 78 { 79 "city": "berlin", 80 "dates_start": "2025-10-20", 81 "dates_end": "2025-10-22", 82 "urgency": "emergency", 83 "notification_text": "need couch in berlin, band tour fell through 😭", 84 "description": "hi! our airbnb cancelled last minute. we're a 3-piece punk band touring europe. need floor space for 2 nights. we're quiet, respectful, can help with dishes/cooking. have gear so need some storage space too." 85 } 86 ``` 87 - response: `{ "post": {...} }` 88 - triggers notifications (see notification flows below) 89 90 **GET /posts** 91 - list active posts (for "recent requests" feed) 92 - query params: 93 - `city` (optional filter) 94 - `status` (default: active) 95 - `limit` (default: 20) 96 - `offset` (for pagination) 97 - response: `{ "posts": [...], "total": 45 }` 98 99 **GET /posts/{id}** 100 - get single post with responses 101 - response: 102 ```json 103 { 104 "post": { 105 "id": "...", 106 "user": { 107 "name": "alex", 108 "pronouns": "they/them", 109 "city": "berlin", 110 "telegram_chat_id": "123456789", 111 "contact_info": "@alex_pdx on ig", 112 "invited_by": { 113 "name": "maya", 114 "city": "berlin" 115 } 116 }, 117 "city": "berlin", 118 "dates_start": "2025-10-20", 119 "dates_end": "2025-10-22", 120 "urgency": "emergency", 121 "notification_text": "need couch in berlin, band tour fell through 😭", 122 "description": "...", 123 "status": "active", 124 "created_at": "...", 125 "responses": [ 126 { 127 "id": "...", 128 "responder": { 129 "name": "jordan", 130 "pronouns": "she/her", 131 "city": "berlin", 132 "telegram_chat_id": "987654321", 133 "contact_info": "@jordan_berlin on tg", 134 "invited_by": { 135 "name": "alex", 136 "city": "berlin" 137 } 138 }, 139 "notes": "i have a couch, cats, quiet hours after 10pm", 140 "status": "pending", 141 "created_at": "..." 142 } 143 ] 144 } 145 } 146 ``` 147 - only post owner sees full responses list 148 - responders can see their own response 149 150 **PATCH /posts/{id}** 151 - update post (owner only) 152 - can update: description, status 153 - request: `{ "status": "fulfilled" }` 154 - response: `{ "post": {...} }` 155 156 --- 157 158 ### **responses** 159 160 **POST /posts/{post_id}/responses** 161 - respond to a seeking post 162 - request: 163 ```json 164 { 165 "notes": "i have a couch available those dates. i have 2 cats, quiet hours after 10pm. lmk!" 166 } 167 ``` 168 - response: `{ "response": {...} }` 169 - sends email notification to post owner 170 171 **PATCH /responses/{id}** 172 - update response status (post owner or responder) 173 - request: `{ "status": "accepted" }` or `{ "status": "completed" }` 174 - response: `{ "response": {...} }` 175 - when status -> "completed": increments responder's housing_provided_count (tracked but not displayed in mvp) 176 177 **GET /responses/mine** 178 - get all responses current user has made 179 - response: `{ "responses": [...] }` 180 181 --- 182 183 ### **notification preferences** 184 185 **GET /preferences** 186 - get current user's notification preferences 187 - response: `{ "preferences": {...} }` 188 189 **PATCH /preferences** 190 - update notification preferences 191 - request: 192 ```json 193 { 194 "email_enabled": true, 195 "telegram_enabled": true, 196 "push_enabled": true, 197 "emergency_only": false, 198 "can_offer_housing": true 199 } 200 ``` 201 - response: `{ "preferences": {...} }` 202 - note: `can_offer_housing` is technically on users table but exposed here for ux simplicity 203 204 --- 205 206 ## notification flows 207 208 ### **when post created (POST /posts)** 209 210 **email to seeker (post creator):** 211 ``` 212 subject: ✅ your housing request is live 213 214 hey [name], 215 216 your housing request for [city] ([dates]) is now live! 217 218 we've notified [count] people in [city] who can offer housing. 219 220 view your post and responses: [link to post] 221 222 you'll get an email each time someone responds. 223 224 - crash 225 ``` 226 227 **email to all potential offerers:** 228 - recipients: all users where `can_offer_housing=true AND city=[post.city]` 229 - subject: `🏠 [notification_text]` 230 - body: 231 ``` 232 hey [name], 233 234 someone in your city needs housing: 235 236 📍 [city] 237 📅 [dates_start] - [dates_end] 238 ⚡ urgency: [urgency] 239 240 [notification_text] 241 242 --- 243 [description] 244 --- 245 246 about them: 247 [seeker name] ([pronouns]) - invited by [inviter name] 248 contact: telegram | [contact_info] 249 250 respond: [link to post] 251 252 update your notification preferences: [link] 253 254 - crash 255 ``` 256 257 ### **when response created (POST /posts/{id}/responses)** 258 259 **email to seeker (post owner):** 260 ``` 261 subject: 🎉 someone responded to your housing request 262 263 hey [name], 264 265 good news! [responder name] can help with your request in [city]. 266 267 their response: 268 "[notes]" 269 270 contact them: 271 telegram: [link to direct message responder] 272 other: [responder contact_info] 273 274 view all responses: [link to post] 275 276 - crash 277 ``` 278 279 ### **when post expires (automatic, dates_end passes)** 280 281 **email to seeker:** 282 ``` 283 subject: your housing request has expired 284 285 hey [name], 286 287 your housing request for [city] ([dates]) has expired. 288 289 if you still need housing, you can create a new request: [link] 290 291 - crash 292 ``` 293 294 --- 295 296 ## status codes 297 298 - 200: success 299 - 201: created 300 - 400: bad request (validation error) 301 - 401: unauthorized (not logged in) 302 - 403: forbidden (not allowed to access this resource) 303 - 404: not found 304 - 429: rate limited 305 - 500: server error 306 307 --- 308 309 ## error response format 310 311 ```json 312 { 313 "error": "validation_error", 314 "message": "city is required", 315 "details": { 316 "field": "city", 317 "issue": "required field missing" 318 } 319 } 320 ``` 321 322 --- 323 324 ## implementation notes 325 326 ### **authentication** 327 - use jwt with http-only cookies (secure, no localstorage xss risk) 328 - magic links expire after 15 minutes 329 - tokens should be single-use (delete after verification) 330 331 ### **authorization** 332 - middleware to check auth on protected routes 333 - users can only edit their own posts/profile 334 - post owners can see all responses, responders see only their own 335 336 ### **post expiration** 337 - check dates_end on GET requests 338 - if `dates_end < today AND status='active'`, update to 'expired' 339 - send email notification to post owner when expired 340 341 ### **notifications** 342 - use a proper email service (sendgrid, postmark, etc) 343 - all emails should have unsubscribe link 344 - respect user's notification preferences 345 - emergency posts bypass "emergency_only" filter 346 347 ### **rate limiting** 348 - magic link requests: 3 per email per hour 349 - post creation: 5 per user per day (prevent spam) 350 - response creation: 10 per user per hour 351 352 ### **validation** 353 - dates_start must be <= dates_end 354 - dates must be in the future (or today for emergency) 355 - city normalization (lowercase, trim whitespace) 356 - email validation (proper format) 357 - notification_text max 150 chars 358 - telegram_chat_id format validation (numeric string) 359 360 ### **privacy** 361 - email addresses are never exposed via api 362 - only show user fields explicitly listed in response schemas 363 - telegram contact and contact_info are shown to facilitate connection 364 365 ### **database queries to optimize** 366 - finding offerers for notifications: `SELECT * FROM users WHERE can_offer_housing=true AND city=$1` 367 - listing active posts: `SELECT * FROM housing_posts WHERE status='active' ORDER BY created_at DESC` 368 - checking for expired posts: done on read, not scheduled job (simpler for mvp) 369 370 --- 371 372 ## future considerations (post-mvp) 373 374 - offering posts (users can list standing availability) 375 - full vouching system with trust chain visualization 376 - response messages/threads (in-app chat before telegram handoff) 377 - image uploads (housing space photos) 378 - push notifications for pwa 379 - geographic radius search (not just exact city match) 380 - multi-city posts ("berlin OR hamburg") 381 - calendar integration (ics export) 382 - moderation/reporting system