Files
MosisService/DEV_PORTAL_M06_API.md

12 KiB

Milestone 6: App Store Backend API

Status: Planning Goal: REST API for app submission, review, and distribution.


Overview

The backend API serves the developer portal, CLI tools, and device-side app management. It handles authentication, app lifecycle, file storage, and telemetry ingestion.


API Design Principles

  1. RESTful - Standard HTTP methods and status codes
  2. JSON - Request and response bodies in JSON
  3. Versioned - /v1/ prefix for breaking changes
  4. Consistent - Same patterns across all endpoints
  5. Documented - OpenAPI specification

Base URL

Production: https://api.mosis.dev/v1
Staging:    https://api.staging.mosis.dev/v1
Local:      http://localhost:8080/v1

Authentication

Headers

Authorization: Bearer <jwt_token>
# or
X-API-Key: mk_live_xxxxxxxx

Scopes

Scope Description
apps:read Read app metadata
apps:write Create/update apps
versions:upload Upload new versions
versions:publish Publish versions
telemetry:read Read analytics
keys:manage Manage API keys

Endpoints

Authentication

POST /v1/auth/oauth/github:
  summary: Start GitHub OAuth flow
  response: { redirect_url: string }

GET /v1/auth/oauth/github/callback:
  summary: GitHub OAuth callback
  query:
    code: string
    state: string
  response: { access_token, refresh_token, user }

POST /v1/auth/oauth/google:
  summary: Start Google OAuth flow
  response: { redirect_url: string }

GET /v1/auth/oauth/google/callback:
  summary: Google OAuth callback

POST /v1/auth/refresh:
  summary: Refresh access token
  body: { refresh_token: string }
  response: { access_token, refresh_token }

POST /v1/auth/logout:
  summary: Invalidate tokens
  auth: required

GET /v1/auth/me:
  summary: Get current user
  auth: required
  response: Developer

Apps

GET /v1/apps:
  summary: List developer's apps
  auth: required
  query:
    status: draft | published | suspended
    page: number
    limit: number
  response: { apps: App[], total: number }

POST /v1/apps:
  summary: Create new app
  auth: required
  body:
    package_id: string  # com.developer.appname
    name: string
    description?: string
    category?: string
  response: App

GET /v1/apps/:id:
  summary: Get app details
  auth: required
  response: App

PATCH /v1/apps/:id:
  summary: Update app metadata
  auth: required
  body:
    name?: string
    description?: string
    category?: string
    tags?: string[]
  response: App

DELETE /v1/apps/:id:
  summary: Delete app (if no published versions)
  auth: required
  response: { success: true }

App Versions

GET /v1/apps/:id/versions:
  summary: List app versions
  auth: required
  query:
    status: draft | review | approved | published | rejected
    page: number
    limit: number
  response: { versions: AppVersion[], total: number }

POST /v1/apps/:id/versions:
  summary: Create new version (get upload URL)
  auth: required
  body:
    version_name: string    # 1.0.0
    version_code: number    # 1
    release_notes?: string
  response:
    version: AppVersion
    upload_url: string      # Presigned S3 URL
    upload_expires: string  # ISO timestamp

PUT /v1/apps/:id/versions/:vid/upload-complete:
  summary: Mark upload as complete, trigger validation
  auth: required
  response: AppVersion

GET /v1/apps/:id/versions/:vid:
  summary: Get version details
  auth: required
  response: AppVersion

POST /v1/apps/:id/versions/:vid/submit:
  summary: Submit version for review
  auth: required
  response: AppVersion  # status: review

POST /v1/apps/:id/versions/:vid/publish:
  summary: Publish approved version
  auth: required
  response: AppVersion  # status: published

DELETE /v1/apps/:id/versions/:vid:
  summary: Delete draft version
  auth: required
  response: { success: true }

Public App Store

GET /v1/store/apps:
  summary: Browse/search published apps
  auth: none
  query:
    q: string           # Search query
    category: string
    sort: popular | recent | name
    page: number
    limit: number
  response: { apps: PublicApp[], total: number }

GET /v1/store/apps/:package_id:
  summary: Get app store listing
  auth: none
  response: PublicApp

GET /v1/store/apps/:package_id/download:
  summary: Get download URL for latest version
  auth: none (or device token)
  response:
    download_url: string
    version: string
    size: number
    signature: string

GET /v1/store/apps/:package_id/versions/:version_code/download:
  summary: Get download URL for specific version
  auth: none
  response: { download_url, version, size, signature }

API Keys

GET /v1/keys:
  summary: List API keys
  auth: required
  response: { keys: APIKey[] }

POST /v1/keys:
  summary: Create API key
  auth: required
  body:
    name: string
    permissions: string[]
    expires_at?: string
  response:
    key: APIKey
    secret: string  # Only shown once!

DELETE /v1/keys/:id:
  summary: Revoke API key
  auth: required
  response: { success: true }

Signing Keys

GET /v1/signing-keys:
  summary: List signing keys
  auth: required
  response: { keys: SigningKey[] }

POST /v1/signing-keys:
  summary: Register signing key
  auth: required
  body:
    name: string
    public_key: string  # PEM format
  response: SigningKey

DELETE /v1/signing-keys/:id:
  summary: Revoke signing key
  auth: required
  response: { success: true }

