{"id":"4acb8d58-376a-453f-b828-a3ded833e954","entityType":"agent","slug":"clawhub-laceletho-smart-news","name":"Smart News","canonicalUrl":"https://www.xpersona.co/agent/clawhub-laceletho-smart-news","canonicalPath":"/agent/clawhub-laceletho-smart-news","generatedAt":"2026-10-11T05:34:49.089Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":null},"description":"Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks...","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1711j71qhdb72tgw6r3r22ra184d1mn:smart-news","sourceUrl":"https://clawhub.ai/laceletho/smart-news","homepage":"https://clawhub.ai/laceletho/skills/smart-news","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/laceletho/smart-news","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/laceletho/skills/smart-news","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Smart News technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":null},"stars":null,"forks":null,"downloads":1183,"packageName":null,"latestVersion":"0.4.6","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T02:49:36.255Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T02:49:36.323Z","lastCrawledAt":"2026-10-11T02:49:36.255Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T02:49:36.255Z","lastVerifiedAt":null,"highlights":[{"version":"0.4.6","createdAt":"2026-06-06T03:40:34.958Z","changelog":"Sync lifecycle to archive-only; remove /pause route and /topic_pause Telegram surface; fix startup flags; correct test paths; align docs with code.","fileCount":8,"zipByteSize":23528},{"version":"0.4.5","createdAt":"2026-06-04T14:32:05.864Z","changelog":"Async merge endpoint: POST /intelligence/topics/{id}/merge now returns 202 with job_id, status_url, result_url. Add GET .../merge/{job_id} for polling and GET .../merge/{job_id}/result for final results. Remove 409 check for parity with /analyze pattern.","fileCount":8,"zipByteSize":23462},{"version":"0.4.4","createdAt":"2026-06-04T13:31:54.814Z","changelog":"Add POST /intelligence/topics/{id}/merge endpoint and documentation; add tests for merge topic workflow (success, 404, 400, 401).","fileCount":8,"zipByteSize":22861},{"version":"0.4.3","createdAt":"2026-05-31T14:05:53.219Z","changelog":"Semantic search: add warning on hours truncation, 5min timeout with processing_step tracking, increase max window to 720h (30d). Analyze: add warning on hours truncation.","fileCount":8,"zipByteSize":22622},{"version":"0.4.2","createdAt":"2026-05-31T13:28:29.457Z","changelog":"Version 0.4.2 of smart-news focuses on unified semantic search and related improvements. - Unified semantic search now retrieves results from both news and intelligence sources, with per-domain breakdown in responses. - The semantic search workflow, API descriptions, and operational constraints are clarified and updated in documentation. - Deprecated entry-based intelligence routes are further cleaned up after the topic-only intelligence refactor. - HNSW indexes and vector search details for semantic search are now documented. - The skill-card.md file was removed.","fileCount":8,"zipByteSize":22054},{"version":"0.4.1","createdAt":"2026-05-28T09:08:28.788Z","changelog":"smart-news v0.4.1 - Updated intelligence topic API section: separated endpoints for topic metadata, findings, and prompt versions for improved clarity. - Revised references to reflect separation of topic detail routes in documentation. - Removed deprecated or obsolete documentation file (skill-card.md). - Minor wording and formatting improvements in SKILL.md for better usability.","fileCount":8,"zipByteSize":20836},{"version":"0.4.0","createdAt":"2026-05-28T03:34:36.539Z","changelog":"associate topic and datasources","fileCount":8,"zipByteSize":21946},{"version":"0.3.0","createdAt":"2026-05-18T09:49:50.423Z","changelog":"Renamed from crypto-news-http-api. Updated to match current API: removed deprecated entry-based routes and converge, added per-topic runs, documented purpose/datasource filtering, all refs synced to api_server.py.","fileCount":7,"zipByteSize":19798}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1711j71qhdb72tgw6r3r22ra184d1mn:smart-news","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s1711j71qhdb72tgw6r3r22ra184d1mn:smart-news` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/laceletho/smart-news before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T05:34:49.084Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-laceletho-smart-news/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":null},"readme":"Skill: Smart News\n\nOwner: laceletho\n\nSummary: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks...\n\nTags: latest:0.4.6\n\nVersion history:\n\nv0.4.6 | 2026-06-06T03:40:34.958Z | user\n\nSync lifecycle to archive-only; remove /pause route and /topic_pause Telegram surface; fix startup flags; correct test paths; align docs with code.\n\nv0.4.5 | 2026-06-04T14:32:05.864Z | user\n\nAsync merge endpoint: POST /intelligence/topics/{id}/merge now returns 202 with job_id, status_url, result_url. Add GET .../merge/{job_id} for polling and GET .../merge/{job_id}/result for final results. Remove 409 check for parity with /analyze pattern.\n\nv0.4.4 | 2026-06-04T13:31:54.814Z | user\n\nAdd POST /intelligence/topics/{id}/merge endpoint and documentation; add tests for merge topic workflow (success, 404, 400, 401).\n\nv0.4.3 | 2026-05-31T14:05:53.219Z | user\n\nSemantic search: add warning on hours truncation, 5min timeout with processing_step tracking, increase max window to 720h (30d). Analyze: add warning on hours truncation.\n\nv0.4.2 | 2026-05-31T13:28:29.457Z | auto\n\nVersion 0.4.2 of smart-news focuses on unified semantic search and related improvements.\n\n- Unified semantic search now retrieves results from both news and intelligence sources, with per-domain breakdown in responses.\n- The semantic search workflow, API descriptions, and operational constraints are clarified and updated in documentation.\n- Deprecated entry-based intelligence routes are further cleaned up after the topic-only intelligence refactor.\n- HNSW indexes and vector search details for semantic search are now documented.\n- The skill-card.md file was removed.\n\nv0.4.1 | 2026-05-28T09:08:28.788Z | auto\n\nsmart-news v0.4.1\n\n- Updated intelligence topic API section: separated endpoints for topic metadata, findings, and prompt versions for improved clarity.\n- Revised references to reflect separation of topic detail routes in documentation.\n- Removed deprecated or obsolete documentation file (skill-card.md).\n- Minor wording and formatting improvements in SKILL.md for better usability.\n\nv0.4.0 | 2026-05-28T03:34:36.539Z | user\n\nassociate topic and datasources\n\nv0.3.0 | 2026-05-18T09:49:50.423Z | user\n\nRenamed from crypto-news-http-api. Updated to match current API: removed deprecated entry-based routes and converge, added per-topic runs, documented purpose/datasource filtering, all refs synced to api_server.py.\n\nArchive index:\n\nArchive v0.4.6: 8 files, 23528 bytes\n\nFiles: references/analyze-workflow.md (9505b), references/datasource-management.md (11331b), references/intelligence-query.md (14754b), references/operations-and-maintenance.md (3679b), references/semantic-search.md (8183b), skill-card.md (2500b), SKILL.md (12584b), _meta.json (129b)\n\nFile v0.4.6:SKILL.md\n\n---\nname: smart-news\ndescription: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks from OpenClaw.\nmetadata: { openclaw: { skillKey: smart-news, primaryEnv: API_KEY } }\n---\n\n# Crypto News HTTP API Skill\n\nUse this skill to call the Crypto News Analyzer HTTP API from OpenClaw.\n\n## When to Use\n\nUse this skill when you need to call `https://news.tradao.xyz` or a compatible private deployment.\n\nTypical triggers:\n\n- Run asynchronous crypto news analysis over a time window\n- Run asynchronous unified semantic search (News + Intelligence) for a freeform topic query\n- Poll an API job until it finishes and then fetch the final result\n- Create, list, or delete datasources through the HTTP API\n- Query and manage intelligence topics through the topic-first API (create, revise, confirm, merge findings, detail, list, archive)\n- View and manage topic-datasource associations (get, set, add, remove) to scope topic research\n- List intelligence topic research run logs per-topic or globally\n- Check service health before or after an API workflow\n\n## Quick Reference\n\nAuthentication is Bearer token style: send `Authorization: Bearer <API_KEY>` with every request.\n\n`POST /analyze` creates a job and returns immediately. It does **not** return the final report. Poll status, then fetch the result.\n\nWorkflow: `POST /analyze` -> `GET /analyze/{job_id}` -> `GET /analyze/{job_id}/result`\n\nJobs move through these states: `queued`, `running`, `completed`, `failed`.\n\n`POST /semantic-search` creates a job, returns `202 Accepted`, and includes `status_url`, `result_url`, plus a `Retry-After` header. When `hours` exceeds the server max (720h default), a `warning` field describes the truncation. Semantic search jobs that do not complete within 5 minutes are automatically failed with a timeout error.\n\nSemantic workflow: `POST /semantic-search` -> `GET /semantic-search/{job_id}` -> `GET /semantic-search/{job_id}/result`\n\nUnified semantic search retrieves from both `content_items` and `raw_intelligence_items` via PostgreSQL with pgvector HNSW indexes (`embedding vector(1536)`). SQLite runtime is unsupported.\n\nFor detailed guides, see:\n\n- [Analyze Workflow Reference](references/analyze-workflow.md)\n- [Semantic Search Reference](references/semantic-search.md)\n- [Datasource Management Reference](references/datasource-management.md)\n- [Intelligence Query Reference](references/intelligence-query.md)\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md)\n\n## OpenClaw Runtime\n\nThis skill declares `metadata.openclaw.primaryEnv: API_KEY`. In OpenClaw, inject the bearer token through `~/.openclaw/openclaw.json`:\n\n```json5\n{\n  skills: {\n    entries: {\n      \"smart-news\": {\n        enabled: true,\n        apiKey: \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\n```\n\nIf `apiKey` is unavailable, do not send unauthenticated requests. Ask the operator to configure the token first.\n\nIf you are using a non-production deployment, replace `https://news.tradao.xyz` with the correct base URL before issuing requests.\n\n## Analyze Workflow\n\nCreate an analysis job by posting to `/analyze` with `hours` and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`. Do not expect the analysis report in the initial POST response. Once completed, fetch the result URL.\n\n## Semantic Search\n\nUnified semantic search retrieves from both News (`content_items`) and Intelligence (`raw_intelligence_items`) domains via UNION ALL over pgvector HNSW indexes. The response includes a `source_breakdown` with per-domain `matched_count` and `retained_count`. Each hit carries a `source_domain` discriminator (`\"news\"` or `\"intelligence\"`).\n\nCreate a semantic search job by posting to `/semantic-search` with `hours`, `query`, and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`. Semantic search job IDs start with `semantic_search_job_`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`, then fetch the report from the result URL. Use the `status` field as the source of truth for lifecycle state; `success` becomes `true` only when the job is completed successfully.\n\nRequest rules:\n\n- `hours` must be a positive integer\n- `query` is required, trimmed, and capped at 300 characters\n- `query` cannot be blank or whitespace-only\n- `user_id` must match `^[A-Za-z0-9_-]{1,128}$`\n\nOperational constraints:\n\n- Semantic search is PostgreSQL-only and returns `503` when the backend does not support pgvector\n- Both `content_items` and `raw_intelligence_items` tables have `embedding vector(1536)` columns with HNSW indexes (`idx_content_embedding_hnsw` and `idx_intelligence_embedding_hnsw`)\n- The API uses vector similarity over stored content embeddings and combines that with deterministic local keyword fallback (no LLM-driven keyword expansion)\n- LLM query decomposition is disabled by default (`query_planning_enabled: false`); when disabled the raw user query is embedded directly as the only subquery. The `max_subqueries` cap (4) only applies when query planning is explicitly re-enabled\n- Final retained results are capped at 200 unique items per domain before merging\n- Embedding generation requires `OPENAI_API_KEY`; report synthesis requires `KIMI_API_KEY` or `GROK_API_KEY` (query planning also requires an LLM key but is disabled by default)\n\nThe result body returns a Markdown report with `query`, `normalized_intent`, `matched_count`, `retained_count`, `time_window_hours`, `source_breakdown`, and `report`.\n\n## Datasource Management\n\nConfigure news and intelligence sources through the datasource API. Create sources with `POST /datasources`, list them with `GET /datasources`, and remove them with `DELETE /datasources/{id}`. All datasource routes require Bearer auth.\n\nEach datasource has a `purpose` field: `news` (RSS/X/REST feeds for analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `GET /datasources` endpoint supports optional `purpose` and `source_type` query parameters for filtering. Results are sorted by purpose, source type, then name.\n\nTags help organize sources. Each datasource accepts up to 16 unique tags. Each tag is capped at 32 characters. Tags are normalized to lowercase and deduplicated automatically.\n\nList and create responses include only safe summaries. For `rest_api` type datasources, secrets are redacted and counts replace raw credential fields. This prevents accidental credential exposure when reviewing configurations.\n\n## Intelligence Query (Topic-First)\n\nAll intelligence routes require Bearer auth. The deprecated entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`) have been removed in the topic-only refactor. Topics are the sole first-class intelligence objects, driving scheduled LLM research from raw ingested messages and storing findings with merge support.\n\nSynchronous topic workflow endpoints:\n\n- `POST /intelligence/topics` — Create a topic draft from a user theme (returns AI-generated prompt draft)\n- `POST /intelligence/topics/{topic_id}/revise` — Revise the draft prompt with feedback\n- `PUT /intelligence/topics/{topic_id}/prompt` — Manually set/replace the prompt text (context-aware: edits active prompt if one exists, otherwise creates draft revision)\n- `POST /intelligence/topics/{topic_id}/confirm` — Confirm and activate the topic for research (requires `prompt_version_id`)\n- `GET /intelligence/topics` — List topics with pagination and `active_only` filter (default: true)\n- `GET /intelligence/topics/{topic_id}` — Get topic metadata and merge availability\n- `GET /intelligence/topics/{topic_id}/findings` — Get paginated active findings with citations and source URLs\n- `GET /intelligence/topics/{topic_id}/prompts` — Get prompt versions and current active prompt\n- `POST /intelligence/topics/{topic_id}/archive` — Archive a topic\n- `GET /intelligence/topics/{topic_id}/runs` — List topic research run logs\n- `GET /intelligence/topic-runs` — List all topic research runs globally\n\nThese endpoints are synchronous; there is no async job/poll flow. Results return immediately.\n\nAsync topic merge endpoint:\n\n- `POST /intelligence/topics/{topic_id}/merge` — Start an async merge job (returns 202 Accepted with `job_id`, `status_url`, `result_url`)\n- `GET /intelligence/topics/{topic_id}/merge/{job_id}` — Check merge job status\n- `GET /intelligence/topics/{topic_id}/merge/{job_id}/result` — Retrieve completed merge results\n\nMerge workflow: `POST /intelligence/topics/{id}/merge` → poll `GET .../merge/{job_id}` → `GET .../merge/{job_id}/result`. Jobs move through states: `queued`, `running`, `completed`, `failed`. The merge LLM call may take several minutes, so polling is required — do not block on the POST response.\n\nTopics have lifecycle states: `draft`, `active`, `archived`. Only `active` topics are researched by the ingestion scheduler. Finding merge is available through both the async HTTP endpoint and the Telegram `/topic_merge` command.\n\n## Telegram Webhook\n\nThe webhook endpoint exists for maintainer-level Telegram integration. It is not the primary path for day-to-day operators. Regular users should interact through the API routes or Telegram slash commands instead.\n\nWhen processing webhook updates, validate the `X-Telegram-Bot-Api-Secret-Token` header to confirm the request originates from Telegram.\n\n## Endpoint Index\n\nSupported HTTP routes:\n\n- `GET /health` - Service health check\n- `POST /analyze` - Create an analysis job (async, returns 202)\n- `GET /analyze/{job_id}` - Check job status\n- `GET /analyze/{job_id}/result` - Retrieve completed job results\n- `POST /semantic-search` - Create a semantic search job (async, returns 202)\n- `GET /semantic-search/{job_id}` - Check semantic search job status\n- `GET /semantic-search/{job_id}/result` - Retrieve completed semantic search results\n- `POST /datasources` - Create a datasource\n- `GET /datasources` - List all datasources\n- `DELETE /datasources/{id}` - Delete a datasource\n- `POST /telegram/webhook` - Telegram webhook receiver\n- `POST /intelligence/topics` - Create topic draft (synchronous, Bearer-protected)\n- `POST /intelligence/topics/{id}/revise` - Revise topic prompt\n- `PUT /intelligence/topics/{id}/prompt` - Manually set topic prompt\n- `POST /intelligence/topics/{id}/confirm` - Confirm and activate topic\n- `GET /intelligence/topics` - List topics with status filters\n- `GET /intelligence/topics/{id}` - Get topic metadata and merge availability\n- `GET /intelligence/topics/{id}/findings` - Get paginated findings with citations\n- `GET /intelligence/topics/{id}/prompts` - Get prompt versions and active prompt\n- `POST /intelligence/topics/{id}/archive` - Archive topic\n- `POST /intelligence/topics/{id}/merge` - Start async merge job (returns 202)\n- `GET /intelligence/topics/{id}/merge/{job_id}` - Check merge job status\n- `GET /intelligence/topics/{id}/merge/{job_id}/result` - Retrieve completed merge results\n- `GET /intelligence/topics/{id}/datasources` - List datasource associations for a topic\n- `PUT /intelligence/topics/{id}/datasources` - Replace all datasource associations atomically\n- `POST /intelligence/topics/{id}/datasources/{datasource_id}` - Add a datasource association (idempotent)\n- `DELETE /intelligence/topics/{id}/datasources/{datasource_id}` - Remove a datasource association (idempotent)\n- `GET /intelligence/topics/{id}/runs` - List topic research run logs\n- `GET /intelligence/topic-runs` - List all topic research runs globally\n\n## Non-Goals\n\nThis skill does not cover:\n\n- Telegram slash commands (use the Telegram bot directly)\n- Autogenerated documentation routes (`/docs`, `/redoc`, `/openapi.json`)\n- Deprecated compatibility aliases are not part of the active runtime surface\n- Direct embedding backfill operations beyond pointing you to the documented command\n\nThese surfaces exist but are intentionally excluded from this API-focused skill.\n\n## Updating\n\nKeep this skill aligned with the live HTTP routes in `api_server.py`, the AI Analyze API Guide at `docs/AI_ANALYZE_API_GUIDE.md`, the semantic search guide at `docs/SEMANTIC_SEARCH_API_GUIDE.md`, and the domain repository contracts in `domain/repositories.py`.\n\nWhen documentation disagrees with implementation, trust the code and tests over prose docs. Source precedence: code first, then reference files, then guides.\n\nFile v0.4.6:_meta.json\n\n{\n  \"ownerId\": \"kn70n844xnvgcz0zzja942av2184cjj5\",\n  \"slug\": \"smart-news\",\n  \"version\": \"0.4.6\",\n  \"publishedAt\": 1780717234958\n}\n\nFile v0.4.6:references/analyze-workflow.md\n\n# Analyze Workflow Reference\n\nThe analyze workflow is the primary way to trigger cryptocurrency news analysis via HTTP API. This reference documents the three-step async pattern: create job, poll status, fetch result.\n\n## Authentication\n\nAll analyze endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nThe `API_KEY` is configured via the `API_KEY` environment variable on the server. Requests without a valid token receive HTTP 401.\n\n## Overview\n\nThe analyze workflow follows an asynchronous pattern:\n\n1. **Create**: POST to `/analyze` with `hours` and `user_id` to enqueue a job\n2. **Poll**: GET `/analyze/{job_id}` to check status until completion\n3. **Fetch**: GET `/analyze/{job_id}/result` to retrieve the final Markdown report\n\nThe initial POST returns immediately with job metadata. It does not return the analysis report. You must poll and fetch separately.\n\n## Creating an Analysis Job\n\n### Endpoint\n\n```\nPOST /analyze\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Analysis time window in hours. Values below server minimum return HTTP 400. Values above maximum are capped to the configured limit (default 24h) and the response includes a `warning` field. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Example Request\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'\n```\n\n### Success Response (HTTP 202 Accepted)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"queued\",\n  \"time_window_hours\": 1,\n  \"status_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"result_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\",\n  \"warning\": null\n}\n```\n\nResponse headers include:\n\n- `Location`: Path to status endpoint\n- `Retry-After`: Recommended polling interval in seconds (typically 5)\n\n### Validation Errors\n\n| Condition | HTTP Status | Notes |\n|-----------|-------------|-------|\n| Missing `user_id` | 422 | FastAPI validation error with field location |\n| Invalid `user_id` (spaces, punctuation, non-ASCII, >128 chars) | 422 | Must match `^[A-Za-z0-9_-]{1,128}$` |\n| `hours <= 0` | 422 | Positive integer required |\n| `hours` below server minimum | 400 | Configurable minimum (default 1) |\n\nExample validation error:\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"user_id\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"hours\": 1}\n    }\n  ]\n}\n```\n\n## Polling Job Status\n\n### Endpoint\n\n```\nGET /analyze/{job_id}\n```\n\n### Example Request\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\"\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The job identifier |\n| `status` | string | Current job state (see Job States below) |\n| `time_window_hours` | integer | Hours requested (after server caps applied) |\n| `created_at` | string (ISO 8601) | Job creation timestamp |\n| `started_at` | string (ISO 8601) or null | When execution began |\n| `completed_at` | string (ISO 8601) or null | When execution finished |\n| `items_processed` | integer | Number of news items analyzed |\n| `error` | string or null | Error message if failed |\n| `result_available` | boolean | `true` when status is `completed` or `failed` |\n\n### Response Examples\n\n**Running job:**\n\n```json\n{\n  \"success\": false,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"running\",\n  \"time_window_hours\": 1,\n  \"created_at\": \"2026-03-28T12:00:00+00:00\",\n  \"started_at\": \"2026-03-28T12:00:03+00:00\",\n  \"completed_at\": null,\n  \"items_processed\": 0,\n  \"error\": null,\n  \"result_available\": false\n}\n```\n\nNote: `success: false` during `running` or `queued` states is expected. Use the `status` field as the source of truth, not the `success` boolean.\n\n**Completed job:**\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"completed\",\n  \"time_window_hours\": 1,\n  \"created_at\": \"2026-03-28T12:00:00+00:00\",\n  \"started_at\": \"2026-03-28T12:00:03+00:00\",\n  \"completed_at\": \"2026-03-28T12:01:15+00:00\",\n  \"items_processed\": 25,\n  \"error\": null,\n  \"result_available\": true\n}\n```\n\n## Fetching the Result\n\n### Endpoint\n\n```\nGET /analyze/{job_id}/result\n```\n\n### Example Request\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\"\n```\n\n### Behavior by Job State\n\n| Job State | HTTP Status | Response |\n|-----------|-------------|----------|\n| `queued` or `running` | 200 | Job metadata with empty `report` |\n| `completed` | 200 | Full result with Markdown `report` |\n| `failed` | 200 | Job metadata with `error` field set |\n| Job not found | 404 | Error detail |\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when job completed successfully |\n| `job_id` | string | The job identifier |\n| `status` | string | Final job state |\n| `report` | string | Markdown-formatted analysis report (empty if not completed) |\n| `items_processed` | integer | Number of news items analyzed |\n| `time_window_hours` | integer | Hours analyzed |\n| `error` | string or null | Error message if job failed |\n\n### Completed Result Example\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"completed\",\n  \"report\": \"# Crypto News Analysis Report\\n\\n## Executive Summary...\",\n  \"items_processed\": 25,\n  \"time_window_hours\": 1,\n  \"error\": null\n}\n```\n\n## Job States\n\nJobs progress through the following states:\n\n| State | Description | Terminal |\n|-------|-------------|----------|\n| `queued` | Job created, waiting for execution slot | No |\n| `running` | Actively analyzing news items | No |\n| `completed` | Analysis finished successfully | Yes |\n| `failed` | Analysis failed with error | Yes |\n\nState transitions: `queued` -> `running` -> (`completed` or `failed`)\n\n## Complete Workflow Example\n\n```bash\n#!/bin/bash\n\nAPI_KEY=\"your-api-key\"\nBASE_URL=\"https://news.tradao.xyz\"\nUSER_ID=\"my_agent_01\"\n\n# 1. Create the job\nCREATE_RESPONSE=$(curl -sS -X POST \"${BASE_URL}/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"hours\\\":1,\\\"user_id\\\":\\\"${USER_ID}\\\"}\")\n\nJOB_ID=$(echo \"${CREATE_RESPONSE}\" | sed -n 's/.*\"job_id\":\"\\([^\"]*\\)\".*/\\1/p')\necho \"Created job: ${JOB_ID}\"\n\n# 2. Poll until completion\nwhile true; do\n  STATUS_RESPONSE=$(curl -sS \\\n    -H \"Authorization: Bearer ${API_KEY}\" \\\n    \"${BASE_URL}/analyze/${JOB_ID}\")\n\n  STATUS=$(echo \"${STATUS_RESPONSE}\" | sed -n 's/.*\"status\":\"\\([^\"]*\\)\".*/\\1/p')\n  echo \"Status: ${STATUS}\"\n\n  if [ \"${STATUS}\" = \"completed\" ]; then\n    # 3. Fetch the result\n    curl -sS \\\n      -H \"Authorization: Bearer ${API_KEY}\" \\\n      \"${BASE_URL}/analyze/${JOB_ID}/result\"\n    break\n  fi\n\n  if [ \"${STATUS}\" = \"failed\" ]; then\n    echo \"Job failed\"\n    exit 1\n  fi\n\n  sleep 5\ndone\n```\n\n## Key Gotchas\n\n1. **The initial POST does not return the report**: Always poll status and fetch result separately. The 202 response only confirms job acceptance.\n\n2. **Hours is required**: Unlike the Telegram `/analyze` command which can auto-calculate a time window, the HTTP API requires explicit `hours` in every request.\n\n3. **User ID has strict validation**: Must be 1-128 characters, alphanumeric plus underscores and hyphens only. No spaces, no special characters, no Unicode.\n\n4. **Success field semantics**: The `success` boolean in status and result responses reflects job completion state, not HTTP success. It is `false` while the job is `queued` or `running`. Check the `status` field for the actual job state.\n\n5. **HTTP 202 means accepted**: On the create endpoint, 202 means \"accepted and processing\". The result endpoint always returns HTTP 200 (with an empty `report` field while the job is still running). Note: some older documentation may mention 202 for the result endpoint, but the current implementation returns 200 for all job states; 404 is only returned when the job ID does not exist.\n\n6. **Header case sensitivity**: Cloudflare and some proxies lowercase header names. The `Location` and `Retry-After` headers may appear as `location` and `retry-after`.\n\n7. **Hours capping**: If you request more hours than the server allows (`max_analysis_window_hours`, default 24), the request succeeds but `time_window_hours` in the response reflects the capped value, not your original request. A `warning` field in the response describes the truncation when it occurs.\n\n8. **User isolation**: Each `user_id` has isolated deduplication context. The same user calling analyze twice will see deduplication of previously reported items. Different users do not share context.\n\n## Updating\n\nKeep this reference aligned with:\n\n- `crypto_news_analyzer/api_server.py` for endpoint implementation details\n- `crypto_news_analyzer/domain/models.py` for `JobStatus` enum values\n- `tests/test_api_server.py` for contract test coverage\n\nWhen the live API behavior diverges from this document, the code and tests take precedence.\n\nFile v0.4.6:references/datasource-management.md\n\n# Datasource Management Reference\n\nThis document describes the HTTP API surface for managing datasources. All datasource routes require Bearer authentication.\n\n## CRUD Routes\n\n### POST /datasources\n\nCreates a new datasource. Returns `201 Created` on success, `409 Conflict` if a datasource with the same type and name already exists, and `422 Unprocessable Entity` for invalid payloads.\n\n**Request body structure:**\n```json\n{\n  \"purpose\": \"news|intelligence\",\n  \"source_type\": \"rss|x|rest_api\",\n  \"tags\": [\"tag1\", \"tag2\"],\n  \"config_payload\": {\n    \"name\": \"My Source\",\n    ...\n  }\n}\n```\n\nThe `purpose` field determines which pipeline the datasource feeds: `news` (RSS/X/REST for content analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `name` field in the top-level request must match `config_payload.name` when both are provided.\n\n### GET /datasources\n\nLists all datasources sorted by purpose, source type, then name. Supports optional filtering by `purpose` and `source_type` query parameters. Returns `200 OK` with a list of datasource summaries.\n\n**Query Parameters:**\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `purpose` | string | No | Filter by `news` or `intelligence` |\n| `source_type` | string | No | Filter by datasource type (`rss`, `x`, etc.) |\n\n**Response structure:**\n```json\n{\n  \"success\": true,\n  \"datasources\": [\n    {\n      \"id\": \"uuid\",\n      \"name\": \"My Source\",\n      \"purpose\": \"news\",\n      \"source_type\": \"rss\",\n      \"tags\": [\"tag1\"],\n      \"config_summary\": {\n        ...\n      }\n    }\n  ]\n}\n```\n\nList responses always return safe summaries. For `rest_api` datasources, sensitive fields are redacted and replaced with counts.\n\n### DELETE /datasources/{id}\n\nDeletes a datasource by its UUID. Returns `204 No Content` on success, `404 Not Found` if the datasource does not exist, and `409 Conflict` if the datasource has active ingestion jobs.\n\nThe delete operation will fail with `409 Conflict` if there are pending or running ingestion jobs associated with this datasource (matched by `source_type:source_name`).\n\n## Supported Datasource Types\n\nThe API supports five datasource types: `rss`, `x`, `rest_api`, `telegram_group`, and `v2ex`.\n\n`telegram_group` and `v2ex` feed the **hidden-channel intelligence pipeline** (raw collection → LLM extraction → canonical knowledge). They are not part of the news analysis pipeline and require the `openclaw+opencode` ingestion service with proper credentials.\n\n### rss\n\nRSS feed datasources crawl RSS/XML feeds.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (string, valid HTTP/HTTPS URL)\n\n**Optional config_payload fields:**\n- `description` (string, defaults to empty string)\n\n**Config summary in responses:**\n- `url`: The RSS feed URL\n- `description`: The description value\n\n### x\n\nX (formerly Twitter) datasources crawl X lists or timelines.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (string, valid HTTPS URL on x.com or www.x.com)\n- `type` (string, must be `\"list\"` or `\"timeline\"`)\n\n**Config summary in responses:**\n- `url`: The X URL\n- `type`: Either `\"list\"` or `\"timeline\"`\n\n### rest_api\n\nREST API datasources fetch content from arbitrary HTTP endpoints.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `endpoint` (string, valid HTTP/HTTPS URL)\n- `method` (string, one of: `GET`, `POST`, `PUT`, `DELETE`)\n- `response_mapping` (object) with required fields:\n  - `title_field` (string, non-empty)\n  - `content_field` (string, non-empty)\n  - `url_field` (string, non-empty)\n  - `time_field` (string, non-empty)\n\n**Optional config_payload fields:**\n- `headers` (object, defaults to empty object)\n- `params` (object, defaults to empty object)\n\n**Config summary in responses:**\n- `endpoint`: The API endpoint URL\n- `method`: The HTTP method\n- `response_mapping`: The full response mapping object\n- `header_count`: Number of headers (count only, values redacted)\n- `param_count`: Number of query params (count only, values redacted)\n\n## Tag Constraints\n\nTags on datasources follow strict normalization and validation rules:\n\n**Normalization:**\n- Tags are converted to lowercase\n- Leading and trailing whitespace is stripped\n- Empty tags after trimming are discarded\n- Tags are sorted alphabetically\n- Duplicate tags are removed\n\n**Limits:**\n- Maximum 16 unique tags per datasource\n- Each tag must be at most 32 characters after normalization\n\n**Validation errors:**\n- Exceeding 16 unique tags returns `422 Unprocessable Entity` with message: \"tags cannot contain more than 16 unique values\"\n- Any tag exceeding 32 characters returns `422 Unprocessable Entity` with message: \"each tag must be at most 32 characters\"\n\nExample: The tags `[\" Markets \", \"markets\", \"Layer2\"]` normalize to `[\"layer2\", \"markets\"]`.\n\n## Safe Summaries and Secret Redaction\n\nAll datasource responses (create and list) return safe summaries instead of the full config payload. This prevents accidental exposure of sensitive credentials.\n\n### rss and x Summaries\n\nFor RSS and X datasources, the config summary includes the URL and type-specific fields without modification.\n\n### rest_api Redaction\n\nFor `rest_api` datasources, the following redaction rules apply:\n\n- The `headers` object is replaced with `header_count` (integer)\n- The `params` object is replaced with `param_count` (integer)\n- The actual header names, parameter names, and their values are never returned\n- The `endpoint`, `method`, and `response_mapping` are returned as-is (these are not secrets)\n\nThis ensures that API keys, bearer tokens, and other credentials stored in headers or params remain secret while still allowing clients to understand the datasource configuration.\n\nExample redacted response for a rest_api datasource:\n```json\n{\n  \"id\": \"uuid\",\n  \"name\": \"News API\",\n  \"purpose\": \"news\",\n  \"source_type\": \"rest_api\",\n  \"tags\": [],\n  \"config_summary\": {\n    \"endpoint\": \"https://api.example.com/news\",\n    \"method\": \"GET\",\n    \"response_mapping\": {\n      \"title_field\": \"title\",\n      \"content_field\": \"body\",\n      \"url_field\": \"url\",\n      \"time_field\": \"published_at\"\n    },\n    \"header_count\": 1,\n    \"param_count\": 2\n  }\n}\n```\n\n## Delete Conflict Behavior\n\nDeleting a datasource can fail with `409 Conflict` in the following scenarios:\n\n**Active Ingestion Jobs:**\nIf there are ingestion jobs for this datasource (matched by `source_type:source_name`) with status `\"pending\"` or `\"running\"`, the delete operation is rejected.\n\n**Error response:**\n```json\n{\n  \"detail\": \"Cannot delete datasource 'rss:CoinDesk' while matching ingestion jobs are active\"\n}\n```\n\n**Topic-Datasource Associations:**\nIf the datasource is associated with any intelligence topic via `intelligence_topic_datasources`, the delete operation is rejected. All associations must be removed first.\n\n**Error response:**\n```json\n{\n  \"detail\": \"Datasource 'ds-uuid' is associated with 3 topic(s) and must be unbound first\"\n}\n```\n\nTo delete a datasource with topic associations, either use the topic datasource API to remove the associations, or clear all associations for each topic before deleting the datasource.\n\n## Intelligence Datasource Types\n\nThe following source types feed the hidden-channel intelligence pipeline. They store raw text (30-day TTL) and produce canonical knowledge entries through LLM extraction. All secrets **must** be provided via environment variables — never inlined in the config payload.\n\n### telegram_group\n\nCollects messages from allowlisted Telegram chats using Telethon MTProto.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `chat_id` (string) **or** `chat_username` (string, with `@` prefix)\n\n**Config payload example (by username):**\n```json\n{\n  \"source_type\": \"telegram_group\",\n  \"config_payload\": {\n    \"name\": \"Crypto Alpha\",\n    \"chat_username\": \"@cryptoalpha\"\n  }\n}\n```\n\n**Config payload example (by chat ID):**\n```json\n{\n  \"source_type\": \"telegram_group\",\n  \"config_payload\": {\n    \"name\": \"Private Group\",\n    \"chat_id\": \"-1001234567890\"\n  }\n}\n```\n\n**Constraints:**\n- Must provide exactly one of `chat_id` or `chat_username` — not both, not neither\n- Cannot enumerate all joined chats; each datasource targets a single explicitly configured chat\n- No session strings, API hashes, passwords, or tokens in the payload — those come from environment variables (`TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `TELEGRAM_STRING_SESSION`)\n\n**Production checklist:**\n1. The server must have `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, and `TELEGRAM_STRING_SESSION` set in environment\n2. The Telegram account used for the session must have joined the target chat\n3. The account must not have 2FA enabled unless the session was generated with it\n\n### v2ex\n\nCollects topics and replies from V2EX nodes using the official API.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `api_version` (string, `\"v1\"` or `\"v2\"`)\n- `node_allowlist` (array of strings, at least one node name)\n\n**Config payload example (v1, no auth):**\n```json\n{\n  \"source_type\": \"v2ex\",\n  \"config_payload\": {\n    \"name\": \"V2EX Crypto & AI\",\n    \"api_version\": \"v1\",\n    \"node_allowlist\": [\"crypto\", \"openai\"]\n  }\n}\n```\n\n**Config payload example (v2, with PAT):**\n```json\n{\n  \"source_type\": \"v2ex\",\n  \"config_payload\": {\n    \"name\": \"V2EX Tech\",\n    \"api_version\": \"v2\",\n    \"node_allowlist\": [\"programmer\"],\n    \"pat_env_var_name\": \"V2EX_PAT\"\n  }\n}\n```\n\n**Constraints:**\n- `api_version` must be `\"v1\"` or `\"v2\"` — no HTML/CSS scraping\n- `node_allowlist` must be a non-empty array of non-empty strings\n- v2 requires `pat_env_var_name` pointing to an environment variable (not the PAT value itself)\n- v1 is public and requires no authentication\n- Node names are the URL path after `/go/`, e.g. `https://www.v2ex.com/go/crypto` → `\"crypto\"`\n\n**Rate limits:**\n- v1: 120 requests/hour per IP (used for both topics and replies)\n- v2: varies by PAT tier\n- The crawler tracks `X-Rate-Limit-Remaining` headers and pauses when exhausted\n\n## Error Reference\n\n| Status Code | Scenario | Detail Message Pattern |\n|-------------|----------|------------------------|\n| 201 | Create success | N/A (returns datasource) |\n| 204 | Delete success | N/A (empty body) |\n| 200 | List success | N/A (returns list) |\n| 401 | Missing or invalid API key | \"Invalid API key\" |\n| 404 | Datasource not found | \"Datasource not found\" |\n| 409 | Duplicate datasource | \"Datasource 'type:name' already exists\" |\n| 409 | Datasource in use | \"Cannot delete datasource 'type:name' while matching ingestion jobs are active\" |\n| 409 | Datasource associated with topics | \"Datasource 'id' is associated with N topic(s) and must be unbound first\" |\n| 422 | Invalid payload structure | Pydantic validation error details |\n| 422 | Invalid semantic payload | e.g., \"x.type must be one of: list, timeline\" |\n| 422 | Tag limit exceeded | \"tags cannot contain more than 16 unique values\" |\n| 422 | Tag too long | \"each tag must be at most 32 characters\" |\n| 500 | Internal server error | Exception message |\n\n## Updating\n\nKeep this reference aligned with `crypto_news_analyzer/api_server.py` and `crypto_news_analyzer/datasource_payloads.py`. When the implementation changes, update this document to reflect the current validation rules, redaction behavior, and error responses.\n\nFile v0.4.6:references/intelligence-query.md\n\n# Intelligence Query Reference\n\nTopic-first intelligence HTTP API. All endpoints require Bearer authentication and manage the topic research lifecycle (create → revise → confirm → research → merge → archive).\n\nThese endpoints are synchronous — results return immediately. Do not use an async job/poll workflow for intelligence routes.\n\n## Authentication\n\nSend `Authorization: Bearer <API_KEY>` with every request. Missing or invalid credentials return `401 Unauthorized`.\n\n## Topic Lifecycle\n\nTopics progress through states: `draft` → `active` → `archived`. Only `active` topics are researched by the ingestion scheduler. Merge previews expire after 24 hours. Finding merge is available through both the HTTP API and the Telegram `/topic_merge` command.\n\n## Deprecated Routes\n\nThe old entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`, `/intelligence/raw/*`, `/intelligence/topics/converge`) have been removed. Use only the topic-first endpoints documented below.\n\n---\n\n## POST /intelligence/topics\n\nCreate a new intelligence topic with an LLM-generated draft prompt.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `theme` | string | Yes | 1–500 characters |\n| `source_context` | object | No | Optional context for prompt generation |\n| `datasource_ids` | string[] | No | Optional list of datasource IDs to associate. Omitted = no associations. |\n\n### Status Codes\n\n| Code | Meaning |\n|------|---------|\n| `201` | Topic draft created |\n| `400` | Invalid theme or topic parameters |\n| `401` | Missing or invalid Bearer token |\n| `503` | LLM service unavailable |\n\n### Response (201)\n\nReturns a `TopicPromptVersionResponse`:\n\n```json\n{\n  \"id\": \"prompt-uuid\",\n  \"intelligence_topic_id\": \"topic-uuid\",\n  \"prompt_version\": \"v1.0\",\n  \"prompt_text\": \"LLM-generated research prompt...\",\n  \"schema_version\": \"v1.0\",\n  \"status\": \"draft\",\n  \"created_by\": \"api\",\n  \"activated_by\": null,\n  \"activation_notes\": null,\n  \"created_at\": \"2026-05-18T10:00:00+00:00\",\n  \"activated_at\": null,\n  \"archived_at\": null,\n  \"updated_at\": \"2026-05-18T10:00:00+00:00\",\n  \"audit_history\": []\n}\n```\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"theme\": \"crypto payment channels in Telegram groups\"}'\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/revise\n\nRevise the most recent draft prompt using LLM and user feedback. Returns a new prompt version.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `feedback` | string | Yes | 1–5000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse` with the revised prompt.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/revise\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"feedback\": \"Focus on stablecoin settlement, exclude NFT marketplaces\"}'\n```\n\n---\n\n## PUT /intelligence/topics/{topic_id}/prompt\n\nManually set or replace the topic prompt text. Context-aware behavior:\n- If an active prompt exists → edits it in place (new version with same activation)\n- If no active prompt → creates a draft revision\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `prompt_text` | string | Yes | 1–50000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse`.\n\n### Example\n\n```bash\ncurl -X PUT \"https://news.tradao.xyz/intelligence/topics/topic-uuid/prompt\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_text\": \"Custom manual research prompt text...\"}'\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/confirm\n\nConfirm a draft prompt version, activating it for scheduled research.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `prompt_version_id` | string | Yes | Must reference a draft prompt version |\n| `activation_notes` | string | No | Max 2000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse` with status `active`.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/confirm\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_version_id\": \"prompt-uuid\", \"activation_notes\": \"Ready for daily research\"}'\n```\n\n---\n\n## GET /intelligence/topics\n\nList intelligence topics with pagination and filtering.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `active_only` | boolean | No | `true` | Filter to active topics only |\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 20 | Items per page |\n\n### Response (200)\n\n```json\n{\n  \"items\": [\n    {\n      \"id\": \"topic-uuid\",\n      \"name\": \"Stablecoin Settlement Channels\",\n      \"finding_count\": 5,\n      \"updated_at\": \"2026-05-18T06:30:00+00:00\"\n    }\n  ],\n  \"total\": 12,\n  \"page\": 1,\n  \"page_size\": 20\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics?active_only=true&page=1&page_size=20\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}\n\nGet topic metadata and merge availability. Findings and prompts are available via separate endpoints below.\n\n### Response (200)\n\n```json\n{\n  \"topic\": {\n    \"id\": \"topic-uuid\",\n    \"name\": \"Stablecoin Settlement Channels\",\n    \"is_active\": true,\n    \"updated_at\": \"2026-05-18T06:30:00+00:00\"\n  },\n  \"merge_available\": false\n}\n```\n\nReturns `404` if the topic ID does not exist.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/findings\n\nReturn paginated active findings with citations. Each citation includes a `source_url` (resolved from raw items when available) for direct linking to original messages.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\n```json\n{\n  \"findings\": [\n    {\n      \"id\": \"finding-uuid\",\n      \"intelligence_topic_id\": \"topic-uuid\",\n      \"prompt_version_id\": \"prompt-uuid\",\n      \"finding_payload\": { /* LLM-generated structured finding */ },\n      \"confidence\": 0.92,\n      \"citations\": [\n        {\n          \"message_id\": \"raw-uuid\",\n          \"message_snippet\": \"Original message text excerpt...\",\n          \"source\": \"telegram_group\",\n          \"published_at\": \"2026-05-18T05:00:00+00:00\",\n          \"source_url\": \"https://t.me/channel/123\"\n        }\n      ],\n      \"source_finding_ids\": [],\n      \"status\": \"active\",\n      \"found_at\": \"2026-05-18T06:00:00+00:00\",\n      \"created_at\": \"2026-05-18T06:00:00+00:00\",\n      \"updated_at\": \"2026-05-18T06:00:00+00:00\"\n    }\n  ],\n  \"total\": 5,\n  \"page\": 1,\n  \"page_size\": 10\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/findings?page=1&page_size=10\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/prompts\n\nReturn all prompt versions and the currently active prompt for a topic.\n\n### Response (200)\n\n```json\n{\n  \"current_prompt\": {\n    \"id\": \"prompt-uuid\",\n    \"intelligence_topic_id\": \"topic-uuid\",\n    \"prompt_version\": \"v1.0\",\n    \"prompt_text\": \"Research prompt text...\",\n    \"schema_version\": \"v1.0\",\n    \"status\": \"active\",\n    \"created_by\": \"api\",\n    \"activated_by\": \"api\",\n    \"activation_notes\": \"Ready for daily research\",\n    \"created_at\": \"2026-05-18T10:00:00+00:00\",\n    \"activated_at\": \"2026-05-18T10:05:00+00:00\",\n    \"archived_at\": null,\n    \"updated_at\": \"2026-05-18T10:05:00+00:00\",\n    \"audit_history\": []\n  },\n  \"prompt_versions\": [ /* all versions, same shape as current_prompt */ ]\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/prompts\"\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/archive\n\nArchive a topic, stopping further scheduled research runs.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"topic_id\": \"topic-uuid\",\n  \"lifecycle_status\": \"archived\",\n  \"updated_at\": \"2026-05-18T12:00:00+00:00\"\n}\n```\n\nReturns `404` if the topic ID does not exist.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/archive\" \\\n  -H \"Authorization: Bearer ${API_KEY}\"\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/merge\n\nStart an **async** merge job for active topic findings. The LLM call may take several minutes — this endpoint returns immediately with a `job_id`. Poll the status endpoint, then fetch the result once the job completes.\n\n### Response (202)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"queued\",\n  \"status_url\": \"/intelligence/topics/topic-uuid/merge/merge_job_abc123\",\n  \"result_url\": \"/intelligence/topics/topic-uuid/merge/merge_job_abc123/result\"\n}\n```\n\nReturns `400` if no active prompt found. Returns `404` if the topic ID does not exist. The `Location` response header points to the status URL.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge\" \\\n  -H \"Authorization: Bearer ${API_KEY}\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/merge/{job_id}\n\nCheck the status of a merge job. Jobs progress through `queued`, `running`, `completed`, and `failed` states.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"completed\",\n  \"created_at\": \"2026-06-04T10:00:00+00:00\",\n  \"started_at\": \"2026-06-04T10:00:01+00:00\",\n  \"completed_at\": \"2026-06-04T10:02:34+00:00\",\n  \"error\": null,\n  \"result_available\": true\n}\n```\n\n`result_available` is `true` when the job status is `completed` or `failed`. Returns `404` if the job or topic is not found.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge/merge_job_abc123\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/merge/{job_id}/result\n\nRetrieve the completed merge results. Only available after the job status is `completed` or `failed`.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"completed\",\n  \"topic_name\": \"Stablecoin Settlement Channels\",\n  \"source_findings_count\": 5,\n  \"merged_findings_count\": 2,\n  \"source_citations_count\": 15,\n  \"merged_citations_count\": 8,\n  \"removed_citations_count\": 7,\n  \"summary\": \"Consolidated findings summary...\",\n  \"change_summary\": {}\n}\n```\n\nReturns `404` if the job is not found. The endpoint returns the current job state at any time — poll status with `result_available` to detect completion. Failed jobs return `success: false` with the error in the `error` field.\n\n### Workflow\n\n```\nPOST /intelligence/topics/{id}/merge  →  202 Accepted + job_id\n  ↓\nGET  /intelligence/topics/{id}/merge/{job_id}  →  poll until completed/failed\n  ↓\nGET  /intelligence/topics/{id}/merge/{job_id}/result  →  final merge results\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge/merge_job_abc123/result\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/runs\n\nGet paginated research run logs for a specific topic. Each run includes the prompt version used, execution status, findings count, and timestamps.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\nReturns paginated list of `TopicResearchRun` objects.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/runs?page=1&page_size=10\"\n```\n\n---\n\n## GET /intelligence/topic-runs\n\nGet paginated research run logs across all topics. Useful for monitoring overall research activity.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\nReturns paginated list of `TopicResearchRun` objects across all topics.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topic-runs?page=1&page_size=10\"\n```\n\n---\n\n## Status Codes\n\n| Status | Meaning |\n|--------|---------|\n| `200` | Success |\n| `201` | Topic draft created |\n| `202` | Merge job accepted (async — poll for result) |\n| `400` | Invalid parameters or merge preview error |\n| `401` | Missing or invalid Bearer token |\n| `404` | Topic or resource not found |\n| `422` | FastAPI validation error |\n| `500` | Internal server error |\n| `503` | LLM service or repository not initialized |\n\n## Notes\n\n- Topic lifecycle endpoints are synchronous — results return immediately without polling.\n- The merge endpoint (`POST /intelligence/topics/{id}/merge`) is **async**: returns 202, then poll status → get result.\n- Only `active` topics receive scheduled research from the ingestion service.\n- Merge previews expire after 24 hours; accepting a stale preview is rejected.\n- Finding merge is available through both the async HTTP endpoint and the Telegram `/topic_merge` command. At least two active findings are required.\n- Prompt lifecycle: create draft → revise (optional) → confirm → active. Manual edits via `PUT /prompt` can shortcut this.\n- These endpoints exist only on `analysis-service` / `api-only` deployments. They are not available from `ingestion`.\n\n## Updating\n\nCanonical sources for this reference:\n\n1. `crypto_news_analyzer/api_server.py` — route definitions and response models\n2. `crypto_news_analyzer/intelligence/topic_prompts.py` — prompt workflow service\n3. `crypto_news_analyzer/intelligence/topic_findings.py` — findings and merge service\n4. `crypto_news_analyzer/domain/models.py` — `TopicLifecycleStatus` enum and `SafeDataSourceSummary`\n5. `crypto_news_analyzer/domain/repositories.py` — `IntelligenceRepository` datasource association contract\n6. `tests/intelligence/test_topic_datasource_api.py` — association API contract tests\n\nWhen sources disagree, trust code over prose.\n\nFile v0.4.6:references/operations-and-maintenance.md\n\n# Operations and Maintenance Reference\n\nThis reference covers operational surfaces and maintenance workflows for the crypto-news-analyzer HTTP API.\n\n## Health Endpoint\n\nThe service exposes a health check endpoint for load balancers and monitoring systems.\n\n- **Endpoint:** `GET /health`\n- **Response:** `{\"status\": \"healthy\", \"initialized\": true/false}`\n- **Use case:** Load balancer health checks, deployment verification, uptime monitoring\n\nThe `initialized` field indicates whether the underlying controller has completed startup initialization. A value of `false` may indicate the service is still starting or encountered an error during initialization.\n\n## Telegram Webhook\n\nThe Telegram webhook is a maintainer-only integration surface for receiving Telegram Bot updates via webhook delivery.\n\n### Webhook Route\n\n- **Endpoint:** `POST` to the path configured in `TELEGRAM_WEBHOOK_PATH` environment variable\n- **Default path:** `/telegram/webhook`\n- **Purpose:** Integration point for Telegram Bot API webhook delivery, not the primary user path\n\n### Authentication Header\n\nWebhook requests must include a secret token header for validation:\n\n- **Header:** `X-Telegram-Bot-Api-Secret-Token`\n- **Behavior:** The handler validates the token against the configured secret; mismatches return HTTP 403\n- **Error responses:**\n  - `403 Forbidden` - Invalid or missing secret token\n  - `503 Service Unavailable` - Runtime processing error (e.g., handler not ready)\n\n### Operational Notes\n\n- Operators and end users should prefer the HTTP API routes and Telegram slash commands instead of interacting directly with the webhook endpoint\n- The webhook is intended for infrastructure integration and bot delivery, not for manual invocation\n- Configure the webhook path via environment variable to match your deployment routing needs\n\n## Self-Update Workflow\n\nWhen updating this skill reference to match code changes, follow this workflow to ensure accuracy.\n\n### Canonical Source Files\n\nThese files contain the ground truth for HTTP behavior:\n\n1. **`crypto_news_analyzer/api_server.py`** - FastAPI route definitions, health endpoint, and webhook handler\n2. **`tests/news/test_api_server.py`** - Contract tests for endpoints, including webhook secret validation\n3. **`docs/AI_ANALYZE_API_GUIDE.md`** - Production verification notes and API usage guidance\n\n### Verification Commands\n\nBefore merge, run the full planned verification suite to verify the skill docs, the backing API contract, and the legacy-reference guardrails stay aligned:\n\n```bash\nuv run pytest tests/shared/test_openclaw_skill_smart_news.py -v\nuv run pytest tests/news/test_api_server.py -k \"health or analyze or datasource or webhook\" -v\nuv run pytest tests/shared/test_banned_legacy_reference_scan.py -v\nuv run python tests/helpers/banned_legacy_reference_scan.py\n```\n\nDo not merge until all four commands pass.\n\n### Release Command\n\nPublish a new ClawHub version from the repo root with:\n\n```bash\nskills/publish_clawhub_skill.sh smart-news 0.3.0 \"Describe the release briefly.\"\n```\n\nUse a semver version string. The script checks `clawhub` login state and runs the skill-specific test file before publishing unless `CLAWHUB_SKIP_TESTS=1` is set.\n\n### Update Steps\n\n1. Read the canonical source files to identify behavioral changes\n2. Update the relevant sections in this reference\n3. Run the full verification suite above before merge\n4. Address any failures before committing or merging\n\n### Source Precedence\n\nWhen documentation sources conflict, trust code and tests over prose. The live implementation in `api_server.py` and its test coverage in `tests/news/test_api_server.py` are the authoritative references.\n\nFile v0.4.6:references/semantic-search.md\n\n# Semantic Search Reference\n\nThis document describes the asynchronous semantic search HTTP API for AI agents and operators.\n\n## Authentication\n\nAll semantic search endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nRequests without a valid token receive HTTP 401.\n\n## Overview\n\nUnified semantic search retrieves from both News (`content_items`) and Intelligence (`raw_intelligence_items`) domains via UNION ALL over pgvector HNSW indexes (`idx_content_embedding_hnsw` and `idx_intelligence_embedding_hnsw`). Both tables use `embedding vector(1536)` columns. Each hit carries a `source_domain` discriminator; the response includes a `source_breakdown` with per-domain `matched_count` and `retained_count`.\n\nSemantic search is asynchronous and follows the same three-step pattern as `/analyze`:\n\n1. **Create**: `POST /semantic-search` with `hours`, `query`, and `user_id`\n2. **Poll**: `GET /semantic-search/{job_id}` until the job reaches a terminal state\n3. **Fetch**: `GET /semantic-search/{job_id}/result` to retrieve the final Markdown report\n\nThe POST response returns immediately. It does not include the final report.\n\n## Request Contract\n\n### Endpoint\n\n```\nPOST /semantic-search\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Search time window in hours. Values below server minimum return HTTP 400. Values above the semantic search maximum (default 720h = 30d) are capped and the response includes a `warning` field. The semantic search hours cap is separate from the analyze cap (24h). |\n| `query` | string | non-blank, max 300 chars | Natural-language topic query for semantic retrieval. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Success Response (HTTP 202 Accepted)\n\nThe response body includes:\n\n- `success`\n- `job_id`\n- `status`\n- `time_window_hours`\n- `status_url`\n- `result_url`\n- `warning` (present when `hours` exceeds the max; `null` otherwise)\n\nResponse headers include:\n\n- `Location`\n- `Retry-After`\n\nJob IDs use the prefix `semantic_search_job_`.\n\n`query`, `normalized_intent`, `matched_count`, `retained_count`, and `source_breakdown` are only available on the status and result endpoints — they are `0`, empty, or `null` at acceptance time and are therefore excluded from the 202 response.\n\n## Job Status Contract\n\n### Endpoint\n\n```\nGET /semantic-search/{job_id}\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The semantic search job identifier |\n| `status` | string | Current state: `queued`, `running`, `completed`, or `failed` |\n| `query` | string | Original normalized query |\n| `normalized_intent` | string | Search intent (equals original query when `query_planning_enabled` is false, which is the default; LLM-normalized only when query planning is explicitly enabled) |\n| `matched_count` | integer | Total matched items before final retention |\n| `retained_count` | integer | Final retained items used for synthesis |\n| `source_breakdown` | object | Per-domain hit counts: `{\"news\": {\"matched_count\": N, \"retained_count\": M}, \"intelligence\": {\"matched_count\": N, \"retained_count\": M}}` |\n| `time_window_hours` | integer | Search time window after server caps |\n| `created_at` | string (ISO 8601) | Job creation timestamp |\n| `started_at` | string (ISO 8601) or null | When execution began |\n| `completed_at` | string (ISO 8601) or null | When execution finished |\n| `error` | string or null | Error message if failed |\n| `processing_step` | string or null | Current processing stage: `\"embedding\"`, `\"retrieving\"`, `\"ranking\"`, or `null`. Used to distinguish \"still processing\" from a stuck job. |\n| `result_available` | boolean | `true` when status is `completed` or `failed` |\n\nUse the `status` field as the source of truth, not the `success` boolean.\n\n## Result Contract\n\n### Endpoint\n\n```\nGET /semantic-search/{job_id}/result\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The semantic search job identifier |\n| `status` | string | Terminal state: `completed` or `failed` |\n| `query` | string | Original query |\n| `normalized_intent` | string | Search intent (equals original query when `query_planning_enabled` is false, which is the default; LLM-normalized only when query planning is explicitly enabled) |\n| `matched_count` | integer | Total matched items |\n| `retained_count` | integer | Final retained items |\n| `source_breakdown` | object | Per-domain hit counts: `{\"news\": {\"matched_count\": N, \"retained_count\": M}, \"intelligence\": {\"matched_count\": N, \"retained_count\": M}}` |\n| `report` | string | Markdown semantic search report |\n| `time_window_hours` | integer | Search time window |\n| `error` | string or null | Error text when failed |\n\n## Report Structure\n\nThe Markdown report follows this structure:\n\n```markdown\n# Topic Search Report\n\n- Normalized intent: ...\n- Original query: ...\n- Time window: N hours\n- Matched items: N\n- Retained items: N\n\n## Key Signals\n\n### Signal 1\nConcise synthesized paragraph.\nSources: [Source Name](https://example.com/article)\n```\n\nThe live service currently returns the headings in Chinese (`# 主题检索报告`, `## 关键信号`). Treat the exact report string as implementation-defined content and preserve it as returned.\n\n## Limits and Dependencies\n\n- Requires PostgreSQL with pgvector; SQLite is unsupported\n- Both `content_items` and `raw_intelligence_items` tables require `embedding vector(1536)` columns with HNSW indexes (`idx_content_embedding_hnsw`, `idx_intelligence_embedding_hnsw`) for performance\n- Query length is capped at 300 characters\n- LLM query decomposition is disabled by default (`query_planning_enabled: false`). The `max_subqueries` cap (4) only applies when query planning is explicitly re-enabled. When disabled, the raw user query is embedded directly as a single subquery\n- Final retained results are capped at 200 unique items per domain before merging\n- `OPENAI_API_KEY` is required for embedding generation\n- `KIMI_API_KEY` or `GROK_API_KEY` is required for report synthesis (and for query planning only when explicitly enabled)\n- **Time window**: Semantic search uses a separate, higher limit from `/analyze`. Default max is 720h (30 days), configured via `max_semantic_search_window_hours` in `analysis_config`. The analyze endpoint uses `max_analysis_window_hours` (default 24h). If `hours` exceeds the max, the response `warning` field indicates the cap was applied\n- **Timeout**: Jobs that do not complete within 5 minutes (300 seconds) are automatically marked as `failed` with the error `\"Semantic search timed out after 300s\"`. The `processing_step` field in the status response helps track progress before timeout\n\n## Telegram and Backfill Notes\n\n- Telegram command: `/semantic_search <hours> <topic>`\n- Historical embedding backfill for News content:\n\n```bash\nuv run python -m crypto_news_analyzer.main --mode embedding-backfill --config ./config.jsonc --batch-size 100\n```\n\nOptional: add `--limit 1000` to process only part of the backlog.\n\n- To also backfill Intelligence embeddings:\n\n```bash\nuv run python -m crypto_news_analyzer.main --mode embedding-backfill --include-intelligence --intelligence-days 7 --config ./config.jsonc --batch-size 100\n```\n\n## Updating\n\nCanonical sources for this reference:\n\n1. `crypto_news_analyzer/api_server.py`\n2. `crypto_news_analyzer/models.py`\n3. `crypto_news_analyzer/semantic_search/models.py` (UnifiedSemanticSearchHit DTO, source_breakdown contracts)\n4. `crypto_news_analyzer/domain/models.py`\n5. `docs/SEMANTIC_SEARCH_API_GUIDE.md`\n6. `migrations/postgresql/012_intelligence_embedding_schema.sql` (HNSW index creation)\n7. `tests/test_api_server_semantic_search.py`\n8. `tests/test_semantic_search_contracts.py`\n9. `tests/shared/test_openclaw_skill_smart_news.py`\n\nWhen sources disagree, trust code and tests over prose.\n\nFile v0.4.6:skill-card.md\n\n## Description:\n\nSmart News helps agents call the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, and health checks from OpenClaw.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[laceletho](https://clawhub.ai/user/laceletho)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal developers and operators use Smart News to authenticate to the Crypto News Analyzer API, run crypto-news analysis and semantic search jobs, manage news and intelligence datasources, and maintain intelligence topics.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: An agent using this skill can call an authenticated external API with a bearer token.\n\nMitigation: Install only when the operator intends to grant that API access, and configure API_KEY only in the intended runtime environment.\n\nRisk: Delete, archive, prompt-edit, topic-confirm, merge, and datasource association endpoints can change stored API state.\n\nMitigation: Require explicit operator confirmation before using state-changing or lifecycle-changing endpoints.\n\nRisk: Telegram and V2EX datasources may collect or process third-party channel or forum content.\n\nMitigation: Configure only sources the operator is authorized to collect and process.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/laceletho/skills/smart-news)\n- [Crypto News Analyzer API](https://news.tradao.xyz)\n- [Analyze Workflow Reference](references/analyze-workflow.md)\n- [Semantic Search Reference](references/semantic-search.md)\n- [Datasource Management Reference](references/datasource-management.md)\n- [Intelligence Query Reference](references/intelligence-query.md)\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md)\n\n## Skill Output:\n\n**Output Type(s):** [guidance, markdown, shell commands, configuration]\n\n**Output Format:** [Markdown guidance with JSON and bash/curl examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires a configured bearer token in API_KEY; API responses may include JSON job metadata and Markdown reports.]\n\n## Skill Version(s):\n\n0.4.6 (source: server release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.4.5: 8 files, 23462 bytes\n\nFiles: references/analyze-workflow.md (9505b), references/datasource-management.md (11331b), references/intelligence-query.md (15230b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (8242b), skill-card.md (2213b), SKILL.md (12719b), _meta.json (129b)\n\nFile v0.4.5:SKILL.md\n\n---\nname: smart-news\ndescription: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks from OpenClaw.\nmetadata: { openclaw: { skillKey: smart-news, primaryEnv: API_KEY } }\n---\n\n# Crypto News HTTP API Skill\n\nUse this skill to call the Crypto News Analyzer HTTP API from OpenClaw.\n\n## When to Use\n\nUse this skill when you need to call `https://news.tradao.xyz` or a compatible private deployment.\n\nTypical triggers:\n\n- Run asynchronous crypto news analysis over a time window\n- Run asynchronous unified semantic search (News + Intelligence) for a freeform topic query\n- Poll an API job until it finishes and then fetch the final result\n- Create, list, or delete datasources through the HTTP API\n- Query and manage intelligence topics through the topic-first API (create, revise, confirm, merge findings, detail, list, pause, archive)\n- View and manage topic-datasource associations (get, set, add, remove) to scope topic research\n- List intelligence topic research run logs per-topic or globally\n- Check service health before or after an API workflow\n\n## Quick Reference\n\nAuthentication is Bearer token style: send `Authorization: Bearer <API_KEY>` with every request.\n\n`POST /analyze` creates a job and returns immediately. It does **not** return the final report. Poll status, then fetch the result.\n\nWorkflow: `POST /analyze` -> `GET /analyze/{job_id}` -> `GET /analyze/{job_id}/result`\n\nJobs move through these states: `queued`, `running`, `completed`, `failed`.\n\n`POST /semantic-search` creates a job, returns `202 Accepted`, and includes `status_url`, `result_url`, plus a `Retry-After` header. When `hours` exceeds the server max (720h default), a `warning` field describes the truncation. Semantic search jobs that do not complete within 5 minutes are automatically failed with a timeout error.\n\nSemantic workflow: `POST /semantic-search` -> `GET /semantic-search/{job_id}` -> `GET /semantic-search/{job_id}/result`\n\nUnified semantic search retrieves from both `content_items` and `raw_intelligence_items` via PostgreSQL with pgvector HNSW indexes (`embedding vector(1536)`). SQLite runtime is unsupported.\n\nFor detailed guides, see:\n\n- [Analyze Workflow Reference](references/analyze-workflow.md)\n- [Semantic Search Reference](references/semantic-search.md)\n- [Datasource Management Reference](references/datasource-management.md)\n- [Intelligence Query Reference](references/intelligence-query.md)\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md)\n\n## OpenClaw Runtime\n\nThis skill declares `metadata.openclaw.primaryEnv: API_KEY`. In OpenClaw, inject the bearer token through `~/.openclaw/openclaw.json`:\n\n```json5\n{\n  skills: {\n    entries: {\n      \"smart-news\": {\n        enabled: true,\n        apiKey: \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\n```\n\nIf `apiKey` is unavailable, do not send unauthenticated requests. Ask the operator to configure the token first.\n\nIf you are using a non-production deployment, replace `https://news.tradao.xyz` with the correct base URL before issuing requests.\n\n## Analyze Workflow\n\nCreate an analysis job by posting to `/analyze` with `hours` and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`. Do not expect the analysis report in the initial POST response. Once completed, fetch the result URL.\n\n## Semantic Search\n\nUnified semantic search retrieves from both News (`content_items`) and Intelligence (`raw_intelligence_items`) domains via UNION ALL over pgvector HNSW indexes. The response includes a `source_breakdown` with per-domain `matched_count` and `retained_count`. Each hit carries a `source_domain` discriminator (`\"news\"` or `\"intelligence\"`).\n\nCreate a semantic search job by posting to `/semantic-search` with `hours`, `query`, and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`. Semantic search job IDs start with `semantic_search_job_`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`, then fetch the report from the result URL. Use the `status` field as the source of truth for lifecycle state; `success` becomes `true` only when the job is completed successfully.\n\nRequest rules:\n\n- `hours` must be a positive integer\n- `query` is required, trimmed, and capped at 300 characters\n- `query` cannot be blank or whitespace-only\n- `user_id` must match `^[A-Za-z0-9_-]{1,128}$`\n\nOperational constraints:\n\n- Semantic search is PostgreSQL-only and returns `503` when the backend does not support pgvector\n- Both `content_items` and `raw_intelligence_items` tables have `embedding vector(1536)` columns with HNSW indexes (`idx_content_embedding_hnsw` and `idx_intelligence_embedding_hnsw`)\n- The API uses vector similarity over stored content embeddings and combines that with deterministic local keyword fallback (no LLM-driven keyword expansion)\n- LLM query decomposition is disabled by default (`query_planning_enabled: false`); when disabled the raw user query is embedded directly as the only subquery. The `max_subqueries` cap (4) only applies when query planning is explicitly re-enabled\n- Final retained results are capped at 200 unique items per domain before merging\n- Embedding generation requires `OPENAI_API_KEY`; report synthesis requires `KIMI_API_KEY` or `GROK_API_KEY` (query planning also requires an LLM key but is disabled by default)\n\nThe result body returns a Markdown report with `query`, `normalized_intent`, `matched_count`, `retained_count`, `time_window_hours`, `source_breakdown`, and `report`.\n\n## Datasource Management\n\nConfigure news and intelligence sources through the datasource API. Create sources with `POST /datasources`, list them with `GET /datasources`, and remove them with `DELETE /datasources/{id}`. All datasource routes require Bearer auth.\n\nEach datasource has a `purpose` field: `news` (RSS/X/REST feeds for analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `GET /datasources` endpoint supports optional `purpose` and `source_type` query parameters for filtering. Results are sorted by purpose, source type, then name.\n\nTags help organize sources. Each datasource accepts up to 16 unique tags. Each tag is capped at 32 characters. Tags are normalized to lowercase and deduplicated automatically.\n\nList and create responses include only safe summaries. For `rest_api` type datasources, secrets are redacted and counts replace raw credential fields. This prevents accidental credential exposure when reviewing configurations.\n\n## Intelligence Query (Topic-First)\n\nAll intelligence routes require Bearer auth. The deprecated entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`) have been removed in the topic-only refactor. Topics are the sole first-class intelligence objects, driving scheduled LLM research from raw ingested messages and storing findings with merge support.\n\nSynchronous topic workflow endpoints:\n\n- `POST /intelligence/topics` — Create a topic draft from a user theme (returns AI-generated prompt draft)\n- `POST /intelligence/topics/{topic_id}/revise` — Revise the draft prompt with feedback\n- `PUT /intelligence/topics/{topic_id}/prompt` — Manually set/replace the prompt text (context-aware: edits active prompt if one exists, otherwise creates draft revision)\n- `POST /intelligence/topics/{topic_id}/confirm` — Confirm and activate the topic for research (requires `prompt_version_id`)\n- `GET /intelligence/topics` — List topics with pagination and `active_only` filter (default: true)\n- `GET /intelligence/topics/{topic_id}` — Get topic metadata and merge availability\n- `GET /intelligence/topics/{topic_id}/findings` — Get paginated active findings with citations and source URLs\n- `GET /intelligence/topics/{topic_id}/prompts` — Get prompt versions and current active prompt\n- `POST /intelligence/topics/{topic_id}/pause` — Pause topic research\n- `POST /intelligence/topics/{topic_id}/archive` — Archive a topic\n- `GET /intelligence/topics/{topic_id}/runs` — List topic research run logs\n- `GET /intelligence/topic-runs` — List all topic research runs globally\n\nThese endpoints are synchronous; there is no async job/poll flow. Results return immediately.\n\nAsync topic merge endpoint:\n\n- `POST /intelligence/topics/{topic_id}/merge` — Start an async merge job (returns 202 Accepted with `job_id`, `status_url`, `result_url`)\n- `GET /intelligence/topics/{topic_id}/merge/{job_id}` — Check merge job status\n- `GET /intelligence/topics/{topic_id}/merge/{job_id}/result` — Retrieve completed merge results\n\nMerge workflow: `POST /intelligence/topics/{id}/merge` → poll `GET .../merge/{job_id}` → `GET .../merge/{job_id}/result`. Jobs move through states: `queued`, `running`, `completed`, `failed`. The merge LLM call may take several minutes, so polling is required — do not block on the POST response.\n\nTopics have lifecycle states: `draft`, `active`, `paused`, `archived`. Only `active` topics are researched by the ingestion scheduler. Finding merge is available through both the async HTTP endpoint and the Telegram `/topic_merge` command.\n\n## Telegram Webhook\n\nThe webhook endpoint exists for maintainer-level Telegram integration. It is not the primary path for day-to-day operators. Regular users should interact through the API routes or Telegram slash commands instead.\n\nWhen processing webhook updates, validate the `X-Telegram-Bot-Api-Secret-Token` header to confirm the request originates from Telegram.\n\n## Endpoint Index\n\nSupported HTTP routes:\n\n- `GET /health` - Service health check\n- `POST /analyze` - Create an analysis job (async, returns 202)\n- `GET /analyze/{job_id}` - Check job status\n- `GET /analyze/{job_id}/result` - Retrieve completed job results\n- `POST /semantic-search` - Create a semantic search job (async, returns 202)\n- `GET /semantic-search/{job_id}` - Check semantic search job status\n- `GET /semantic-search/{job_id}/result` - Retrieve completed semantic search results\n- `POST /datasources` - Create a datasource\n- `GET /datasources` - List all datasources\n- `DELETE /datasources/{id}` - Delete a datasource\n- `POST /telegram/webhook` - Telegram webhook receiver\n- `POST /intelligence/topics` - Create topic draft (synchronous, Bearer-protected)\n- `POST /intelligence/topics/{id}/revise` - Revise topic prompt\n- `PUT /intelligence/topics/{id}/prompt` - Manually set topic prompt\n- `POST /intelligence/topics/{id}/confirm` - Confirm and activate topic\n- `GET /intelligence/topics` - List topics with status filters\n- `GET /intelligence/topics/{id}` - Get topic metadata and merge availability\n- `GET /intelligence/topics/{id}/findings` - Get paginated findings with citations\n- `GET /intelligence/topics/{id}/prompts` - Get prompt versions and active prompt\n- `POST /intelligence/topics/{id}/pause` - Pause topic\n- `POST /intelligence/topics/{id}/archive` - Archive topic\n- `POST /intelligence/topics/{id}/merge` - Start async merge job (returns 202)\n- `GET /intelligence/topics/{id}/merge/{job_id}` - Check merge job status\n- `GET /intelligence/topics/{id}/merge/{job_id}/result` - Retrieve completed merge results\n- `GET /intelligence/topics/{id}/datasources` - List datasource associations for a topic\n- `PUT /intelligence/topics/{id}/datasources` - Replace all datasource associations atomically\n- `POST /intelligence/topics/{id}/datasources/{datasource_id}` - Add a datasource association (idempotent)\n- `DELETE /intelligence/topics/{id}/datasources/{datasource_id}` - Remove a datasource association (idempotent)\n- `GET /intelligence/topics/{id}/runs` - List topic research run logs\n- `GET /intelligence/topic-runs` - List all topic research runs globally\n\n## Non-Goals\n\nThis skill does not cover:\n\n- Telegram slash commands (use the Telegram bot directly)\n- Autogenerated documentation routes (`/docs`, `/redoc`, `/openapi.json`)\n- Deprecated compatibility aliases (`api-server`, `crypto-news-api`)\n- Direct embedding backfill operations beyond pointing you to the documented command\n\nThese surfaces exist but are intentionally excluded from this API-focused skill.\n\n## Updating\n\nKeep this skill aligned with the live HTTP routes in `api_server.py`, the AI Analyze API Guide at `docs/AI_ANALYZE_API_GUIDE.md`, the semantic search guide at `docs/SEMANTIC_SEARCH_API_GUIDE.md`, and the domain repository contracts in `domain/repositories.py`.\n\nWhen documentation disagrees with implementation, trust the code and tests over prose docs. Source precedence: code first, then reference files, then guides.\n\nFile v0.4.5:_meta.json\n\n{\n  \"ownerId\": \"kn70n844xnvgcz0zzja942av2184cjj5\",\n  \"slug\": \"smart-news\",\n  \"version\": \"0.4.5\",\n  \"publishedAt\": 1780583525864\n}\n\nFile v0.4.5:references/analyze-workflow.md\n\n# Analyze Workflow Reference\n\nThe analyze workflow is the primary way to trigger cryptocurrency news analysis via HTTP API. This reference documents the three-step async pattern: create job, poll status, fetch result.\n\n## Authentication\n\nAll analyze endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nThe `API_KEY` is configured via the `API_KEY` environment variable on the server. Requests without a valid token receive HTTP 401.\n\n## Overview\n\nThe analyze workflow follows an asynchronous pattern:\n\n1. **Create**: POST to `/analyze` with `hours` and `user_id` to enqueue a job\n2. **Poll**: GET `/analyze/{job_id}` to check status until completion\n3. **Fetch**: GET `/analyze/{job_id}/result` to retrieve the final Markdown report\n\nThe initial POST returns immediately with job metadata. It does not return the analysis report. You must poll and fetch separately.\n\n## Creating an Analysis Job\n\n### Endpoint\n\n```\nPOST /analyze\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Analysis time window in hours. Values below server minimum return HTTP 400. Values above maximum are capped to the configured limit (default 24h) and the response includes a `warning` field. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Example Request\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'\n```\n\n### Success Response (HTTP 202 Accepted)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"queued\",\n  \"time_window_hours\": 1,\n  \"status_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"result_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\",\n  \"warning\": null\n}\n```\n\nResponse headers include:\n\n- `Location`: Path to status endpoint\n- `Retry-After`: Recommended polling interval in seconds (typically 5)\n\n### Validation Errors\n\n| Condition | HTTP Status | Notes |\n|-----------|-------------|-------|\n| Missing `user_id` | 422 | FastAPI validation error with field location |\n| Invalid `user_id` (spaces, punctuation, non-ASCII, >128 chars) | 422 | Must match `^[A-Za-z0-9_-]{1,128}$` |\n| `hours <= 0` | 422 | Positive integer required |\n| `hours` below server minimum | 400 | Configurable minimum (default 1) |\n\nExample validation error:\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"user_id\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"hours\": 1}\n    }\n  ]\n}\n```\n\n## Polling Job Status\n\n### Endpoint\n\n```\nGET /analyze/{job_id}\n```\n\n### Example Request\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\"\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The job identifier |\n| `status` | string | Current job state (see Job States below) |\n| `time_window_hours` | integer | Hours requested (after server caps applied) |\n| `created_at` | string (ISO 8601) | Job creation timestamp |\n| `started_at` | string (ISO 8601) or null | When execution began |\n| `completed_at` | string (ISO 8601) or null | When execution finished |\n| `items_processed` | integer | Number of news items analyzed |\n| `error` | string or null | Error message if failed |\n| `result_available` | boolean | `true` when status is `completed` or `failed` |\n\n### Response Examples\n\n**Running job:**\n\n```json\n{\n  \"success\": false,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"running\",\n  \"time_window_hours\": 1,\n  \"created_at\": \"2026-03-28T12:00:00+00:00\",\n  \"started_at\": \"2026-03-28T12:00:03+00:00\",\n  \"completed_at\": null,\n  \"items_processed\": 0,\n  \"error\": null,\n  \"result_available\": false\n}\n```\n\nNote: `success: false` during `running` or `queued` states is expected. Use the `status` field as the source of truth, not the `success` boolean.\n\n**Completed job:**\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"completed\",\n  \"time_window_hours\": 1,\n  \"created_at\": \"2026-03-28T12:00:00+00:00\",\n  \"started_at\": \"2026-03-28T12:00:03+00:00\",\n  \"completed_at\": \"2026-03-28T12:01:15+00:00\",\n  \"items_processed\": 25,\n  \"error\": null,\n  \"result_available\": true\n}\n```\n\n## Fetching the Result\n\n### Endpoint\n\n```\nGET /analyze/{job_id}/result\n```\n\n### Example Request\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\"\n```\n\n### Behavior by Job State\n\n| Job State | HTTP Status | Response |\n|-----------|-------------|----------|\n| `queued` or `running` | 200 | Job metadata with empty `report` |\n| `completed` | 200 | Full result with Markdown `report` |\n| `failed` | 200 | Job metadata with `error` field set |\n| Job not found | 404 | Error detail |\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when job completed successfully |\n| `job_id` | string | The job identifier |\n| `status` | string | Final job state |\n| `report` | string | Markdown-formatted analysis report (empty if not completed) |\n| `items_processed` | integer | Number of news items analyzed |\n| `time_window_hours` | integer | Hours analyzed |\n| `error` | string or null | Error message if job failed |\n\n### Completed Result Example\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"completed\",\n  \"report\": \"# Crypto News Analysis Report\\n\\n## Executive Summary...\",\n  \"items_processed\": 25,\n  \"time_window_hours\": 1,\n  \"error\": null\n}\n```\n\n## Job States\n\nJobs progress through the following states:\n\n| State | Description | Terminal |\n|-------|-------------|----------|\n| `queued` | Job created, waiting for execution slot | No |\n| `running` | Actively analyzing news items | No |\n| `completed` | Analysis finished successfully | Yes |\n| `failed` | Analysis failed with error | Yes |\n\nState transitions: `queued` -> `running` -> (`completed` or `failed`)\n\n## Complete Workflow Example\n\n```bash\n#!/bin/bash\n\nAPI_KEY=\"your-api-key\"\nBASE_URL=\"https://news.tradao.xyz\"\nUSER_ID=\"my_agent_01\"\n\n# 1. Create the job\nCREATE_RESPONSE=$(curl -sS -X POST \"${BASE_URL}/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"hours\\\":1,\\\"user_id\\\":\\\"${USER_ID}\\\"}\")\n\nJOB_ID=$(echo \"${CREATE_RESPONSE}\" | sed -n 's/.*\"job_id\":\"\\([^\"]*\\)\".*/\\1/p')\necho \"Created job: ${JOB_ID}\"\n\n# 2. Poll until completion\nwhile true; do\n  STATUS_RESPONSE=$(curl -sS \\\n    -H \"Authorization: Bearer ${API_KEY}\" \\\n    \"${BASE_URL}/analyze/${JOB_ID}\")\n\n  STATUS=$(echo \"${STATUS_RESPONSE}\" | sed -n 's/.*\"status\":\"\\([^\"]*\\)\".*/\\1/p')\n  echo \"Status: ${STATUS}\"\n\n  if [ \"${STATUS}\" = \"completed\" ]; then\n    # 3. Fetch the result\n    curl -sS \\\n      -H \"Authorization: Bearer ${API_KEY}\" \\\n      \"${BASE_URL}/analyze/${JOB_ID}/result\"\n    break\n  fi\n\n  if [ \"${STATUS}\" = \"failed\" ]; then\n    echo \"Job failed\"\n    exit 1\n  fi\n\n  sleep 5\ndone\n```\n\n## Key Gotchas\n\n1. **The initial POST does not return the report**: Always poll status and fetch result separately. The 202 response only confirms job acceptance.\n\n2. **Hours is required**: Unlike the Telegram `/analyze` command which can auto-calculate a time window, the HTTP API requires explicit `hours` in every request.\n\n3. **User ID has strict validation**: Must be 1-128 characters, alphanumeric plus underscores and hyphens only. No spaces, no special characters, no Unicode.\n\n4. **Success field semantics**: The `success` boolean in status and result responses reflects job completion state, not HTTP success. It is `false` while the job is `queued` or `running`. Check the `status` field for the actual job state.\n\n5. **HTTP 202 means accepted**: On the create endpoint, 202 means \"accepted and processing\". The result endpoint always returns HTTP 200 (with an empty `report` field while the job is still running). Note: some older documentation may mention 202 for the result endpoint, but the current implementation returns 200 for all job states; 404 is only returned when the job ID does not exist.\n\n6. **Header case sensitivity**: Cloudflare and some proxies lowercase header names. The `Location` and `Retry-After` headers may appear as `location` and `retry-after`.\n\n7. **Hours capping**: If you request more hours than the server allows (`max_analysis_window_hours`, default 24), the request succeeds but `time_window_hours` in the response reflects the capped value, not your original request. A `warning` field in the response describes the truncation when it occurs.\n\n8. **User isolation**: Each `user_id` has isolated deduplication context. The same user calling analyze twice will see deduplication of previously reported items. Different users do not share context.\n\n## Updating\n\nKeep this reference aligned with:\n\n- `crypto_news_analyzer/api_server.py` for endpoint implementation details\n- `crypto_news_analyzer/domain/models.py` for `JobStatus` enum values\n- `tests/test_api_server.py` for contract test coverage\n\nWhen the live API behavior diverges from this document, the code and tests take precedence.\n\nFile v0.4.5:references/datasource-management.md\n\n# Datasource Management Reference\n\nThis document describes the HTTP API surface for managing datasources. All datasource routes require Bearer authentication.\n\n## CRUD Routes\n\n### POST /datasources\n\nCreates a new datasource. Returns `201 Created` on success, `409 Conflict` if a datasource with the same type and name already exists, and `422 Unprocessable Entity` for invalid payloads.\n\n**Request body structure:**\n```json\n{\n  \"purpose\": \"news|intelligence\",\n  \"source_type\": \"rss|x|rest_api\",\n  \"tags\": [\"tag1\", \"tag2\"],\n  \"config_payload\": {\n    \"name\": \"My Source\",\n    ...\n  }\n}\n```\n\nThe `purpose` field determines which pipeline the datasource feeds: `news` (RSS/X/REST for content analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `name` field in the top-level request must match `config_payload.name` when both are provided.\n\n### GET /datasources\n\nLists all datasources sorted by purpose, source type, then name. Supports optional filtering by `purpose` and `source_type` query parameters. Returns `200 OK` with a list of datasource summaries.\n\n**Query Parameters:**\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `purpose` | string | No | Filter by `news` or `intelligence` |\n| `source_type` | string | No | Filter by datasource type (`rss`, `x`, etc.) |\n\n**Response structure:**\n```json\n{\n  \"success\": true,\n  \"datasources\": [\n    {\n      \"id\": \"uuid\",\n      \"name\": \"My Source\",\n      \"purpose\": \"news\",\n      \"source_type\": \"rss\",\n      \"tags\": [\"tag1\"],\n      \"config_summary\": {\n        ...\n      }\n    }\n  ]\n}\n```\n\nList responses always return safe summaries. For `rest_api` datasources, sensitive fields are redacted and replaced with counts.\n\n### DELETE /datasources/{id}\n\nDeletes a datasource by its UUID. Returns `204 No Content` on success, `404 Not Found` if the datasource does not exist, and `409 Conflict` if the datasource has active ingestion jobs.\n\nThe delete operation will fail with `409 Conflict` if there are pending or running ingestion jobs associated with this datasource (matched by `source_type:source_name`).\n\n## Supported Datasource Types\n\nThe API supports five datasource types: `rss`, `x`, `rest_api`, `telegram_group`, and `v2ex`.\n\n`telegram_group` and `v2ex` feed the **hidden-channel intelligence pipeline** (raw collection → LLM extraction → canonical knowledge). They are not part of the news analysis pipeline and require the `openclaw+opencode` ingestion service with proper credentials.\n\n### rss\n\nRSS feed datasources crawl RSS/XML feeds.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (string, valid HTTP/HTTPS URL)\n\n**Optional config_payload fields:**\n- `description` (string, defaults to empty string)\n\n**Config summary in responses:**\n- `url`: The RSS feed URL\n- `description`: The description value\n\n### x\n\nX (formerly Twitter) datasources crawl X lists or timelines.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (string, valid HTTPS URL on x.com or www.x.com)\n- `type` (string, must be `\"list\"` or `\"timeline\"`)\n\n**Config summary in responses:**\n- `url`: The X URL\n- `type`: Either `\"list\"` or `\"timeline\"`\n\n### rest_api\n\nREST API datasources fetch content from arbitrary HTTP endpoints.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `endpoint` (string, valid HTTP/HTTPS URL)\n- `method` (string, one of: `GET`, `POST`, `PUT`, `DELETE`)\n- `response_mapping` (object) with required fields:\n  - `title_field` (string, non-empty)\n  - `content_field` (string, non-empty)\n  - `url_field` (string, non-empty)\n  - `time_field` (string, non-empty)\n\n**Optional config_payload fields:**\n- `headers` (object, defaults to empty object)\n- `params` (object, defaults to empty object)\n\n**Config summary in responses:**\n- `endpoint`: The API endpoint URL\n- `method`: The HTTP method\n- `response_mapping`: The full response mapping object\n- `header_count`: Number of headers (count only, values redacted)\n- `param_count`: Number of query params (count only, values redacted)\n\n## Tag Constraints\n\nTags on datasources follow strict normalization and validation rules:\n\n**Normalization:**\n- Tags are converted to lowercase\n- Leading and trailing whitespace is stripped\n- Empty tags after trimming are discarded\n- Tags are sorted alphabetically\n- Duplicate tags are removed\n\n**Limits:**\n- Maximum 16 unique tags per datasource\n- Each tag must be at most 32 characters after normalization\n\n**Validation errors:**\n- Exceeding 16 unique tags returns `422 Unprocessable Entity` with message: \"tags cannot contain more than 16 unique values\"\n- Any tag exceeding 32 characters returns `422 Unprocessable Entity` with message: \"each tag must be at most 32 characters\"\n\nExample: The tags `[\" Markets \", \"markets\", \"Layer2\"]` normalize to `[\"layer2\", \"markets\"]`.\n\n## Safe Summaries and Secret Redaction\n\nAll datasource responses (create and list) return safe summaries instead of the full config payload. This prevents accidental exposure of sensitive credentials.\n\n### rss and x Summaries\n\nFor RSS and X datasources, the config summary includes the URL and type-specific fields without modification.\n\n### rest_api Redaction\n\nFor `rest_api` datasources, the following redaction rules apply:\n\n- The `headers` object is replaced with `header_count` (integer)\n- The `params` object is replaced with `param_count` (integer)\n- The actual header names, parameter names, and their values are never returned\n- The `endpoint`, `method`, and `response_mapping` are returned as-is (these are not secrets)\n\nThis ensures that API keys, bearer tokens, and other credentials stored in headers or params remain secret while still allowing clients to understand the datasource configuration.\n\nExample redacted response for a rest_api datasource:\n```json\n{\n  \"id\": \"uuid\",\n  \"name\": \"News API\",\n  \"purpose\": \"news\",\n  \"source_type\": \"rest_api\",\n  \"tags\": [],\n  \"config_summary\": {\n    \"endpoint\": \"https://api.example.com/news\",\n    \"method\": \"GET\",\n    \"response_mapping\": {\n      \"title_field\": \"title\",\n      \"content_field\": \"body\",\n      \"url_field\": \"url\",\n      \"time_field\": \"published_at\"\n    },\n    \"header_count\": 1,\n    \"param_count\": 2\n  }\n}\n```\n\n## Delete Conflict Behavior\n\nDeleting a datasource can fail with `409 Conflict` in the following scenarios:\n\n**Active Ingestion Jobs:**\nIf there are ingestion jobs for this datasource (matched by `source_type:source_name`) with status `\"pending\"` or `\"running\"`, the delete operation is rejected.\n\n**Error response:**\n```json\n{\n  \"detail\": \"Cannot delete datasource 'rss:CoinDesk' while matching ingestion jobs are active\"\n}\n```\n\n**Topic-Datasource Associations:**\nIf the datasource is associated with any intelligence topic via `intelligence_topic_datasources`, the delete operation is rejected. All associations must be removed first.\n\n**Error response:**\n```json\n{\n  \"detail\": \"Datasource 'ds-uuid' is associated with 3 topic(s) and must be unbound first\"\n}\n```\n\nTo delete a datasource with topic associations, either use the topic datasource API to remove the associations, or clear all associations for each topic before deleting the datasource.\n\n## Intelligence Datasource Types\n\nThe following source types feed the hidden-channel intelligence pipeline. They store raw text (30-day TTL) and produce canonical knowledge entries through LLM extraction. All secrets **must** be provided via environment variables — never inlined in the config payload.\n\n### telegram_group\n\nCollects messages from allowlisted Telegram chats using Telethon MTProto.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `chat_id` (string) **or** `chat_username` (string, with `@` prefix)\n\n**Config payload example (by username):**\n```json\n{\n  \"source_type\": \"telegram_group\",\n  \"config_payload\": {\n    \"name\": \"Crypto Alpha\",\n    \"chat_username\": \"@cryptoalpha\"\n  }\n}\n```\n\n**Config payload example (by chat ID):**\n```json\n{\n  \"source_type\": \"telegram_group\",\n  \"config_payload\": {\n    \"name\": \"Private Group\",\n    \"chat_id\": \"-1001234567890\"\n  }\n}\n```\n\n**Constraints:**\n- Must provide exactly one of `chat_id` or `chat_username` — not both, not neither\n- Cannot enumerate all joined chats; each datasource targets a single explicitly configured chat\n- No session strings, API hashes, passwords, or tokens in the payload — those come from environment variables (`TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `TELEGRAM_STRING_SESSION`)\n\n**Production checklist:**\n1. The server must have `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, and `TELEGRAM_STRING_SESSION` set in environment\n2. The Telegram account used for the session must have joined the target chat\n3. The account must not have 2FA enabled unless the session was generated with it\n\n### v2ex\n\nCollects topics and replies from V2EX nodes using the official API.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `api_version` (string, `\"v1\"` or `\"v2\"`)\n- `node_allowlist` (array of strings, at least one node name)\n\n**Config payload example (v1, no auth):**\n```json\n{\n  \"source_type\": \"v2ex\",\n  \"config_payload\": {\n    \"name\": \"V2EX Crypto & AI\",\n    \"api_version\": \"v1\",\n    \"node_allowlist\": [\"crypto\", \"openai\"]\n  }\n}\n```\n\n**Config payload example (v2, with PAT):**\n```json\n{\n  \"source_type\": \"v2ex\",\n  \"config_payload\": {\n    \"name\": \"V2EX Tech\",\n    \"api_version\": \"v2\",\n    \"node_allowlist\": [\"programmer\"],\n    \"pat_env_var_name\": \"V2EX_PAT\"\n  }\n}\n```\n\n**Constraints:**\n- `api_version` must be `\"v1\"` or `\"v2\"` — no HTML/CSS scraping\n- `node_allowlist` must be a non-empty array of non-empty strings\n- v2 requires `pat_env_var_name` pointing to an environment variable (not the PAT value itself)\n- v1 is public and requires no authentication\n- Node names are the URL path after `/go/`, e.g. `https://www.v2ex.com/go/crypto` → `\"crypto\"`\n\n**Rate limits:**\n- v1: 120 requests/hour per IP (used for both topics and replies)\n- v2: varies by PAT tier\n- The crawler tracks `X-Rate-Limit-Remaining` headers and pauses when exhausted\n\n## Error Reference\n\n| Status Code | Scenario | Detail Message Pattern |\n|-------------|----------|------------------------|\n| 201 | Create success | N/A (returns datasource) |\n| 204 | Delete success | N/A (empty body) |\n| 200 | List success | N/A (returns list) |\n| 401 | Missing or invalid API key | \"Invalid API key\" |\n| 404 | Datasource not found | \"Datasource not found\" |\n| 409 | Duplicate datasource | \"Datasource 'type:name' already exists\" |\n| 409 | Datasource in use | \"Cannot delete datasource 'type:name' while matching ingestion jobs are active\" |\n| 409 | Datasource associated with topics | \"Datasource 'id' is associated with N topic(s) and must be unbound first\" |\n| 422 | Invalid payload structure | Pydantic validation error details |\n| 422 | Invalid semantic payload | e.g., \"x.type must be one of: list, timeline\" |\n| 422 | Tag limit exceeded | \"tags cannot contain more than 16 unique values\" |\n| 422 | Tag too long | \"each tag must be at most 32 characters\" |\n| 500 | Internal server error | Exception message |\n\n## Updating\n\nKeep this reference aligned with `crypto_news_analyzer/api_server.py` and `crypto_news_analyzer/datasource_payloads.py`. When the implementation changes, update this document to reflect the current validation rules, redaction behavior, and error responses.\n\nFile v0.4.5:references/intelligence-query.md\n\n# Intelligence Query Reference\n\nTopic-first intelligence HTTP API. All endpoints require Bearer authentication and manage the topic research lifecycle (create → revise → confirm → research → merge → archive).\n\nThese endpoints are synchronous — results return immediately. Do not use an async job/poll workflow for intelligence routes.\n\n## Authentication\n\nSend `Authorization: Bearer <API_KEY>` with every request. Missing or invalid credentials return `401 Unauthorized`.\n\n## Topic Lifecycle\n\nTopics progress through states: `draft` → `active` → `paused` / `archived`. Only `active` topics are researched by the ingestion scheduler. Merge previews expire after 24 hours. Finding merge is available through both the HTTP API and the Telegram `/topic_merge` command.\n\n## Deprecated Routes\n\nThe old entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`, `/intelligence/raw/*`, `/intelligence/topics/converge`) have been removed. Use only the topic-first endpoints documented below.\n\n---\n\n## POST /intelligence/topics\n\nCreate a new intelligence topic with an LLM-generated draft prompt.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `theme` | string | Yes | 1–500 characters |\n| `source_context` | object | No | Optional context for prompt generation |\n| `datasource_ids` | string[] | No | Optional list of datasource IDs to associate. Omitted = no associations. |\n\n### Status Codes\n\n| Code | Meaning |\n|------|---------|\n| `201` | Topic draft created |\n| `400` | Invalid theme or topic parameters |\n| `401` | Missing or invalid Bearer token |\n| `503` | LLM service unavailable |\n\n### Response (201)\n\nReturns a `TopicPromptVersionResponse`:\n\n```json\n{\n  \"id\": \"prompt-uuid\",\n  \"intelligence_topic_id\": \"topic-uuid\",\n  \"prompt_version\": \"v1.0\",\n  \"prompt_text\": \"LLM-generated research prompt...\",\n  \"schema_version\": \"v1.0\",\n  \"status\": \"draft\",\n  \"created_by\": \"api\",\n  \"activated_by\": null,\n  \"activation_notes\": null,\n  \"created_at\": \"2026-05-18T10:00:00+00:00\",\n  \"activated_at\": null,\n  \"archived_at\": null,\n  \"updated_at\": \"2026-05-18T10:00:00+00:00\",\n  \"audit_history\": []\n}\n```\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"theme\": \"crypto payment channels in Telegram groups\"}'\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/revise\n\nRevise the most recent draft prompt using LLM and user feedback. Returns a new prompt version.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `feedback` | string | Yes | 1–5000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse` with the revised prompt.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/revise\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"feedback\": \"Focus on stablecoin settlement, exclude NFT marketplaces\"}'\n```\n\n---\n\n## PUT /intelligence/topics/{topic_id}/prompt\n\nManually set or replace the topic prompt text. Context-aware behavior:\n- If an active prompt exists → edits it in place (new version with same activation)\n- If no active prompt → creates a draft revision\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `prompt_text` | string | Yes | 1–50000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse`.\n\n### Example\n\n```bash\ncurl -X PUT \"https://news.tradao.xyz/intelligence/topics/topic-uuid/prompt\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_text\": \"Custom manual research prompt text...\"}'\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/confirm\n\nConfirm a draft prompt version, activating it for scheduled research.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `prompt_version_id` | string | Yes | Must reference a draft prompt version |\n| `activation_notes` | string | No | Max 2000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse` with status `active`.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/confirm\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt_version_id\": \"prompt-uuid\", \"activation_notes\": \"Ready for daily research\"}'\n```\n\n---\n\n## GET /intelligence/topics\n\nList intelligence topics with pagination and filtering.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `active_only` | boolean | No | `true` | Filter to active topics only |\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 20 | Items per page |\n\n### Response (200)\n\n```json\n{\n  \"items\": [\n    {\n      \"id\": \"topic-uuid\",\n      \"name\": \"Stablecoin Settlement Channels\",\n      \"finding_count\": 5,\n      \"updated_at\": \"2026-05-18T06:30:00+00:00\"\n    }\n  ],\n  \"total\": 12,\n  \"page\": 1,\n  \"page_size\": 20\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics?active_only=true&page=1&page_size=20\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}\n\nGet topic metadata and merge availability. Findings and prompts are available via separate endpoints below.\n\n### Response (200)\n\n```json\n{\n  \"topic\": {\n    \"id\": \"topic-uuid\",\n    \"name\": \"Stablecoin Settlement Channels\",\n    \"is_active\": true,\n    \"updated_at\": \"2026-05-18T06:30:00+00:00\"\n  },\n  \"merge_available\": false\n}\n```\n\nReturns `404` if the topic ID does not exist.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/findings\n\nReturn paginated active findings with citations. Each citation includes a `source_url` (resolved from raw items when available) for direct linking to original messages.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\n```json\n{\n  \"findings\": [\n    {\n      \"id\": \"finding-uuid\",\n      \"intelligence_topic_id\": \"topic-uuid\",\n      \"prompt_version_id\": \"prompt-uuid\",\n      \"finding_payload\": { /* LLM-generated structured finding */ },\n      \"confidence\": 0.92,\n      \"citations\": [\n        {\n          \"message_id\": \"raw-uuid\",\n          \"message_snippet\": \"Original message text excerpt...\",\n          \"source\": \"telegram_group\",\n          \"published_at\": \"2026-05-18T05:00:00+00:00\",\n          \"source_url\": \"https://t.me/channel/123\"\n        }\n      ],\n      \"source_finding_ids\": [],\n      \"status\": \"active\",\n      \"found_at\": \"2026-05-18T06:00:00+00:00\",\n      \"created_at\": \"2026-05-18T06:00:00+00:00\",\n      \"updated_at\": \"2026-05-18T06:00:00+00:00\"\n    }\n  ],\n  \"total\": 5,\n  \"page\": 1,\n  \"page_size\": 10\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/findings?page=1&page_size=10\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/prompts\n\nReturn all prompt versions and the currently active prompt for a topic.\n\n### Response (200)\n\n```json\n{\n  \"current_prompt\": {\n    \"id\": \"prompt-uuid\",\n    \"intelligence_topic_id\": \"topic-uuid\",\n    \"prompt_version\": \"v1.0\",\n    \"prompt_text\": \"Research prompt text...\",\n    \"schema_version\": \"v1.0\",\n    \"status\": \"active\",\n    \"created_by\": \"api\",\n    \"activated_by\": \"api\",\n    \"activation_notes\": \"Ready for daily research\",\n    \"created_at\": \"2026-05-18T10:00:00+00:00\",\n    \"activated_at\": \"2026-05-18T10:05:00+00:00\",\n    \"archived_at\": null,\n    \"updated_at\": \"2026-05-18T10:05:00+00:00\",\n    \"audit_history\": []\n  },\n  \"prompt_versions\": [ /* all versions, same shape as current_prompt */ ]\n}\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/prompts\"\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/pause\n\nPause a topic, stopping further scheduled research runs.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"topic_id\": \"topic-uuid\",\n  \"lifecycle_status\": \"paused\",\n  \"updated_at\": \"2026-05-18T11:00:00+00:00\"\n}\n```\n\nReturns `404` if the topic ID does not exist.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/pause\" \\\n  -H \"Authorization: Bearer ${API_KEY}\"\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/archive\n\nArchive a topic, removing it from active research permanently.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"topic_id\": \"topic-uuid\",\n  \"lifecycle_status\": \"archived\",\n  \"updated_at\": \"2026-05-18T12:00:00+00:00\"\n}\n```\n\nReturns `404` if the topic ID does not exist.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/archive\" \\\n  -H \"Authorization: Bearer ${API_KEY}\"\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/merge\n\nStart an **async** merge job for active topic findings. The LLM call may take several minutes — this endpoint returns immediately with a `job_id`. Poll the status endpoint, then fetch the result once the job completes.\n\n### Response (202)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"queued\",\n  \"status_url\": \"/intelligence/topics/topic-uuid/merge/merge_job_abc123\",\n  \"result_url\": \"/intelligence/topics/topic-uuid/merge/merge_job_abc123/result\"\n}\n```\n\nReturns `400` if no active prompt found. Returns `404` if the topic ID does not exist. The `Location` response header points to the status URL.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge\" \\\n  -H \"Authorization: Bearer ${API_KEY}\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/merge/{job_id}\n\nCheck the status of a merge job. Jobs progress through `queued`, `running`, `completed`, and `failed` states.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"completed\",\n  \"created_at\": \"2026-06-04T10:00:00+00:00\",\n  \"started_at\": \"2026-06-04T10:00:01+00:00\",\n  \"completed_at\": \"2026-06-04T10:02:34+00:00\",\n  \"error\": null,\n  \"result_available\": true\n}\n```\n\n`result_available` is `true` when the job status is `completed` or `failed`. Returns `404` if the job or topic is not found.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge/merge_job_abc123\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/merge/{job_id}/result\n\nRetrieve the completed merge results. Only available after the job status is `completed` or `failed`.\n\n### Response (200)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"merge_job_abc123\",\n  \"topic_id\": \"topic-uuid\",\n  \"status\": \"completed\",\n  \"topic_name\": \"Stablecoin Settlement Channels\",\n  \"source_findings_count\": 5,\n  \"merged_findings_count\": 2,\n  \"source_citations_count\": 15,\n  \"merged_citations_count\": 8,\n  \"removed_citations_count\": 7,\n  \"summary\": \"Consolidated findings summary...\",\n  \"change_summary\": {}\n}\n```\n\nReturns `404` if the job is not found. The endpoint returns the current job state at any time — poll status with `result_available` to detect completion. Failed jobs return `success: false` with the error in the `error` field.\n\n### Workflow\n\n```\nPOST /intelligence/topics/{id}/merge  →  202 Accepted + job_id\n  ↓\nGET  /intelligence/topics/{id}/merge/{job_id}  →  poll until completed/failed\n  ↓\nGET  /intelligence/topics/{id}/merge/{job_id}/result  →  final merge results\n```\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/merge/merge_job_abc123/result\"\n```\n\n---\n\n## GET /intelligence/topics/{topic_id}/runs\n\nGet paginated research run logs for a specific topic. Each run includes the prompt version used, execution status, findings count, and timestamps.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\nReturns paginated list of `TopicResearchRun` objects.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topics/topic-uuid/runs?page=1&page_size=10\"\n```\n\n---\n\n## GET /intelligence/topic-runs\n\nGet paginated research run logs across all topics. Useful for monitoring overall research activity.\n\n### Query Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `page` | integer | No | 1 | Page number (1-based) |\n| `page_size` | integer | No | 10 | Items per page |\n\n### Response (200)\n\nReturns paginated list of `TopicResearchRun` objects across all topics.\n\n### Example\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/intelligence/topic-runs?page=1&page_size=10\"\n```\n\n---\n\n## Status Codes\n\n| Status | Meaning |\n|--------|---------|\n| `200` | Success |\n| `201` | Topic draft created |\n| `202` | Merge job accepted (async — poll for result) |\n| `400` | Invalid parameters or merge preview error |\n| `401` | Missing or invalid Bearer token |\n| `404` | Topic or resource not found |\n| `422` | FastAPI validation error |\n| `500` | Internal server error |\n| `503` | LLM service or repository not initialized |\n\n## Notes\n\n- Topic lifecycle endpoints are synchronous — results return immediately without polling.\n- The merge endpoint (`POST /intelligence/topics/{id}/merge`) is **async**: returns 202, then poll status → get result.\n- Only `active` topics receive scheduled research from the ingestion service.\n- Merge previews expire after 24 hours; accepting a stale preview is rejected.\n- Finding merge is available through both the async HTTP endpoint and the Telegram `/topic_merge` command. At least two active findings are required.\n- Prompt lifecycle: create draft → revise (optional) → confirm → active. Manual edits via `PUT /prompt` can shortcut this.\n- These endpoints exist only on `analysis-service` / `api-only` deployments. They are not available from `ingestion`.\n\n## Updating\n\nCanonical sources for this reference:\n\n1. `crypto_news_analyzer/api_server.py` — route definitions and response models\n2. `crypto_news_analyzer/intelligence/topic_prompts.py` — prompt workflow service\n3. `crypto_news_analyzer/intelligence/topic_findings.py` — findings and merge service\n4. `crypto_news_analyzer/domain/models.py` — `TopicLifecycleStatus` enum and `SafeDataSourceSummary`\n5. `crypto_news_analyzer/domain/repositories.py` — `IntelligenceRepository` datasource association contract\n6. `tests/intelligence/test_topic_datasource_api.py` — association API contract tests\n\nWhen sources disagree, trust code over prose.\n\nFile v0.4.5:references/operations-and-maintenance.md\n\n# Operations and Maintenance Reference\n\nThis reference covers operational surfaces and maintenance workflows for the crypto-news-analyzer HTTP API.\n\n## Health Endpoint\n\nThe service exposes a health check endpoint for load balancers and monitoring systems.\n\n- **Endpoint:** `GET /health`\n- **Response:** `{\"status\": \"healthy\", \"initialized\": true/false}`\n- **Use case:** Load balancer health checks, deployment verification, uptime monitoring\n\nThe `initialized` field indicates whether the underlying controller has completed startup initialization. A value of `false` may indicate the service is still starting or encountered an error during initialization.\n\n## Telegram Webhook\n\nThe Telegram webhook is a maintainer-only integration surface for receiving Telegram Bot updates via webhook delivery.\n\n### Webhook Route\n\n- **Endpoint:** `POST` to the path configured in `TELEGRAM_WEBHOOK_PATH` environment variable\n- **Default path:** `/telegram/webhook`\n- **Purpose:** Integration point for Telegram Bot API webhook delivery, not the primary user path\n\n### Authentication Header\n\nWebhook requests must include a secret token header for validation:\n\n- **Header:** `X-Telegram-Bot-Api-Secret-Token`\n- **Behavior:** The handler validates the token against the configured secret; mismatches return HTTP 403\n- **Error responses:**\n  - `403 Forbidden` - Invalid or missing secret token\n  - `503 Service Unavailable` - Runtime processing error (e.g., handler not ready)\n\n### Operational Notes\n\n- Operators and end users should prefer the HTTP API routes and Telegram slash commands instead of interacting directly with the webhook endpoint\n- The webhook is intended for infrastructure integration and bot delivery, not for manual invocation\n- Configure the webhook path via environment variable to match your deployment routing needs\n\n## Self-Update Workflow\n\nWhen updating this skill reference to match code changes, follow this workflow to ensure accuracy.\n\n### Canonical Source Files\n\nThese files contain the ground truth for HTTP behavior:\n\n1. **`crypto_news_analyzer/api_server.py`** - FastAPI route definitions, health endpoint, and webhook handler\n2. **`tests/test_api_server.py`** - Contract tests for endpoints, including webhook secret validation\n3. **`docs/AI_ANALYZE_API_GUIDE.md`** - Production verification notes and API usage guidance\n\n### Verification Commands\n\nBefore merge, run the full planned verification suite to verify the skill docs, the backing API contract, and the legacy-reference guardrails stay aligned:\n\n```bash\nuv run pytest tests/test_openclaw_skill_smart_news.py -v\nuv run pytest tests/test_api_server.py -k \"health or analyze or datasource or webhook\" -v\nuv run pytest tests/test_banned_legacy_reference_scan.py -v\nuv run python tests/helpers/banned_legacy_reference_scan.py\n```\n\nDo not merge until all four commands pass.\n\n### Release Command\n\nPublish a new ClawHub version from the repo root with:\n\n```bash\nskills/publish_clawhub_skill.sh smart-news 0.3.0 \"Describe the release briefly.\"\n```\n\nUse a semver version string. The script checks `clawhub` login state and runs the skill-specific test file before publishing unless `CLAWHUB_SKIP_TESTS=1` is set.\n\n### Update Steps\n\n1. Read the canonical source files to identify behavioral changes\n2. Update the relevant sections in this reference\n3. Run the full verification suite above before merge\n4. Address any failures before committing or merging\n\n### Source Precedence\n\nWhen documentation sources conflict, trust code and tests over prose. The live implementation in `api_server.py` and its test coverage in `test_api_server.py` are the authoritative references.\n\nFile v0.4.5:references/semantic-search.md\n\n# Semantic Search Reference\n\nThis document describes the asynchronous semantic search HTTP API for AI agents and operators.\n\n## Authentication\n\nAll semantic search endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nRequests without a valid token receive HTTP 401.\n\n## Overview\n\nUnified semantic search retrieves from both News (`content_items`) and Intelligence (`raw_intelligence_items`) domains via UNION ALL over pgvector HNSW indexes (`idx_content_embedding_hnsw` and `idx_intelligence_embedding_hnsw`). Both tables use `embedding vector(1536)` columns. Each hit carries a `source_domain` discriminator; the response includes a `source_breakdown` with per-domain `matched_count` and `retained_count`.\n\nSemantic search is asynchronous and follows the same three-step pattern as `/analyze`:\n\n1. **Create**: `POST /semantic-search` with `hours`, `query`, and `user_id`\n2. **Poll**: `GET /semantic-search/{job_id}` until the job reaches a terminal state\n3. **Fetch**: `GET /semantic-search/{job_id}/result` to retrieve the final Markdown report\n\nThe POST response returns immediately. It does not include the final report.\n\n## Request Contract\n\n### Endpoint\n\n```\nPOST /semantic-search\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Search time window in hours. Values below server minimum return HTTP 400. Values above the semantic search maximum (default 720h = 30d) are capped and the response includes a `warning` field. The semantic search hours cap is separate from the analyze cap (24h). |\n| `query` | string | non-blank, max 300 chars | Natural-language topic query for semantic retrieval. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Success Response (HTTP 202 Accepted)\n\nThe response body includes:\n\n- `success`\n- `job_id`\n- `status`\n- `time_window_hours`\n- `status_url`\n- `result_url`\n- `warning` (present when `hours` exceeds the max; `null` otherwise)\n\nResponse headers include:\n\n- `Location`\n- `Retry-After`\n\nJob IDs use the prefix `semantic_search_job_`.\n\n`query`, `normalized_intent`, `matched_count`, `retained_count`, and `source_breakdown` are only available on the status and result endpoints — they are `0`, empty, or `null` at acceptance time and are therefore excluded from the 202 response.\n\n## Job Status Contract\n\n### Endpoint\n\n```\nGET /semantic-search/{job_id}\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The semantic search job identifier |\n| `status` | string | Current state: `queued`, `running`, `completed`, or `failed` |\n| `query` | string | Original normalized query |\n| `normalized_intent` | string | Search intent (equals original query when `query_planning_enabled` is false, which is the default; LLM-normalized only when query planning is explicitly enabled) |\n| `matched_count` | integer | Total matched items before final retention |\n| `retained_count` | integer | Final retained items used for synthesis |\n| `source_breakdown` | object | Per-domain hit counts: `{\"news\": {\"matched_count\": N, \"retained_count\": M}, \"intelligence\": {\"matched_count\": N, \"retained_count\": M}}` |\n| `time_window_hours` | integer | Search time window after server caps |\n| `created_at` | string (ISO 8601) | Job creation timestamp |\n| `started_at` | string (ISO 8601) or null | When execution began |\n| `completed_at` | string (ISO 8601) or null | When execution finished |\n| `error` | string or null | Error message if failed |\n| `processing_step` | string or null | Current processing stage: `\"embedding\"`, `\"retrieving\"`, `\"ranking\"`, or `null`. Used to distinguish \"still processing\" from a stuck job. |\n| `result_available` | boolean | `true` when status is `completed` or `failed` |\n\nUse the `status` field as the source of truth, not the `success` boolean.\n\n## Result Contract\n\n### Endpoint\n\n```\nGET /semantic-search/{job_id}/result\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `success` | boolean | `true` only when `status` is `completed` |\n| `job_id` | string | The semantic search job identifier |\n| `status` | string | Terminal state: `completed` or `failed` |\n| `query` | string | Original query |\n| `normalized_intent` | string | Search intent (equals original query when `query_planning_enabled` is false, which is the default; LLM-normalized only when query planning is explicitly enabled) |\n| `matched_count` | integer | Total matched items |\n| `retained_count` | integer | Final retained items |\n| `source_breakdown` | object | Per-domain hit counts: `{\"news\": {\"matched_count\": N, \"retained_count\": M}, \"intelligence\": {\"matched_count\": N, \"retained_count\": M}}` |\n| `report` | string | Markdown semantic search report |\n| `time_window_hours` | integer | Search time window |\n| `error` | string or null | Error text when failed |\n\n## Report Structure\n\nThe Markdown report follows this structure:\n\n```markdown\n# Topic Search Report\n\n- Normalized intent: ...\n- Original query: ...\n- Time window: N hours\n- Matched items: N\n- Retained items: N\n\n## Key Signals\n\n### Signal 1\nConcise synthesized paragraph.\nSources: [Source Name](https://example.com/article)\n```\n\nThe live service currently returns the headings in Chinese (`# 主题检索报告`, `## 关键信号`). Treat the exact report string as implementation-defined content and preserve it as returned.\n\n## Limits and Dependencies\n\n- Requires PostgreSQL with pgvector; SQLite is unsupported\n- Both `content_items` and `raw_intelligence_items` tables require `embedding vector(1536)` columns with HNSW indexes (`idx_content_embedding_hnsw`, `idx_intelligence_embedding_hnsw`) for performance\n- Query length is capped at 300 characters\n- LLM query decomposition is disabled by default (`query_planning_enabled: false`). The `max_subqueries` cap (4) only applies when query planning is explicitly re-enabled. When disabled, the raw user query is embedded directly as a single subquery\n- Final retained results are capped at 200 unique items per domain before merging\n- `OPENAI_API_KEY` is required for embedding generation\n- `KIMI_API_KEY` or `GROK_API_KEY` is required for report synthesis (and for query planning only when explicitly enabled)\n- **Time window**: Semantic search uses a separate, higher limit from `/analyze`. Default max is 720h (30 days), configured via `max_semantic_search_window_hours` in `analysis_config`. The analyze endpoint uses `max_analysis_window_hours` (default 24h). If `hours` exceeds the max, the response `warning` field indicates the cap was applied\n- **Timeout**: Jobs that do not complete within 5 minutes (300 seconds) are automatically marked as `failed` with the error `\"Semantic search timed out after 300s\"`. The `processing_step` field in the status response helps track progress before timeout\n\n## Telegram and Backfill Notes\n\n- Telegram command: `/semantic_search <hours> <topic>` (canonical); `/news_semantic_search` is a deprecated alias\n- Historical embedding backfill for News content:\n\n```bash\nuv run python -m crypto_news_analyzer.main --mode embedding-backfill --config ./config.jsonc --batch-size 100\n```\n\nOptional: add `--limit 1000` to process only part of the backlog.\n\n- To also backfill Intelligence embeddings:\n\n```bash\nuv run python -m crypto_news_analyzer.main --mode embedding-backfill --include-intelligence --intelligence-days 7 --config ./config.jsonc --batch-size 100\n```\n\n## Updating\n\nCanonical sources for this reference:\n\n1. `crypto_news_analyzer/api_server.py`\n2. `crypto_news_analyzer/models.py`\n3. `crypto_news_analyzer/semantic_search/models.py` (UnifiedSemanticSearchHit DTO, source_breakdown contracts)\n4. `crypto_news_analyzer/domain/models.py`\n5. `docs/SEMANTIC_SEARCH_API_GUIDE.md`\n6. `migrations/postgresql/012_intelligence_embedding_schema.sql` (HNSW index creation)\n7. `tests/test_api_server_semantic_search.py`\n8. `tests/test_semantic_search_contracts.py`\n9. `tests/shared/test_openclaw_skill_smart_news.py`\n\nWhen sources disagree, trust code and tests over prose.\n\nFile v0.4.5:skill-card.md\n\n## Description: <br>\nUse when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks from OpenClaw. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[laceletho](https://clawhub.ai/user/laceletho) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and agent operators use this skill to call an authenticated crypto news and intelligence HTTP API for async news analysis, unified semantic search, datasource management, topic lifecycle operations, and service health checks. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: Review before execution as proposals could introduce incorrect or misleading guidance into skills. <br>\nMitigation: Review and scan skill before deployment. <br>\n\n## Reference(s): <br>\n- [ClawHub Skill Page](https://clawhub.ai/laceletho/smart-news) <br>\n- [Crypto News Analyzer API](https://news.tradao.xyz) <br>\n- [Analyze Workflow Reference](references/analyze-workflow.md) <br>\n- [Semantic Search Reference](references/semantic-search.md) <br>\n- [Datasource Management Reference](references/datasource-management.md) <br>\n- [Intelligence Query Reference](references/intelligence-query.md) <br>\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance, API calls] <br>\n**Output Format:** [Markdown guidance with JSON examples, curl commands, and API response contracts.] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [Requires a bearer token supplied through API_KEY; authorized calls can create, update, pause, archive, merge, or delete remote API resources.] <br>\n\n## Skill Version(s): <br>\n0.4.5 (source: server release evidence) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v0.4.4: 8 files, 22861 bytes\n\nFiles: references/analyze-workflow.md (9505b), references/datasource-management.md (11331b), references/intelligence-query.md (13069b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (8242b), skill-card.md (2475b), SKILL.md (11925b), _meta.json (129b)\n\nFile v0.4.4:SKILL.md\n\n---\nname: smart-news\ndescription: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks from OpenClaw.\nmetadata: { openclaw: { skillKey: smart-news, primaryEnv: API_KEY } }\n---\n\n# Crypto News HTTP API Skill\n\nUse this skill to call the Crypto News Analyzer HTTP API from OpenClaw.\n\n## When to Use\n\nUse this skill when you need to call `https://news.tradao.xyz` or a compatible private deployment.\n\nTypical triggers:\n\n- Run asynchronous crypto news analysis over a time window\n- Run asynchronous unified semantic search (News + Intelligence) for a freeform topic query\n- Poll an API job until it finishes and then fetch the final result\n- Create, list, or delete datasources through the HTTP API\n- Query and manage intelligence topics through the topic-first API (create, revise, confirm, merge findings, detail, list, pause, archive)\n- View and manage topic-datasource associations (get, set, add, remove) to scope topic research\n- List intelligence topic research run logs per-topic or globally\n- Check service health before or after an API workflow\n\n## Quick Reference\n\nAuthentication is Bearer token style: send `Authorization: Bearer <API_KEY>` with every request.\n\n`POST /analyze` creates a job and returns immediately. It does **not** return the final report. Poll status, then fetch the result.\n\nWorkflow: `POST /analyze` -> `GET /analyze/{job_id}` -> `GET /analyze/{job_id}/result`\n\nJobs move through these states: `queued`, `running`, `completed`, `failed`.\n\n`POST /semantic-search` creates a job, returns `202 Accepted`, and includes `status_url`, `result_url`, plus a `Retry-After` header. When `hours` exceeds the server max (720h default), a `warning` field describes the truncation. Semantic search jobs that do not complete within 5 minutes are automatically failed with a timeout error.\n\nSemantic workflow: `POST /semantic-search` -> `GET /semantic-search/{job_id}` -> `GET /semantic-search/{job_id}/result`\n\nUnified semantic search retrieves from both `content_items` and `raw_intelligence_items` via PostgreSQL with pgvector HNSW indexes (`embedding vector(1536)`). SQLite runtime is unsupported.\n\nFor detailed guides, see:\n\n- [Analyze Workflow Reference](references/analyze-workflow.md)\n- [Semantic Search Reference](references/semantic-search.md)\n- [Datasource Management Reference](references/datasource-management.md)\n- [Intelligence Query Reference](references/intelligence-query.md)\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md)\n\n## OpenClaw Runtime\n\nThis skill declares `metadata.openclaw.primaryEnv: API_KEY`. In OpenClaw, inject the bearer token through `~/.openclaw/openclaw.json`:\n\n```json5\n{\n  skills: {\n    entries: {\n      \"smart-news\": {\n        enabled: true,\n        apiKey: \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\n```\n\nIf `apiKey` is unavailable, do not send unauthenticated requests. Ask the operator to configure the token first.\n\nIf you are using a non-production deployment, replace `https://news.tradao.xyz` with the correct base URL before issuing requests.\n\n## Analyze Workflow\n\nCreate an analysis job by posting to `/analyze` with `hours` and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`. Do not expect the analysis report in the initial POST response. Once completed, fetch the result URL.\n\n## Semantic Search\n\nUnified semantic search retrieves from both News (`content_items`) and Intelligence (`raw_intelligence_items`) domains via UNION ALL over pgvector HNSW indexes. The response includes a `source_breakdown` with per-domain `matched_count` and `retained_count`. Each hit carries a `source_domain` discriminator (`\"news\"` or `\"intelligence\"`).\n\nCreate a semantic search job by posting to `/semantic-search` with `hours`, `query`, and `user_id`. The server responds with `202 Accepted`, a `job_id`, `status_url`, and `result_url`. Semantic search job IDs start with `semantic_search_job_`.\n\nPoll the status endpoint until the job reaches `completed` or `failed`, then fetch the report from the result URL. Use the `status` field as the source of truth for lifecycle state; `success` becomes `true` only when the job is completed successfully.\n\nRequest rules:\n\n- `hours` must be a positive integer\n- `query` is required, trimmed, and capped at 300 characters\n- `query` cannot be blank or whitespace-only\n- `user_id` must match `^[A-Za-z0-9_-]{1,128}$`\n\nOperational constraints:\n\n- Semantic search is PostgreSQL-only and returns `503` when the backend does not support pgvector\n- Both `content_items` and `raw_intelligence_items` tables have `embedding vector(1536)` columns with HNSW indexes (`idx_content_embedding_hnsw` and `idx_intelligence_embedding_hnsw`)\n- The API uses vector similarity over stored content embeddings and combines that with deterministic local keyword fallback (no LLM-driven keyword expansion)\n- LLM query decomposition is disabled by default (`query_planning_enabled: false`); when disabled the raw user query is embedded directly as the only subquery. The `max_subqueries` cap (4) only applies when query planning is explicitly re-enabled\n- Final retained results are capped at 200 unique items per domain before merging\n- Embedding generation requires `OPENAI_API_KEY`; report synthesis requires `KIMI_API_KEY` or `GROK_API_KEY` (query planning also requires an LLM key but is disabled by default)\n\nThe result body returns a Markdown report with `query`, `normalized_intent`, `matched_count`, `retained_count`, `time_window_hours`, `source_breakdown`, and `report`.\n\n## Datasource Management\n\nConfigure news and intelligence sources through the datasource API. Create sources with `POST /datasources`, list them with `GET /datasources`, and remove them with `DELETE /datasources/{id}`. All datasource routes require Bearer auth.\n\nEach datasource has a `purpose` field: `news` (RSS/X/REST feeds for analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `GET /datasources` endpoint supports optional `purpose` and `source_type` query parameters for filtering. Results are sorted by purpose, source type, then name.\n\nTags help organize sources. Each datasource accepts up to 16 unique tags. Each tag is capped at 32 characters. Tags are normalized to lowercase and deduplicated automatically.\n\nList and create responses include only safe summaries. For `rest_api` type datasources, secrets are redacted and counts replace raw credential fields. This prevents accidental credential exposure when reviewing configurations.\n\n## Intelligence Query (Topic-First)\n\nAll intelligence routes require Bearer auth. The deprecated entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`) have been removed in the topic-only refactor. Topics are the sole first-class intelligence objects, driving scheduled LLM research from raw ingested messages and storing findings with merge support.\n\nSynchronous topic workflow endpoints:\n\n- `POST /intelligence/topics` — Create a topic draft from a user theme (returns AI-generated prompt draft)\n- `POST /intelligence/topics/{topic_id}/revise` — Revise the draft prompt with feedback\n- `PUT /intelligence/topics/{topic_id}/prompt` — Manually set/replace the prompt text (context-aware: edits active prompt if one exists, otherwise creates draft revision)\n- `POST /intelligence/topics/{topic_id}/confirm` — Confirm and activate the topic for research (requires `prompt_version_id`)\n- `GET /intelligence/topics` — List topics with pagination and `active_only` filter (default: true)\n- `GET /intelligence/topics/{topic_id}` — Get topic metadata and merge availability\n- `GET /intelligence/topics/{topic_id}/findings` — Get paginated active findings with citations and source URLs\n- `GET /intelligence/topics/{topic_id}/prompts` — Get prompt versions and current active prompt\n- `POST /intelligence/topics/{topic_id}/pause` — Pause topic research\n- `POST /intelligence/topics/{topic_id}/archive` — Archive a topic\n- `GET /intelligence/topics/{topic_id}/runs` — List topic research run logs\n- `GET /intelligence/topic-runs` — List all topic research runs globally\n\nThese endpoints are synchronous; there is no async job/poll flow. Results return immediately.\n\nTopics have lifecycle states: `draft`, `active`, `paused`, `archived`. Only `active` topics are researched by the ingestion scheduler. Finding merge is available through both `POST /intelligence/topics/{id}/merge` and the Telegram `/topic_merge` command.\n\n## Telegram Webhook\n\nThe webhook endpoint exists for maintainer-level Telegram integration. It is not the primary path for day-to-day operators. Regular users should interact through the API routes or Telegram slash commands instead.\n\nWhen processing webhook updates, validate the `X-Telegram-Bot-Api-Secret-Token` header to confirm the request originates from Telegram.\n\n## Endpoint Index\n\nSupported HTTP routes:\n\n- `GET /health` - Service health check\n- `POST /analyze` - Create an analysis job (async, returns 202)\n- `GET /analyze/{job_id}` - Check job status\n- `GET /analyze/{job_id}/result` - Retrieve completed job results\n- `POST /semantic-search` - Create a semantic search job (async, returns 202)\n- `GET /semantic-search/{job_id}` - Check semantic search job status\n- `GET /semantic-search/{job_id}/result` - Retrieve completed semantic search results\n- `POST /datasources` - Create a datasource\n- `GET /datasources` - List all datasources\n- `DELETE /datasources/{id}` - Delete a datasource\n- `POST /telegram/webhook` - Telegram webhook receiver\n- `POST /intelligence/topics` - Create topic draft (synchronous, Bearer-protected)\n- `POST /intelligence/topics/{id}/revise` - Revise topic prompt\n- `PUT /intelligence/topics/{id}/prompt` - Manually set topic prompt\n- `POST /intelligence/topics/{id}/confirm` - Confirm and activate topic\n- `GET /intelligence/topics` - List topics with status filters\n- `GET /intelligence/topics/{id}` - Get topic metadata and merge availability\n- `GET /intelligence/topics/{id}/findings` - Get paginated findings with citations\n- `GET /intelligence/topics/{id}/prompts` - Get prompt versions and active prompt\n- `POST /intelligence/topics/{id}/pause` - Pause topic\n- `POST /intelligence/topics/{id}/archive` - Archive topic\n- `POST /intelligence/topics/{id}/merge` - Merge active findings into consolidated results\n- `GET /intelligence/topics/{id}/datasources` - List datasource associations for a topic\n- `PUT /intelligence/topics/{id}/datasources` - Replace all datasource associations atomically\n- `POST /intelligence/topics/{id}/datasources/{datasource_id}` - Add a datasource association (idempotent)\n- `DELETE /intelligence/topics/{id}/datasources/{datasource_id}` - Remove a datasource association (idempotent)\n- `GET /intelligence/topics/{id}/runs` - List topic research run logs\n- `GET /intelligence/topic-runs` - List all topic research runs globally\n\n## Non-Goals\n\nThis skill does not cover:\n\n- Telegram slash commands (use the Telegram bot directly)\n- Autogenerated documentation routes (`/docs`, `/redoc`, `/openapi.json`)\n- Deprecated compatibility aliases (`api-server`, `crypto-news-api`)\n- Direct embedding backfill operations beyond pointing you to the documented command\n\nThese surfaces exist but are intentionally excluded from this API-focused skill.\n\n## Updating\n\nKeep this skill aligned with the live HTTP routes in `api_server.py`, the AI Analyze API Guide at `docs/AI_ANALYZE_API_GUIDE.md`, the semantic search guide at `docs/SEMANTIC_SEARCH_API_GUIDE.md`, and the domain repository contracts in `domain/repositories.py`.\n\nWhen documentation disagrees with implementation, trust the code and tests over prose docs. Source precedence: code first, then reference files, then guides.\n\nFile v0.4.4:_meta.json\n\n{\n  \"ownerId\": \"kn70n844xnvgcz0zzja942av2184cjj5\",\n  \"slug\": \"smart-news\",\n  \"version\": \"0.4.4\",\n  \"publishedAt\": 1780579914814\n}\n\nFile v0.4.4:references/analyze-workflow.md\n\n# Analyze Workflow Reference\n\nThe analyze workflow is the primary way to trigger cryptocurrency news analysis via HTTP API. This reference documents the three-step async pattern: create job, poll status, fetch result.\n\n## Authentication\n\nAll analyze endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nThe `API_KEY` is configured via the `API_KEY` environment variable on the server. Requests without a valid token receive HTTP 401.\n\n## Overview\n\nThe analyze workflow follows an asynchronous pattern:\n\n1. **Create**: POST to `/analyze` with `hours` and `user_id` to enqueue a job\n2. **Poll**: GET `/analyze/{job_id}` to check status until completion\n3. **Fetch**: GET `/analyze/{job_id}/result` to retrieve the final Markdown report\n\nThe initial POST returns immediately with job metadata. It does not return the analysis report. You must poll and fetch separately.\n\n## Creating an Analysis Job\n\n### Endpoint\n\n```\nPOST /analyze\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Analysis time window in hours. Values below server minimum return HTTP 400. Values above maximum are capped to the configured limit (default 24h) and the response includes a `warning` field. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Example Request\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'\n```\n\n### Success Response (HTTP 202 Accepted)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"queued\",\n  \"time_window_hours\": 1,\n  \"status_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"result_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\",\n  \"warning\": null\n}\n```\n\nResponse headers include:\n\n- `Location`: Path to status endpoint\n- `Retry-After`: Recommended polling interval in seconds (typically 5)\n\n### Validation Errors\n\n| Condition | HTTP Status | Notes |\n|-----------|-------------|-------|\n| Missing `user_id` | 422 | FastAPI validation error with field location |\n| Invalid `user_id` (spaces, punctuation, non-ASCII, >128 chars) | 422 | Must match `^[A-Za-z0-9_-]{1,128}$` |\n| `hours <= 0` | 422 | Positive integer required |\n| `hours` below server minimum | 400 | Configurable minimum (default 1) |\n\nExample validation error:\n\n```jso\n\nArchive v0.4.3: 8 files, 22622 bytes\n\nFiles: references/analyze-workflow.md (9505b), references/datasource-management.md (11331b), references/intelligence-query.md (11956b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (8242b), skill-card.md (2580b), SKILL.md (11812b), _meta.json (129b)\n\nArchive v0.4.2: 8 files, 22054 bytes\n\nFiles: references/analyze-workflow.md (9326b), references/datasource-management.md (11331b), references/intelligence-query.md (11956b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (7252b), skill-card.md (2552b), SKILL.md (11610b), _meta.json (129b)\n\nArchive v0.4.1: 8 files, 20836 bytes\n\nFiles: references/analyze-workflow.md (9326b), references/datasource-management.md (11331b), references/intelligence-query.md (10669b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (5197b), skill-card.md (2472b), SKILL.md (10306b), _meta.json (129b)\n\nArchive v0.4.0: 8 files, 21946 bytes\n\nFiles: references/analyze-workflow.md (9326b), references/datasource-management.md (11331b), references/intelligence-query.md (16013b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (5048b), skill-card.md (2789b), SKILL.md (10386b), _meta.json (129b)\n\nArchive v0.3.0: 7 files, 19798 bytes\n\nFiles: references/analyze-workflow.md (9326b), references/datasource-management.md (10790b), references/intelligence-query.md (13708b), references/operations-and-maintenance.md (3644b), references/semantic-search.md (5048b), SKILL.md (10318b), _meta.json (129b)","readmeExcerpt":"Skill: Smart News Owner: laceletho Summary: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks... Tags: latest:0.4.6 Version history: v0.4.6 | 2026-06-06T03:40:34.958Z | user Sync lifecycle to archive-only; remove /pause route and /topic_pause Telegram surface; fix startup flags; correct test paths; align docs w","codeSnippets":[],"executableExamples":[{"language":"json5","snippet":"{\n  skills: {\n    entries: {\n      \"smart-news\": {\n        enabled: true,\n        apiKey: \"YOUR_API_KEY\"\n      }\n    }\n  }\n}"},{"language":"text","snippet":"Authorization: Bearer <API_KEY>"},{"language":"text","snippet":"POST /analyze"},{"language":"bash","snippet":"curl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'"},{"language":"bash","snippet":"curl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'"},{"language":"json","snippet":"{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"queued\",\n  \"time_window_hours\": 1,\n  \"status_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"result_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\",\n  \"warning\": null\n}"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: smart-news\ndescription: Use when calling the Crypto News Analyzer HTTP API for async analysis jobs, semantic search, datasource management, intelligence operations, or health checks from OpenClaw.\nmetadata: { openclaw: { skillKey: smart-news, primaryEnv: API_KEY } }\n---\n\n# Crypto News HTTP API Skill\n\nUse this skill to call the Crypto News Analyzer HTTP API from OpenClaw.\n\n## When to Use\n\nUse this skill when you need to call `https://news.tradao.xyz` or a compatible private deployment.\n\nTypical triggers:\n\n- Run asynchronous crypto news analysis over a time window\n- Run asynchronous unified semantic search (News + Intelligence) for a freeform topic query\n- Poll an API job until it finishes and then fetch the final result\n- Create, list, or delete datasources through the HTTP API\n- Query and manage intelligence topics through the topic-first API (create, revise, confirm, merge findings, detail, list, archive)\n- View and manage topic-datasource associations (get, set, add, remove) to scope topic research\n- List intelligence topic research run logs per-topic or globally\n- Check service health before or after an API workflow\n\n## Quick Reference\n\nAuthentication is Bearer token style: send `Authorization: Bearer <API_KEY>` with every request.\n\n`POST /analyze` creates a job and returns immediately. It does **not** return the final report. Poll status, then fetch the result.\n\nWorkflow: `POST /analyze` -> `GET /analyze/{job_id}` -> `GET /analyze/{job_id}/result`\n\nJobs move through these states: `queued`, `running`, `completed`, `failed`.\n\n`POST /semantic-search` creates a job, returns `202 Accepted`, and includes `status_url`, `result_url`, plus a `Retry-After` header. When `hours` exceeds the server max (720h default), a `warning` field describes the truncation. Semantic search jobs that do not complete within 5 minutes are automatically failed with a timeout error.\n\nSemantic workflow: `POST /semantic-search` -> `GET /semantic-search/{job_id}` -> `GET /semantic-search/{job_id}/result`\n\nUnified semantic search retrieves from both `content_items` and `raw_intelligence_items` via PostgreSQL with pgvector HNSW indexes (`embedding vector(1536)`). SQLite runtime is unsupported.\n\nFor detailed guides, see:\n\n- [Analyze Workflow Reference](references/analyze-workflow.md)\n- [Semantic Search Reference](references/semantic-search.md)\n- [Datasource Management Reference](references/datasource-management.md)\n- [Intelligence Query Reference](references/intelligence-query.md)\n- [Operations and Maintenance Reference](references/operations-and-maintenance.md)\n\n## OpenClaw Runtime\n\nThis skill declares `metadata.openclaw.primaryEnv: API_KEY`. In OpenClaw, inject the bearer token through `~/.openclaw/openclaw.json`:\n\n```json5\n{\n  skills: {\n    entries: {\n      \"smart-news\": {\n        enabled: true,\n        apiKey: \"YOUR_API_KEY\"\n      }\n    }\n  }\n}\n```\n\nIf `apiKey` is unavailable, do not send unauthenticated requests. Ask the operator to configure the token first.\n\nIf "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn70n844xnvgcz0zzja942av2184cjj5\",\n  \"slug\": \"smart-news\",\n  \"version\": \"0.4.6\",\n  \"publishedAt\": 1780717234958\n}"},{"path":"references/analyze-workflow.md","content":"# Analyze Workflow Reference\n\nThe analyze workflow is the primary way to trigger cryptocurrency news analysis via HTTP API. This reference documents the three-step async pattern: create job, poll status, fetch result.\n\n## Authentication\n\nAll analyze endpoints require Bearer token authentication:\n\n```\nAuthorization: Bearer <API_KEY>\n```\n\nThe `API_KEY` is configured via the `API_KEY` environment variable on the server. Requests without a valid token receive HTTP 401.\n\n## Overview\n\nThe analyze workflow follows an asynchronous pattern:\n\n1. **Create**: POST to `/analyze` with `hours` and `user_id` to enqueue a job\n2. **Poll**: GET `/analyze/{job_id}` to check status until completion\n3. **Fetch**: GET `/analyze/{job_id}/result` to retrieve the final Markdown report\n\nThe initial POST returns immediately with job metadata. It does not return the analysis report. You must poll and fetch separately.\n\n## Creating an Analysis Job\n\n### Endpoint\n\n```\nPOST /analyze\n```\n\n### Required Parameters\n\n| Field | Type | Constraints | Description |\n|-------|------|-------------|-------------|\n| `hours` | integer | `> 0` | Analysis time window in hours. Values below server minimum return HTTP 400. Values above maximum are capped to the configured limit (default 24h) and the response includes a `warning` field. |\n| `user_id` | string | `^[A-Za-z0-9_-]{1,128}$` | Requesting user identifier. Server trims whitespace before validation. |\n\n### Example Request\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/analyze\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"hours\": 1, \"user_id\": \"my_agent_01\"}'\n```\n\n### Success Response (HTTP 202 Accepted)\n\n```json\n{\n  \"success\": true,\n  \"job_id\": \"analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"status\": \"queued\",\n  \"time_window_hours\": 1,\n  \"status_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\",\n  \"result_url\": \"/analyze/analyze_job_2f205899562a4104868384e65f81c8c1/result\",\n  \"warning\": null\n}\n```\n\nResponse headers include:\n\n- `Location`: Path to status endpoint\n- `Retry-After`: Recommended polling interval in seconds (typically 5)\n\n### Validation Errors\n\n| Condition | HTTP Status | Notes |\n|-----------|-------------|-------|\n| Missing `user_id` | 422 | FastAPI validation error with field location |\n| Invalid `user_id` (spaces, punctuation, non-ASCII, >128 chars) | 422 | Must match `^[A-Za-z0-9_-]{1,128}$` |\n| `hours <= 0` | 422 | Positive integer required |\n| `hours` below server minimum | 400 | Configurable minimum (default 1) |\n\nExample validation error:\n\n```json\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"body\", \"user_id\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"hours\": 1}\n    }\n  ]\n}\n```\n\n## Polling Job Status\n\n### Endpoint\n\n```\nGET /analyze/{job_id}\n```\n\n### Example Request\n\n```bash\ncurl -H \"Authorization: Bearer ${API_KEY}\" \\\n  \"https://news.tradao.xyz/analyze/analyze_job_2f205899562a4104868384e65f81c8c1\"\n```\n\n### Response Fields\n\n| Field | Type "},{"path":"references/datasource-management.md","content":"# Datasource Management Reference\n\nThis document describes the HTTP API surface for managing datasources. All datasource routes require Bearer authentication.\n\n## CRUD Routes\n\n### POST /datasources\n\nCreates a new datasource. Returns `201 Created` on success, `409 Conflict` if a datasource with the same type and name already exists, and `422 Unprocessable Entity` for invalid payloads.\n\n**Request body structure:**\n```json\n{\n  \"purpose\": \"news|intelligence\",\n  \"source_type\": \"rss|x|rest_api\",\n  \"tags\": [\"tag1\", \"tag2\"],\n  \"config_payload\": {\n    \"name\": \"My Source\",\n    ...\n  }\n}\n```\n\nThe `purpose` field determines which pipeline the datasource feeds: `news` (RSS/X/REST for content analysis) or `intelligence` (Telegram groups, V2EX for topic research). The `name` field in the top-level request must match `config_payload.name` when both are provided.\n\n### GET /datasources\n\nLists all datasources sorted by purpose, source type, then name. Supports optional filtering by `purpose` and `source_type` query parameters. Returns `200 OK` with a list of datasource summaries.\n\n**Query Parameters:**\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `purpose` | string | No | Filter by `news` or `intelligence` |\n| `source_type` | string | No | Filter by datasource type (`rss`, `x`, etc.) |\n\n**Response structure:**\n```json\n{\n  \"success\": true,\n  \"datasources\": [\n    {\n      \"id\": \"uuid\",\n      \"name\": \"My Source\",\n      \"purpose\": \"news\",\n      \"source_type\": \"rss\",\n      \"tags\": [\"tag1\"],\n      \"config_summary\": {\n        ...\n      }\n    }\n  ]\n}\n```\n\nList responses always return safe summaries. For `rest_api` datasources, sensitive fields are redacted and replaced with counts.\n\n### DELETE /datasources/{id}\n\nDeletes a datasource by its UUID. Returns `204 No Content` on success, `404 Not Found` if the datasource does not exist, and `409 Conflict` if the datasource has active ingestion jobs.\n\nThe delete operation will fail with `409 Conflict` if there are pending or running ingestion jobs associated with this datasource (matched by `source_type:source_name`).\n\n## Supported Datasource Types\n\nThe API supports five datasource types: `rss`, `x`, `rest_api`, `telegram_group`, and `v2ex`.\n\n`telegram_group` and `v2ex` feed the **hidden-channel intelligence pipeline** (raw collection → LLM extraction → canonical knowledge). They are not part of the news analysis pipeline and require the `openclaw+opencode` ingestion service with proper credentials.\n\n### rss\n\nRSS feed datasources crawl RSS/XML feeds.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (string, valid HTTP/HTTPS URL)\n\n**Optional config_payload fields:**\n- `description` (string, defaults to empty string)\n\n**Config summary in responses:**\n- `url`: The RSS feed URL\n- `description`: The description value\n\n### x\n\nX (formerly Twitter) datasources crawl X lists or timelines.\n\n**Required config_payload fields:**\n- `name` (string, non-empty)\n- `url` (str"},{"path":"references/intelligence-query.md","content":"# Intelligence Query Reference\n\nTopic-first intelligence HTTP API. All endpoints require Bearer authentication and manage the topic research lifecycle (create → revise → confirm → research → merge → archive).\n\nThese endpoints are synchronous — results return immediately. Do not use an async job/poll workflow for intelligence routes.\n\n## Authentication\n\nSend `Authorization: Bearer <API_KEY>` with every request. Missing or invalid credentials return `401 Unauthorized`.\n\n## Topic Lifecycle\n\nTopics progress through states: `draft` → `active` → `archived`. Only `active` topics are researched by the ingestion scheduler. Merge previews expire after 24 hours. Finding merge is available through both the HTTP API and the Telegram `/topic_merge` command.\n\n## Deprecated Routes\n\nThe old entry-based routes (`/intelligence/entries*`, `/intelligence/discovery`, `/intelligence/labels`, `/intelligence/search`, `/intelligence/raw/*`, `/intelligence/topics/converge`) have been removed. Use only the topic-first endpoints documented below.\n\n---\n\n## POST /intelligence/topics\n\nCreate a new intelligence topic with an LLM-generated draft prompt.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `theme` | string | Yes | 1–500 characters |\n| `source_context` | object | No | Optional context for prompt generation |\n| `datasource_ids` | string[] | No | Optional list of datasource IDs to associate. Omitted = no associations. |\n\n### Status Codes\n\n| Code | Meaning |\n|------|---------|\n| `201` | Topic draft created |\n| `400` | Invalid theme or topic parameters |\n| `401` | Missing or invalid Bearer token |\n| `503` | LLM service unavailable |\n\n### Response (201)\n\nReturns a `TopicPromptVersionResponse`:\n\n```json\n{\n  \"id\": \"prompt-uuid\",\n  \"intelligence_topic_id\": \"topic-uuid\",\n  \"prompt_version\": \"v1.0\",\n  \"prompt_text\": \"LLM-generated research prompt...\",\n  \"schema_version\": \"v1.0\",\n  \"status\": \"draft\",\n  \"created_by\": \"api\",\n  \"activated_by\": null,\n  \"activation_notes\": null,\n  \"created_at\": \"2026-05-18T10:00:00+00:00\",\n  \"activated_at\": null,\n  \"archived_at\": null,\n  \"updated_at\": \"2026-05-18T10:00:00+00:00\",\n  \"audit_history\": []\n}\n```\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"theme\": \"crypto payment channels in Telegram groups\"}'\n```\n\n---\n\n## POST /intelligence/topics/{topic_id}/revise\n\nRevise the most recent draft prompt using LLM and user feedback. Returns a new prompt version.\n\n### Request Body\n\n| Field | Type | Required | Constraints |\n|-------|------|----------|-------------|\n| `feedback` | string | Yes | 1–5000 characters |\n\n### Response\n\nReturns a `TopicPromptVersionResponse` with the revised prompt.\n\n### Example\n\n```bash\ncurl -X POST \"https://news.tradao.xyz/intelligence/topics/topic-uuid/revise\" \\\n  -H \"Authorization: Bearer ${API_KEY}\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\""}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1911,"uniquenessScore":39,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T02:49:36.323Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T05:34:49.089Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}