# Admin API

The Admin API provides endpoints for system administration, configuration management, monitoring, and file operations through the Phoenix backend.

## Base URL

```
http://localhost:4000/admin
```

## Authentication

The Admin API supports two authentication methods:

### 1. Phoenix Token Authentication

Use a Bearer token obtained from the authentication endpoint:

```bash
curl -H "Authorization: Bearer <your-phoenix-token>" \
  http://localhost:4000/admin/health
```

### 2. Demo Credentials (Development Only)

For development and testing, use the demo credentials:

- **Username:** `admin`
- **Password:** `admin123`

```bash
curl -u admin:admin123 http://localhost:4000/admin/health
```

Or with header:

```bash
curl -H "Authorization: Basic $(echo -n 'admin:admin123' | base64)" \
  http://localhost:4000/admin/health
```

## Endpoints

### Health

#### Get Health Status

Check the overall system health:

```http
GET /admin/health
```

Response (200 OK):

```json
{
  "status": "healthy",
  "version": "1.0.0",
  "checks": {
    "store": "healthy",
    "metrics": "healthy",
    "poller": "idle"
  },
  "timestamp": "2026-01-31T10:30:00Z"
}
```

| Field | Description |
|-------|-------------|
| `status` | Overall status: `healthy`, `degraded`, `unhealthy` |
| `version` | Connector version |
| `checks` | Component health checks |
| `timestamp` | Check timestamp (UTC) |

Check status values:

| Component | Possible Values |
|-----------|-----------------|
| `store` | `healthy`, `unhealthy: {error}`, `unavailable` |
| `metrics` | `healthy`, `unavailable` |
| `poller` | `polling`, `idle`, `unavailable` |

---

### Configuration

#### Get Configuration

Retrieve current configuration with sensitive values redacted:

```http
GET /admin/config
```

Response (200 OK):

```json
{
  "service": {
    "name": "sta-connector",
    "log_level": "info"
  },
  "sta": {
    "environment": "homologation",
    "base_url": "https://sta-h.bcb.gov.br/staws",
    "username": "user123",
    "password": "[REDACTED]",
    "institution_code": "12345",
    "timeout_seconds": 30,
    "max_retries": 3
  },
  "poller": {
    "enabled": true,
    "interval_seconds": 30,
    "systems": ["CCS", "SPI", "DICT"],
    "batch_size": 10
  },
  "outbound_api": {
    "enabled": true,
    "host": "0.0.0.0",
    "port": 8080,
    "auth": {
      "type": "api_key",
      "key": "[REDACTED]"
    }
  },
  "admin_api": {
    "enabled": true,
    "host": "0.0.0.0",
    "port": 4000,
    "auth": {
      "username": "admin",
      "password": "[REDACTED]"
    }
  },
  "routes": {
    "CCS": {
      "enabled": true,
      "url": "https://staapi-planner.monetarie.com.br/api/v1/ccs",
      "method": "POST",
      "timeout_seconds": 30,
      "retry_attempts": 3
    }
  },
  "state_store": {
    "type": "sqlite",
    "sqlite_path": "./data/state.db"
  }
}
```

#### Update Configuration

Update configuration values at runtime (hot-reload):

```http
PATCH /admin/config
Content-Type: application/json

{
  "poller": {
    "interval_seconds": 60,
    "batch_size": 20
  },
  "routes": {
    "DICT": {
      "enabled": false
    }
  },
  "service": {
    "log_level": "debug"
  }
}
```

Response (200 OK):

```json
{
  "service": {
    "name": "sta-connector",
    "log_level": "debug"
  },
  "poller": {
    "enabled": true,
    "interval_seconds": 60,
    "batch_size": 20
  }
  // ... full updated config
}
```

Error Response (400 Bad Request):

```json
{
  "error": "poller interval_seconds must be positive",
  "code": "VALIDATION_ERROR",
  "timestamp": "2026-01-31T10:30:00Z"
}
```

Updatable fields:

| Field | Description |
|-------|-------------|
| `service.log_level` | Log verbosity |
| `poller.enabled` | Enable/disable poller |
| `poller.interval_seconds` | Polling interval |
| `poller.batch_size` | Files per poll |
| `routes.{system}.enabled` | Enable/disable route |
| `routes.{system}.url` | Route destination URL |
| `routes.{system}.timeout_seconds` | Request timeout |
| `routes.{system}.retry_attempts` | Retry count |

---

### Metrics

#### Get Metrics

Retrieve operational metrics:

```http
GET /admin/metrics
```

Response (200 OK):

```json
{
  "timestamp": "2026-01-31T10:30:00Z",
  "uptime_seconds": 86400,
  "inbound": {
    "files_discovered": 10234,
    "files_processed": 10200,
    "files_failed": 12,
    "files_pending": 22
  },
  "outbound": {
    "files_submitted": 5000,
    "files_uploaded": 4995,
    "files_failed": 3,
    "files_pending": 2
  },
  "poller": {
    "enabled": true,
    "last_poll": "2026-01-31T10:29:00Z",
    "poll_count": 500,
    "avg_poll_duration_ms": 1250
  },
  "sta_client": {
    "requests_total": 50000,
    "requests_failed": 45,
    "avg_latency_ms": 450,
    "rate_limit_remaining": 85
  },
  "system": {
    "memory_mb": 256,
    "goroutines": 50,
    "db_connections": 10
  }
}
```

