updated_mongodb_p / docs /confluence /09-api-reference.md
ape-dev
Remove Signals tab + signal correlation matrix (UI + code)
a622bb0

09 - API Reference

Base URL in local dev: http://127.0.0.1:7860. The Vite dev server at http://127.0.0.1:5173 proxies the same paths.


Authentication

Public app endpoints:

GET  /health
POST /turn
POST /turn/stream
POST /feedback
GET  /sessions/*
GET  /users/*
DELETE /sessions/*

Protected operational endpoints:

/config*
/admin/*
/analytics/*

Protected endpoints require one of:

X-APE-Admin-Token: <APE_ADMIN_TOKEN>
Authorization: Bearer <APE_ADMIN_TOKEN>

If APE_ADMIN_TOKEN is not configured on the server, protected endpoints return 503. If the header is missing or wrong, they return 401.


Core Flow

GET /health

{ "status": "ok" }

POST /turn

Generate one assistant response using the non-streaming path.

Request:

{
  "user_id": "alex_retiree",
  "session_id": "optional-session-id",
  "query": "Compare Roth IRA vs Traditional IRA",
  "generate_response": true
}

Response:

{
  "response_id": "resp_...",
  "session_id": "sess_...",
  "assistant_message_id": "msg_...",
  "classification": {
    "intent": "Comparison",
    "topic": "roth_vs_traditional_ira",
    "signal": "no_signal"
  },
  "selection": {
    "selected_strategy": "comparison_table",
    "strategies_available": [
      "standard_llm",
      "comparison_table",
      "pros_cons_table",
      "bullet_contrast"
    ],
    "ucb_at_selection": 1.522,
    "policy_version": "v1"
  },
  "answer": "| Feature | Roth IRA | Traditional IRA | ...",
  "rendered_format": "comparison_table",
  "timings_ms": {
    "total": 2100.4
  }
}

POST /turn/stream

Same request body as /turn, but returns Server-Sent Events with Content-Type: text/event-stream.

Event payloads are JSON objects sent as SSE data: lines.

metadata event:

{
  "event": "metadata",
  "response_id": "resp_...",
  "session_id": "sess_...",
  "intent": "Comparison",
  "topic": "roth_vs_traditional_ira",
  "selected_strategy": "comparison_table",
  "candidate_strategies": ["standard_llm", "comparison_table"],
  "select_timings_ms": {}
}

delta event:

{ "event": "delta", "text": "partial raw model text" }

done event:

{
  "event": "done",
  "response_id": "resp_...",
  "session_id": "sess_...",
  "assistant_message_id": "msg_...",
  "answer": "final parsed answer",
  "rendered_format": "comparison_table",
  "format_compliance": 1,
  "selection": {},
  "classification": {},
  "timings_ms": {}
}

error event:

{ "event": "error", "message": "..." }

Streaming and non-streaming paths both write ape_messages, ape_turn_record, and pending format compliance signals.

POST /feedback

Append a UI signal to one response. The response may finalize immediately or queue the signal for later finalization.

Request:

{
  "response_id": "resp_...",
  "user_id": "alex_retiree",
  "signal": "copy_save"
}

Possible response when queued:

{
  "status": "queued",
  "response_id": "resp_..."
}

Possible response when applied:

{
  "status": "applied",
  "response_id": "resp_...",
  "signal": "pattern_engaged_positive",
  "reward_category": "weak_positive",
  "normalized_reward": 0.5,
  "strategy_row_after": {
    "strategy": "comparison_table",
    "count": 4,
    "avg_reward": 0.625,
    "cached_ucb": 1.457
  }
}

Possible response with no bandit update:

{
  "status": "applied_no_bandit_update",
  "response_id": "resp_...",
  "signal": "thumbs_up",
  "reward_category": null,
  "normalized_reward": null
}

Possible rejected response:

{
  "status": "rejected",
  "response_id": "resp_...",
  "reason": "already_finalized",
  "current_status": "APPLIED"
}

Feedback statuses:

Status Meaning
queued Signal buffered; turn remains PENDING
applied Turn finalized and bandit updated
applied_no_bandit_update Turn finalized but no format-relevant reward existed
rejected Missing response, wrong user, already finalized, or race
skipped Unknown signal

Sessions and History

GET /sessions/{session_id}/messages?user_id=X&limit=200

Returns raw chat messages for one session, scoped to the requesting user. user_id is required.

GET /sessions/{session_id}/turns?user_id=X&limit=100

Returns ape_turn_record rows for one session, scoped to the requesting user. user_id is required.

GET /users/{user_id}/sessions?limit=20

Returns recent session summaries for a user.

GET /users/{user_id}/latest-session

Returns:

{ "session_id": "sess_..." }

or:

{ "session_id": null }

GET /users/{user_id}/responses?limit=50

Returns recent turn records for one user.

DELETE /sessions/{session_id}?user_id=X

Deletes one user's messages and response records for that session. Bandit state is preserved.


Config Reads

All config endpoints require admin token.

Endpoint Purpose
GET /config/intents List intents
GET /config/strategies List strategies
GET /config/policies List policies
GET /config/signal-rules List signal routing rules
GET /config/reward-scale List reward categories
GET /config/instructions?strategy_id=X&status=Y List instruction versions
GET /config/offers?status=Y List outreach policies

Config Writes

POST /config/intents

{
  "intent_id": "Comparison",
  "description": "User wants two or more options compared side by side.",
  "changed_by": "admin_user"
}

POST /config/strategies

{
  "strategy_id": "comparison_table",
  "format_type": "comparison_table",
  "changed_by": "admin_user"
}

POST /config/signal-rules

{
  "signal_name": "format_change_request",
  "format_relevant": true,
  "content_relevant": false,
  "format_category": "strong_negative",
  "content_category": null,
  "source": "llm",
  "feature_id": 1,
  "expected_frequency": "rare",
  "evidence_quality": "high",
  "consumers": ["bandit", "instruction_quality"],
  "changed_by": "admin_user"
}

POST /config/reward-scale

{
  "category": "strong_positive",
  "normalized_reward": 1.0,
  "changed_by": "admin_user"
}

raw_reward is not part of the current request model.

POST /config/policies

{
  "domain": "finance",
  "intent": "Comparison",
  "topic": "_default",
  "strategy_id": "comparison_table",
  "policy_version": "v1",
  "exploration_constant": 1.0,
  "changed_by": "admin_user"
}

POST /config/instructions

{
  "strategy_id": "comparison_table",
  "version": "v2",
  "instruction_text": "Format as a markdown table comparing the options.",
  "instruction_uri": null,
  "activate": true,
  "changed_by": "admin_user"
}

POST /config/instructions/activate?strategy_id=X&version=Y&changed_by=admin_user

Activates an existing instruction version.

POST /config/status

{
  "entity_type": "strategy",
  "entity_id": "comparison_table",
  "version": "v1",
  "status": "INACTIVE",
  "changed_by": "admin_user"
}

version is required for instruction rows and optional for most other entity types.

POST /config/offers

{
  "topic": "retirement_accounts",
  "offer_type": "retirement_planning_consultation",
  "description": "Schedule a 30-minute planning call",
  "min_interest_score": 0.8,
  "weight_frequency": 0.4,
  "weight_recency": 0.25,
  "weight_engagement": 0.25,
  "weight_followup": 0.1,
  "changed_by": "admin_user"
}

Config Deletes

Endpoint Purpose
DELETE /config/intents/{intent_id} Delete an intent
DELETE /config/strategies/{strategy_id} Delete a strategy and its instructions
DELETE /config/signal-rules/{signal_name} Delete a signal rule
DELETE /config/reward-scale/{category} Delete a reward value
DELETE /config/policies?intent=Y&topic=Z&strategy_id=S Delete a policy
DELETE /config/instructions/{strategy_id}/{version} Delete an instruction version
DELETE /config/offers/{topic} Delete an outreach policy

All deletes are audited.


Admin and Ops

All admin endpoints require admin token.

Endpoint Purpose
DELETE /admin/clear-user/{user_id} Remove one user's runtime data
DELETE /admin/clear-all Clear runtime collections while preserving config/audit
POST /admin/seed Seed default config
GET /admin/db-snapshot?user_id=X&limit=N Diagnostic snapshot
GET /admin/bandit-state?user_id=X&only_pulled=true Inspect bandit rows
DELETE /admin/bandit-state/cell?user_id=X&domain=D&intent=I&topic=T&strategy=S Delete one bandit cell or one arm when strategy is supplied
GET /admin/audit?date=YYYY-MM-DD&limit=N Audit log

There is no /admin/rebuild-bandit endpoint. UCB cache refresh happens during feedback finalization, and bulk repair should be done with a script if needed.


Analytics

All analytics endpoints require admin token.

Endpoint Purpose
POST /analytics/recompute?days=N Rebuild derived collections from ape_turn_record
GET /analytics/user-interests?user_id=X&limit=K&refresh=bool User topic interest
GET /analytics/topic-users?topic=X&limit=K&min_score=Y Users interested in topic
GET /analytics/trends?days=N&limit=K&refresh=bool Trending topics
GET /analytics/topic-timeseries?topic=X&days=N Daily trend for one topic
GET /analytics/platform-timeseries?days=N Daily platform activity
GET /analytics/topics-timeseries?days=N&top_n=K Daily series for top topics
GET /analytics/user-timeseries?user_id=X&days=N Daily user activity
GET /analytics/offers/{user_id}?domain=Y Outreach recommendations
GET /analytics/customer-health?days=N&cohort_weeks=K Retention/satisfaction health
GET /analytics/rag-quality?days=N&min_turns=K&sample_limit=S Content-quality indicators
GET /analytics/instruction-quality?days=N&min_turns=K&sample_limit=S Format/instruction quality
GET /analytics/strategy-performance?user_id=X&min_pulls=N Strategy performance tiers
GET /analytics/platform-overview?days=N&top_n=K Dashboard overview
GET /analytics/active-users?days=N&min_interest=X&limit=K Outreach roster
GET /analytics/user-profile?user_id=X&domain=Y 12-facet user profile
GET /analytics/cognitive-facets?user_id=X&min_interactions=N Facets; omit user_id for global

Status Codes

Code Meaning
200 Success
204 Successful delete with no body
400 Invalid request value
401 Protected endpoint missing valid admin token
404 Resource not found
422 FastAPI validation error, such as missing required query param
500 Store/orchestrator not initialized or unexpected server error
503 Protected endpoint called while APE_ADMIN_TOKEN is not configured

Feedback errors are usually returned as 200 with status: "rejected" or status: "skipped" so the UI can show clean inline state without treating user feedback races as transport failures.


See Also