Media

File Upload

Upload a file to S3-backed storage and resolve an accessible file URL.

Overview

The media module currently exposes two protected endpoints:

  • POST /api/v1/media/file
  • GET /api/v1/media/file/{path}

Both endpoints are behind the normal mobile API bearer-token middleware, so call them with:

Authorization: Bearer <access_token>

1. Upload a File

POST /api/v1/media/file

Uploads a single file to the backend S3 storage.

Request type

This endpoint uses multipart/form-data, not JSON.

Form fields

FieldRequiredNotes
fileYesOne file only, max size 20MB.
is_publicNoBoolean string. Defaults to true.

Example cURL

curl -X POST "http://localhost:5053/api/v1/media/file" \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept-Language: en" \
  -F "file=@/path/to/avatar.png" \
  -F "is_public=false"

Storage behavior

  • When is_public=true (default), the file is stored under the public/ prefix.
  • When is_public=false, the file is stored under the private/ prefix.
  • The backend generates a unique filename based on the current timestamp while preserving the original extension.

Success response

{
  "status_code": 200,
  "message": "File uploaded successfully",
  "data": {
    "url": "https://storage.example.com/private/1740823335123456000.png",
    "path": "private/1740823335123456000.png",
    "filename": "1740823335123456000.png",
    "original_name": "avatar.png",
    "size": 245120,
    "etag": "\"9b2cf535f27731c974343645a3985328\"",
    "bucket": "htp-mobile",
    "key": "private/1740823335123456000.png",
    "mime_type": "image/png",
    "uploaded_at": "2026-02-28T10:30:00Z",
    "expire_in": 1800
  }
}

Response fields

FieldMeaning
urlPublic URL or pre-signed private URL
pathStored object path
filenameGenerated filename only
original_nameFilename received from client
keyS3 object key
expire_innull for public files, 1800 seconds for private files

Common failures

  • 400: missing file form field.
  • 400: file larger than 20MB.
  • 400: invalid is_public boolean.
  • 500: S3 upload failed.

2. Get an Accessible URL for a Stored File

GET /api/v1/media/file/{path}

Resolves a stored file path into a usable URL.

Path format

Pass the storage path returned by upload, for example:

GET /api/v1/media/file/private/1740823335123456000.png

Recommended inputs:

  • public/<filename>
  • private/<filename>

Success response

{
  "status_code": 200,
  "message": "Success",
  "data": {
    "url": "https://storage.example.com/private/1740823335123456000.png?signature=...",
    "expire_in": 1800
  }
}

URL behavior

  • For public/ files, the backend returns a direct public URL and expire_in is null.
  • For private/ files, the backend returns a pre-signed URL that currently expires in 1800 seconds.
  • The handler first checks whether the object exists in S3 before generating the URL.

Integration Pattern

For most clients, the normal flow is:

  1. Upload the file with POST /api/v1/media/file.
  2. Save the returned path or key.
  3. Use GET /api/v1/media/file/{path} whenever you need a fresh URL for a private object.

If the uploaded object is public, the original upload response already contains a reusable public URL.

Copyright © 2026