---

### Files

#### List Files

List processed files with optional filtering:

```http
GET /admin/files?status=failed&system=SPI&limit=50&offset=0
```

Query Parameters:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `status` | string | - | Filter by status |
| `system` | string | - | Filter by BCB system |
| `limit` | integer | 100 | Max results (1-1000) |
| `offset` | integer | 0 | Pagination offset |
| `start_date` | string | - | Start date (ISO 8601 or YYYY-MM-DD) |
| `end_date` | string | - | End date (ISO 8601 or YYYY-MM-DD) |

Response (200 OK):

```json
{
  "files": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "protocol": "2026013100001234",
      "file_name": "SPI_20260131_001.xml",
      "file_type": "PACS.008",
      "file_size": 12345,
      "source_system": "SPI",
      "status": "failed",
      "last_error": "connection timeout",
      "retry_count": 3,
      "created_at": "2026-01-31T10:30:00Z",
      "updated_at": "2026-01-31T10:35:00Z"
    }
  ],
  "total": 45,
  "limit": 50,
  "offset": 0,
  "has_more": false
}
```

Status values:

| Status | Description |
|--------|-------------|
| `pending` | Queued for processing |
| `uploading` | Upload in progress |
| `uploaded` | Sent to BCB |
| `processing` | BCB is processing |
| `completed` | Successfully processed |
| `failed` | Processing failed |
| `retrying` | Scheduled for retry |

#### Retry File

Retry a failed file:

```http
POST /admin/files/:id/retry
```

Response (202 Accepted):

```json
{
  "message": "file queued for retry",
  "file_id": "550e8400-e29b-41d4-a716-446655440000",
  "queued": true,
  "retry_count": 4,
  "timestamp": "2026-01-31T10:30:00Z"
}
```

Error Response (400 Bad Request):

```json
{
  "error": "only failed files can be retried, current status: completed",
  "code": "INVALID_STATUS",
  "timestamp": "2026-01-31T10:30:00Z"
}
```

Error Response (404 Not Found):

```json
{
  "error": "file not found: 550e8400-...",
  "code": "FILE_NOT_FOUND",
  "timestamp": "2026-01-31T10:30:00Z"
}
```

---

### Poller Control

#### Trigger Poll

Manually trigger a poll cycle:

```http
POST /admin/poller/trigger
```

Response (202 Accepted):

```json
{
  "message": "poll cycle triggered successfully",
  "triggered": true,
  "timestamp": "2026-01-31T10:30:00Z"
}
```

Response (409 Conflict) - Poll already in progress:

```json
{
  "message": "poll cycle already in progress",
  "triggered": false,
  "timestamp": "2026-01-31T10:30:00Z"
}
```

---

## Phoenix Channel Events

The Admin API supports real-time notifications via Phoenix Channels for admin events.

### Connecting to the Admin Channel

```javascript
import { Socket } from "phoenix";

const socket = new Socket("ws://localhost:4000/socket", {
  params: { token: "<your-phoenix-token>" }
});

socket.connect();

const channel = socket.channel("admin:notifications", {});

channel.join()
  .receive("ok", resp => console.log("Joined admin channel", resp))
  .receive("error", resp => console.log("Unable to join", resp));
```

### Available Events

#### `health_changed`

Broadcast when system health status changes:

```javascript
channel.on("health_changed", payload => {
  console.log("Health changed:", payload);
  // { status: "degraded", checks: {...}, timestamp: "..." }
});
```

#### `file_status_changed`

Broadcast when a file's processing status changes:

```javascript
channel.on("file_status_changed", payload => {
  console.log("File status changed:", payload);
  // { file_id: "...", old_status: "pending", new_status: "completed", timestamp: "..." }
});
```

#### `file_failed`

Broadcast when a file processing fails:

```javascript
channel.on("file_failed", payload => {
  console.log("File failed:", payload);
  // { file_id: "...", file_name: "...", error: "...", retry_count: 3, timestamp: "..." }
});
```

#### `config_updated`

Broadcast when configuration is updated:

```javascript
channel.on("config_updated", payload => {
  console.log("Config updated:", payload);
  // { updated_fields: ["poller.interval_seconds"], timestamp: "..." }
});
```

#### `metrics_update`

Periodic metrics broadcast (every 30 seconds):

```javascript
channel.on("metrics_update", payload => {
  console.log("Metrics:", payload);
  // { inbound: {...}, outbound: {...}, timestamp: "..." }
});
```

#### `poller_status`

Broadcast when poller status changes:

```javascript
channel.on("poller_status", payload => {
  console.log("Poller status:", payload);
  // { status: "polling", files_found: 5, timestamp: "..." }
});
```

---

## Error Responses

All error responses follow this format:

```json
{
  "error": "description of the error",
  "code": "ERROR_CODE",
  "timestamp": "2026-01-31T10:30:00Z"
}
```

### HTTP Status Codes

