# PhotonCloud REST API Patterns

**Status:** Draft (T050.S1)
**Created:** 2025-12-12
**Author:** PeerA

## Overview

This document defines consistent REST API patterns for all PhotonCloud services.
Goal: curl/シェルスクリプト/組み込み環境で簡単に使える HTTP API.

## URL Structure

```
{scheme}://{host}:{port}/api/v1/{resource}[/{id}][/{action}]
```

### Examples
```
GET    /api/v1/kv/mykey              # Get key
PUT    /api/v1/kv/mykey              # Put key
DELETE /api/v1/kv/mykey              # Delete key
GET    /api/v1/vms                   # List VMs
POST   /api/v1/vms                   # Create VM
GET    /api/v1/vms/vm-123            # Get VM
DELETE /api/v1/vms/vm-123            # Delete VM
POST   /api/v1/vms/vm-123/start      # Start VM (action)
POST   /api/v1/vms/vm-123/stop       # Stop VM (action)
```

## HTTP Methods

| Method | Usage | Idempotent |
|--------|-------|------------|
| GET | Read resource(s) | Yes |
| POST | Create resource or execute action | No |
| PUT | Create or replace resource | Yes |
| DELETE | Delete resource | Yes |
| PATCH | Partial update (optional) | No |

## Request Format

### Content-Type
```
Content-Type: application/json
```

### Authentication
```
Authorization: Bearer <jwt-token>
```

Token obtained from IAM:
```bash
# Get token
curl -X POST http://iam:8081/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "secret"}'

# Response
{"token": "eyJ..."}

# Use token
curl http://chainfire:8082/api/v1/kv/mykey \
  -H "Authorization: Bearer eyJ..."
```

### Request Body (POST/PUT)
```json
{
  "field1": "value1",
  "field2": 123
}
```

## Response Format

### Success Response

```json
{
  "data": {
    // Resource data
  },
  "meta": {
    "request_id": "req-abc123",
    "timestamp": "2025-12-12T01:40:00Z"
  }
}
```

### List Response

```json
{
  "data": [
    { "id": "item-1", ... },
    { "id": "item-2", ... }
  ],
  "meta": {
    "total": 42,
    "limit": 20,
    "offset": 0,
    "request_id": "req-abc123"
  }
}
```

### Error Response

```json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found",
    "details": {
      "resource": "vm",
      "id": "vm-123"
    }
  },
  "meta": {
    "request_id": "req-abc123",
    "timestamp": "2025-12-12T01:40:00Z"
  }
}
```

### Error Codes

| HTTP Status | Error Code | Description |
|-------------|------------|-------------|
| 400 | BAD_REQUEST | Invalid request format |
| 401 | UNAUTHORIZED | Missing or invalid token |
| 403 | FORBIDDEN | Insufficient permissions |
| 404 | NOT_FOUND | Resource not found |
| 409 | CONFLICT | Resource already exists |
| 422 | VALIDATION_ERROR | Request validation failed |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_ERROR | Server error |
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable |

## Pagination

### Request
```
GET /api/v1/vms?limit=20&offset=40
```

### Parameters
| Param | Type | Default | Max | Description |
|-------|------|---------|-----|-------------|
| limit | int | 20 | 100 | Items per page |
| offset | int | 0 | - | Skip N items |

## Filtering

### Query Parameters
```
GET /api/v1/vms?status=running&project_id=proj-123
```

### Prefix Search (KV)
```
GET /api/v1/kv?prefix=config/
```

## Port Convention

| Service | gRPC Port | HTTP Port |
|---------|-----------|-----------|
| ChainFire | 50051 | 8081 |
| FlareDB | 50052 | 8082 |
| IAM | 50053 | 8083 |
| PlasmaVMC | 50054 | 8084 |
| k8shost | 50055 | 8085 |
| LightningSTOR | 50056 | 8086 |
| CreditService | 50057 | 8087 |
| PrismNET | 50058 | 8088 |
| NightLight | 50059 | 8089 |
| FiberLB | 50060 | 8090 |
| FlashDNS | 50061 | 8091 |

## Service-Specific Endpoints

### ChainFire (KV Store)
```
GET    /api/v1/kv/{key}                 # Get value
PUT    /api/v1/kv/{key}                 # Put value {"value": "..."}
DELETE /api/v1/kv/{key}                 # Delete key
GET    /api/v1/kv?prefix={prefix}       # Range scan
GET    /api/v1/cluster/status           # Cluster health
POST   /api/v1/cluster/members          # Add member
```

### FlareDB (Database)
```
POST   /api/v1/sql                      # Execute SQL {"query": "SELECT ..."}
GET    /api/v1/tables                   # List tables
GET    /api/v1/kv/{key}                 # KV get
PUT    /api/v1/kv/{key}                 # KV put
GET    /api/v1/scan?start={}&end={}     # Range scan
```

