Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

Media API Documentation

The Media API handles image uploads and storage.

Quick Start

Examples: API examples index includes uploads, jan_* IDs, and OCR/preview flows.

URLs

What You Can Do

  • Upload images - From URLs or base64 data. See Upload Method Guide to choose the best approach.
  • Get jan_* IDs - Unique identifiers for each image. See Jan ID System Guide to understand how they work.
  • Generate download links - Temporary URLs that expire after 7 days. See Presigned URL Workflow.
  • Prevent duplicates - Same image uploaded twice gets same ID
  • Store in S3 - Images saved to cloud storage

Service Ports & Configuration

Component Port Key Environment Variables
HTTP Server 8285 MEDIA_API_PORT
Database (PostgreSQL) 5432 DB_POSTGRESQL_WRITE_DSN, DB_POSTGRESQL_READ1_DSN (optional replica)
Object Storage (S3-compatible) 443 MEDIA_STORAGE_BACKEND (s3 or local), MEDIA_S3_ENDPOINT, MEDIA_S3_BUCKET, MEDIA_S3_ACCESS_KEY_ID, MEDIA_S3_SECRET_ACCESS_KEY

Required Environment Variables

# Core service + database
MEDIA_API_PORT=8285
DB_POSTGRESQL_WRITE_DSN=postgres://media:password@api-db:5432/media_api?sslmode=disable
# Optional read replica
DB_POSTGRESQL_READ1_DSN=postgres://media_ro:password@api-db-ro:5432/media_api?sslmode=disable

# Auth (enable when fronted by Kong)
AUTH_ENABLED=true
AUTH_ISSUER=http://localhost:8085/realms/jan
ACCOUNT=account
AUTH_JWKS_URL=http://keycloak:8085/realms/jan/protocol/openid-connect/certs

# Storage backend selection
MEDIA_STORAGE_BACKEND=s3    # or "local"

# S3 configuration (required when MEDIA_STORAGE_BACKEND=s3)
MEDIA_S3_BUCKET=platform-dev
MEDIA_S3_REGION=us-west-2
MEDIA_S3_ENDPOINT=https://s3.menlo.ai
MEDIA_S3_ACCESS_KEY_ID=XXXXX
MEDIA_S3_SECRET_ACCESS_KEY=YYYYY
MEDIA_S3_USE_PATH_STYLE=true

Optional Configuration

# Public endpoint for download links (falls back to MEDIA_S3_ENDPOINT when empty)
MEDIA_S3_PUBLIC_ENDPOINT=https://cdn.example.com
# Presigned URL lifetime
MEDIA_S3_PRESIGN_TTL=168h
# Upload limits + retention
MEDIA_MAX_BYTES=20971520      # 20 MB
MEDIA_RETENTION_DAYS=30
MEDIA_REMOTE_FETCH_TIMEOUT=15s
# Download behavior
MEDIA_PROXY_DOWNLOAD=true     # stream bytes through the API instead of redirecting

# Local filesystem backend overrides (when MEDIA_STORAGE_BACKEND=local)
MEDIA_LOCAL_STORAGE_PATH=./media-data
MEDIA_LOCAL_STORAGE_BASE_URL=http://localhost:8285/v1/files

Authentication

All endpoints require authentication through the Kong gateway.

For complete authentication documentation, see Authentication Guide

Quick example:

# Get guest token
TOKEN=$(curl -s -X POST http://localhost:8000/llm/auth/guest-login | jq -r '.access_token')

# Use in requests
curl -H "Authorization: Bearer $TOKEN" \
 http://localhost:8000/media/v1/media

Key points:

  • Use Kong gateway (port 8000) for all client requests: http://localhost:8000/media/...
  • Both Bearer tokens and API keys (X-API-Key) work through Kong
  • Direct service access (port 8285) requires valid JWT token

Direct calls to port 8285 still honor JWT validation when AUTH_ENABLED=true on the service. Use the gateway whenever possible so rate-limiting/cors policies apply consistently.

Main Endpoints

Upload Media

POST /v1/media

Upload media from a remote URL or base64 data. Examples below go through Kong (recommended); replace the host with http://localhost:8285 if you need to hit the service directly.

# Upload from remote URL
curl -X POST http://localhost:8000/media/v1/media \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/json" \
 -d '{
 "source": {
 "type": "remote_url",
 "url": "https://example.com/image.jpg"
 },
 "user_id": "user123"
 }'

# Upload from data URL (base64 image)
curl -X POST http://localhost:8000/media/v1/media \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/json" \
 -d '{
 "source": {
 "type": "data_url",
 "data_url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
 },
 "user_id": "user123"
 }'

Response:

{
  "id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
  "mime": "image/jpeg",
  "bytes": 45678,
  "deduped": false,
  "presigned_url": "https://s3.menlo.ai/platform-dev/images/jan_...?X-Amz-Signature=..."
}

Prepare Upload (Presigned URL)

POST /v1/media/prepare-upload

Get a presigned URL for client-side S3 upload.

curl -X POST http://localhost:8000/media/v1/media/prepare-upload \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/json" \
 -d '{
 "content_type": "image/jpeg",
 "user_id": "user123"
 }'

Direct Upload (Local Storage Only)

If MEDIA_STORAGE_BACKEND=local, presigned uploads are disabled. Use the multipart endpoint instead:

