Skip to main content
POST
/
api
/
photos
Upload Photo
curl --request POST \
  --url https://api.example.com/api/photos \
  --header 'Content-Type: application/json' \
  --data '
{
  "context": "<string>",
  "routeId": "<string>",
  "routeCallId": "<string>",
  "caption": "<string>"
}
'
{
  "success": true,
  "data": {
    "id": "<string>",
    "context": "<string>",
    "routeId": {},
    "routeCallId": {},
    "userId": "<string>",
    "imageUrl": "<string>",
    "caption": {},
    "status": "<string>",
    "createdAt": "<string>"
  },
  "message": "<string>"
}

Overview

Upload photos with context-based permissions. Photos are uploaded to Supabase Storage and require authentication.

Authentication

This endpoint requires a valid Bearer token in the Authorization header.
Authorization: Bearer YOUR_ACCESS_TOKEN

Request Body

This endpoint accepts multipart/form-data.
image
file
required
Image file to uploadAllowed formats: JPEG, JPG, PNG, GIF, WebPMaximum size: 5MBValidation:
  • MIME type must match file extension
  • Filename sanitized (only letters, numbers, dots, hyphens, underscores)
  • Maximum filename length: 255 characters
context
string
required
Photo context that determines where the photo will be displayedValid values:
  • ROUTE_CALL_COVER - Cover photo for a route call (organizer only)
  • ROUTE_GALLERY - Photo for a route’s general gallery
  • ROUTE_CALL_GALLERY - Photo for a specific route call gallery (confirmed attendees only)
routeId
string
Route ID (UUID format)Required when: context is ROUTE_GALLERY
routeCallId
string
Route Call ID (UUID format)Required when: context is ROUTE_CALL_COVER or ROUTE_CALL_GALLERY
caption
string
Optional caption for the photo (maximum 500 characters)

Context-Based Permissions

Different contexts have different permission requirements:
  • ROUTE_CALL_COVER: Only the route call organizer can upload
  • ROUTE_GALLERY: Any authenticated user can upload (route must exist)
  • ROUTE_CALL_GALLERY: Only confirmed attendees of the route call can upload

Response

success
boolean
Indicates if the photo was uploaded successfully
data
object
Uploaded photo details
id
string
Photo UUID
context
string
Photo context
routeId
string | null
Associated route ID
routeCallId
string | null
Associated route call ID
userId
string
Uploader’s user ID
imageUrl
string
Full URL to the uploaded image
caption
string | null
Photo caption
status
string
Photo status (always ACTIVE on upload)
createdAt
string
ISO 8601 timestamp
message
string
Success message

Error Responses

400 Bad Request
  • No file uploaded
  • Invalid file type
  • File size exceeds 5MB
  • Missing required fields (routeId or routeCallId)
  • File extension doesn’t match MIME type
401 Unauthorized
Invalid or missing authentication token
403 Forbidden
  • Only the organizer can upload a cover photo
  • Only confirmed attendees can upload to route call gallery
404 Not Found
  • Route not found
  • Route call not found

Example Request

curl --request POST \
  --url https://api.losinmaduros.com/api/photos \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --form 'image=@/path/to/photo.jpg' \
  --form 'context=ROUTE_GALLERY' \
  --form 'routeId=987e6543-e21b-12d3-a456-426614174000' \
  --form 'caption=Beautiful route through the park!'

Example Response

{
  "success": true,
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "context": "ROUTE_GALLERY",
    "routeId": "987e6543-e21b-12d3-a456-426614174000",
    "routeCallId": null,
    "userId": "user_2abc123def456",
    "imageUrl": "https://your-project.supabase.co/storage/v1/object/public/photos/routes/image.jpg",
    "caption": "Beautiful route through the park!",
    "status": "ACTIVE",
    "createdAt": "2026-02-10T10:00:00Z",
    "updatedAt": "2026-02-10T10:00:00Z",
    "user": {
      "id": "user_2abc123def456",
      "name": "John",
      "imageUrl": "https://example.com/avatar.jpg"
    },
    "route": {
      "id": "987e6543-e21b-12d3-a456-426614174000",
      "name": "Casa de Campo",
      "slug": "casa-de-campo"
    }
  },
  "message": "Photo uploaded successfully"
}

Upload Middleware Details

The endpoint uses Multer with the following configuration:
  • Storage: Memory storage (files are buffered in memory)
  • File limit: 1 file per request
  • Size limit: 5MB (5,242,880 bytes)
  • Field name: image
Files larger than 5MB will be rejected before validation

Build docs developers (and LLMs) love