# 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 # 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 ```yaml 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 ```yaml 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 ```yaml 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 ```yaml 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 ```yaml 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 ```yaml 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 ```yaml 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 ```typescript interface Developer { id: string; email: string; name: string; avatar_url?: string; verified: boolean; created_at: string; updated_at: string; } ``` ### App ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```json { "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 ```json { "apps": [...], "pagination": { "page": 2, "limit": 20, "total": 45, "total_pages": 3 } } ``` --- ## Webhooks (Future) ```yaml 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 - [REST API Design Guidelines](https://github.com/microsoft/api-guidelines) - [OpenAPI 3.0 Specification](https://swagger.io/specification/) - [HTTP Status Codes](https://httpstatuses.com/)