Telemetry

POST /v1/telemetry/events:
  summary: Submit telemetry events (batch)
  auth: device token or API key
  body:
    events:
      - app_id: string
        event_type: string
        event_data: object
        timestamp: string
  response: { received: number }

POST /v1/telemetry/crash:
  summary: Submit crash report
  auth: device token or API key
  body:
    app_id: string
    app_version: string
    crash_type: string
    message: string
    stack_trace: string
    context: object
    timestamp: string
  response: { id: string }

GET /v1/apps/:id/analytics:
  summary: Get app analytics
  auth: required
  query:
    start_date: string
    end_date: string
    metrics: downloads | active_users | crashes
  response:
    data:
      - date: string
        downloads: number
        active_users: number
        crashes: number

GET /v1/apps/:id/crashes:
  summary: Get crash reports
  auth: required
  query:
    version?: string
    page: number
    limit: number
  response: { crashes: CrashReport[], total: number }

Data Models

Developer

interface Developer {
  id: string;
  email: string;
  name: string;
  avatar_url?: string;
  verified: boolean;
  created_at: string;
  updated_at: string;
}

App

interface App {
  id: string;
  package_id: string;
  name: string;
  description?: string;
  category?: string;
  tags: string[];
  status: 'draft' | 'published' | 'suspended';
  icon_url?: string;
  latest_version?: AppVersion;
  created_at: string;
  updated_at: string;
}

AppVersion

interface AppVersion {
  id: string;
  app_id: string;
  version_name: string;
  version_code: number;
  package_url?: string;
  package_size?: number;
  signature?: string;
  permissions: string[];
  min_mosis_version?: string;
  release_notes?: string;
  status: 'draft' | 'uploading' | 'validating' | 'review' | 'approved' | 'published' | 'rejected';
  review_notes?: string;
  published_at?: string;
  created_at: string;
}

PublicApp

interface PublicApp {
  package_id: string;
  name: string;
  description?: string;
  category?: string;
  tags: string[];
  icon_url?: string;
  author_name: string;
  latest_version: string;
  download_count: number;
  rating?: number;
  created_at: string;
  updated_at: string;
}

APIKey

interface APIKey {
  id: string;
  name: string;
  key_prefix: string;  // "mk_live_abc..."
  permissions: string[];
  last_used_at?: string;
  expires_at?: string;
  created_at: string;
}

CrashReport

interface CrashReport {
  id: string;
  app_id: string;
  app_version: string;
  crash_type: string;
  message: string;
  stack_trace: string;
  context: object;
  occurrences: number;
  first_seen: string;
  last_seen: string;
}

Error Handling

Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid package_id format",
    "details": {
      "field": "package_id",
      "constraint": "Must match pattern: ^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+$"
    }
  }
}

Error Codes

Code HTTP Status Description
UNAUTHORIZED 401 Missing or invalid auth
FORBIDDEN 403 Insufficient permissions
NOT_FOUND 404 Resource not found
VALIDATION_ERROR 400 Invalid request body
CONFLICT 409 Resource already exists
RATE_LIMITED 429 Too many requests
INTERNAL_ERROR 500 Server error

Rate Limiting

Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200

Limits

Endpoint Category Limit Window
Auth 10 1 minute
Read 1000 1 hour
Write 100 1 hour
Upload 10 1 hour
Telemetry 10000 1 hour

Pagination

Request

GET /v1/apps?page=2&limit=20

Response

{
  "apps": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 45,
    "total_pages": 3
  }
}

Webhooks (Future)

POST /v1/webhooks:
  summary: Register webhook
  body:
    url: string
    events: string[]  # version.published, crash.new
    secret: string

Webhook Payload:
  headers:
    X-Mosis-Signature: sha256=xxx
  body:
    event: string
    data: object
    timestamp: string

OpenAPI Specification

Full OpenAPI 3.0 spec will be generated and hosted at:

  • https://api.mosis.dev/v1/openapi.json
  • https://api.mosis.dev/v1/docs (Swagger UI)

Implementation Structure

src/
├── main.go (or index.ts)
├── api/
│   ├── routes.go
│   ├── middleware/
│   │   ├── auth.go
│   │   ├── ratelimit.go
│   │   └── logging.go
│   └── handlers/
│       ├── auth.go
│       ├── apps.go
│       ├── versions.go
│       ├── store.go
│       ├── keys.go
│       └── telemetry.go
├── service/
│   ├── app_service.go
│   ├── version_service.go
│   ├── auth_service.go
│   └── storage_service.go
├── repository/
│   ├── app_repo.go
│   ├── version_repo.go
│   └── developer_repo.go
├── domain/
│   ├── app.go
│   ├── version.go
│   └── developer.go
└── pkg/
    ├── validator/
    └── crypto/

Deliverables

  • OpenAPI specification
  • Authentication middleware
  • Rate limiting middleware
  • Auth endpoints
  • Apps CRUD endpoints
  • Versions endpoints with upload flow
  • Store public endpoints
  • API keys management
  • Signing keys management
  • Telemetry ingestion
  • Error handling
  • Request validation
  • Integration tests

Open Questions

  1. GraphQL alongside REST?
  2. WebSocket for real-time review status?
  3. Batch operations for bulk updates?
  4. API versioning strategy (URL vs header)?

References