1
Routes Reference
Gabriel Kaszewski edited this page 2026-05-29 02:13:18 +00:00

Routes Reference

service.router() registers exactly these routes:

Method Path Description
POST /inbox Shared inbox — HTTP sig verification + dispatch, 1 MB limit
POST /users/{id}/inbox Per-user inbox — same
GET /users/{id}/outbox Cursor-based OrderedCollection
GET /users/{id}/featured Pinned posts OrderedCollection
GET /.well-known/webfinger JRD with aliases field
GET /.well-known/nodeinfo Redirect to /nodeinfo/2.0
GET /nodeinfo/2.0 NodeInfo 2.0 document

Routes NOT registered

GET /users/{id}, GET /users/{id}/followers, and GET /users/{id}/following are not registered by service.router().

These paths must serve both AP JSON (for federation) and your UI JSON (for your frontend) based on the Accept header. k-ap can't do the UI half, so your application owns the route and calls k-ap's helper methods for the AP half.


Content negotiation pattern

use axum::{extract::{Path, State}, http::HeaderMap, response::IntoResponse, Json};

async fn actor_handler(
    Path(username): Path<String>,
    headers: HeaderMap,
    State(state): State<AppState>,
) -> impl IntoResponse {
    let user = state.db.find_user_by_username(&username).await?;

    if wants_ap_json(&headers) {
        // actor_json takes a UUID as a string
        let json = state.service.actor_json(&user.id.to_string()).await?;
        return (
            [(axum::http::header::CONTENT_TYPE, "application/activity+json")],
            json,
        ).into_response();
    }

    // Return your UI JSON or HTML
    Json(MyUserResponse::from(user)).into_response()
}

fn wants_ap_json(headers: &HeaderMap) -> bool {
    headers
        .get("accept")
        .and_then(|v| v.to_str().ok())
        .map(|v| v.contains("application/activity+json") || v.contains("application/ld+json"))
        .unwrap_or(false)
}

The same pattern for followers and following. page is Option<u32> — pass None for the collection root, Some(n) for a page:

// In your followers handler:
let json = state.service.followers_collection_json(user.id, page).await?;

// In your following handler:
let json = state.service.following_collection_json(user.id, page).await?;

Both return JSON strings with the correct @context and OrderedCollection structure.