12 KiB
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
- RESTful - Standard HTTP methods and status codes
- JSON - Request and response bodies in JSON
- Versioned -
/v1/prefix for breaking changes - Consistent - Same patterns across all endpoints
- 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.jsonhttps://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
- GraphQL alongside REST?
- WebSocket for real-time review status?
- Batch operations for bulk updates?
- API versioning strategy (URL vs header)?