### IAM (Authentication)
```
POST   /api/v1/auth/token               # Get token
POST   /api/v1/auth/verify              # Verify token
GET    /api/v1/users                    # List users
POST   /api/v1/users                    # Create user
GET    /api/v1/users/{id}               # Get user
DELETE /api/v1/users/{id}               # Delete user
GET    /api/v1/projects                 # List projects
POST   /api/v1/projects                 # Create project
```

### PlasmaVMC (VM Management)
```
GET    /api/v1/vms                      # List VMs
POST   /api/v1/vms                      # Create VM
GET    /api/v1/vms/{id}                 # Get VM
DELETE /api/v1/vms/{id}                 # Delete VM
POST   /api/v1/vms/{id}/start           # Start VM
POST   /api/v1/vms/{id}/stop            # Stop VM
POST   /api/v1/vms/{id}/reboot          # Reboot VM
GET    /api/v1/vms/{id}/console         # Get console URL
```

### k8shost (Kubernetes)
```
GET    /api/v1/pods                     # List pods
POST   /api/v1/pods                     # Create pod
GET    /api/v1/pods/{name}              # Get pod
DELETE /api/v1/pods/{name}              # Delete pod
GET    /api/v1/services                 # List services
POST   /api/v1/services                 # Create service
GET    /api/v1/nodes                    # List nodes
```

### CreditService (Billing)
```
GET    /api/v1/wallets/{project_id}     # Get wallet balance
POST   /api/v1/wallets/{project_id}/reserve   # Reserve credits
POST   /api/v1/wallets/{project_id}/commit    # Commit reservation
POST   /api/v1/wallets/{project_id}/release   # Release reservation
GET    /api/v1/quotas/{project_id}      # Get quotas
PUT    /api/v1/quotas/{project_id}      # Set quotas
```

### NightLight (Metrics) - Already HTTP
```
POST   /api/v1/write                    # Push metrics (Prometheus remote write)
GET    /api/v1/query?query={promql}     # Instant query
GET    /api/v1/query_range?query={}&start={}&end={}&step={}  # Range query
GET    /api/v1/series?match[]={}&start={}&end={}  # Series metadata
GET    /api/v1/labels                   # List labels
```

### LightningSTOR (S3) - Already HTTP
```
# S3-compatible (different port/path)
PUT    /{bucket}                        # Create bucket
DELETE /{bucket}                        # Delete bucket
GET    /                                # List buckets
PUT    /{bucket}/{key}                  # Put object
GET    /{bucket}/{key}                  # Get object
DELETE /{bucket}/{key}                  # Delete object
GET    /{bucket}?list-type=2            # List objects
```

## curl Examples

### ChainFire KV
```bash
# Put
curl -X PUT http://localhost:8081/api/v1/kv/mykey \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"value": "hello world"}'

# Get
curl http://localhost:8081/api/v1/kv/mykey \
  -H "Authorization: Bearer $TOKEN"

# Delete
curl -X DELETE http://localhost:8081/api/v1/kv/mykey \
  -H "Authorization: Bearer $TOKEN"
```

### PlasmaVMC
```bash
# Create VM
curl -X POST http://localhost:8084/api/v1/vms \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "my-vm",
    "cpu_cores": 2,
    "memory_mb": 4096,
    "disk_gb": 20,
    "image": "ubuntu-22.04"
  }'

# List VMs
curl http://localhost:8084/api/v1/vms \
  -H "Authorization: Bearer $TOKEN"

# Start VM
curl -X POST http://localhost:8084/api/v1/vms/vm-123/start \
  -H "Authorization: Bearer $TOKEN"
```

### FlareDB SQL
```bash
# Execute query
curl -X POST http://localhost:8082/api/v1/sql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"query": "SELECT * FROM users WHERE id = 1"}'
```

## Implementation Notes

### Rust Framework
Use `axum` (already in most services):
```rust
use axum::{
    routing::{get, post, put, delete},
    Router, Json, extract::Path,
};

let app = Router::new()
    .route("/api/v1/kv/:key", get(get_kv).put(put_kv).delete(delete_kv))
    .route("/api/v1/cluster/status", get(cluster_status));
```

### Run Alongside gRPC
```rust
// Start both servers
tokio::select! {
    _ = grpc_server.serve(grpc_addr) => {},
    _ = axum::Server::bind(&http_addr).serve(app.into_make_service()) => {},
}
```

### Error Mapping
```rust
impl From<ServiceError> for HttpError {
    fn from(e: ServiceError) -> Self {
        match e {
            ServiceError::NotFound(_) => HttpError::not_found(e.to_string()),
            ServiceError::AlreadyExists(_) => HttpError::conflict(e.to_string()),
            ServiceError::InvalidArgument(_) => HttpError::bad_request(e.to_string()),
            _ => HttpError::internal(e.to_string()),
        }
    }
}
```

## References

- T050 Task: docs/por/T050-rest-api/task.yaml
- PROJECT.md: 統一API/仕様
- Existing HTTP: NightLight (metrics), LightningSTOR (S3)