| Code | Description |
|------|-------------|
| 200 | Success |
| 202 | Accepted (async operation) |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 409 | Conflict |
| 500 | Internal Server Error |
| 503 | Service Unavailable |

### Error Codes

| Code | Description |
|------|-------------|
| `VALIDATION_ERROR` | Request validation failed |
| `INVALID_JSON` | Malformed JSON body |
| `INVALID_REQUEST` | Invalid request format |
| `INVALID_STATUS` | Invalid file status for operation |
| `FILE_NOT_FOUND` | File not found |
| `MISSING_FILE_ID` | File ID not provided |
| `CONFIG_UPDATE_UNAVAILABLE` | Config updates not supported |
| `METRICS_UNAVAILABLE` | Metrics collector not available |
| `POLLER_UNAVAILABLE` | Poller not available |
| `STORE_UNAVAILABLE` | State store not available |
| `UPDATE_FAILED` | Failed to apply configuration |
| `TRIGGER_FAILED` | Failed to trigger poll |
| `GET_FILE_FAILED` | Failed to retrieve file |
| `UPDATE_STATUS_FAILED` | Failed to update file status |
| `RETRY_QUEUE_FAILED` | Failed to add to retry queue |
| `LIST_FAILED` | Failed to list files |

---

## Code Examples

### cURL

```bash
# Health check
curl -u admin:admin123 http://localhost:4000/admin/health

# Health check with Phoenix token
curl -H "Authorization: Bearer <your-phoenix-token>" \
  http://localhost:4000/admin/health

# Get configuration
curl -u admin:admin123 http://localhost:4000/admin/config

# Update configuration
curl -u admin:admin123 -X PATCH http://localhost:4000/admin/config \
  -H "Content-Type: application/json" \
  -d '{"poller": {"interval_seconds": 60}}'

# Get metrics
curl -u admin:admin123 http://localhost:4000/admin/metrics

# List failed files
curl -u admin:admin123 "http://localhost:4000/admin/files?status=failed&limit=10"

# Retry a file
curl -u admin:admin123 -X POST http://localhost:4000/admin/files/550e8400-.../retry

# Trigger poll
curl -u admin:admin123 -X POST http://localhost:4000/admin/poller/trigger
```

### Python

```python
import requests
from requests.auth import HTTPBasicAuth

auth = HTTPBasicAuth('admin', 'admin123')
base_url = 'http://localhost:4000/admin'

# Health check
health = requests.get(f'{base_url}/health', auth=auth).json()
print(f"Status: {health['status']}")

# Get metrics
metrics = requests.get(f'{base_url}/metrics', auth=auth).json()
print(f"Files processed: {metrics['inbound']['files_processed']}")

# Update configuration
update = {'poller': {'interval_seconds': 60}}
response = requests.patch(
    f'{base_url}/config',
    auth=auth,
    json=update
)
print(f"Updated: {response.status_code == 200}")

# List failed files
files = requests.get(
    f'{base_url}/files',
    auth=auth,
    params={'status': 'failed', 'limit': 10}
).json()

for file in files['files']:
    print(f"Failed: {file['id']} - {file['last_error']}")

    # Retry the file
    retry = requests.post(f'{base_url}/files/{file["id"]}/retry', auth=auth)
    print(f"Retry queued: {retry.json()['queued']}")
```

### Node.js

```javascript
const axios = require('axios');

const client = axios.create({
  baseURL: 'http://localhost:4000/admin',
  auth: {
    username: 'admin',
    password: 'admin123'
  }
});

async function monitorConnector() {
  // Health check
  const health = await client.get('/health');
  console.log('Status:', health.data.status);

  // Get metrics
  const metrics = await client.get('/metrics');
  console.log('Files processed:', metrics.data.inbound.files_processed);

  // List and retry failed files
  const files = await client.get('/files', {
    params: { status: 'failed', limit: 10 }
  });

  for (const file of files.data.files) {
    console.log(`Retrying: ${file.id}`);
    await client.post(`/files/${file.id}/retry`);
  }
}

monitorConnector().catch(console.error);
```

### Phoenix Channel (JavaScript)

```javascript
import { Socket } from "phoenix";

// Connect with Phoenix token
const socket = new Socket("ws://localhost:4000/socket", {
  params: { token: "<your-phoenix-token>" }
});

socket.connect();

// Join admin notifications channel
const channel = socket.channel("admin:notifications", {});

channel.join()
  .receive("ok", resp => console.log("Joined successfully", resp))
  .receive("error", resp => console.error("Failed to join", resp));

// Listen for real-time events
channel.on("health_changed", payload => {
  console.log("Health status changed:", payload.status);
});

channel.on("file_failed", payload => {
  console.error(`File ${payload.file_name} failed: ${payload.error}`);
});

channel.on("metrics_update", payload => {
  console.log("Current metrics:", payload);
});

channel.on("config_updated", payload => {
  console.log("Configuration updated:", payload.updated_fields);
});
```

---

## Next Steps

- [Outbound API](/api/outbound) - File upload endpoints
- [Configuration Guide](/guide/configuration) - Configuration options
- [Troubleshooting](/troubleshooting) - Common issues
