stemedb/.claude/guides/backend/api-endpoints.md
jordan 1ce4004807 feat: Complete Phase 2 (The Cortex) - query, lens, and API layers
This commit adds the read path (Cortex) to complement the write path (Spine):

## Crates
- stemedb-api: HTTP API with axum + utoipa OpenAPI
  - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit
  - Metered endpoints with quota enforcement
  - Ed25519 signature verification
- stemedb-lens: Truth resolution lenses
  - RecencyLens, ConsensusLens, ConfidenceLens
  - VoteAwareConsensusLens (Ballot Box pattern)
  - TrustAwareAuthorityLens (The Hive pattern)
  - SkepticLens (conflict analysis)
  - EpochAwareLens (paradigm-safe queries)
- stemedb-query: Query engine with materialized views

## Storage Extensions
- VoteStore: Vote aggregation with cached counts
- TrustRankStore: Agent reputation with decay
- AuditStore: Query audit trail
- IndexStore: SP/P/S index structures
- SupersessionStore: Epoch supersession chains

## SDKs
- sdk/go/steme: Go HTTP client with Ed25519 signing
- sdk/go/adk: ADK-Go tools for AI agents

## Documentation
- Updated CLAUDE.md, architecture.md, roadmap.md
- New ai-lookup entries for all services
- Use case docs for consumer health intelligence
- Arena roadmap for simulation advancement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:22:44 -07:00

3.9 KiB

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 for architecture overview

Quick Start

# 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):

#[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:

#[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:

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

# Build and test
cargo test -p stemedb-api

# Check docs generated correctly
cargo run -p stemedb-api &
curl localhost:3000/api-doc/openapi.json | jq .paths

Error Responses

All error responses use a consistent ApiError type that implements ToSchema:

#[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.