stemedb/.claude/guides/backend/api-endpoints.md
jordan b3e8a9a058 feat: Multi-application expansion with chaos testing and community UI
Major additions:
- Community Next.js app (port 18187) for browsing claims with API docs
- stemedb-chaos crate: Fault injection, chaos testing, CRDT properties
- Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents
- Disputed claims handling: Manual review workflows and validation
- Aphoria security scanner: New extractors (SQL injection, command
  injection, weak crypto, TLS version), policy-based ignores, UAT reports
- Docker infrastructure: Dockerfile, docker-compose.yml for full stack
- VulnBank demo: Intentionally vulnerable multi-language test corpus

SDK & API enhancements:
- Source registry handlers for tracking data provenance
- Metrics endpoint
- Skeptic filtering improvements

Code quality:
- Split 14 large files (>500 lines) into focused modules
- All files now under 500-line limit per project guidelines

Documentation:
- Chaos testing guide, circuit breakers, observability docs
- Phase 7 UAT documentation updates
- Martin Kleppmann technical writer agent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 01:24:14 -07:00

146 lines
3.9 KiB
Markdown

# Add an API Endpoint
**When to use:** You need to add a new HTTP endpoint to the Episteme API.
## Prerequisites
- Familiarity with `axum` handler patterns
- Understanding of `utoipa` derive macros
- Read [ai-lookup/services/api.md](../../../ai-lookup/services/api.md) for architecture overview
## Quick Start
```bash
# After adding endpoint code:
cargo build -p stemedb-api
cargo test -p stemedb-api
cargo clippy -p stemedb-api -- -D warnings
```
## Step-by-Step
### 1. Define DTO Types
Create request/response types in `crates/stemedb-api/src/dto.rs`. These derive BOTH serde (for JSON) and utoipa (for OpenAPI docs):
```rust
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MyRequest {
/// Description appears in OpenAPI docs.
#[schema(example = "Tesla_Inc")]
pub subject: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MyResponse {
pub hash: String,
pub status: String,
}
```
Rules:
- DTOs live in `dto.rs`, NOT in handler files
- Implement `From<stemedb_core::types::X>` for conversions to/from internal types
- Use `#[schema(example = "...")]` for meaningful OpenAPI examples
- Never expose internal rkyv types in the public API
### 2. Write the Handler
Create handler in `crates/stemedb-api/src/handlers/{name}.rs`:
```rust
#[utoipa::path(
post,
path = "/my-endpoint",
request_body = MyRequest,
responses(
(status = 202, description = "Accepted", body = MyResponse),
(status = 400, description = "Validation failed", body = ApiError),
),
tag = "assertions"
)]
pub async fn my_handler(
State(state): State<AppState>,
Json(req): Json<MyRequest>,
) -> Result<(StatusCode, Json<MyResponse>), ApiError> {
// Convert DTO -> internal type
// Call library code (QueryEngine, Ingestor, etc.)
// Convert result -> DTO
// Return
}
```
Rules:
- Handlers are thin: convert types, delegate to library code, convert back
- No business logic in handlers
- Always return `Result<_, ApiError>` for consistent error responses
- Use `tag = "..."` to group related endpoints in docs
### 3. Register the Route
Add to the `OpenApiRouter` in `crates/stemedb-api/src/lib.rs`:
```rust
let app = OpenApiRouter::new()
.route("/my-endpoint", post(my_handler))
// ...existing routes...
```
The `OpenApiRouter` automatically collects the `#[utoipa::path]` metadata. No separate registration step.
### 4. Verify
```bash
# Build and test
cargo test -p stemedb-api
# Check docs generated correctly
cargo run -p stemedb-api &
curl localhost:18180/api-doc/openapi.json | jq .paths
```
## Error Responses
All error responses use a consistent `ApiError` type that implements `ToSchema`:
```rust
#[derive(Serialize, ToSchema)]
pub struct ApiError {
pub error: String,
pub code: String,
}
```
Map internal errors to API errors in the handler layer. Never leak internal error details.
## Checklist
- [ ] DTO types in `dto.rs` with `Serialize, Deserialize, ToSchema`
- [ ] `From<>` conversions between DTOs and internal types
- [ ] Handler annotated with `#[utoipa::path]`
- [ ] Error responses documented in `responses(...)`
- [ ] Route registered on `OpenApiRouter`
- [ ] Tests cover happy path and error cases
- [ ] `cargo clippy` passes
- [ ] Schema examples are meaningful (not "string")
## Troubleshooting
### OpenAPI spec missing my endpoint
The handler must have `#[utoipa::path]` AND be registered on `OpenApiRouter` (not a plain `axum::Router`).
### Type not showing in schemas
The DTO must derive `ToSchema`. If it references other types, those must also derive `ToSchema`.
### Swagger UI not updating
Hard refresh the browser. The spec is regenerated on each startup.
## Related
- [ai-lookup/patterns/api-documentation.md](../../../ai-lookup/patterns/api-documentation.md) - Toolchain overview
- [ai-lookup/services/api.md](../../../ai-lookup/services/api.md) - Service facts
- [Rust Guidelines](./rust-guidelines.md) - General Rust patterns