curl -X POST http://localhost:8000/media/v1/media/upload \
 -H "Authorization: Bearer <token>" \
 -F "file=@/path/to/image.png" \
 -F "user_id=user123"

The service converts the upload to a data URL and stores it on disk (MEDIA_LOCAL_STORAGE_PATH).

Response:

{
  "jan_id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
  "presigned_url": "https://s3.menlo.ai/platform-dev/images/jan_...?X-Amz-Signature=...",
  "presigned_post": {
    "url": "https://s3.menlo.ai",
    "fields": {
      "key": "images/jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
      "policy": "...",
      "x-amz-signature": "...",
      "x-amz-date": "..."
    }
  }
}

Resolve Media IDs

POST /v1/media/resolve

Resolve jan_* IDs to presigned URLs.

curl -X POST http://localhost:8000/media/v1/media/resolve \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/json" \
 -d '{
 "ids": [
 "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
 "jan_01hqr8v9k2x3f4g5h6j7k8m9n1"
 ]
 }'

Response:

{
  "media": [
    {
      "id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
      "presigned_url": "https://s3.menlo.ai/platform-dev/images/jan_...?X-Amz-Signature=...",
      "expires_at": "2025-11-10T10:35:00Z"
    }
  ]
}

Get Media

GET /v1/media/{id}

Retrieve media metadata and presigned URL.

curl -H "Authorization: Bearer <token>" \
 http://localhost:8000/media/v1/media/jan_01hqr8v9k2x3f4g5h6j7k8m9n0

Response:

{
  "id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
  "mime": "image/jpeg",
  "bytes": 45678,
  "created_at": "2025-11-10T10:30:00Z",
  "presigned_url": "https://s3.menlo.ai/...",
  "expires_at": "2025-11-10T10:35:00Z"
}

Get Presigned URL

GET /v1/media/{id}/presign

Get a temporary signed URL for downloading media by jan_id. This is the dedicated endpoint for obtaining presigned URLs without additional metadata.

curl -H "Authorization: Bearer <token>" \
 http://localhost:8000/media/v1/media/jan_01hqr8v9k2x3f4g5h6j7k8m9n0/presign

Response:

{
  "id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
  "url": "https://s3.menlo.ai/platform-dev/images/jan_...?X-Amz-Signature=...",
  "expires_in": 300
}

Use Cases:

  • Get download URL after client-side upload via prepare-upload
  • Refresh expired presigned URLs
  • Obtain direct S3 access for large file downloads
  • Integration with external services requiring temporary URLs

Health Check

GET /healthz

# Via gateway
curl http://localhost:8000/media/healthz

# Direct service port
curl http://localhost:8285/healthz

Jan ID System

Format: jan_ prefix + 26-character base32 identifier

Characteristics

  • Globally Unique: No collision across instances
  • Sortable: Sequential generation ensures chronological ordering
  • Opaque: No encoded information (privacy-preserving)
  • Example: jan_01hqr8v9k2x3f4g5h6j7k8m9n0

Usage in Other Services

Reference jan_* IDs in LLM API for media:

curl -X POST http://localhost:8000/v1/chat/completions \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/json" \
 -d '{
 "model": "jan-v2-30b",
 "messages": [{
 "role": "user",
 "content": [
 {"type": "text", "text": "What is this?"},
 {
 "type": "image_url",
 "image_url": {"url": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0"}
 }
 ]
 }]
 }'

Deduplication

Media is deduplicated by content hash (SHA-256):

  • First Upload: Stored in S3, new jan_* ID created
  • Duplicate Upload: Returns existing jan_* ID, skips S3 storage
  • Response: "deduped": true indicates existing media
{
  "id": "jan_01hqr8v9k2x3f4g5h6j7k8m9n0",
  "deduped": true
}

Presigned URL Management

TTL Configuration

Default: 7 days (604800 seconds)

MEDIA_S3_PRESIGN_TTL=168h # 7 days
MEDIA_S3_PRESIGN_TTL=30m # 30 minutes
MEDIA_S3_PRESIGN_TTL=1h # 1 hour

Expiration

  • URLs are valid for specified TTL
  • Each request to resolve/get generates new presigned URL
  • Expired URLs are no longer valid

Storage Flow

1. Remote URL Upload

Client -> Media API (remote_url)
 v
Media API -> Remote Server (fetch)
 v
Media API -> S3 (upload)
 v
Media API <- S3 (confirmed)
 v
Client <- Media API (jan_id + presigned_url)

2. Client-Side Direct Upload

Client -> Media API (prepare-upload request)
 v
Media API -> Client (presigned_url + jan_id)
 v
Client -> S3 (direct upload using presigned_url)
 v
Client <- S3 (upload confirmed)
 v
Client -> Media API GET /v1/media/{jan_id}/presign
 v
Client <- Media API (download presigned_url)

Error Handling

Status Error Cause
400 Invalid request Malformed parameters
401 Unauthorized Missing/invalid bearer token
404 Not found Media ID doesn't exist
413 Payload too large Exceeds max file size
500 S3 error Storage operation failed

Example error:

{
  "error": {
    "message": "File size exceeds maximum allowed",
    "type": "size_error",
    "code": "max_size_exceeded"
  }
}

Related Services

  • LLM API (Port 8080) - Media resolution
  • Response API (Port 8082) - Tool outputs
  • Kong Gateway (Port 8000) - API routing
  • PostgreSQL - Metadata storage
  • Menlo S3 - Media storage

See Also