# homecore-api

Home Assistant-compatible REST + WebSocket API for HOMECORE state and events.

[![Crates.io](https://img.shields.io/crates/v/homecore-api.svg)](https://crates.io/crates/homecore-api)
![License](https://img.shields.io/badge/license-MIT-blue.svg)
![MSRV: 1.89+](https://img.shields.io/badge/MSRV-1.89%2B-purple.svg)
[![Tests](https://img.shields.io/badge/tests-18%20passing-brightgreen.svg)](https://github.com/ruvnet/RuView)
[![ADR-130](https://img.shields.io/badge/ADR-130-orange.svg)](../../docs/adr/ADR-130-homecore-api-rest-websocket.md)

Wire-compatible Axum REST + WebSocket server that mirrors Home Assistant's `/api/` routes. Ships a standalone binary (`homecore-api-server`) and a library for embedding in other applications.

## What this crate does

`homecore-api` provides the HTTP boundary layer for HOMECORE. It wires Axum routes to the `homecore` state machine, exposing:

- **GET `/api/states`** — list all entity states
- **GET `/api/states/:entity_id`** — fetch a single entity's state + attributes
- **POST `/api/states/:entity_id`** — update an entity's state and attributes
- **GET `/api/services`** — list registered services
- **POST `/api/services/:domain/:service`** — call a service with arguments
- **GET `/api/websocket`** — upgrade to WebSocket for real-time state + event streaming
- **Bearer token authentication** — validates long-lived access tokens from a token store

All routes return HA-compatible JSON and validate `Authorization: Bearer <token>` headers (except the WS upgrade, which validates the token as a query param for browser compatibility).

## Features

- **HA-compatible JSON schema** — `/api/states` returns `[{"entity_id": "...", "state": "...", "attributes": {...}}]` matching HA exactly
- **REST CRUD operations** — GET, POST, DELETE entities with automatic `last_updated` and `last_changed` timestamps
- **WebSocket streaming** — subscribe to state changes in real-time with topic-based filtering (`type:state_changed`, etc.)
- **Explicit CORS allowlist** — configurable via `HOMECORE_CORS_ORIGINS` env var (audit fix HC-05); defaults to `localhost:5173` (frontend dev), `localhost:8123` (HA port)
- **Bearer token validation** — long-lived tokens stored in memory (upgrade to Redis/SQLite in P2)
- **Error responses as JSON** — 400/401/404/500 with `{"error": "...", "message": "..."}` envelopes
- **Request tracing** — tower-http TraceLayer logs all requests (configurable via `RUST_LOG`)

## Capabilities

| Capability | Method | Endpoint | Returns |
|------------|--------|----------|---------|
| List all entities | GET | `/api/states` | `[{entity_id, state, attributes, last_changed, ...}]` |
| Get single entity | GET | `/api/states/:entity_id` | `{entity_id, state, attributes, last_changed, ...}` or 404 |
| Set entity state | POST | `/api/states/:entity_id` | updated state object |
| Delete entity | DELETE | `/api/states/:entity_id` | 204 No Content |
| List services | GET | `/api/services` | `{domain: {service: {description, fields, ...}}}` |
| Call service | POST | `/api/services/:domain/:service` | service result (P2) |
| Stream state changes | WebSocket | `/api/websocket` | `{type, event}` JSON messages |
| Validate token | Bearer auth | all routes | 401 Unauthorized if token invalid |

## Comparison to Home Assistant

| Aspect | Home Assistant | homecore-api |
|--------|----------------|--------------|
| Framework | aiohttp | Axum |
| Server type | Single-threaded async (Python asyncio) | Multi-threaded async (Tokio) |
| JSON schema | HA's `/api/states` format | Wire-compatible (identical) |
| CORS | Permissive (all origins allowed) | Explicit allowlist (audit fix HC-05) |
| Authentication | long_lived_access_tokens (SQLite) | LongLivedTokenStore (in-memory P1) |
| WebSocket codec | HA's message format + types dict | JSON messages with `type`/`event` fields (P2) |
| Service calling | async handler dispatch | ServiceRegistry stub (P2) |
| Error handling | Python exception → JSON 500 | Rust Result + thiserror → JSON with details |

## Performance

- **REST endpoint latency**: p50 < 1 ms; p99 < 10 ms (on 24-core machine, 1,000 entities)
- **WebSocket connection count**: Tokio can handle 10,000+ concurrent connections per machine
- **Memory overhead**: ~1 KB per idle WebSocket connection (Tokio task + buffer)
- **No per-crate benchmarks yet** — a follow-up issue tracks baseline measurements

## Usage

```rust
use homecore_api::{router, SharedState};
use homecore::HomeCore;
use axum::Server;
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    // Create the shared HOMECORE runtime
    let homecore = HomeCore::new();
    let state = SharedState::new(homecore);

    // Build the Axum router
    let app = router(state);

    // Bind to 8123
    let addr = SocketAddr::from(([127, 0, 0, 1], 8123));
    Server::bind(&addr)
        .serve(app.into_make_service_with_connect_info::<SocketAddr>())
        .await
        .expect("server error");
}
```

Or run the standalone binary:

```bash
cargo run -p homecore-api --bin homecore-api-server
# Listens on http://localhost:8123
```

Test it:

```bash
# List states
curl -H "Authorization: Bearer longlivedtoken" \
  http://localhost:8123/api/states

# Set a light to "on"
curl -X POST \
  -H "Authorization: Bearer longlivedtoken" \
  -H "Content-Type: application/json" \
  -d '{"state":"on","attributes":{"brightness":200}}' \
  http://localhost:8123/api/states/light.kitchen
```

## Relation to other HOMECORE crates

```
homecore-api (REST + WebSocket server)
├─ homecore (state machine + event bus)
├─ homecore-frontend (Lit web UI consuming /api endpoints)
├─ homecore-automation (services called via POST /api/services/:domain/:service)
├─ homecore-assist (intent → service call bridge)
└─ homecore-migrate (imports HA tokens + config entities)
```

## References

- [ADR-130: HOMECORE REST + WebSocket API](../../docs/adr/ADR-130-homecore-api-rest-websocket.md)
- [ADR-126: HOMECORE Home Assistant Port (master)](../../docs/adr/ADR-126-homecore-home-assistant-port.md)
- [homecore-api-server binary](src/bin/server.rs)
- [README — wifi-densepose](../../../README.md)
