{"id":"cad9a356-21aa-4b07-9e84-d4ca6c5ff0c4","entityType":"agent","slug":"clawhub-thesentitrader-stock-sentiment","name":"stock-sentiment","canonicalUrl":"https://www.xpersona.co/agent/clawhub-thesentitrader-stock-sentiment","canonicalPath":"/agent/clawhub-thesentitrader-stock-sentiment","generatedAt":"2026-10-10T08:45:35.573Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T22:53:07.892Z","emptyReason":null},"description":"Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a mention spike is good or bad news, read from the ticker's own bullish and bearish coverage. Use for stock sentiment, stock sentiment analysis, is the mood bullish or bearish, market mood today, fear and greed index, smart money tracker, insider buying and analyst upgrades, mention spike, sentiment vs price divergence, pre-earnings sentiment. Read-only. No trading, no purchases, no write operations, no wallet access. Skill: stock-sentiment Owner: thesentitrader Summary: Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.9K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s171aan38r2dn7ew5hddyscf1983x1h9:stock-sentiment","sourceUrl":"https://clawhub.ai/thesentitrader/stock-sentiment","homepage":"https://clawhub.ai/thesentitrader/skills/stock-sentiment","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/thesentitrader/stock-sentiment","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/thesentitrader/skills/stock-sentiment","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":66,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T22:53:07.892Z","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-09T22:53:07.892Z","emptyReason":null},"stars":null,"forks":null,"downloads":1925,"packageName":null,"latestVersion":"0.6.0","tractionLabel":"1.9K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T22:53:07.891Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T22:53:07.892Z","lastCrawledAt":"2026-10-09T22:53:07.891Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T22:53:07.891Z","lastVerifiedAt":null,"highlights":[{"version":"0.6.0","createdAt":"2026-10-01T18:48:06.672Z","changelog":"Adds a peers command and start and end dates to the bundled client, a 30-day default for the smart-money overlap, disclosure-date windows for congressional trades, a sharper description, and partial-slice labels for free-tier previews.","fileCount":4,"zipByteSize":22412},{"version":"0.5.2","createdAt":"2026-09-29T06:51:21.068Z","changelog":"Clarifies that an empty bull or bear view in story detail means no case on that side, never null.","fileCount":4,"zipByteSize":19832},{"version":"0.5.1","createdAt":"2026-09-26T04:36:21.279Z","changelog":"Mention volume now comes from the daily mentions series; the documents feed's totalCount counts the page, not the day's mentions.","fileCount":4,"zipByteSize":19783},{"version":"0.5.0","createdAt":"2026-09-26T04:12:41.208Z","changelog":"Adds a mention-spike check: confirm a spike is the ticker's own against its peers, then read good or bad news only from its own bullish vs bearish analyses and price move, with a live worked example.","fileCount":4,"zipByteSize":19600},{"version":"0.4.5","createdAt":"2026-09-24T16:33:40.314Z","changelog":"Fix: the convergence screen now pages the congressional and analyst feeds to totalCount; one page silently dropped most of a 30-day window and could report an empty overlap. The bundled helper pages both feeds too.","fileCount":4,"zipByteSize":17474},{"version":"0.4.4","createdAt":"2026-09-08T07:38:43.320Z","changelog":"Adds a scoped handoff to the analysis skill for testing a signal disagreement against a stated thesis; removes the npx execution path and declares permissions.","fileCount":4,"zipByteSize":17158},{"version":"0.4.3","createdAt":"2026-09-06T07:56:58.144Z","changelog":"Corrects the metric series shape: each point carries a flat value field, and mentions is a count object. Bundled client updated to match.","fileCount":4,"zipByteSize":16949},{"version":"0.4.2","createdAt":"2026-09-05T04:00:35.990Z","changelog":"Adds the app_review_count and app_rating metric endpoints (Free tier, products with a tracked iOS app), notes that from 2026-09-05 the mentions series no longer includes App Store reviews, and updates the pinned CLI version.","fileCount":4,"zipByteSize":16545}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s171aan38r2dn7ew5hddyscf1983x1h9:stock-sentiment","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"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-thesentitrader-stock-sentiment/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/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-10T08:45:35.571Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-thesentitrader-stock-sentiment/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":"high","updatedAt":"2026-10-09T22:53:07.892Z","emptyReason":null},"readme":"Skill: stock-sentiment\n\nOwner: thesentitrader\n\nSummary: Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a mention spike is good or bad news, read from the ticker's own bullish and bearish coverage. Use for stock sentiment, stock sentiment analysis, is the mood bullish or bearish, market mood today, fear and greed index, smart money tracker, insider buying and analyst upgrades, mention spike, sentiment vs price divergence, pre-earnings sentiment. Read-only. No trading, no purchases, no write operations, no wallet access.\n\nTags: latest:0.6.0\n\nVersion history:\n\nv0.6.0 | 2026-10-01T18:48:06.672Z | user\n\nAdds a peers command and start and end dates to the bundled client, a 30-day default for the smart-money overlap, disclosure-date windows for congressional trades, a sharper description, and partial-slice labels for free-tier previews.\n\nv0.5.2 | 2026-09-29T06:51:21.068Z | user\n\nClarifies that an empty bull or bear view in story detail means no case on that side, never null.\n\nv0.5.1 | 2026-09-26T04:36:21.279Z | user\n\nMention volume now comes from the daily mentions series; the documents feed's totalCount counts the page, not the day's mentions.\n\nv0.5.0 | 2026-09-26T04:12:41.208Z | user\n\nAdds a mention-spike check: confirm a spike is the ticker's own against its peers, then read good or bad news only from its own bullish vs bearish analyses and price move, with a live worked example.\n\nv0.4.5 | 2026-09-24T16:33:40.314Z | user\n\nFix: the convergence screen now pages the congressional and analyst feeds to totalCount; one page silently dropped most of a 30-day window and could report an empty overlap. The bundled helper pages both feeds too.\n\nv0.4.4 | 2026-09-08T07:38:43.320Z | user\n\nAdds a scoped handoff to the analysis skill for testing a signal disagreement against a stated thesis; removes the npx execution path and declares permissions.\n\nv0.4.3 | 2026-09-06T07:56:58.144Z | user\n\nCorrects the metric series shape: each point carries a flat value field, and mentions is a count object. Bundled client updated to match.\n\nv0.4.2 | 2026-09-05T04:00:35.990Z | user\n\nAdds the app_review_count and app_rating metric endpoints (Free tier, products with a tracked iOS app), notes that from 2026-09-05 the mentions series no longer includes App Store reviews, and updates the pinned CLI version.\n\nv0.4.1 | 2026-09-02T07:26:30.966Z | user\n\nCorrection: the CLI pointer said mood prints four sub-signals; the live Market Mood composite has six (Social Sentiment, Market Direction, Risk Appetite, Social Momentum, S&P 500 Trend, Options Flow) and the CLI prints all six plus the sector table.\n\nv0.4.0 | 2026-09-01T21:58:07.285Z | user\n\nCompany-name resolution rung (kb/entities/search, first non-null ticker, subsidiary-outranks-parent gotcha, type=etf for funds) as the first Pitfall and a RESOLVE line in Quick Reference; CLI quickstart pointer (sentiment/mood, pinned 0.47.1) with the Score-vs-polarity split spelled out.\n\nv0.3.3 | 2026-09-01T02:25:28.643Z | user\n\nFix the smart-money convergence recipe: at lookbackDays=7 the three feeds intersect to zero names, and because no individual bucket is empty the old widen-if-empty check never fires. The recipe now starts at 30 days, says to widen on a thin intersection rather than an empty bucket, and sets two-of-three as the working bar.\n\nv0.3.2 | 2026-08-23T23:42:53.958Z | user\n\nCorrect chart timeframe set; fix deep-dive routing slug reference; CLI pin 0.47.1\n\nv0.3.1 | 2026-08-21T00:34:11.681Z | user\n\nInsider read guidance aligned with the served sells aggregation.\n\nv0.3.0 | 2026-08-20T09:02:28.299Z | user\n\nCorrected quota and auth notes, standard disclaimer, sibling skill cross-links, agent identity guidance\n\nv0.2.0 | 2026-08-15T21:27:57.542Z | user\n\nDrop an unverifiable real-time claim from the catalog comparison. Add client identification guidance.\n\nv0.1.6 | 2026-08-13T20:54:43.399Z | user\n\nFreshness language corrected: price and chart carry a 15-minute delay rather than being real time, and price annotates with priceAsOf\n\nv0.1.5 | 2026-08-02T19:42:09.506Z | user\n\nRefreshed sentiment endpoint coverage and reporting-currency notes.\n\nv0.1.4 | 2026-07-24T05:26:39.115Z | user\n\nClarify options coverage; footer refresh.\n\nv0.1.3 | 2026-07-10T08:26:37.315Z | user\n\nAccuracy fixes from an independent review: corrected response shapes and field lists, and 429 Retry-After semantics.\n\nv0.1.2 | 2026-07-10T05:48:10.430Z | user\n\nPoint API-key acquisition at the /get-api-key link (Developer Console for management).\n\nv0.1.1 | 2026-07-01T19:42:19.426Z | user\n\nHarden the bundled client: preserve free-tier preview flags, handle non-JSON responses cleanly, and percent-encode tickers in URL paths. Tighten the description.\n\nv0.1.0 | 2026-07-01T07:48:04.377Z | user\n\nFirst release: the sentiment + smart-money + AI-insight layer for US stocks (SentiSense Score, sentiment polarity, market mood, insider/congressional/13F flows, analyst actions, AI insights, sentiment-tagged news). Read-only; ships a stdlib client.\n\nArchive index:\n\nArchive v0.6.0: 4 files, 22412 bytes\n\nFiles: scripts/sentiment_client.py (15112b), skill-card.md (1997b), SKILL.md (40636b), _meta.json (134b)\n\nFile v0.6.0:SKILL.md\n\n---\nname: stock-sentiment\ndescription: \"Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a mention spike is good or bad news, read from the ticker's own bullish and bearish coverage. Use for stock sentiment, stock sentiment analysis, is the mood bullish or bearish, market mood today, fear and greed index, smart money tracker, insider buying and analyst upgrades, mention spike, sentiment vs price divergence, pre-earnings sentiment. Read-only. No trading, no purchases, no write operations, no wallet access.\"\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n---\n# Stock Sentiment Skill\n\nThe sentiment and smart-money layer for US equities. A quote skill tells you the price; this skill reads what the market feels about a stock (the SentiSense Score, sentiment polarity, mentions, share of voice), where the smart money is moving (insider, congressional, and institutional flows plus analyst actions), and what the AI read of the tape is (per-stock and market-wide insights, sentiment-tagged news), all through the read-only SentiSense API.\n\nRead-only educational data interface. Output is informational context, never a personalized buy or sell recommendation.\n\n## When to Use\n\nReach for this skill when the question is about perception, positioning, or signal rather than raw price:\n\n- \"What is the sentiment on $NVDA?\" or \"Is the mood on $TSLA bullish or bearish?\"\n- \"What is the smart money doing this week?\" (insider cluster-buys, congressional trades, 13F flows, and analyst upgrades converging on the same tickers).\n- \"What is the overall market mood today, fear or greed?\"\n- \"Is sentiment diverging from price on $COIN?\" (price up while sentiment falls, or the reverse).\n- \"Mentions of $ZS just spiked: is that good or bad news for the stock?\"\n- \"What is the pre-earnings sentiment setup on $AAPL?\"\n- \"What is the AI insight on $MSFT, and what are people saying in the news?\"\n\nFor \"does this change my thesis?\", hand off to the `us-stocks-analysis` skill when available. Pass the ticker, user thesis, horizon, and dated signal disagreements. Return an evidence-led bull/bear assessment and unresolved objections. Hand off only when the user changes the question; do not automatically route back. If the sibling is unavailable, answer the supported part here using a connected tool or the inline REST workflow, state any remaining gap, and never require an install.\n\nDo not use it for order entry, portfolio management, or personalized advice. It has no write, trading, or wallet surface; every endpoint is a GET.\n\n## Prerequisites\n\n- Python 3.8+ using only the standard library (`urllib`, `json`); no third-party packages required. Any HTTP client or plain `curl` works too. On macOS python.org installs the client can raise `CERTIFICATE_VERIFY_FAILED` (missing CA certs): run the bundled `Install Certificates.command`, use the system `/usr/bin/python3`, or use `curl` (which uses the system trust store).\n- A free `SENTISENSE_API_KEY`. Get one at https://app.sentisense.ai/get-api-key. The key is required on every call; anonymous requests return `401 api_key_required`.\n- Network access to `https://app.sentisense.ai`.\n- Read-only scope. Every endpoint here is a GET. Nothing this skill does can place a trade, move money, or modify account state.\n\n## Permissions\n\n- Network: HTTPS to app.sentisense.ai only.\n- Credentials: SENTISENSE_API_KEY from the environment.\n- Shell: none required.\n- Files: none.\n\nTiers:\n\n| Tier | Quota | Rate |\n|------|-------|------|\n| Free | 1,000 requests/month | 30 requests/min |\n| PRO ($15/mo) | Unlimited | 300 requests/min |\n\nThe free tier exercises every workflow below. Preview-gated endpoints return a truncated but real slice on a free key (for example the top 3 insights); PRO removes the monthly cap and returns full history and full lists. A slice is not the window: when `isPreview` is true and `totalCount` is larger than the rows returned, label the result with both numbers (\"newest 5 of 17 insider trades, free preview\") and never infer absence from it. \"No insider buying\", \"no congressional purchase\" or \"no earnings date\" cannot come from a slice.\n\n## How to Run\n\nThis skill is invoked through the agent's terminal or shell tool: issue HTTP GET requests to the SentiSense API and synthesize the JSON into a concise, sourced answer. The base URL is `https://app.sentisense.ai`. Authenticate every request with the `X-SentiSense-API-Key` header; keep the key in the shell environment and never place it in a query string or in user-facing output.\n\n**Identify your client.** Send a `User-Agent` naming your agent runtime and this skill, for\nexample `OpenClaw/1.4 (stock-sentiment)` or `ClaudeCode/2.1 (stock-sentiment)`. Substitute your own runtime and\nversion if neither matches. You can also volunteer what your agent is called by adding an\n`agent/<your-agent-name>` token inside the same parentheses, as in\n`OpenClaw/1.4 (stock-sentiment; agent/research-desk)`. All of it is optional, and it is what tells\nus this skill has real integrations behind it, so it gets prioritized and you get notice before it\nchanges.\n\n```bash\ncurl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v2/metrics/entity/NVDA/metric/sentiment\"\n```\n\nAn anonymous call returns `401 api_key_required`. A rate-limited call returns `429` with a `Retry-After` header; back off for the indicated seconds rather than retrying immediately or serving a stale value.\n\nOn Windows, use the bundled Python client (cross-platform) and reference the key as `%SENTISENSE_API_KEY%` (cmd) or `$env:SENTISENSE_API_KEY` (PowerShell) rather than the POSIX `$SENTISENSE_API_KEY` shown above.\n\nThe REST recipe in this file is the primary path. A maintained command-line client is available as the separate `sentisense-cli` skill for hosts that prefer one.\n\nTwo response envelopes exist; unwrap correctly before reading fields:\n\n- Read FLAT (top-level, no `.data`): `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, and the metric series (`sentiment`, `sentisense`, `mentions`, and `social_dominance` are bare arrays). `institutional/quarters` is also a bare array.\n- Read WRAPPED as `{ isPreview, previewReason, data }` (use `.data`): `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings` (here `data` is a dict, so read `data.earnings[]`).\n- `documents/ticker` has its own shape `{ documents, totalCount }`; read `.documents[]`.\n\nWhen unsure, accept both: `rows = raw if isinstance(raw, list) else raw.get(\"data\", raw)`.\n\nAn optional stdlib helper, `scripts/sentiment_client.py`, covers the single-endpoint reads used by these workflows plus the paged feeds: it injects the auth header, prepends the base URL, normalizes both envelopes (reading the metric scalar from the flat `value`), walks paged feeds to `totalCount`, and labels a free-tier preview slice with its `totalCount` so the agent reasons over clean values. Workflow 6's ratio arithmetic is left to the agent; the `peers` and `mentions --start/--end` subcommands fetch its inputs. Use it or plain `curl`, whichever fits the host. The core of the helper is small enough to inline:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal stdlib client for the read-only SentiSense API.\"\"\"\nimport json, os, urllib.parse, urllib.request\n\nAPI_ORIGIN = \"https://app.sentisense.ai\"\n\nclass NoRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        return None\n\ndef sentisense_api_url(path, params=None):\n    url = urllib.parse.urljoin(API_ORIGIN + \"/\", path)\n    parsed = urllib.parse.urlparse(url)\n    if (parsed.scheme != \"https\" or parsed.hostname != \"app.sentisense.ai\"\n            or parsed.netloc != \"app.sentisense.ai\"\n            or parsed.username is not None or parsed.password is not None\n            or parsed.port is not None):\n        raise ValueError(\"API URL must use https://app.sentisense.ai with no credentials or port\")\n    if params:\n        url += (\"&\" if parsed.query else \"?\") + urllib.parse.urlencode(params)\n    return url\n\ndef get(path, **params):\n    url = sentisense_api_url(path, params)\n    req = urllib.request.Request(\n        url, headers={\"X-SentiSense-API-Key\": os.environ[\"SENTISENSE_API_KEY\"]})\n    with urllib.request.build_opener(NoRedirect).open(req, timeout=20) as r:\n        return json.load(r)\n\ndef rows(raw):\n    \"\"\"Wrap-vs-flat: some endpoints return a bare array, others {isPreview, data}.\"\"\"\n    if isinstance(raw, list):\n        return raw\n    if isinstance(raw, dict) and \"data\" in raw:\n        return raw[\"data\"]\n    return raw\n\ndef latest_metric(ticker, slug=\"sentiment\"):\n    \"\"\"Latest reading of any metric series. Read the flat top-level `value`: it is present\n    on every point and holds the scalar, while the nested metricValue is a dict for value\n    metrics and a bare number for count metrics like `mentions`.\"\"\"\n    series = get(f\"/api/v2/metrics/entity/{ticker}/metric/{slug}\")\n    if not series or series[-1].get(\"value\") is None:\n        return None\n    return float(series[-1][\"value\"])\n```\n\n```bash\npython scripts/sentiment_client.py sentiment NVDA\npython scripts/sentiment_client.py mood\n```\n\n## Quick Reference\n\nAll paths are relative to `https://app.sentisense.ai` and are GET. Every call requires the `X-SentiSense-API-Key` header. `{T}` is an uppercase ticker, `{slug}` a member slug, `{id}` a story id. Full schema: https://sentisense.ai/skill.md.\n\n```\nRESOLVE A NAME (only when the user typed a company or fund name, not a symbol)\n  GET /api/v1/kb/entities/search?q={name}&type=company&limit=5\n        Bare array of {name, urlSlug, type, ticker}, best match first. Take the first match with a\n        non-null ticker. Use type=etf for fund names (SPY resolves only there). See Pitfalls.\n\nPEERS (to test whether a mention spike is the ticker's own; see workflow 6)\n  GET /api/v1/stocks/{T}/graph?depth=1&cap=75\n        groups.peers[] is the curated comparable set, as entity slugs that the metric endpoints\n        accept in place of {T}; join a slug to nodes[] for its displayName. Peers from depth=1 only.\n\nSENTIMENT & MOOD\n  GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}\n        Sentiment polarity time series. Omit params for the server default 7-day window.\n        Bare array; latest scalar is series[-1].value (a float in [-1, 1]). Every series\n        below reads the same way: take the flat top-level value, not the nested metricValue,\n        whose depth differs between value metrics and count metrics.\n  GET /api/v2/metrics/entity/{T}/metric/sentisense\n        The SentiSense Score (unbounded composite; report as-is, never normalize to 0-100).\n        Each daily point also carries metricValue.properties.{bull, bear, directional}: that\n        day's bullish and bearish analyses and their sum. Direction lives here, not in mentions.\n  GET /api/v2/metrics/entity/{T}/metric/mentions\n        Mention-volume time series (how much a ticker is being talked about). Direction-blind.\n  GET /api/v2/metrics/entity/{T}/metric/social_dominance\n        Share-of-conversation time series (a ticker's dominance of the chatter).\n  GET /api/v2/metrics/entity/{E}/metric/app_review_count\n        New Apple App Store reviews per day for a product's iOS app. Products with a\n        tracked app only, so {E} is a product entity slug, never a ticker.\n  GET /api/v2/metrics/entity/{E}/metric/app_rating\n        Mean star rating (1 to 5) of that day's new App Store reviews. Same coverage.\n  GET /api/v2/market-mood\n        Composite fear/greed plus sub-signals and per-sector breakdowns. Flat, but the\n        composite is nested: market.currentScore, market.phase, market.weeklyChange,\n        market.signals[]; sectors.{SectorName}.{currentScore, phase, weeklyChange}.\n\nSMART MONEY  (wrapped in {isPreview, previewReason, data}; free key returns a preview slice)\n  GET /api/v1/insider/cluster-buys?lookbackDays=N         Tickers with multiple insider buys.\n  GET /api/v1/insider/trades/{T}?lookbackDays=N           Form 4 rows; transactionType BUY|SELL plus\n                                                          EXERCISE, AWARD, GIFT, OTHER (filter explicitly);\n                                                          raw SEC letter in transactionCode.\n  GET /api/v1/politicians/activity?lookbackDays=N         Congressional trades; PURCHASE|SALE. Window is on\n                                                          disclosureDate; transactionDate is often weeks earlier.\n  GET /api/v1/politicians/filings/{T}?lookbackDays=N      Per-ticker congressional filings.\n  GET /api/v1/politicians/member/{slug}                   Member profile (data.recentTrades[]).\n  GET /api/v1/institutional/quarters                      Call FIRST; bare array. Use reportDate of first entry whose pending is not true; skip pending:true (fall back to [0] only if all pending).\n  GET /api/v1/institutional/holders/{T}?reportDate={Q}    Top 13F holders (data.holders[], largest first).\n  GET /api/v1/analyst/{T}/consensus                       Price-target band; data IS the consensus object (data.consensusLabel).\n  GET /api/v1/analyst/{T}/actions?lookbackDays=N          Recent rating changes for one ticker.\n  GET /api/v1/analyst/{T}/estimates                       EPS band at data.estimates[0].{estimateLow/Mean/High,\n                                                          numberOfAnalysts} + data.surprises[]; no revenue.\n  GET /api/v1/analyst/activity?lookbackDays=N             Market-wide actions; add &actionTypes=UPGRADE,DOWNGRADE,INITIATE\n                                                          for real rating changes (~83% of raw rows are REITERATE).\n\nAI INSIGHTS  (wrapped; batch, carry generatedAt)\n  GET /api/v1/insights/stock/{T}         Per-stock signals ranked by importance; data[0].insightText is the headline. Free preview top 3.\n  GET /api/v1/insights/stock/{T}/types   Available insight types for the ticker; bare string array.\n  GET /api/v1/insights/market            Top market-wide signals (data[], insightText; ticker embedded in insightText).\n\nNEWS & STORIES\n  GET /api/v1/documents/ticker/{T}?limit=N          Sentiment-tagged feed ({documents, totalCount}); each doc\n                                                   has url, source, sourceName, published (epoch seconds), averageSentiment; no title.\n  GET /api/v1/documents/stories?limit=N             Pre-clustered stories; cluster.title is SentiSense-authored and safe to show.\n  GET /api/v1/documents/stories/ticker/{T}?limit=N  Stories for one ticker.\n  GET /api/v1/documents/stories/{id}                Story detail (PublicStoryDetailDto; aspectPerspectives[], bullishView/bearishView).\n                                                   An empty view (\"\" or blank hook/conclusion, no bullets) = no case on that side; never null.\n  GET /api/v1/documents/search?query=...            Topical document search.\n\nSUPPORTING  (price, prices, chart are 15-minute delayed; profile, popular, calendar, market-summary are reference or batch)\n  GET /api/v1/stocks/price?ticker={T}                       Flat (no wrapper): currentPrice, changePercent at root.\n  GET /api/v1/stocks/prices?tickers=A,B,C                   Batch quotes.\n  GET /api/v1/stocks/{T}/profile                            name, sector, industry (flat at root; no profile key).\n  GET /api/v1/stocks/chart?ticker={T}&timeframe=1D|5D|1W|1M|3M|6M|1Y|5Y|10Y|MAX   Bars; read each point's timestamp (Unix ms). Invalid timeframe returns 400.\n  GET /api/v1/stocks/popular                                Bare array of ticker strings (screen universe).\n  GET /api/v1/calendar/earnings?ticker={T}                  data.earnings[]; next date + consensus EPS + confirmed.\n                                                            Free: current Monday-Sunday week only (see workflow 4).\n  GET /api/v1/market-summary                                Market-wide narrative headline.\n```\n\nSentiment is polarity: a float in [-1, 1] where the sign is the direction (negative is bearish and meaningful, positive is bullish) and the magnitude is conviction. Represent the sign unmistakably; do not map it onto a 0-100 scale. The SentiSense Score is a separate, unbounded composite; report it as-is. Mentions and social dominance are their own metric series on the same `/metric/{metricType}` endpoint (`mentions` for talk volume, `social_dominance` for share of the conversation); all four series (`sentiment`, `sentisense`, `mentions`, `social_dominance`) are available on the Free tier, and like every metrics call each request counts against your monthly quota. Two more Free-tier series cover consumer products rather than tickers: `app_review_count` (new Apple App Store reviews for a product's iOS app that day) and `app_rating` (the mean star rating, 1 to 5, of those reviews). **From 2026-09-05 `mentions` excludes App Store reviews**, because a review is a rating rather than chatter and for review-heavy products the two read as one number; the App Store slice still appears under `distribution/mentions?dimension=source`, so read review volume from `app_review_count`. A separate `/api/v2/metrics/entity/{T}/distribution/{metricType}` endpoint breaks a metric down by source, and its `valueType` says what the numbers are: for `mentions` each source's share of voice in percent (`SHARE_PERCENT`), for `sentiment` each source's mean polarity in [-1, 1] (`MEAN`). `sentisense` has no per-source split and returns an empty map.\n\n## Workflows\n\nOpinionated recipes. Each fans out its independent calls in parallel, then synthesizes; none recommends buying or selling. Frame every result as educational context on positioning and mood.\n\n### 1. Sentiment read on a ticker\n\nAnswer \"what is the market feeling about $T\" in a few dense lines. Fire these in parallel:\n\n1. `GET /api/v2/metrics/entity/{T}/metric/sentiment` for the polarity trend (server default 7-day window; the latest scalar is `series[-1].value`, a float in [-1, 1]).\n2. `GET /api/v2/metrics/entity/{T}/metric/sentisense` for the composite score, and each point's `metricValue.properties.bull` / `bear` for that day's bullish and bearish analyses (the day's direction).\n3. `GET /api/v2/metrics/entity/{T}/metric/mentions` for mention volume: one daily count per point, read from the flat `value`. This series carries no direction; take bull and bear from step 2.\n4. `GET /api/v1/documents/ticker/{T}?limit=8` for the sentiment-tagged feed only. Its `totalCount` counts the documents on this page (it equals `limit`), so never report it as mention volume.\n5. `GET /api/v1/insights/stock/{T}` for the top AI insight (`data[0].insightText`, with `generatedAt` for freshness).\n\nSynthesize as educational context, leading with the differentiated sentiment read, not the price: \"$NVDA sentiment +0.42 over 7d and rising; SentiSense Score elevated; mention volume heavy; latest AI insight: 'Data-center demand commentary firming' (as of the batch time).\" Show the `generatedAt` age so the reader knows these are batch metrics.\n\n### 2. Market mood (fear and greed)\n\nAnswer \"what is the overall market mood today.\"\n\n1. `GET /api/v2/market-mood`.\n\nThe response is flat, but the composite is nested under `market`, not the root: `market.currentScore`, `market.phase` (e.g. Fear, Neutral, Optimism, Greed), `market.weeklyChange`, and `market.signals[]` (each sub-gauge with its value and change). Per-sector readings live at `sectors.{SectorName}.{ currentScore, phase, weeklyChange }`; `sectors` is a string-keyed dict, not an array, and its GICS labels have historically overlapped (`Technology` alongside `Information Technology`, `Healthcare` alongside `Health Care`), so treat the pairs defensively: if both members of a pair appear in one response, dedupe them before ranking top and bottom sectors. A clean response with neither pair duplicated is the common case and needs no special handling. Report as context: \"Market mood 62 (Greed), +4 over the week. Greed leaders: Technology, Communications. Fear: Energy, Utilities.\" Optionally pair with `GET /api/v1/market-summary` for the narrative headline and `GET /api/v1/insights/market` for the top market-wide signals.\n\n### 3. Smart-money convergence screen\n\nFind tickers where insider buying, congressional purchases, and analyst upgrades line up in the same window; convergence is the signal a quote feed cannot produce.\n\n1. `GET /api/v1/insider/cluster-buys?lookbackDays=30`.\n2. `GET /api/v1/politicians/activity?lookbackDays=30&limit=500`, keeping rows with `transactionType == \"PURCHASE\"`.\n3. `GET /api/v1/analyst/activity?lookbackDays=30&actionTypes=UPGRADE&limit=500` (server-side filter; also accepts a CSV like `UPGRADE,DOWNGRADE,INITIATE`).\n\nAll three are wrapped: read `.data`. **Steps 2 and 3 are paged feeds: one call is one page, not the window.** Both carry `totalCount` for the whole window on the envelope, so keep requesting with `offset` while `offset + len(data) < totalCount`. A 30-day congressional window runs to several hundred rows, which the default page (200) cuts short, and the analyst default page is 50. Reading one page silently drops most of the window and can turn a real overlap into an empty one. On a FREE key all three feeds stop at a preview slice (`isPreview: true`, with the window's real size in `totalCount`) and an `offset` past the slice returns nothing, so report the screen as partial (\"congressional leg: 5 of 599 rows, free preview\") rather than reporting \"no convergence\".\n\n**The congressional leg is windowed by disclosure date, not trade date.** `lookbackDays` on `politicians/activity` filters on `disclosureDate`, and members disclose weeks after they trade (each row carries `transactionDate`, `disclosureDate` and `disclosureDelayDays`; delays of a month or more are common). So a 30-day screen pairs purchases disclosed in the last 30 days, many of them traded a month or two earlier, with insider buys and analyst upgrades that happened in the window. Label that leg \"disclosed\" and quote `transactionDate` when you cite a purchase.\n\nIntersect the three ticker lists and report names appearing in two or more buckets, ranked by total signal count, with a one-liner each: \"$NVDA: 4 insiders bought, 1 congressional purchase disclosed (traded 2026-08-14), 2 analyst upgrades (30d).\"\n\n**Start this one at `lookbackDays=30`, not 7.** A 7-day window is too narrow for three slow feeds to overlap: on a representative run each of the three buckets held at least one ticker at 7 days and the intersection was still **zero** names in two or more buckets, while the same three calls at 30 days, fully paged, produced more than ten convergent names. The trap is that no individual bucket was empty at 7 days, so an \"is this bucket empty\" check passes on all three and you still report nothing found. **Widen when the INTERSECTION is thin, not when a bucket is empty**, and say which window you used. Also expect the three-way overlap to be empty even at 30 days on a fully paged read: two-of-three is the working bar for this screen, and requiring all three will show a blank almost every time. A genuinely empty bucket (quiet week, disclosure lag) is `isPreview:false` and not an error either way. For one ticker's full flow, run `insider/trades/{T}`, `politicians/filings/{T}`, `institutional/quarters` then `institutional/holders/{T}?reportDate={Q}`, and `analyst/{T}/actions`. Present as observed positioning, never as advice.\n\n### 4. Pre-earnings sentiment check\n\nRead the sentiment and positioning into an earnings print.\n\n1. `GET /api/v1/calendar/earnings?ticker={T}` for the next report date and consensus (`data.earnings[0].earningsDate`, `confirmed`). Read the envelope before reading an empty list. On a free key the calendar covers only the current Monday-to-Sunday week, so `earnings: []` with `isPreview: true` and `totalCount` above 0 means the date exists but falls after this week: say the date is outside the free window, not that none is scheduled. Only an empty list with `isPreview: false` (or `totalCount: 0`) means the name is outside the forward window; then ask the user for the date instead of guessing.\n2. `GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={now-30d}&endTime={now}` (epoch milliseconds) for the 30-day sentiment trend.\n3. `GET /api/v1/insider/trades/{T}?lookbackDays=60` for recent insider activity (`transactionType` BUY or SELL), dropping `transactionCode == \"F\"` rows before you call anything selling (see the code-F note below). On a free key this is the newest few rows of `totalCount`: tally only what came back, say so (\"5 of 17 rows, free preview\"), and never report \"no insider selling\" or \"no buys\" from the slice.\n4. `GET /api/v1/analyst/{T}/estimates` for the EPS band and `surprises[]` beat/miss history.\n5. `GET /api/v1/analyst/{T}/actions?lookbackDays=30` for recent rating changes.\n6. `GET /api/v1/insights/stock/{T}` for the current AI read.\n\nSynthesize the setup as educational context: \"$AAPL earnings in 5d: sentiment +0.22 over 30d and trending up; insiders net sellers (2 sells, 0 buys); EPS consensus $1.52 (range $1.48 to $1.55, 28 analysts), beat in 3 of the last 4 quarters; 3 upgrades in 30d. Setup reads mixed-to-constructive.\" Do not tell the user how to trade the print.\n\n### 5. Sentiment-versus-price divergence\n\nSurface names where perception and price disagree; a bullish gap (price down, sentiment up) and a bearish gap (price up, sentiment down) are the two shapes of interest.\n\n1. `GET /api/v1/stocks/popular` for the candidate list.\n2. For each candidate, in parallel: `GET /api/v1/stocks/chart?ticker={T}&timeframe=1M` (a bare array of intraday bars; filter to `timestamp >= now-7d` and compare the first versus last bar for the 7-day move) and `GET /api/v2/metrics/entity/{T}/metric/sentiment` (server default 7-day window; measure the trend across the returned series).\n3. Rank by the absolute gap between the price move and the sentiment move; report the top few in each direction.\n\nFrame the result as an observed divergence, not a signal to act: \"Bullish divergence: $TSLA price -8% while sentiment +0.11 over 7d. Bearish divergence: $COIN price +14% while sentiment -0.09.\" Keep the delayed price and the batch sentiment labeled with their own freshness; do not blend them into one implied \"now.\"\n\n### 6. Is this mention spike good or bad news?\n\nAnswer \"mentions of $T just jumped: is that good or bad for the stock?\" in two separate steps, because size and direction live in different fields. **A mention spike is direction-blind: it fires just as hard on a crash as on a rally, so the size of a spike never tells you which way it points.** Step A settles whether the spike is real and belongs to $T; step B settles direction from $T's own numbers only.\n\nStep A, is it really a spike? Fire the first two calls in parallel, then the peer calls:\n\n1. `GET /api/v1/stocks/{T}/graph?depth=1&cap=75` and read `groups.peers[]`.\n2. `GET /api/v2/metrics/entity/{T}/metric/mentions?startTime={now-30d}&endTime={now}`, then the same call for three to five peer slugs from step 1. For a spike day in the past, start the window about 30 days before that day instead, so roughly 28 earlier points sit in front of it.\n\nFor each name, the spike ratio is its mentions on the spike day divided by its median daily mentions over the earlier points of the window (the median shrugs off older spikes and quiet weekends). The spike is $T's own when its ratio is roughly 2x or more AND clearly above the peers' median ratio; judge by that median, not by one busy peer. If the peers jumped too, it is a group story (sector news, a rival's print, a macro day): say so, then still read each name's direction from its own counts. Points are one per New York calendar day and the last is the current day so far, so test a finished day, or compare today-so-far only against the peers' today-so-far. An empty `groups.peers` means no curated comparables: ask the user for two or three, or say the spike was measured against $T's own history only.\n\nStep B, which way does it point? Read direction from $T alone:\n\n3. `GET /api/v2/metrics/entity/{T}/metric/sentisense?startTime={now-30d}&endTime={now}`: on the spike day's point read `metricValue.properties.bull` and `.bear`, the bullish and bearish analyses (each analysis is one news article or social post our models read as bullish or bearish for the ticker). Sum `bull` and `bear` over the earlier points for $T's usual lean: coverage leans bullish as a genre, so a day at 55% bullish on a name that normally runs 80% is a turn for the worse even with bulls still ahead.\n4. `GET /api/v1/stocks/price?ticker={T}` for `changePercent` (15-minute delayed) when the spike day is the latest session; for an older day, read that session from `stocks/chart` and compare that day's close with the previous trading day's close, the same close-to-close basis as `changePercent`.\n\nPeers normalize volume only: never borrow a peer's, sector's or index's tone for $T, and never read direction from `mentions` or the spike ratio. Treat a thin day (under about 20 `directional` analyses) as unreadable rather than calling a 3-to-2 split. When the tone and the price disagree, report both and call it mixed. Budget: about eight requests with four peers.\n\nWorked example for the New York day of 2026-09-25, read on 2026-10-01 after that day had closed; every call in it works on a free key. Counts are read when you call, so a re-run can return different numbers, and a day still in progress reads lower than the same day once it has closed. Reuse the method, not the figures. Response shapes, trimmed to the fields this workflow reads:\n\n```text\nGET /api/v1/stocks/ZS/graph?depth=1&cap=75\n  {\"ticker\": \"ZS\", \"root\": \"Zscaler-Inc\", \"depth\": 1, \"truncated\": false,\n   \"groups\": {\"peers\": [\"Cloudflare-Inc\", \"CrowdStrike-Holdings-Inc\", \"Okta-Inc\", \"Palo-Alto-Networks-Inc\"], ...},\n   \"nodes\": [{\"slug\": \"Cloudflare-Inc\", \"displayName\": \"Cloudflare, Inc.\", \"type\": \"COMPANY\"}, ...]}\nGET /api/v2/metrics/entity/ZS/metric/mentions  (spike-day point)\n  {\"timestamp\": 1790308800000, \"metricType\": \"MENTIONS\", \"value\": 66.0,\n   \"metricValue\": {\"type\": \"CountMetricValue\", \"value\": 66, \"count\": 66}}\nGET /api/v2/metrics/entity/ZS/metric/sentisense  (same day, metricValue.properties)\n  {\"bear\": 25.0, \"bull\": 16.0, \"directional\": 41.0}\n```\n\n```\n$ZS mention spike, New York day 2026-09-25\nSpike      66 mentions vs a 16.5/day median over the prior 28 days: 4.0x\nPeers      Cloudflare 1.6x, CrowdStrike 1.4x, Okta 3.5x, Palo Alto 1.4x (median 1.5x): ZS's own, though Okta rose too\nDirection  bearish analyses led 25 to 16 (39% bullish, against 71% over the prior 28 days)\nPrice      -10.1% close to close\nRead       a real spike, and bad news by ZS's own numbers: coverage turned bearish, price fell\n\n$COST mention spike, New York day 2026-09-25\nSpike      197 mentions vs a 75.5/day median over the prior 28 days: 2.6x\nPeers      Walmart 1.2x, Kroger 1.5x, Target 1.0x, Dollar Tree 0.7x (median 1.1x): COST's own\nDirection  bullish analyses led 112 to 36 (76% bullish, in line with 76% over the prior 28 days)\nPrice      +2.9% close to close\nRead       a real spike, and good news by COST's own numbers: coverage stayed bullish, price rose\n```\n\nTwo real spikes on the same day, pointing opposite ways: any rule that reads direction off the spike itself gets one of them wrong. Report what the coverage and the price did, not what the stock does next.\n\n## Pitfalls\n\n- **Company names are not tickers.** When the user names the company (\"sentiment on tesla\", \"is the mood on alphabet bullish\") instead of typing a symbol, resolve it first with `GET /api/v1/kb/entities/search?q={name}&type=company&limit=5`: a bare array of `{name, urlSlug, type, ticker}`, best match first (`type=etf` for a fund, since `SPY` resolves only there). Take the first match with a non-null `ticker`; a tracked subsidiary or private company can outrank its listed parent (\"google\" returns Google LLC with `ticker: null` before Alphabet `GOOGL`). Several plausible ticker-bearing matches means ask a one-line clarification; an empty array means say so. Never uppercase the word and hope: `$TESLA` fails the metric series with `404 entity_not_found` (that error carries up to three `suggestions`, which is a resolution hint, not data), while the smart-money feeds return an empty `data: []` that reads like a quiet name when the real failure was the identifier. An exact ticker the user typed skips this step, and one resolution call per name covers the whole session.\n- **Nothing here is real time.** Sentiment, the SentiSense Score, mentions, share of voice, news clustering, and AI insights are batch metrics computed on a schedule; quote, price, and chart points are the fresher class but carry a 15-minute delay. State a batch value with its `generatedAt` age, annotate price with `priceAsOf` where present, and never label either \"real time.\"\n- **Empty smart-money windows are normal.** The 7-day insider and congressional feeds often return empty arrays on quiet weeks (disclosure lag, `isPreview:false`, not an error). Widen that specific call to `lookbackDays=30` and note the wider window rather than showing a blank result.\n- **Preview gating is data, not failure, but a slice is not the window.** On the free tier, preview-gated endpoints return `isPreview:true` with a real truncated slice (for example the top 3 insights, the current earnings week, a sliced holder list) and the full window's size in `totalCount`. Render the slice and label it with both numbers (\"top 3 of 11 insights, free preview\"). Never infer absence from it: a tally of a slice is a tally of the slice, so no \"zero insider buying\", \"no congressional activity\" or \"not scheduled\" from a preview. An empty free-tier earnings calendar with `totalCount` above 0 means the date falls outside the free week. Mention PRO only when the truncation is materially limiting the answer.\n- **Wrap versus flat differs by endpoint.** Reading `.data` on a flat endpoint (or the reverse) yields nothing. Flat: `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, the `sentiment`, `sentisense`, `mentions`, and `social_dominance` series, and `institutional/quarters`. Wrapped under `.data`: `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings`. When unsure, accept both.\n- **Read the metric scalar from the flat `value`.** Every point in a metric series carries a top-level `series[i].value` alongside the nested `metricValue`, and it holds the reading: the polarity for `sentiment`, the composite for `sentisense`, the count for `mentions`, the share for `social_dominance`. Prefer it, because the nested depth is **not** the same for every metric. A value metric (`sentiment`, `sentisense`, `social_dominance`) nests at `metricValue.value.value` because `metricValue.value` is itself a dict; a count metric (`mentions`) is `{\"type\":\"CountMetricValue\",\"value\":36,\"count\":36}`, so `metricValue.value` is already the integer and `metricValue.value.value` throws. The flat field spares you the branch. A point with no reading omits `value`; skip that point rather than reading it as zero.\n- **A mention spike is direction-blind.** `mentions` counts every mention whatever its tone, so it fires as hard on a crash as on a rally. Never call a spike good or bad from its size, and never borrow a peer's, sector's or index's tone: direction comes from the ticker's own `bull` against `bear` and its own price move. Workflow 6 is the full recipe.\n- **Congress and insider use different verbs.** Insider rows carry `transactionType` BUY or SELL (plus EXERCISE, AWARD, GIFT and OTHER, which are neither); congressional rows carry PURCHASE or SALE. Filter each with its own vocabulary, explicitly.\n- **Not every insider SELL is a sale.** `transactionType` is a simplified rollup of the SEC's one-letter codes, and code `F` lands on `SELL`: those are shares the company withheld to cover the insider's taxes when a grant vested. Nobody chose to sell and no shares reached the market. On companies that grant heavily this is the majority of the reported \"sold\" dollars, so a bearish read built on a raw `SELL` filter is describing a vesting schedule. Read `transactionCode` and drop `F` before you tally selling. The market-wide `/insider/activity` rollup already excludes it for you; `/insider/trades/{T}` returns every filed row, so there you filter yourself.\n- **Always fetch quarters first.** Call `institutional/quarters` and pass the `reportDate` of the first quarter whose `pending` is not true to `institutional/holders`; skip any `pending:true` entry (within ~45 days of a quarter close the most-recent quarter is still filing and holds almost no holders), and fall back to `[0]` only if every entry is `pending:true`. Never hardcode a quarter.\n- **Documents carry no article title.** The document feed returns URLs, `source`, `published` (epoch seconds), and `averageSentiment`, not the publisher's headline. Pre-clustered story titles (`cluster.title`) are SentiSense-authored and safe to display verbatim; prefer stories when a readable title is needed.\n- **No invented endpoints.** There is no real-time options order flow and no dark pool (options exist, but only as end-of-day analytics at `/api/v1/options/overview` and `/api/v1/stocks/{ticker}/options/summary`), and no `/congress` (congressional data lives under `/politicians`). The earnings calendar is `/api/v1/calendar/earnings`.\n- **No advice.** When asked \"should I buy,\" return data-grounded synthesis (sentiment, smart-money flow, analyst consensus, AI insight) framed as educational context, not a personal recommendation.\n\n## Verification\n\nConfirm the skill is wired correctly before trusting a synthesis:\n\n1. **Reachability and auth.** Every endpoint here takes an API key, so one call checks both: `curl -s -o /dev/null -w \"%{http_code}\" -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \"https://app.sentisense.ai/api/v2/market-mood\"`. A `200` confirms the base URL, the network, the header and the key. A `401 api_key_required` means the header or `SENTISENSE_API_KEY` is missing; a `401 invalid_api_key` means the key itself is wrong or revoked; a `429` means the per-minute rate was exceeded, so honor the `Retry-After` hint.\n2. **Sentiment parses.** Fetch `/api/v2/metrics/entity/AAPL/metric/sentiment`, confirm a non-empty array, and read `series[-1].value`; it should be a float in [-1, 1]. A value outside that range means the wrong field was read.\n3. **Mood nests as expected.** Fetch `/api/v2/market-mood` and confirm `market.currentScore`, `market.phase`, and `market.weeklyChange` are present (not at the root), and that `sectors` is a populated dict.\n4. **Envelope check.** Confirm `institutional/quarters` parses as a bare array and `insider/cluster-buys?lookbackDays=30` parses as `{ isPreview, data }` with `data` an array (an empty array on a quiet window is a valid result, not a failure).\n5. **Freshness is surfaced.** Any batch value presented to the user carries its `generatedAt`; if a synthesis omits the age on a sentiment or insight figure, or describes a batch surface as real time, it is not verified.\n\nA run passes when every quoted number traces to a `200` response read this turn, batch and delayed-price surfaces are labeled distinctly with their own ages, and the output reads as educational context rather than a recommendation.\n\n## Use & Disclaimer\n\nThis skill is an **educational data interface** to SentiSense's read-only Data API. Output is informational only. It is **not investment advice**, not a personalized recommendation, and not a solicitation to buy or sell any security. The user is responsible for their own decisions. Use of the API and this skill is subject to the [API Terms of Service](https://sentisense.ai/agreement/API-Terms-of-Service.pdf) and [Terms of Service](https://sentisense.ai/agreement/Terms-of-Service.pdf).\n\n---\n\n**ClawHub Skill:** [clawhub.ai/TheSentiTrader/stock-sentiment](https://clawhub.ai/TheSentiTrader/stock-sentiment)\n\nFile v0.6.0:_meta.json\n\n{\n  \"ownerId\": \"kn71ca3nrt3w6w0v3nhv3c4tan82x1ym\",\n  \"slug\": \"stock-sentiment\",\n  \"version\": \"0.6.0\",\n  \"publishedAt\": 1790880486672\n}\n\nFile v0.6.0:skill-card.md\n\n## Description:\n\nProvides read-only sentiment, market-mood, news, and investor-positioning context for US stocks through SentiSense.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[thesentitrader](https://clawhub.ai/user/thesentitrader)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nInvestors and analysts use this skill to review US-stock sentiment, market mood, and insider, congressional, institutional, and analyst activity as informational context, not personalized investment advice.\n\n### Deployment Geography for Use:\n\nGlobal (US equities coverage)\n\n## Known Risks and Mitigations:\n\nRisk: The skill sends a SentiSense API key to SentiSense for data requests.\n\nMitigation: Use a dedicated API key and provide it only to the intended SentiSense service.\n\nRisk: Financial sentiment could be mistaken for investment advice or a trading instruction.\n\nMitigation: Present results as educational context, not personalized buy or sell recommendations.\n\nRisk: Batch data and limited previews can obscure recency or omit relevant activity.\n\nMitigation: State data timestamps and label partial previews without inferring absence from a slice.\n\n## Reference(s):\n\n- [ClawHub stock-sentiment release](https://clawhub.ai/thesentitrader/skills/stock-sentiment)\n- [SentiSense API skill documentation](https://sentisense.ai/skill.md)\n- [SentiSense API key](https://app.sentisense.ai/get-api-key)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Analysis]\n\n**Output Format:** [Plain text or Markdown summary]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Dated market signals; free-tier results may be partial previews.]\n\n## Skill Version(s):\n\n0.6.0 (source: server-resolved ClawHub release)\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.5.2: 4 files, 19832 bytes\n\nFiles: scripts/sentiment_client.py (11483b), skill-card.md (1860b), SKILL.md (36621b), _meta.json (134b)\n\nFile v0.5.2:SKILL.md\n\n---\nname: stock-sentiment\ndescription: \"Sentiment and smart-money positioning for US stocks.\"\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n---\n# Stock Sentiment Skill\n\nThe sentiment and smart-money layer for US equities. A quote skill tells you the price; this skill reads what the market feels about a stock (the SentiSense Score, sentiment polarity, mentions, share of voice), where the smart money is moving (insider, congressional, and institutional flows plus analyst actions), and what the AI read of the tape is (per-stock and market-wide insights, sentiment-tagged news), all through the read-only SentiSense API.\n\nRead-only educational data interface. Output is informational context, never a personalized buy or sell recommendation.\n\n## When to Use\n\nReach for this skill when the question is about perception, positioning, or signal rather than raw price:\n\n- \"What is the sentiment on $NVDA?\" or \"Is the mood on $TSLA bullish or bearish?\"\n- \"What is the smart money doing this week?\" (insider cluster-buys, congressional trades, 13F flows, and analyst upgrades converging on the same tickers).\n- \"What is the overall market mood today, fear or greed?\"\n- \"Is sentiment diverging from price on $COIN?\" (price up while sentiment falls, or the reverse).\n- \"Mentions of $ZS just spiked: is that good or bad news for the stock?\"\n- \"What is the pre-earnings sentiment setup on $AAPL?\"\n- \"What is the AI insight on $MSFT, and what are people saying in the news?\"\n\nFor \"does this change my thesis?\", hand off to the `us-stocks-analysis` skill when available. Pass the ticker, user thesis, horizon, and dated signal disagreements. Return an evidence-led bull/bear assessment and unresolved objections. Hand off only when the user changes the question; do not automatically route back. If the sibling is unavailable, answer the supported part here using a connected tool or the inline REST workflow, state any remaining gap, and never require an install.\n\nDo not use it for order entry, portfolio management, or personalized advice. It has no write, trading, or wallet surface; every endpoint is a GET.\n\n## Prerequisites\n\n- Python 3.8+ using only the standard library (`urllib`, `json`); no third-party packages required. Any HTTP client or plain `curl` works too. On macOS python.org installs the client can raise `CERTIFICATE_VERIFY_FAILED` (missing CA certs): run the bundled `Install Certificates.command`, use the system `/usr/bin/python3`, or use `curl` (which uses the system trust store).\n- A free `SENTISENSE_API_KEY`. Get one at https://app.sentisense.ai/get-api-key. The key is required on every call; anonymous requests return `401 api_key_required`.\n- Network access to `https://app.sentisense.ai`.\n- Read-only scope. Every endpoint here is a GET. Nothing this skill does can place a trade, move money, or modify account state.\n\n## Permissions\n\n- Network: HTTPS to app.sentisense.ai only.\n- Credentials: SENTISENSE_API_KEY from the environment.\n- Shell: none required.\n- Files: none.\n\nTiers:\n\n| Tier | Quota | Rate |\n|------|-------|------|\n| Free | 1,000 requests/month | 30 requests/min |\n| PRO ($15/mo) | Unlimited | 300 requests/min |\n\nThe free tier exercises every workflow below. Preview-gated endpoints return a truncated but real slice on a free key (for example the top 3 insights); PRO removes the monthly cap and returns full history and full lists.\n\n## How to Run\n\nThis skill is invoked through the agent's terminal or shell tool: issue HTTP GET requests to the SentiSense API and synthesize the JSON into a concise, sourced answer. The base URL is `https://app.sentisense.ai`. Authenticate every request with the `X-SentiSense-API-Key` header; keep the key in the shell environment and never place it in a query string or in user-facing output.\n\n**Identify your client.** Send a `User-Agent` naming your agent runtime and this skill, for\nexample `OpenClaw/1.4 (stock-sentiment)` or `ClaudeCode/2.1 (stock-sentiment)`. Substitute your own runtime and\nversion if neither matches. You can also volunteer what your agent is called by adding an\n`agent/<your-agent-name>` token inside the same parentheses, as in\n`OpenClaw/1.4 (stock-sentiment; agent/research-desk)`. All of it is optional, and it is what tells\nus this skill has real integrations behind it, so it gets prioritized and you get notice before it\nchanges.\n\n```bash\ncurl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v2/metrics/entity/NVDA/metric/sentiment\"\n```\n\nAn anonymous call returns `401 api_key_required`. A rate-limited call returns `429` with a `Retry-After` header; back off for the indicated seconds rather than retrying immediately or serving a stale value.\n\nOn Windows, use the bundled Python client (cross-platform) and reference the key as `%SENTISENSE_API_KEY%` (cmd) or `$env:SENTISENSE_API_KEY` (PowerShell) rather than the POSIX `$SENTISENSE_API_KEY` shown above.\n\nThe REST recipe in this file is the primary path. A maintained command-line client is available as the separate `sentisense-cli` skill for hosts that prefer one.\n\nTwo response envelopes exist; unwrap correctly before reading fields:\n\n- Read FLAT (top-level, no `.data`): `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, and the metric series (`sentiment`, `sentisense`, `mentions`, and `social_dominance` are bare arrays). `institutional/quarters` is also a bare array.\n- Read WRAPPED as `{ isPreview, previewReason, data }` (use `.data`): `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings` (here `data` is a dict, so read `data.earnings[]`).\n- `documents/ticker` has its own shape `{ documents, totalCount }`; read `.documents[]`.\n\nWhen unsure, accept both: `rows = raw if isinstance(raw, list) else raw.get(\"data\", raw)`.\n\nAn optional stdlib helper, `scripts/sentiment_client.py`, wraps all of this: it injects the auth header, prepends the base URL, and normalizes both envelopes (reading the metric scalar from the flat `value`) so the agent reasons over clean values. Use it or plain `curl`, whichever fits the host. The core of the helper is small enough to inline:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal stdlib client for the read-only SentiSense API.\"\"\"\nimport json, os, urllib.parse, urllib.request\n\nAPI_ORIGIN = \"https://app.sentisense.ai\"\n\nclass NoRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        return None\n\ndef sentisense_api_url(path, params=None):\n    url = urllib.parse.urljoin(API_ORIGIN + \"/\", path)\n    parsed = urllib.parse.urlparse(url)\n    if (parsed.scheme != \"https\" or parsed.hostname != \"app.sentisense.ai\"\n            or parsed.netloc != \"app.sentisense.ai\"\n            or parsed.username is not None or parsed.password is not None\n            or parsed.port is not None):\n        raise ValueError(\"API URL must use https://app.sentisense.ai with no credentials or port\")\n    if params:\n        url += (\"&\" if parsed.query else \"?\") + urllib.parse.urlencode(params)\n    return url\n\ndef get(path, **params):\n    url = sentisense_api_url(path, params)\n    req = urllib.request.Request(\n        url, headers={\"X-SentiSense-API-Key\": os.environ[\"SENTISENSE_API_KEY\"]})\n    with urllib.request.build_opener(NoRedirect).open(req, timeout=20) as r:\n        return json.load(r)\n\ndef rows(raw):\n    \"\"\"Wrap-vs-flat: some endpoints return a bare array, others {isPreview, data}.\"\"\"\n    if isinstance(raw, list):\n        return raw\n    if isinstance(raw, dict) and \"data\" in raw:\n        return raw[\"data\"]\n    return raw\n\ndef latest_metric(ticker, slug=\"sentiment\"):\n    \"\"\"Latest reading of any metric series. Read the flat top-level `value`: it is present\n    on every point and holds the scalar, while the nested metricValue is a dict for value\n    metrics and a bare number for count metrics like `mentions`.\"\"\"\n    series = get(f\"/api/v2/metrics/entity/{ticker}/metric/{slug}\")\n    if not series or series[-1].get(\"value\") is None:\n        return None\n    return float(series[-1][\"value\"])\n```\n\n```bash\npython scripts/sentiment_client.py sentiment NVDA\npython scripts/sentiment_client.py mood\n```\n\n## Quick Reference\n\nAll paths are relative to `https://app.sentisense.ai` and are GET. Every call requires the `X-SentiSense-API-Key` header. `{T}` is an uppercase ticker, `{slug}` a member slug, `{id}` a story id. Full schema: https://sentisense.ai/skill.md.\n\n```\nRESOLVE A NAME (only when the user typed a company or fund name, not a symbol)\n  GET /api/v1/kb/entities/search?q={name}&type=company&limit=5\n        Bare array of {name, urlSlug, type, ticker}, best match first. Take the first match with a\n        non-null ticker. Use type=etf for fund names (SPY resolves only there). See Pitfalls.\n\nPEERS (to test whether a mention spike is the ticker's own; see workflow 6)\n  GET /api/v1/stocks/{T}/graph?depth=1&cap=75\n        groups.peers[] is the curated comparable set, as entity slugs that the metric endpoints\n        accept in place of {T}; join a slug to nodes[] for its displayName. Peers from depth=1 only.\n\nSENTIMENT & MOOD\n  GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}\n        Sentiment polarity time series. Omit params for the server default 7-day window.\n        Bare array; latest scalar is series[-1].value (a float in [-1, 1]). Every series\n        below reads the same way: take the flat top-level value, not the nested metricValue,\n        whose depth differs between value metrics and count metrics.\n  GET /api/v2/metrics/entity/{T}/metric/sentisense\n        The SentiSense Score (unbounded composite; report as-is, never normalize to 0-100).\n        Each daily point also carries metricValue.properties.{bull, bear, directional}: that\n        day's bullish and bearish analyses and their sum. Direction lives here, not in mentions.\n  GET /api/v2/metrics/entity/{T}/metric/mentions\n        Mention-volume time series (how much a ticker is being talked about). Direction-blind.\n  GET /api/v2/metrics/entity/{T}/metric/social_dominance\n        Share-of-conversation time series (a ticker's dominance of the chatter).\n  GET /api/v2/metrics/entity/{E}/metric/app_review_count\n        New Apple App Store reviews per day for a product's iOS app. Products with a\n        tracked app only, so {E} is a product entity slug, never a ticker.\n  GET /api/v2/metrics/entity/{E}/metric/app_rating\n        Mean star rating (1 to 5) of that day's new App Store reviews. Same coverage.\n  GET /api/v2/market-mood\n        Composite fear/greed plus sub-signals and per-sector breakdowns. Flat, but the\n        composite is nested: market.currentScore, market.phase, market.weeklyChange,\n        market.signals[]; sectors.{SectorName}.{currentScore, phase, weeklyChange}.\n\nSMART MONEY  (wrapped in {isPreview, previewReason, data}; free key returns a preview slice)\n  GET /api/v1/insider/cluster-buys?lookbackDays=N         Tickers with multiple insider buys.\n  GET /api/v1/insider/trades/{T}?lookbackDays=N           Form 4 rows; transactionType BUY|SELL,\n                                                          raw SEC letter in transactionCode.\n  GET /api/v1/politicians/activity?lookbackDays=N         Congressional trades; PURCHASE|SALE.\n  GET /api/v1/politicians/filings/{T}?lookbackDays=N      Per-ticker congressional filings.\n  GET /api/v1/politicians/member/{slug}                   Member profile (data.recentTrades[]).\n  GET /api/v1/institutional/quarters                      Call FIRST; bare array. Use reportDate of first entry whose pending is not true; skip pending:true (fall back to [0] only if all pending).\n  GET /api/v1/institutional/holders/{T}?reportDate={Q}    Top 13F holders (data.holders[], largest first).\n  GET /api/v1/analyst/{T}/consensus                       Price-target band; data IS the consensus object (data.consensusLabel).\n  GET /api/v1/analyst/{T}/actions?lookbackDays=N          Recent rating changes for one ticker.\n  GET /api/v1/analyst/{T}/estimates                       EPS band at data.estimates[0].{estimateLow/Mean/High,\n                                                          numberOfAnalysts} + data.surprises[]; no revenue.\n  GET /api/v1/analyst/activity?lookbackDays=N             Market-wide actions; add &actionTypes=UPGRADE,DOWNGRADE,INITIATE\n                                                          for real rating changes (~83% of raw rows are REITERATE).\n\nAI INSIGHTS  (wrapped; batch, carry generatedAt)\n  GET /api/v1/insights/stock/{T}         Per-stock signals ranked by importance; data[0].insightText is the headline. Free preview top 3.\n  GET /api/v1/insights/stock/{T}/types   Available insight types for the ticker; bare string array.\n  GET /api/v1/insights/market            Top market-wide signals (data[], insightText; ticker embedded in insightText).\n\nNEWS & STORIES\n  GET /api/v1/documents/ticker/{T}?limit=N          Sentiment-tagged feed ({documents, totalCount}); each doc\n                                                   has url, source, sourceName, published (epoch seconds), averageSentiment; no title.\n  GET /api/v1/documents/stories?limit=N             Pre-clustered stories; cluster.title is SentiSense-authored and safe to show.\n  GET /api/v1/documents/stories/ticker/{T}?limit=N  Stories for one ticker.\n  GET /api/v1/documents/stories/{id}                Story detail (PublicStoryDetailDto; aspectPerspectives[], bullishView/bearishView).\n                                                   An empty view (\"\" or blank hook/conclusion, no bullets) = no case on that side; never null.\n  GET /api/v1/documents/search?query=...            Topical document search.\n\nSUPPORTING  (price, prices, chart are 15-minute delayed; profile, popular, calendar, market-summary are reference or batch)\n  GET /api/v1/stocks/price?ticker={T}                       Flat (no wrapper): currentPrice, changePercent at root.\n  GET /api/v1/stocks/prices?tickers=A,B,C                   Batch quotes.\n  GET /api/v1/stocks/{T}/profile                            name, sector, industry (flat at root; no profile key).\n  GET /api/v1/stocks/chart?ticker={T}&timeframe=1D|5D|1W|1M|3M|6M|1Y|5Y|10Y|MAX   Bars; read each point's timestamp (Unix ms). Invalid timeframe returns 400.\n  GET /api/v1/stocks/popular                                Bare array of ~75 ticker strings (screen universe).\n  GET /api/v1/calendar/earnings?ticker={T}                  data.earnings[]; next date + consensus EPS + confirmed.\n  GET /api/v1/market-summary                                Market-wide narrative headline.\n```\n\nSentiment is polarity: a float in [-1, 1] where the sign is the direction (negative is bearish and meaningful, positive is bullish) and the magnitude is conviction. Represent the sign unmistakably; do not map it onto a 0-100 scale. The SentiSense Score is a separate, unbounded composite; report it as-is. Mentions and social dominance are their own metric series on the same `/metric/{metricType}` endpoint (`mentions` for talk volume, `social_dominance` for share of the conversation); all four series (`sentiment`, `sentisense`, `mentions`, `social_dominance`) are available on the Free tier, and like every metrics call each request counts against your monthly quota. Two more Free-tier series cover consumer products rather than tickers: `app_review_count` (new Apple App Store reviews for a product's iOS app that day) and `app_rating` (the mean star rating, 1 to 5, of those reviews). **From 2026-09-05 `mentions` excludes App Store reviews**, because a review is a rating rather than chatter and for review-heavy products the two read as one number; the App Store slice still appears under `distribution/mentions?dimension=source`, so read review volume from `app_review_count`. A separate `/api/v2/metrics/entity/{T}/distribution/{metricType}` endpoint breaks a metric down by source, and its `valueType` says what the numbers are: for `mentions` each source's share of voice in percent (`SHARE_PERCENT`), for `sentiment` each source's mean polarity in [-1, 1] (`MEAN`). `sentisense` has no per-source split and returns an empty map.\n\n## Workflows\n\nOpinionated recipes. Each fans out its independent calls in parallel, then synthesizes; none recommends buying or selling. Frame every result as educational context on positioning and mood.\n\n### 1. Sentiment read on a ticker\n\nAnswer \"what is the market feeling about $T\" in a few dense lines. Fire these in parallel:\n\n1. `GET /api/v2/metrics/entity/{T}/metric/sentiment` for the polarity trend (server default 7-day window; the latest scalar is `series[-1].value`, a float in [-1, 1]).\n2. `GET /api/v2/metrics/entity/{T}/metric/sentisense` for the composite score.\n3. `GET /api/v2/metrics/entity/{T}/metric/mentions` for mention volume: one daily count per point, and each point's `metricValue.properties.bull` / `bear` for that day's direction.\n4. `GET /api/v1/documents/ticker/{T}?limit=8` for the sentiment-tagged feed only. Its `totalCount` counts the documents on this page (it equals `limit`), so never report it as mention volume.\n5. `GET /api/v1/insights/stock/{T}` for the top AI insight (`data[0].insightText`, with `generatedAt` for freshness).\n\nSynthesize as educational context, leading with the differentiated sentiment read, not the price: \"$NVDA sentiment +0.42 over 7d and rising; SentiSense Score elevated; mention volume heavy; latest AI insight: 'Data-center demand commentary firming' (as of the batch time).\" Show the `generatedAt` age so the reader knows these are batch metrics.\n\n### 2. Market mood (fear and greed)\n\nAnswer \"what is the overall market mood today.\"\n\n1. `GET /api/v2/market-mood`.\n\nThe response is flat, but the composite is nested under `market`, not the root: `market.currentScore`, `market.phase` (e.g. Fear, Neutral, Optimism, Greed), `market.weeklyChange`, and `market.signals[]` (each sub-gauge with its value and change). Per-sector readings live at `sectors.{SectorName}.{ currentScore, phase, weeklyChange }`; `sectors` is a string-keyed dict, not an array, and its GICS labels have historically overlapped (`Technology` alongside `Information Technology`, `Healthcare` alongside `Health Care`), so treat the pairs defensively: if both members of a pair appear in one response, dedupe them before ranking top and bottom sectors. A clean response with neither pair duplicated is the common case and needs no special handling. Report as context: \"Market mood 62 (Greed), +4 over the week. Greed leaders: Technology, Communications. Fear: Energy, Utilities.\" Optionally pair with `GET /api/v1/market-summary` for the narrative headline and `GET /api/v1/insights/market` for the top market-wide signals.\n\n### 3. Smart-money convergence screen\n\nFind tickers where insider buying, congressional purchases, and analyst upgrades line up in the same window; convergence is the signal a quote feed cannot produce.\n\n1. `GET /api/v1/insider/cluster-buys?lookbackDays=30`.\n2. `GET /api/v1/politicians/activity?lookbackDays=30&limit=500`, keeping rows with `transactionType == \"PURCHASE\"`.\n3. `GET /api/v1/analyst/activity?lookbackDays=30&actionTypes=UPGRADE&limit=500` (server-side filter; also accepts a CSV like `UPGRADE,DOWNGRADE,INITIATE`).\n\nAll three are wrapped: read `.data`. **Steps 2 and 3 are paged feeds: one call is one page, not the window.** Both carry `totalCount` for the whole window on the envelope, so keep requesting with `offset` while `offset + len(data) < totalCount`. A 30-day congressional window runs to several hundred rows, which the default page (200) cuts short, and the analyst default page is 50. Reading one page silently drops most of the window and can turn a real overlap into an empty one. On a FREE key both feeds stop at a preview slice, so say the screen covered a partial window rather than reporting \"no convergence\". Intersect the three ticker lists and report names appearing in two or more buckets, ranked by total signal count, with a one-liner each: \"$NVDA: 4 insiders bought, 1 congressional purchase, 2 analyst upgrades (30d).\"\n\n**Start this one at `lookbackDays=30`, not 7.** A 7-day window is too narrow for three slow feeds to overlap: on a representative run it returned 1 cluster-buy ticker, 1 congressional purchase ticker and 21 upgraded tickers, which intersected to **zero** names in two or more buckets. The same three calls at 30 days returned 8, 65 and 45 tickers and produced 7 convergent names. The trap is that no individual bucket was empty at 7 days, so an \"is this bucket empty\" check passes on all three and you still report nothing found. **Widen when the INTERSECTION is thin, not when a bucket is empty**, and say which window you used. Also expect the three-way overlap to be empty even at 30 days on a fully paged read: two-of-three is the working bar for this screen, and requiring all three will show a blank almost every time. A genuinely empty bucket (quiet week, disclosure lag) is `isPreview:false` and not an error either way. For one ticker's full flow, run `insider/trades/{T}`, `politicians/filings/{T}`, `institutional/quarters` then `institutional/holders/{T}?reportDate={Q}`, and `analyst/{T}/actions`. Present as observed positioning, never as advice.\n\n### 4. Pre-earnings sentiment check\n\nRead the sentiment and positioning into an earnings print.\n\n1. `GET /api/v1/calendar/earnings?ticker={T}` for the next report date and consensus (`data.earnings[0].earningsDate`, `confirmed`); an empty response means the name is outside the forward window, so ask the user for the date instead of guessing.\n2. `GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={now-30d}&endTime={now}` (epoch milliseconds) for the 30-day sentiment trend.\n3. `GET /api/v1/insider/trades/{T}?lookbackDays=60` for recent insider activity (`transactionType` BUY or SELL), dropping `transactionCode == \"F\"` rows before you call anything selling (see the code-F note below).\n4. `GET /api/v1/analyst/{T}/estimates` for the EPS band and `surprises[]` beat/miss history.\n5. `GET /api/v1/analyst/{T}/actions?lookbackDays=30` for recent rating changes.\n6. `GET /api/v1/insights/stock/{T}` for the current AI read.\n\nSynthesize the setup as educational context: \"$AAPL earnings in 5d: sentiment +0.22 over 30d and trending up; insiders net sellers (2 sells, 0 buys); EPS consensus $1.52 (range $1.48 to $1.55, 28 analysts), beat in 3 of the last 4 quarters; 3 upgrades in 30d. Setup reads mixed-to-constructive.\" Do not tell the user how to trade the print.\n\n### 5. Sentiment-versus-price divergence\n\nSurface names where perception and price disagree; a bullish gap (price down, sentiment up) and a bearish gap (price up, sentiment down) are the two shapes of interest.\n\n1. `GET /api/v1/stocks/popular` for the candidate list.\n2. For each candidate, in parallel: `GET /api/v1/stocks/chart?ticker={T}&timeframe=1M` (a bare array of intraday bars; filter to `timestamp >= now-7d` and compare the first versus last bar for the 7-day move) and `GET /api/v2/metrics/entity/{T}/metric/sentiment` (server default 7-day window; measure the trend across the returned series).\n3. Rank by the absolute gap between the price move and the sentiment move; report the top few in each direction.\n\nFrame the result as an observed divergence, not a signal to act: \"Bullish divergence: $TSLA price -8% while sentiment +0.11 over 7d. Bearish divergence: $COIN price +14% while sentiment -0.09.\" Keep the delayed price and the batch sentiment labeled with their own freshness; do not blend them into one implied \"now.\"\n\n### 6. Is this mention spike good or bad news?\n\nAnswer \"mentions of $T just jumped: is that good or bad for the stock?\" in two separate steps, because size and direction live in different fields. **A mention spike is direction-blind: it fires just as hard on a crash as on a rally, so the size of a spike never tells you which way it points.** Step A settles whether the spike is real and belongs to $T; step B settles direction from $T's own numbers only.\n\nStep A, is it really a spike? Fire the first two calls in parallel, then the peer calls:\n\n1. `GET /api/v1/stocks/{T}/graph?depth=1&cap=75` and read `groups.peers[]`.\n2. `GET /api/v2/metrics/entity/{T}/metric/mentions?startTime={now-30d}&endTime={now}`, then the same call for three to five peer slugs from step 1.\n\nFor each name, the spike ratio is its mentions on the spike day divided by its median daily mentions over the earlier points of the window (the median shrugs off older spikes and quiet weekends). The spike is $T's own when its ratio is roughly 2x or more AND clearly above the peers' median ratio; judge by that median, not by one busy peer. If the peers jumped too, it is a group story (sector news, a rival's print, a macro day): say so, then still read each name's direction from its own counts. Points are one per New York calendar day and the last is the current day so far, so test a finished day, or compare today-so-far only against the peers' today-so-far. An empty `groups.peers` means no curated comparables: ask the user for two or three, or say the spike was measured against $T's own history only.\n\nStep B, which way does it point? Read direction from $T alone:\n\n3. `GET /api/v2/metrics/entity/{T}/metric/sentisense?startTime={now-30d}&endTime={now}`: on the spike day's point read `metricValue.properties.bull` and `.bear`, the bullish and bearish analyses (each analysis is one news article or social post our models read as bullish or bearish for the ticker). Sum `bull` and `bear` over the earlier points for $T's usual lean: coverage leans bullish as a genre, so a day at 55% bullish on a name that normally runs 80% is a turn for the worse even with bulls still ahead.\n4. `GET /api/v1/stocks/price?ticker={T}` for `changePercent` (15-minute delayed) when the spike day is the latest session; for an older day, read that session from `stocks/chart`.\n\nPeers normalize volume only: never borrow a peer's, sector's or index's tone for $T, and never read direction from `mentions` or the spike ratio. Treat a thin day (under about 20 `directional` analyses) as unreadable rather than calling a 3-to-2 split. When the tone and the price disagree, report both and call it mixed. Budget: about eight requests with four peers.\n\nWorked example, live on a free key for the New York day of 2026-09-25. Response shapes, trimmed to the fields this workflow reads:\n\n```text\nGET /api/v1/stocks/ZS/graph?depth=1&cap=75\n  {\"ticker\": \"ZS\", \"root\": \"Zscaler-Inc\", \"depth\": 1, \"truncated\": false,\n   \"groups\": {\"peers\": [\"Cloudflare-Inc\", \"CrowdStrike-Holdings-Inc\", \"Okta-Inc\", \"Palo-Alto-Networks-Inc\"], ...},\n   \"nodes\": [{\"slug\": \"Cloudflare-Inc\", \"displayName\": \"Cloudflare, Inc.\", \"type\": \"COMPANY\"}, ...]}\nGET /api/v2/metrics/entity/ZS/metric/mentions  (spike-day point)\n  {\"timestamp\": 1790308800000, \"metricType\": \"MENTIONS\", \"value\": 49.0,\n   \"metricValue\": {\"type\": \"CountMetricValue\", \"value\": 49, \"count\": 49}}\nGET /api/v2/metrics/entity/ZS/metric/sentisense  (same day, metricValue.properties)\n  {\"bear\": 18.0, \"bull\": 14.0, \"directional\": 32.0}\n```\n\n```\n$ZS mention spike, New York day 2026-09-25\nSpike      49 mentions vs a 17/day median over the prior 28 days: 2.9x\nPeers      Cloudflare 1.0x, CrowdStrike 0.7x, Okta 2.0x, Palo Alto 0.7x (median 0.9x): ZS's own\nDirection  bearish analyses led 18 to 14 (44% bullish, against 72% over the prior 28 days)\nPrice      -10.1% on the session (15-min delayed)\nRead       a real spike, and bad news by ZS's own numbers: coverage turned bearish, price fell\n\n$COST mention spike, New York day 2026-09-25\nSpike      160 mentions vs a 74/day median over the prior 28 days: 2.2x\nPeers      Walmart 1.0x, Kroger 0.8x, Target 0.6x, Dollar Tree 0.2x (median 0.7x): COST's own\nDirection  bullish analyses led 91 to 29 (76% bullish, in line with 76% over the prior 28 days)\nPrice      +2.9% on the session (15-min delayed)\nRead       a real spike, and good news by COST's own numbers: coverage stayed bullish, price rose\n```\n\nTwo spikes of about the same size on the same day, pointing opposite ways: any rule that reads direction off the spike itself gets one of them wrong. Report what the coverage and the price did, not what the stock does next.\n\n## Pitfalls\n\n- **Company names are not tickers.** When the user names the company (\"sentiment on tesla\", \"is the mood on alphabet bullish\") instead of typing a symbol, resolve it first with `GET /api/v1/kb/entities/search?q={name}&type=company&limit=5`: a bare array of `{name, urlSlug, type, ticker}`, best match first (`type=etf` for a fund, since `SPY` resolves only there). Take the first match with a non-null `ticker`; a tracked subsidiary or private company can outrank its listed parent (\"google\" returns Google LLC with `ticker: null` before Alphabet `GOOGL`). Several plausible ticker-bearing matches means ask a one-line clarification; an empty array means say so. Never uppercase the word and hope: `$TESLA` fails the metric series with `404 entity_not_found` (that error carries up to three `suggestions`, which is a resolution hint, not data), while the smart-money feeds return an empty `data: []` that reads like a quiet name when the real failure was the identifier. An exact ticker the user typed skips this step, and one resolution call per name covers the whole session.\n- **Nothing here is real time.** Sentiment, the SentiSense Score, mentions, share of voice, news clustering, and AI insights are batch metrics computed on a schedule; quote, price, and chart points are the fresher class but carry a 15-minute delay. State a batch value with its `generatedAt` age, annotate price with `priceAsOf` where present, and never label either \"real time.\"\n- **Empty smart-money windows are normal.** The 7-day insider and congressional feeds often return empty arrays on quiet weeks (disclosure lag, `isPreview:false`, not an error). Widen that specific call to `lookbackDays=30` and note the wider window rather than showing a blank result.\n- **Preview gating is data, not failure.** On the free tier, preview-gated endpoints return `isPreview:true` with a real truncated slice (for example the top 3 insights, the current earnings week, a sliced holder list). Render the slice as the answer and tag it `(preview)`. Mention PRO only when the truncation is materially limiting the answer.\n- **Wrap versus flat differs by endpoint.** Reading `.data` on a flat endpoint (or the reverse) yields nothing. Flat: `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, the `sentiment`, `sentisense`, `mentions`, and `social_dominance` series, and `institutional/quarters`. Wrapped under `.data`: `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings`. When unsure, accept both.\n- **Read the metric scalar from the flat `value`.** Every point in a metric series carries a top-level `series[i].value` alongside the nested `metricValue`, and it holds the reading: the polarity for `sentiment`, the composite for `sentisense`, the count for `mentions`, the share for `social_dominance`. Prefer it, because the nested depth is **not** the same for every metric. A value metric (`sentiment`, `sentisense`, `social_dominance`) nests at `metricValue.value.value` because `metricValue.value` is itself a dict; a count metric (`mentions`) is `{\"type\":\"CountMetricValue\",\"value\":36,\"count\":36}`, so `metricValue.value` is already the integer and `metricValue.value.value` throws. The flat field spares you the branch. A point with no reading omits `value`; skip that point rather than reading it as zero.\n- **A mention spike is direction-blind.** `mentions` counts every mention whatever its tone, so it fires as hard on a crash as on a rally. Never call a spike good or bad from its size, and never borrow a peer's, sector's or index's tone: direction comes from the ticker's own `bull` against `bear` and its own price move. Workflow 6 is the full recipe.\n- **Congress and insider use different verbs.** Insider rows carry `transactionType` BUY or SELL; congressional rows carry PURCHASE or SALE. Filter each with its own vocabulary.\n- **Not every insider SELL is a sale.** `transactionType` is a simplified rollup of the SEC's one-letter codes, and code `F` lands on `SELL`: those are shares the company withheld to cover the insider's taxes when a grant vested. Nobody chose to sell and no shares reached the market. On companies that grant heavily this is the majority of the reported \"sold\" dollars, so a bearish read built on a raw `SELL` filter is describing a vesting schedule. Read `transactionCode` and drop `F` before you tally selling. The market-wide `/insider/activity` rollup already excludes it for you; `/insider/trades/{T}` returns every filed row, so there you filter yourself.\n- **Always fetch quarters first.** Call `institutional/quarters` and pass the `reportDate` of the first quarter whose `pending` is not true to `institutional/holders`; skip any `pending:true` entry (within ~45 days of a quarter close the most-recent quarter is still filing and holds almost no holders), and fall back to `[0]` only if every entry is `pending:true`. Never hardcode a quarter.\n- **Documents carry no article title.** The document feed returns URLs, `source`, `published` (epoch seconds), and `averageSentiment`, not the publisher's headline. Pre-clustered story titles (`cluster.title`) are SentiSense-authored and safe to display verbatim; prefer stories when a readable title is needed.\n- **No invented endpoints.** There is no real-time options order flow and no dark pool (options exist, but only as end-of-day analytics at `/api/v1/options/overview` and `/api/v1/stocks/{ticker}/options/summary`), and no `/congress` (congressional data lives under `/politicians`). The earnings calendar is `/api/v1/calendar/earnings`.\n- **No advice.** When asked \"should I buy,\" return data-grounded synthesis (sentiment, smart-money flow, analyst consensus, AI insight) framed as educational context, not a personal recommendation.\n\n## Verification\n\nConfirm the skill is wired correctly before trusting a synthesis:\n\n1. **Reachability and auth.** Every endpoint here takes an API key, so one call checks both: `curl -s -o /dev/null -w \"%{http_code}\" -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \"https://app.sentisense.ai/api/v2/market-mood\"`. A `200` confirms the base URL, the network, the header and the key. A `401 api_key_required` means the header or `SENTISENSE_API_KEY` is missing; a `401 invalid_api_key` means the key itself is wrong or revoked; a `429` means the per-minute rate was exceeded, so honor the `Retry-After` hint.\n2. **Sentiment parses.** Fetch `/api/v2/metrics/entity/AAPL/metric/sentiment`, confirm a non-empty array, and read `series[-1].value`; it should be a float in [-1, 1]. A value outside that range means the wrong field was read.\n3. **Mood nests as expected.** Fetch `/api/v2/market-mood` and confirm `market.currentScore`, `market.phase`, and `market.weeklyChange` are present (not at the root), and that `sectors` is a populated dict.\n4. **Envelope check.** Confirm `institutional/quarters` parses as a bare array and `insider/cluster-buys?lookbackDays=30` parses as `{ isPreview, data }` with `data` an array (an empty array on a quiet window is a valid result, not a failure).\n5. **Freshness is surfaced.** Any batch value presented to the user carries its `generatedAt`; if a synthesis omits the age on a sentiment or insight figure, or describes a batch surface as real time, it is not verified.\n\nA run passes when every quoted number traces to a `200` response read this turn, batch and delayed-price surfaces are labeled distinctly with their own ages, and the output reads as educational context rather than a recommendation.\n\n## Use & Disclaimer\n\nThis skill is an **educational data interface** to SentiSense's read-only Data API. Output is informational only. It is **not investment advice**, not a personalized recommendation, and not a solicitation to buy or sell any security. The user is responsible for their own decisions. Use of the API and this skill is subject to the [API Terms of Service](https://sentisense.ai/agreement/API-Terms-of-Service.pdf) and [Terms of Service](https://sentisense.ai/agreement/Terms-of-Service.pdf).\n\n---\n\n**ClawHub Skill:** [clawhub.ai/TheSentiTrader/stock-sentiment](https://clawhub.ai/TheSentiTrader/stock-sentiment)\n\nFile v0.5.2:_meta.json\n\n{\n  \"ownerId\": \"kn71ca3nrt3w6w0v3nhv3c4tan82x1ym\",\n  \"slug\": \"stock-sentiment\",\n  \"version\": \"0.5.2\",\n  \"publishedAt\": 1790664681068\n}\n\nFile v0.5.2:skill-card.md\n\n## Description:\n\nSentiment and smart-money positioning for US stocks.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[thesentitrader](https://clawhub.ai/user/thesentitrader)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nInvestors and research agents use this skill to review US-stock sentiment, market mood, news, and insider, congressional, institutional, and analyst activity as educational market context rather than investment advice.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Exposure of the SentiSense API key in prompts or shared logs.\n\nMitigation: Keep SENTISENSE_API_KEY in the environment, avoid printing it, and allow read-only HTTPS requests only to app.sentisense.ai.\n\nRisk: Treating sentiment or delayed market data as personalized investment advice or real-time prices.\n\nMitigation: Present outputs as educational context, identify data age and preview limitations, and avoid buy or sell recommendations.\n\n## Reference(s):\n\n- [ClawHub release](https://clawhub.ai/thesentitrader/skills/stock-sentiment)\n- [SentiSense API skill reference](https://sentisense.ai/skill.md)\n- [SentiSense website](https://sentisense.ai)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Guidance]\n\n**Output Format:** [Markdown prose with dated market-data observations]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Requires a SentiSense API key; free-tier responses may be preview-limited and supporting prices may be delayed.]\n\n## Skill Version(s):\n\n0.5.2 (source: server-resolved ClawHub release)\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.5.1: 4 files, 19783 bytes\n\nFiles: scripts/sentiment_client.py (11483b), skill-card.md (1993b), SKILL.md (36317b), _meta.json (134b)\n\nFile v0.5.1:SKILL.md\n\n---\nname: stock-sentiment\ndescription: \"Sentiment and smart-money positioning for US stocks.\"\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n---\n# Stock Sentiment Skill\n\nThe sentiment and smart-money layer for US equities. A quote skill tells you the price; this skill reads what the market feels about a stock (the SentiSense Score, sentiment polarity, mentions, share of voice), where the smart money is moving (insider, congressional, and institutional flows plus analyst actions), and what the AI read of the tape is (per-stock and market-wide insights, sentiment-tagged news), all through the read-only SentiSense API.\n\nRead-only educational data interface. Output is informational context, never a personalized buy or sell recommendation.\n\n## When to Use\n\nReach for this skill when the question is about perception, positioning, or signal rather than raw price:\n\n- \"What is the sentiment on $NVDA?\" or \"Is the mood on $TSLA bullish or bearish?\"\n- \"What is the smart money doing this week?\" (insider cluster-buys, congressional trades, 13F flows, and analyst upgrades converging on the same tickers).\n- \"What is the overall market mood today, fear or greed?\"\n- \"Is sentiment diverging from price on $COIN?\" (price up while sentiment falls, or the reverse).\n- \"Mentions of $ZS just spiked: is that good or bad news for the stock?\"\n- \"What is the pre-earnings sentiment setup on $AAPL?\"\n- \"What is the AI insight on $MSFT, and what are people saying in the news?\"\n\nFor \"does this change my thesis?\", hand off to the `us-stocks-analysis` skill when available. Pass the ticker, user thesis, horizon, and dated signal disagreements. Return an evidence-led bull/bear assessment and unresolved objections. Hand off only when the user changes the question; do not automatically route back. If the sibling is unavailable, answer the supported part here using a connected tool or the inline REST workflow, state any remaining gap, and never require an install.\n\nDo not use it for order entry, portfolio management, or personalized advice. It has no write, trading, or wallet surface; every endpoint is a GET.\n\n## Prerequisites\n\n- Python 3.8+ using only the standard library (`urllib`, `json`); no third-party packages required. Any HTTP client or plain `curl` works too. On macOS python.org installs the client can raise `CERTIFICATE_VERIFY_FAILED` (missing CA certs): run the bundled `Install Certificates.command`, use the system `/usr/bin/python3`, or use `curl` (which uses the system trust store).\n- A free `SENTISENSE_API_KEY`. Get one at https://app.sentisense.ai/get-api-key. The key is required on every call; anonymous requests return `401 api_key_required`.\n- Network access to `https://app.sentisense.ai`.\n- Read-only scope. Every endpoint here is a GET. Nothing this skill does can place a trade, move money, or modify account state.\n\n## Permissions\n\n- Network: HTTPS to app.sentisense.ai only.\n- Credentials: SENTISENSE_API_KEY from the environment.\n- Shell: none required.\n- Files: none.\n\nTiers:\n\n| Tier | Quota | Rate |\n|------|-------|------|\n| Free | 1,000 requests/month | 30 requests/min |\n| PRO ($15/mo) | Unlimited | 300 requests/min |\n\nThe free tier exercises every workflow below. Preview-gated endpoints return a truncated but real slice on a free key (for example the top 3 insights); PRO removes the monthly cap and returns full history and full lists.\n\n## How to Run\n\nThis skill is invoked through the agent's terminal or shell tool: issue HTTP GET requests to the SentiSense API and synthesize the JSON into a concise, sourced answer. The base URL is `https://app.sentisense.ai`. Authenticate every request with the `X-SentiSense-API-Key` header; keep the key in the shell environment and never place it in a query string or in user-facing output.\n\n**Identify your client.** Send a `User-Agent` naming your agent runtime and this skill, for\nexample `OpenClaw/1.4 (stock-sentiment)` or `ClaudeCode/2.1 (stock-sentiment)`. Substitute your own runtime and\nversion if neither matches. You can also volunteer what your agent is called by adding an\n`agent/<your-agent-name>` token inside the same parentheses, as in\n`OpenClaw/1.4 (stock-sentiment; agent/research-desk)`. All of it is optional, and it is what tells\nus this skill has real integrations behind it, so it gets prioritized and you get notice before it\nchanges.\n\n```bash\ncurl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v2/metrics/entity/NVDA/metric/sentiment\"\n```\n\nAn anonymous call returns `401 api_key_required`. A rate-limited call returns `429` with a `Retry-After` header; back off for the indicated seconds rather than retrying immediately or serving a stale value.\n\nOn Windows, use the bundled Python client (cross-platform) and reference the key as `%SENTISENSE_API_KEY%` (cmd) or `$env:SENTISENSE_API_KEY` (PowerShell) rather than the POSIX `$SENTISENSE_API_KEY` shown above.\n\nThe REST recipe in this file is the primary path. A maintained command-line client is available as the separate `sentisense-cli` skill for hosts that prefer one.\n\nTwo response envelopes exist; unwrap correctly before reading fields:\n\n- Read FLAT (top-level, no `.data`): `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, and the metric series (`sentiment`, `sentisense`, `mentions`, and `social_dominance` are bare arrays). `institutional/quarters` is also a bare array.\n- Read WRAPPED as `{ isPreview, previewReason, data }` (use `.data`): `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings` (here `data` is a dict, so read `data.earnings[]`).\n- `documents/ticker` has its own shape `{ documents, totalCount }`; read `.documents[]`.\n\nWhen unsure, accept both: `rows = raw if isinstance(raw, list) else raw.get(\"data\", raw)`.\n\nAn optional stdlib helper, `scripts/sentiment_client.py`, wraps all of this: it injects the auth header, prepends the base URL, and normalizes both envelopes (reading the metric scalar from the flat `value`) so the agent reasons over clean values. Use it or plain `curl`, whichever fits the host. The core of the helper is small enough to inline:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal stdlib client for the read-only SentiSense API.\"\"\"\nimport json, os, urllib.parse, urllib.request\n\nAPI_ORIGIN = \"https://app.sentisense.ai\"\n\nclass NoRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        return None\n\ndef sentisense_api_url(path, params=None):\n    url = urllib.parse.urljoin(API_ORIGIN + \"/\", path)\n    parsed = urllib.parse.urlparse(url)\n    if (parsed.scheme != \"https\" or parsed.hostname != \"app.sentisense.ai\"\n            or parsed.netloc != \"app.sentisense.ai\"\n            or parsed.username is not None or parsed.password is not None\n            or parsed.port is not None):\n        raise ValueError(\"API URL must use https://app.sentisense.ai with no credentials or port\")\n    if params:\n        url += (\"&\" if parsed.query else \"?\") + urllib.parse.urlencode(params)\n    return url\n\ndef get(path, **params):\n    url = sentisense_api_url(path, params)\n    req = urllib.request.Request(\n        url, headers={\"X-SentiSense-API-Key\": os.environ[\"SENTISENSE_API_KEY\"]})\n    with urllib.request.build_opener(NoRedirect).open(req, timeout=20) as r:\n        return json.load(r)\n\ndef rows(raw):\n    \"\"\"Wrap-vs-flat: some endpoints return a bare array, others {isPreview, data}.\"\"\"\n    if isinstance(raw, list):\n        return raw\n    if isinstance(raw, dict) and \"data\" in raw:\n        return raw[\"data\"]\n    return raw\n\ndef latest_metric(ticker, slug=\"sentiment\"):\n    \"\"\"Latest reading of any metric series. Read the flat top-level `value`: it is present\n    on every point and holds the scalar, while the nested metricValue is a dict for value\n    metrics and a bare number for count metrics like `mentions`.\"\"\"\n    series = get(f\"/api/v2/metrics/entity/{ticker}/metric/{slug}\")\n    if not series or series[-1].get(\"value\") is None:\n        return None\n    return float(series[-1][\"value\"])\n```\n\n```bash\npython scripts/sentiment_client.py sentiment NVDA\npython scripts/sentiment_client.py mood\n```\n\n## Quick Reference\n\nAll paths are relative to `https://app.sentisense.ai` and are GET. Every call requires the `X-SentiSense-API-Key` header. `{T}` is an uppercase ticker, `{slug}` a member slug, `{id}` a story id. Full schema: https://sentisense.ai/skill.md.\n\n```\nRESOLVE A NAME (only when the user typed a company or fund name, not a symbol)\n  GET /api/v1/kb/entities/search?q={name}&type=company&limit=5\n        Bare array of {name, urlSlug, type, ticker}, best match first. Take the first match with a\n        non-null ticker. Use type=etf for fund names (SPY resolves only there). See Pitfalls.\n\nPEERS (to test whether a mention spike is the ticker's own; see workflow 6)\n  GET /api/v1/stocks/{T}/graph?depth=1&cap=75\n        groups.peers[] is the curated comparable set, as entity slugs that the metric endpoints\n        accept in place of {T}; join a slug to nodes[] for its displayName. Peers from depth=1 only.\n\nSENTIMENT & MOOD\n  GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}\n        Sentiment polarity time series. Omit params for the server default 7-day window.\n        Bare array; latest scalar is series[-1].value (a float in [-1, 1]). Every series\n        below reads the same way: take the flat top-level value, not the nested metricValue,\n        whose depth differs between value metrics and count metrics.\n  GET /api/v2/metrics/entity/{T}/metric/sentisense\n        The SentiSense Score (unbounded composite; report as-is, never normalize to 0-100).\n        Each daily point also carries metricValue.properties.{bull, bear, directional}: that\n        day's bullish and bearish analyses and their sum. Direction lives here, not in mentions.\n  GET /api/v2/metrics/entity/{T}/metric/mentions\n        Mention-volume time series (how much a ticker is being talked about). Direction-blind.\n  GET /api/v2/metrics/entity/{T}/metric/social_dominance\n        Share-of-conversation time series (a ticker's dominance of the chatter).\n  GET /api/v2/metrics/entity/{E}/metric/app_review_count\n        New Apple App Store reviews per day for a product's iOS app. Products with a\n        tracked app only, so {E} is a product entity slug, never a ticker.\n  GET /api/v2/metrics/entity/{E}/metric/app_rating\n        Mean star rating (1 to 5) of that day's new App Store reviews. Same coverage.\n  GET /api/v2/market-mood\n        Composite fear/greed plus sub-signals and per-sector breakdowns. Flat, but the\n        composite is nested: market.currentScore, market.phase, market.weeklyChange,\n        market.signals[]; sectors.{SectorName}.{currentScore, phase, weeklyChange}.\n\nSMART MONEY  (wrapped in {isPreview, previewReason, data}; free key returns a preview slice)\n  GET /api/v1/insider/cluster-buys?lookbackDays=N         Tickers with multiple insider buys.\n  GET /api/v1/insider/trades/{T}?lookbackDays=N           Form 4 rows; transactionType BUY|SELL,\n                                                          raw SEC letter in transactionCode.\n  GET /api/v1/politicians/activity?lookbackDays=N         Congressional trades; PURCHASE|SALE.\n  GET /api/v1/politicians/filings/{T}?lookbackDays=N      Per-ticker congressional filings.\n  GET /api/v1/politicians/member/{slug}                   Member profile (data.recentTrades[]).\n  GET /api/v1/institutional/quarters                      Call FIRST; bare array. Use reportDate of first entry whose pending is not true; skip pending:true (fall back to [0] only if all pending).\n  GET /api/v1/institutional/holders/{T}?reportDate={Q}    Top 13F holders (data.holders[], largest first).\n  GET /api/v1/analyst/{T}/consensus                       Price-target band; data IS the consensus object (data.consensusLabel).\n  GET /api/v1/analyst/{T}/actions?lookbackDays=N          Recent rating changes for one ticker.\n  GET /api/v1/analyst/{T}/estimates                       EPS band at data.estimates[0].{estimateLow/Mean/High,\n                                                          numberOfAnalysts} + data.surprises[]; no revenue.\n  GET /api/v1/analyst/activity?lookbackDays=N             Market-wide actions; add &actionTypes=UPGRADE,DOWNGRADE,INITIATE\n                                                          for real rating changes (~83% of raw rows are REITERATE).\n\nAI INSIGHTS  (wrapped; batch, carry generatedAt)\n  GET /api/v1/insights/stock/{T}         Per-stock signals ranked by importance; data[0].insightText is the headline. Free preview top 3.\n  GET /api/v1/insights/stock/{T}/types   Available insight types for the ticker; bare string array.\n  GET /api/v1/insights/market            Top market-wide signals (data[], insightText; ticker embedded in insightText).\n\nNEWS & STORIES\n  GET /api/v1/documents/ticker/{T}?limit=N          Sentiment-tagged feed ({documents, totalCount}); each doc\n                                                   has url, source, sourceName, published (epoch seconds), averageSentiment; no title.\n  GET /api/v1/documents/stories?limit=N             Pre-clustered stories; cluster.title is SentiSense-authored and safe to show.\n  GET /api/v1/documents/stories/ticker/{T}?limit=N  Stories for one ticker.\n  GET /api/v1/documents/stories/{id}                Story detail (PublicStoryDetailDto; aspectPerspectives[], bullishView/bearishView).\n  GET /api/v1/documents/search?query=...            Topical document search.\n\nSUPPORTING  (price, prices, chart are 15-minute delayed; profile, popular, calendar, market-summary are reference or batch)\n  GET /api/v1/stocks/price?ticker={T}                       Flat (no wrapper): currentPrice, changePercent at root.\n  GET /api/v1/stocks/prices?tickers=A,B,C                   Batch quotes.\n  GET /api/v1/stocks/{T}/profile                            name, sector, industry (flat at root; no profile key).\n  GET /api/v1/stocks/chart?ticker={T}&timeframe=1D|5D|1W|1M|3M|6M|1Y|5Y|10Y|MAX   Bars; read each point's timestamp (Unix ms). Invalid timeframe returns 400.\n  GET /api/v1/stocks/popular                                Bare array of ~75 ticker strings (screen universe).\n  GET /api/v1/calendar/earnings?ticker={T}                  data.earnings[]; next date + consensus EPS + confirmed.\n  GET /api/v1/market-summary                                Market-wide narrative headline.\n```\n\nSentiment is polarity: a float in [-1, 1] where the sign is the direction (negative is bearish and meaningful, positive is bullish) and the magnitude is conviction. Represent the sign unmistakably; do not map it onto a 0-100 scale. The SentiSense Score is a separate, unbounded composite; report it as-is. Mentions and social dominance are their own metric series on the same `/metric/{metricType}` endpoint (`mentions` for talk volume, `social_dominance` for share of the conversation); all four series (`sentiment`, `sentisense`, `mentions`, `social_dominance`) are available on the Free tier, and like every metrics call each request counts against your monthly quota. Two more Free-tier series cover consumer products rather than tickers: `app_review_count` (new Apple App Store reviews for a product's iOS app that day) and `app_rating` (the mean star rating, 1 to 5, of those reviews). **From 2026-09-05 `mentions` excludes App Store reviews**, because a review is a rating rather than chatter and for review-heavy products the two read as one number; the App Store slice still appears under `distribution/mentions?dimension=source`, so read review volume from `app_review_count`. A separate `/api/v2/metrics/entity/{T}/distribution/{metricType}` endpoint breaks a metric down by source (share of voice, a \"where this signal came from\" view, not per-source sentiment values).\n\n## Workflows\n\nOpinionated recipes. Each fans out its independent calls in parallel, then synthesizes; none recommends buying or selling. Frame every result as educational context on positioning and mood.\n\n### 1. Sentiment read on a ticker\n\nAnswer \"what is the market feeling about $T\" in a few dense lines. Fire these in parallel:\n\n1. `GET /api/v2/metrics/entity/{T}/metric/sentiment` for the polarity trend (server default 7-day window; the latest scalar is `series[-1].value`, a float in [-1, 1]).\n2. `GET /api/v2/metrics/entity/{T}/metric/sentisense` for the composite score.\n3. `GET /api/v2/metrics/entity/{T}/metric/mentions` for mention volume: one daily count per point, and each point's `metricValue.properties.bull` / `bear` for that day's direction.\n4. `GET /api/v1/documents/ticker/{T}?limit=8` for the sentiment-tagged feed only. Its `totalCount` counts the documents on this page (it equals `limit`), so never report it as mention volume.\n5. `GET /api/v1/insights/stock/{T}` for the top AI insight (`data[0].insightText`, with `generatedAt` for freshness).\n\nSynthesize as educational context, leading with the differentiated sentiment read, not the price: \"$NVDA sentiment +0.42 over 7d and rising; SentiSense Score elevated; mention volume heavy; latest AI insight: 'Data-center demand commentary firming' (as of the batch time).\" Show the `generatedAt` age so the reader knows these are batch metrics.\n\n### 2. Market mood (fear and greed)\n\nAnswer \"what is the overall market mood today.\"\n\n1. `GET /api/v2/market-mood`.\n\nThe response is flat, but the composite is nested under `market`, not the root: `market.currentScore`, `market.phase` (e.g. Fear, Neutral, Optimism, Greed), `market.weeklyChange`, and `market.signals[]` (each sub-gauge with its value and change). Per-sector readings live at `sectors.{SectorName}.{ currentScore, phase, weeklyChange }`; `sectors` is a string-keyed dict, not an array, and its GICS labels have historically overlapped (`Technology` alongside `Information Technology`, `Healthcare` alongside `Health Care`), so treat the pairs defensively: if both members of a pair appear in one response, dedupe them before ranking top and bottom sectors. A clean response with neither pair duplicated is the common case and needs no special handling. Report as context: \"Market mood 62 (Greed), +4 over the week. Greed leaders: Technology, Communications. Fear: Energy, Utilities.\" Optionally pair with `GET /api/v1/market-summary` for the narrative headline and `GET /api/v1/insights/market` for the top market-wide signals.\n\n### 3. Smart-money convergence screen\n\nFind tickers where insider buying, congressional purchases, and analyst upgrades line up in the same window; convergence is the signal a quote feed cannot produce.\n\n1. `GET /api/v1/insider/cluster-buys?lookbackDays=30`.\n2. `GET /api/v1/politicians/activity?lookbackDays=30&limit=500`, keeping rows with `transactionType == \"PURCHASE\"`.\n3. `GET /api/v1/analyst/activity?lookbackDays=30&actionTypes=UPGRADE&limit=500` (server-side filter; also accepts a CSV like `UPGRADE,DOWNGRADE,INITIATE`).\n\nAll three are wrapped: read `.data`. **Steps 2 and 3 are paged feeds: one call is one page, not the window.** Both carry `totalCount` for the whole window on the envelope, so keep requesting with `offset` while `offset + len(data) < totalCount`. A 30-day congressional window runs to several hundred rows, which the default page (200) cuts short, and the analyst default page is 50. Reading one page silently drops most of the window and can turn a real overlap into an empty one. On a FREE key both feeds stop at a preview slice, so say the screen covered a partial window rather than reporting \"no convergence\". Intersect the three ticker lists and report names appearing in two or more buckets, ranked by total signal count, with a one-liner each: \"$NVDA: 4 insiders bought, 1 congressional purchase, 2 analyst upgrades (30d).\"\n\n**Start this one at `lookbackDays=30`, not 7.** A 7-day window is too narrow for three slow feeds to overlap: on a representative run it returned 1 cluster-buy ticker, 1 congressional purchase ticker and 21 upgraded tickers, which intersected to **zero** names in two or more buckets. The same three calls at 30 days returned 8, 65 and 45 tickers and produced 7 convergent names. The trap is that no individual bucket was empty at 7 days, so an \"is this bucket empty\" check passes on all three and you still report nothing found. **Widen when the INTERSECTION is thin, not when a bucket is empty**, and say which window you used. Also expect the three-way overlap to be empty even at 30 days on a fully paged read: two-of-three is the working bar for this screen, and requiring all three will show a blank almost every time. A genuinely empty bucket (quiet week, disclosure lag) is `isPreview:false` and not an error either way. For one ticker's full flow, run `insider/trades/{T}`, `politicians/filings/{T}`, `institutional/quarters` then `institutional/holders/{T}?reportDate={Q}`, and `analyst/{T}/actions`. Present as observed positioning, never as advice.\n\n### 4. Pre-earnings sentiment check\n\nRead the sentiment and positioning into an earnings print.\n\n1. `GET /api/v1/calendar/earnings?ticker={T}` for the next report date and consensus (`data.earnings[0].earningsDate`, `confirmed`); an empty response means the name is outside the forward window, so ask the user for the date instead of guessing.\n2. `GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={now-30d}&endTime={now}` (epoch milliseconds) for the 30-day sentiment trend.\n3. `GET /api/v1/insider/trades/{T}?lookbackDays=60` for recent insider activity (`transactionType` BUY or SELL), dropping `transactionCode == \"F\"` rows before you call anything selling (see the code-F note below).\n4. `GET /api/v1/analyst/{T}/estimates` for the EPS band and `surprises[]` beat/miss history.\n5. `GET /api/v1/analyst/{T}/actions?lookbackDays=30` for recent rating changes.\n6. `GET /api/v1/insights/stock/{T}` for the current AI read.\n\nSynthesize the setup as educational context: \"$AAPL earnings in 5d: sentiment +0.22 over 30d and trending up; insiders net sellers (2 sells, 0 buys); EPS consensus $1.52 (range $1.48 to $1.55, 28 analysts), beat in 3 of the last 4 quarters; 3 upgrades in 30d. Setup reads mixed-to-constructive.\" Do not tell the user how to trade the print.\n\n### 5. Sentiment-versus-price divergence\n\nSurface names where perception and price disagree; a bullish gap (price down, sentiment up) and a bearish gap (price up, sentiment down) are the two shapes of interest.\n\n1. `GET /api/v1/stocks/popular` for the candidate list.\n2. For each candidate, in parallel: `GET /api/v1/stocks/chart?ticker={T}&timeframe=1M` (a bare array of intraday bars; filter to `timestamp >= now-7d` and compare the first versus last bar for the 7-day move) and `GET /api/v2/metrics/entity/{T}/metric/sentiment` (server default 7-day window; measure the trend across the returned series).\n3. Rank by the absolute gap between the price move and the sentiment move; report the top few in each direction.\n\nFrame the result as an observed divergence, not a signal to act: \"Bullish divergence: $TSLA price -8% while sentiment +0.11 over 7d. Bearish divergence: $COIN price +14% while sentiment -0.09.\" Keep the delayed price and the batch sentiment labeled with their own freshness; do not blend them into one implied \"now.\"\n\n### 6. Is this mention spike good or bad news?\n\nAnswer \"mentions of $T just jumped: is that good or bad for the stock?\" in two separate steps, because size and direction live in different fields. **A mention spike is direction-blind: it fires just as hard on a crash as on a rally, so the size of a spike never tells you which way it points.** Step A settles whether the spike is real and belongs to $T; step B settles direction from $T's own numbers only.\n\nStep A, is it really a spike? Fire the first two calls in parallel, then the peer calls:\n\n1. `GET /api/v1/stocks/{T}/graph?depth=1&cap=75` and read `groups.peers[]`.\n2. `GET /api/v2/metrics/entity/{T}/metric/mentions?startTime={now-30d}&endTime={now}`, then the same call for three to five peer slugs from step 1.\n\nFor each name, the spike ratio is its mentions on the spike day divided by its median daily mentions over the earlier points of the window (the median shrugs off older spikes and quiet weekends). The spike is $T's own when its ratio is roughly 2x or more AND clearly above the peers' median ratio; judge by that median, not by one busy peer. If the peers jumped too, it is a group story (sector news, a rival's print, a macro day): say so, then still read each name's direction from its own counts. Points are one per New York calendar day and the last is the current day so far, so test a finished day, or compare today-so-far only against the peers' today-so-far. An empty `groups.peers` means no curated comparables: ask the user for two or three, or say the spike was measured against $T's own history only.\n\nStep B, which way does it point? Read direction from $T alone:\n\n3. `GET /api/v2/metrics/entity/{T}/metric/sentisense?startTime={now-30d}&endTime={now}`: on the spike day's point read `metricValue.properties.bull` and `.bear`, the bullish and bearish analyses (each analysis is one news article or social post our models read as bullish or bearish for the ticker). Sum `bull` and `bear` over the earlier points for $T's usual lean: coverage leans bullish as a genre, so a day at 55% bullish on a name that normally runs 80% is a turn for the worse even with bulls still ahead.\n4. `GET /api/v1/stocks/price?ticker={T}` for `changePercent` (15-minute delayed) when the spike day is the latest session; for an older day, read that session from `stocks/chart`.\n\nPeers normalize volume only: never borrow a peer's, sector's or index's tone for $T, and never read direction from `mentions` or the spike ratio. Treat a thin day (under about 20 `directional` analyses) as unreadable rather than calling a 3-to-2 split. When the tone and the price disagree, report both and call it mixed. Budget: about eight requests with four peers.\n\nWorked example, live on a free key for the New York day of 2026-09-25. Response shapes, trimmed to the fields this workflow reads:\n\n```text\nGET /api/v1/stocks/ZS/graph?depth=1&cap=75\n  {\"ticker\": \"ZS\", \"root\": \"Zscaler-Inc\", \"depth\": 1, \"truncated\": false,\n   \"groups\": {\"peers\": [\"Cloudflare-Inc\", \"CrowdStrike-Holdings-Inc\", \"Okta-Inc\", \"Palo-Alto-Networks-Inc\"], ...},\n   \"nodes\": [{\"slug\": \"Cloudflare-Inc\", \"displayName\": \"Cloudflare, Inc.\", \"type\": \"COMPANY\"}, ...]}\nGET /api/v2/metrics/entity/ZS/metric/mentions  (spike-day point)\n  {\"timestamp\": 1790308800000, \"metricType\": \"MENTIONS\", \"value\": 49.0,\n   \"metricValue\": {\"type\": \"CountMetricValue\", \"value\": 49, \"count\": 49}}\nGET /api/v2/metrics/entity/ZS/metric/sentisense  (same day, metricValue.properties)\n  {\"bear\": 18.0, \"bull\": 14.0, \"directional\": 32.0}\n```\n\n```\n$ZS mention spike, New York day 2026-09-25\nSpike      49 mentions vs a 17/day median over the prior 28 days: 2.9x\nPeers      Cloudflare 1.0x, CrowdStrike 0.7x, Okta 2.0x, Palo Alto 0.7x (median 0.9x): ZS's own\nDirection  bearish analyses led 18 to 14 (44% bullish, against 72% over the prior 28 days)\nPrice      -10.1% on the session (15-min delayed)\nRead       a real spike, and bad news by ZS's own numbers: coverage turned bearish, price fell\n\n$COST mention spike, New York day 2026-09-25\nSpike      160 mentions vs a 74/day median over the prior 28 days: 2.2x\nPeers      Walmart 1.0x, Kroger 0.8x, Target 0.6x, Dollar Tree 0.2x (median 0.7x): COST's own\nDirection  bullish analyses led 91 to 29 (76% bullish, in line with 76% over the prior 28 days)\nPrice      +2.9% on the session (15-min delayed)\nRead       a real spike, and good news by COST's own numbers: coverage stayed bullish, price rose\n```\n\nTwo spikes of about the same size on the same day, pointing opposite ways: any rule that reads direction off the spike itself gets one of them wrong. Report what the coverage and the price did, not what the stock does next.\n\n## Pitfalls\n\n- **Company names are not tickers.** When the user names the company (\"sentiment on tesla\", \"is the mood on alphabet bullish\") instead of typing a symbol, resolve it first with `GET /api/v1/kb/entities/search?q={name}&type=company&limit=5`: a bare array of `{name, urlSlug, type, ticker}`, best match first (`type=etf` for a fund, since `SPY` resolves only there). Take the first match with a non-null `ticker`; a tracked subsidiary or private company can outrank its listed parent (\"google\" returns Google LLC with `ticker: null` before Alphabet `GOOGL`). Several plausible ticker-bearing matches means ask a one-line clarification; an empty array means say so. Never uppercase the word and hope: `$TESLA` fails the metric series with `404 entity_not_found` (that error carries up to three `suggestions`, which is a resolution hint, not data), while the smart-money feeds return an empty `data: []` that reads like a quiet name when the real failure was the identifier. An exact ticker the user typed skips this step, and one resolution call per name covers the whole session.\n- **Nothing here is real time.** Sentiment, the SentiSense Score, mentions, share of voice, news clustering, and AI insights are batch metrics computed on a schedule; quote, price, and chart points are the fresher class but carry a 15-minute delay. State a batch value with its `generatedAt` age, annotate price with `priceAsOf` where present, and never label either \"real time.\"\n- **Empty smart-money windows are normal.** The 7-day insider and congressional feeds often return empty arrays on quiet weeks (disclosure lag, `isPreview:false`, not an error). Widen that specific call to `lookbackDays=30` and note the wider window rather than showing a blank result.\n- **Preview gating is data, not failure.** On the free tier, preview-gated endpoints return `isPreview:true` with a real truncated slice (for example the top 3 insights, the current earnings week, a sliced holder list). Render the slice as the answer and tag it `(preview)`. Mention PRO only when the truncation is materially limiting the answer.\n- **Wrap versus flat differs by endpoint.** Reading `.data` on a flat endpoint (or the reverse) yields nothing. Flat: `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, the `sentiment`, `sentisense`, `mentions`, and `social_dominance` series, and `institutional/quarters`. Wrapped under `.data`: `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings`. When unsure, accept both.\n- **Read the metric scalar from the flat `value`.** Every point in a metric series carries a top-level `series[i].value` alongside the nested `metricValue`, and it holds the reading: the polarity for `sentiment`, the composite for `sentisense`, the count for `mentions`, the share for `social_dominance`. Prefer it, because the nested depth is **not** the same for every metric. A value metric (`sentiment`, `sentisense`, `social_dominance`) nests at `metricValue.value.value` because `metricValue.value` is itself a dict; a count metric (`mentions`) is `{\"type\":\"CountMetricValue\",\"value\":36,\"count\":36}`, so `metricValue.value` is already the integer and `metricValue.value.value` throws. The flat field spares you the branch. A point with no reading omits `value`; skip that point rather than reading it as zero.\n- **A mention spike is direction-blind.** `mentions` counts every mention whatever its tone, so it fires as hard on a crash as on a rally. Never call a spike good or bad from its size, and never borrow a peer's, sector's or index's tone: direction comes from the ticker's own `bull` against `bear` and its own price move. Workflow 6 is the full recipe.\n- **Congress and insider use different verbs.** Insider rows carry `transactionType` BUY or SELL; congressional rows carry PURCHASE or SALE. Filter each with its own vocabulary.\n- **Not every insider SELL is a sale.** `transactionType` is a simplified rollup of the SEC's one-letter codes, and code `F` lands on `SELL`: those are shares the company withheld to cover the insider's taxes when a grant vested. Nobody chose to sell and no shares reached the market. On companies that grant heavily this is the majority of the reported \"sold\" dollars, so a bearish read built on a raw `SELL` filter is describing a vesting schedule. Read `transactionCode` and drop `F` before you tally selling. The market-wide `/insider/activity` rollup already excludes it for you; `/insider/trades/{T}` returns every filed row, so there you filter yourself.\n- **Always fetch quarters first.** Call `institutional/quarters` and pass the `reportDate` of the first quarter whose `pending` is not true to `institutional/holders`; skip any `pending:true` entry (within ~45 days of a quarter close the most-recent quarter is still filing and holds almost no holders), and fall back to `[0]` only if every entry is `pending:true`. Never hardcode a quarter.\n- **Documents carry no article title.** The document feed returns URLs, `source`, `published` (epoch seconds), and `averageSentiment`, not the publisher's headline. Pre-clustered story titles (`cluster.title`) are SentiSense-authored and safe to display verbatim; prefer stories when a readable title is needed.\n- **No invented endpoints.** There is no real-time options order flow and no dark pool (options exist, but only as end-of-day analytics at `/api/v1/options/overview` and `/api/v1/stocks/{ticker}/options/summary`), and no `/congress` (congressional data lives under `/politicians`). The earnings calendar is `/api/v1/calendar/earnings`.\n- **No advice.** When asked \"should I buy,\" return data-grounded synthesis (sentiment, smart-money flow, analyst consensus, AI insight) framed as educational context, not a personal recommendation.\n\n## Verification\n\nConfirm the skill is wired correctly before trusting a synthesis:\n\n1. **Reachability and auth.** Every endpoint here takes an API key, so one call checks both: `curl -s -o /dev/null -w \"%{http_code}\" -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \"https://app.sentisense.ai/api/v2/market-mood\"`. A `200` confirms the base URL, the network, the header and the key. A `401 api_key_required` means the header or `SENTISENSE_API_KEY` is missing; a `401 invalid_api_key` means the key itself is wrong or revoked; a `429` means the per-minute rate was exceeded, so honor the `Retry-After` hint.\n2. **Sentiment parses.** Fetch `/api/v2/metrics/entity/AAPL/metric/sentiment`, confirm a non-empty array, and read `series[-1].value`; it should be a float in [-1, 1]. A value outside that range means the wrong field was read.\n3. **Mood nests as expected.** Fetch `/api/v2/market-mood` and confirm `market.currentScore`, `market.phase`, and `market.weeklyChange` are present (not at the root), and that `sectors` is a populated dict.\n4. **Envelope check.** Confirm `institutional/quarters` parses as a bare array and `insider/cluster-buys?lookbackDays=30` parses as `{ isPreview, data }` with `data` an array (an empty array on a quiet window is a valid result, not a failure).\n5. **Freshness is surfaced.** Any batch value presented to the user carries its `generatedAt`; if a synthesis omits the age on a sentiment or insight figure, or describes a batch surface as real time, it is not verified.\n\nA run passes when every quoted number traces to a `200` response read this turn, batch and delayed-price surfaces are labeled distinctly with their own ages, and the output reads as educational context rather than a recommendation.\n\n## Use & Disclaimer\n\nThis skill is an **educational data interface** to SentiSense's read-only Data API. Output is informational only. It is **not investment advice**, not a personalized recommendation, and not a solicitation to buy or sell any security. The user is responsible for their own decisions. Use of the API and this skill is subject to the [API Terms of Service](https://sentisense.ai/agreement/API-Terms-of-Service.pdf) and [Terms of Service](https://sentisense.ai/agreement/Terms-of-Service.pdf).\n\n---\n\n**ClawHub Skill:** [clawhub.ai/TheSentiTrader/stock-sentiment](https://clawhub.ai/TheSentiTrader/stock-sentiment)\n\nFile v0.5.1:_meta.json\n\n{\n  \"ownerId\": \"kn71ca3nrt3w6w0v3nhv3c4tan82x1ym\",\n  \"slug\": \"stock-sentiment\",\n  \"version\": \"0.5.1\",\n  \"publishedAt\": 1790397381279\n}\n\nFile v0.5.1:skill-card.md\n\n## Description:\n\nSentiment and smart-money positioning for US stocks.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[thesentitrader](https://clawhub.ai/user/thesentitrader)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nInvestors and market researchers use this skill to assess US-stock sentiment, market mood, smart-money activity, and news signals as educational context, not personalized investment advice.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Ticker and market-data requests transmit an API key to SentiSense.\n\nMitigation: Use a dedicated SENTISENSE_API_KEY in the environment and do not expose it in URLs or answers.\n\nRisk: Stale, delayed, or preview-limited figures can mislead financial decisions.\n\nMitigation: Check timestamps and coverage, verify important numbers, and present results as informational context rather than investment advice.\n\nRisk: Inconsistent documentation for bull/bear direction can lead to a wrong reading of mention spikes.\n\nMitigation: Verify bull/bear fields against the actual response before describing a spike as positive or negative.\n\n## Reference(s):\n\n- [ClawHub stock-sentiment release](https://clawhub.ai/thesentitrader/skills/stock-sentiment)\n- [SentiSense API reference](https://sentisense.ai/skill.md)\n- [SentiSense API key](https://app.sentisense.ai/get-api-key)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Guidance]\n\n**Output Format:** [Markdown with sourced market-data summaries]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Read-only, informational analysis; some data is delayed or preview-limited.]\n\n## Skill Version(s):\n\n0.5.1 (source: ClawHub release metadata)\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.5.0: 4 files, 19600 bytes\n\nFiles: scripts/sentiment_client.py (11483b), skill-card.md (1837b), SKILL.md (36055b), _meta.json (134b)\n\nFile v0.5.0:SKILL.md\n\n---\nname: stock-sentiment\ndescription: \"Sentiment and smart-money positioning for US stocks.\"\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n---\n# Stock Sentiment Skill\n\nThe sentiment and smart-money layer for US equities. A quote skill tells you the price; this skill reads what the market feels about a stock (the SentiSense Score, sentiment polarity, mentions, share of voice), where the smart money is moving (insider, congressional, and institutional flows plus analyst actions), and what the AI read of the tape is (per-stock and market-wide insights, sentiment-tagged news), all through the read-only SentiSense API.\n\nRead-only educational data interface. Output is informational context, never a personalized buy or sell recommendation.\n\n## When to Use\n\nReach for this skill when the question is about perception, positioning, or signal rather than raw price:\n\n- \"What is the sentiment on $NVDA?\" or \"Is the mood on $TSLA bullish or bearish?\"\n- \"What is the smart money doing this week?\" (insider cluster-buys, congressional trades, 13F flows, and analyst upgrades converging on the same tickers).\n- \"What is the overall market mood today, fear or greed?\"\n- \"Is sentiment diverging from price on $COIN?\" (price up while sentiment falls, or the reverse).\n- \"Mentions of $ZS just spiked: is that good or bad news for the stock?\"\n- \"What is the pre-earnings sentiment setup on $AAPL?\"\n- \"What is the AI insight on $MSFT, and what are people saying in the news?\"\n\nFor \"does this change my thesis?\", hand off to the `us-stocks-analysis` skill when available. Pass the ticker, user thesis, horizon, and dated signal disagreements. Return an evidence-led bull/bear assessment and unresolved objections. Hand off only when the user changes the question; do not automatically route back. If the sibling is unavailable, answer the supported part here using a connected tool or the inline REST workflow, state any remaining gap, and never require an install.\n\nDo not use it for order entry, portfolio management, or personalized advice. It has no write, trading, or wallet surface; every endpoint is a GET.\n\n## Prerequisites\n\n- Python 3.8+ using only the standard library (`urllib`, `json`); no third-party packages required. Any HTTP client or plain `curl` works too. On macOS python.org installs the client can raise `CERTIFICATE_VERIFY_FAILED` (missing CA certs): run the bundled `Install Certificates.command`, use the system `/usr/bin/python3`, or use `curl` (which uses the system trust store).\n- A free `SENTISENSE_API_KEY`. Get one at https://app.sentisense.ai/get-api-key. The key is required on every call; anonymous requests return `401 api_key_required`.\n- Network access to `https://app.sentisense.ai`.\n- Read-only scope. Every endpoint here is a GET. Nothing this skill does can place a trade, move money, or modify account state.\n\n## Permissions\n\n- Network: HTTPS to app.sentisense.ai only.\n- Credentials: SENTISENSE_API_KEY from the environment.\n- Shell: none required.\n- Files: none.\n\nTiers:\n\n| Tier | Quota | Rate |\n|------|-------|------|\n| Free | 1,000 requests/month | 30 requests/min |\n| PRO ($15/mo) | Unlimited | 300 requests/min |\n\nThe free tier exercises every workflow below. Preview-gated endpoints return a truncated but real slice on a free key (for example the top 3 insights); PRO removes the monthly cap and returns full history and full lists.\n\n## How to Run\n\nThis skill is invoked through the agent's terminal or shell tool: issue HTTP GET requests to the SentiSense API and synthesize the JSON into a concise, sourced answer. The base URL is `https://app.sentisense.ai`. Authenticate every request with the `X-SentiSense-API-Key` header; keep the key in the shell environment and never place it in a query string or in user-facing output.\n\n**Identify your client.** Send a `User-Agent` naming your agent runtime and this skill, for\nexample `OpenClaw/1.4 (stock-sentiment)` or `ClaudeCode/2.1 (stock-sentiment)`. Substitute your own runtime and\nversion if neither matches. You can also volunteer what your agent is called by adding an\n`agent/<your-agent-name>` token inside the same parentheses, as in\n`OpenClaw/1.4 (stock-sentiment; agent/research-desk)`. All of it is optional, and it is what tells\nus this skill has real integrations behind it, so it gets prioritized and you get notice before it\nchanges.\n\n```bash\ncurl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v2/metrics/entity/NVDA/metric/sentiment\"\n```\n\nAn anonymous call returns `401 api_key_required`. A rate-limited call returns `429` with a `Retry-After` header; back off for the indicated seconds rather than retrying immediately or serving a stale value.\n\nOn Windows, use the bundled Python client (cross-platform) and reference the key as `%SENTISENSE_API_KEY%` (cmd) or `$env:SENTISENSE_API_KEY` (PowerShell) rather than the POSIX `$SENTISENSE_API_KEY` shown above.\n\nThe REST recipe in this file is the primary path. A maintained command-line client is available as the separate `sentisense-cli` skill for hosts that prefer one.\n\nTwo response envelopes exist; unwrap correctly before reading fields:\n\n- Read FLAT (top-level, no `.data`): `stocks/price`, `stocks/prices`, `stocks/chart`, `stocks/popular`, `stocks/{T}/profile`, `market-mood`, and the metric series (`sentiment`, `sentisense`, `mentions`, and `social_dominance` are bare arrays). `institutional/quarters` is also a bare array.\n- Read WRAPPED as `{ isPreview, previewReason, data }` (use `.data`): `insider/*`, `politicians/*`, `institutional/holders`, `analyst/*`, `insights/*`, and `calendar/earnings` (here `data` is a dict, so read `data.earnings[]`).\n- `documents/ticker` has its own shape `{ documents, totalCount }`; read `.documents[]`.\n\nWhen unsure, accept both: `rows = raw if isinstance(raw, list) else raw.get(\"data\", raw)`.\n\nAn optional stdlib helper, `scripts/sentiment_client.py`, wraps all of this: it injects the auth header, prepends the base URL, and normalizes both envelopes (reading the metric scalar from the flat `value`) so the agent reasons over clean values. Use it or plain `curl`, whichever fits the host. The core of the helper is small enough to inline:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Minimal stdlib client for the read-only SentiSense API.\"\"\"\nimport json, os, urllib.parse, urllib.request\n\nAPI_ORIGIN = \"https://app.sentisense.ai\"\n\nclass NoRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        return None\n\ndef sentisense_api_url(path, params=None):\n    url = urllib.parse.urljoin(API_ORIGIN + \"/\", path)\n    parsed = urllib.parse.urlparse(url)\n    if (parsed.scheme != \"https\" or parsed.hostname != \"app.sentisense.ai\"\n            or parsed.netloc != \"app.sentisense.ai\"\n            or parsed.username is not None or parsed.password is not None\n            or parsed.port is not None):\n        raise ValueError(\"API URL must use https://app.sentisense.ai with no credentials or port\")\n    if params:\n        url += (\"&\" if parsed.query else \"?\") + urllib.parse.urlencode(params)\n    return url\n\ndef get(path, **params):\n    url = sentisense_api_url(path, params)\n    req = urllib.request.Request(\n        url, headers={\"X-SentiSense-API-Key\": os.environ[\"SENTISENSE_API_KEY\"]})\n    with urllib.request.build_opener(NoRedirect).open(req, timeout=20) as r:\n        return json.load(r)\n\ndef rows(raw):\n    \"\"\"Wrap-vs-flat: some endpoints return a bare array, others {isPreview, data}.\"\"\"\n    if isinstance(raw, list):\n        return raw\n    if isinstance(raw, dict) and \"data\" in raw:\n        return raw[\"data\"]\n    return raw\n\ndef latest_metric(ticker, slug=\"sentiment\"):\n    \"\"\"Latest reading of any metric series. Read the flat top-level `value`: it is present\n    on every point and holds the scalar, while the nested metricValue is a dict for value\n    metrics and a bare number for count metrics like `mentions`.\"\"\"\n    series = get(f\"/api/v2/metrics/entity/{ticker}/metric/{slug}\")\n    if not series or series[-1].get(\"value\") is None:\n        return None\n    return float(series[-1][\"value\"])\n```\n\n```bash\npython scripts/sentiment_client.py sentiment NVDA\npython scripts/sentiment_client.py mood\n```\n\n## Quick Reference\n\nAll paths are relative to `https://app.sentisense.ai` and are GET. Every call requires the `X-SentiSense-API-Key` header. `{T}` is an uppercase ticker, `{slug}` a member slug, `{id}` a story id. Full schema: https://sentisense.ai/skill.md.\n\n```\nRESOLVE A NAME (only when the user typed a company or fund name, not a symbol)\n  GET /api/v1/kb/entities/search?q={name}&type=company&limit=5\n        Bare array of {name, urlSlug, type, ticker}, best match first. Take the first match with a\n        non-null ticker. Use type=etf for fund names (SPY resolves only there). See Pitfalls.\n\nPEERS (to test whether a mention spike is the ticker's own; see workflow 6)\n  GET /api/v1/stocks/{T}/graph?depth=1&cap=75\n        groups.peers[] is the curated comparable set, as entity slugs that the metric endpoints\n        accept in place of {T}; join a slug to nodes[] for its displayName. Peers from depth=1 only.\n\nSENTIMENT & MOOD\n  GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}\n        Sentiment polarity time series. Omit params for the server default 7-day window.\n        Bare array; latest scalar is series[-1].value (a float in [-1, 1]). Every series\n        below reads the same way: take the flat top-level value, not the nested metricValue,\n        whose depth differs between value metrics and count metrics.\n  GET /api/v2/metrics/entity/{T}/metric/sentisense\n        The SentiSense Score (unbounded composite; report as-is, never normalize to 0-100).\n        Each daily point also carries metricValue.properties.{bull, bear, directional}: that\n        day's bullish and bearish analyses and their sum. Direction lives here, not in mentions.\n  GET /api/v2/metrics/entity/{T}/metric/mentions\n        Mention-volume time series (how much a ticker is being talked about). Direction-blind.\n  GET /api/v2/metrics/entity/{T}/metric/social_dominance\n        Share-of-conversation time series (a ticker's dominance of the chatter).\n  GET /api/v2/metrics/entity/{E}/metric/app_review_count\n        New Apple App Store reviews per day for a product's iOS app. Products with a\n        tracked app only, so {E} is a product entity slug, never a ticker.\n  GET /api/v2/metrics/entity/{E}/metric/app_rating\n        Mean star rating (1 to 5) of that day's new App Store reviews. Same coverage.\n  GET /api/v2/market-mood\n        Composite fear/greed plus sub-signals and per-sector breakdowns. Flat, but the\n        composite is nested: market.currentScore, market.phase, market.weeklyChange,\n        market.signals[]; sectors.{SectorName}.{currentScore, phase, weeklyChange}.\n\nSMART MONEY  (wrapped in {isPreview, previewReason, data}; free key returns a preview slice)\n  GET /api/v1/insider/cluster-buys?lookbackDays=N         Tickers with multiple insider buys.\n  GET /api/v1/insider/trades/{T}?lookbackDays=N           Form 4 rows; transactionType BUY|SELL,\n                                                          raw SEC letter in transactionCode.\n  GET /api/v1/politicians/activity?lookbackDays=N         Congressional trades; PURCHASE|SALE.\n  GET /api/v1/politicians/filings/{T}?lookbackDays=N      Per-ticker congressional filings.\n  GET /api/v1/politicians/member/{slug}                   Member profile (data.recentTrades[]).\n  GET /api/v1/institutional/quarters                      Call FIRST; bare array. Use reportDate of first entry whose pending is not true; skip pending:true (fall back to [0] only if all pending).\n  GET /api/v1/institutional/holders/{T}?reportDate={Q}    Top 13F holders (data.holders[], largest first).\n  GET /api/v1/analyst/{T}/consensus                       Price-target band; data IS the consensus object (data.consensusLabel).\n  GET /api/v1/analyst/{T}/actions?lookbackDays=N          Recent rating changes for one ticker.\n  GET /api/v1/analyst/{T}/estimates                       EPS band at data.estimates[0].{estimateLow/Mean/High,\n                                                          numberOfAnalysts} + data.surprises[]; no revenue.\n  GET /api/v1/analyst/activity?lookbackDays=N             Market-wide actions; add &actionTypes=UPGRADE,DOWNGRADE,INITIATE\n                                                          for real rating changes (~83% of raw rows are REITERATE).\n\nAI INSIGHTS  (wrapped; batch, carry generatedAt)\n  GET /api/v1/insights/stock/{T}         Per-stock signals ranked by importance; data[0].insightText is the headline. Free preview top 3.\n  GET /api/v1/insights/stock/{T}/types   Available insight types for the ticker; bare string array.\n  GET /api/v1/insights/market            Top market-wide signals (data[], insightText; ticker embedded in insightText).\n\nNEWS & STORIES\n  GET /api/v1/documents/ticker/{T}?limit=N          Sentiment-tagged feed ({documents, totalCount}); each doc\n                                                   has url, source, sourceName, published (epoch seconds), averageSentiment; no title.\n  GET /api/v1/documents/stories?limit=N             Pre-clustered stories; cluster.title is SentiSense-authored and safe to show.\n  GET /api/v1/documents/stories/ticker/{T}?limit=N  Stories for one ticker.\n  GET /api/v1/documents/stories/{id}                Story detail (PublicStoryDetailDto; aspectPerspectives[], bullishView/bearishView).\n  GET /api/v1/documents/search?query=...            Topical document search.\n\nSUPPORTING  (price, prices, chart are 15-minute delayed; profile, popular, calendar, market-summary are reference or batch)\n  GET /api/v1/stocks/price?ticker={T}                       Flat (no wrapper): currentPrice, changePercent at root.\n  GET /api/v1/stocks/prices?tickers=A,B,C                   Batch quotes.\n  GET /api/v1/stocks/{T}/profile                            name, sector, industry (flat at root; no profile key).\n  GET /api/v1/stocks/chart?ticker={T}&timeframe=1D|5D|1W|1M|3M|6M|1Y|5Y|10Y|MAX   Bars; read each point's timestamp (Unix ms). Invalid timeframe returns 400.\n  GET /api/v1/stocks/popular                                Bare array of ~75 ticker strings (screen universe).\n  GET /api/v1/calendar/earnings?ticker={T}                  data.earnings[]; next date + consensus EPS + confirmed.\n  GET /api/v1/market-summary                                Market-wide narrative headline.\n```\n\nSentiment is polarity: a float in [-1, 1] where the sign is the direction (negative is bearish and meaningful, positive is bullish) and the magnitude is conviction. Represent the sign unmistakably; do not map it onto a 0-100 scale. The SentiSense Score is a separate, unbounded composite; report it as-is. Mentions and social dominance are their own metric series on the same `/metric/{metricType}` endpoint (`mentions` for talk volume, `social_dominance` for share of the conversation); all four series (`sentiment`, `sentisense`, `mentions`, `social_dominance`) are available on the Free tier, and like every metrics call each request counts against your monthly quota. Two more Free-tier series cover consumer products rather than tickers: `app_review_count` (new Apple App Store reviews for a product's iOS app that day) and `app_rating` (the mean star rating, 1 to 5, of those reviews). **From 2026-09-05 `mentions` excludes App Store reviews**, because a review is a rating rather than chatter and for review-heavy products the two read as one number; the App Store slice still appears under `distribution/mentions?dimension=source`, so read review volume from `app_review_count`. A separate `/api/v2/metrics/entity/{T}/distribution/{metricType}` endpoint breaks a metric down by source (share of voice, a \"where this signal came from\" view, not per-source sentiment values).\n\n## Workflows\n\nOpinionated recipes. Each fans out its independent calls in parallel, then synthesizes; none recommends buying or selling. Frame every result as educational context on positioning and mood.\n\n### 1. Sentiment read on a ticker\n\nAnswer \"what is the market feeling about $T\" in a few dense lines. Fire these in parallel:\n\n1. `GET /api/v2/metrics/entity/{T}/metric/sentiment` for the polarity trend (server default 7-day window; the latest scalar is `series[-1].value`, a float in [-1, 1]).\n2. `GET /api/v2/metrics/entity/{T}/metric/sentisense` for the composite score.\n3. `GET /api/v1/documents/ticker/{T}?limit=8` for mention volume (`totalCount`) and the sentiment-tagged feed.\n4. `GET /api/v1/insights/stock/{T}` for the top AI insight (`data[0].insightText`, with `generatedAt` for freshness).\n\nSynthesize as educational context, leading with the differentiated sentiment read, not the price: \"$NVDA sentiment +0.42 over 7d and rising; SentiSense Score elevated; mention volume heavy; latest AI insight: 'Data-center demand commentary firming' (as of the batch time).\" Show the `generatedAt` age so the reader knows these are batch metrics.\n\n### 2. Market mood (fear and greed)\n\nAnswer \"what is the overall market mood today.\"\n\n1. `GET /api/v2/market-mood`.\n\nThe response is flat, but the composite is nested under `market`, not the root: `market.currentScore`, `market.phase` (e.g. Fear, Neutral, Optimism, Greed), `market.weeklyChange`, and `market.signals[]` (each sub-gauge with its value and change). Per-sector readings live at `sectors.{SectorName}.{ currentScore, phase, weeklyChange }`; `sectors` is a string-keyed dict, not an array, and its GICS labels have historically overlapped (`Technology` alongside `Information Technology`, `Healthcare` alongside `Health Care`), so treat the pairs defensively: if both members of a pair appear in one response, dedupe them before ranking top and bottom sectors. A clean response with neither pair duplicated is the common case and needs no special handling. Report as context: \"Market mood 62 (Greed), +4 over the week. Greed leaders: Technology, Communications. Fear: Energy, Utilities.\" Optionally pair with `GET /api/v1/market-summary` for the narrative headline and `GET /api/v1/insights/market` for the top market-wide signals.\n\n### 3. Smart-money convergence screen\n\nFind tickers where insider buying, congressional purchases, and analyst upgrades line up in the same window; convergence is the signal a quote feed cannot produce.\n\n1. `GET /api/v1/insider/cluster-buys?lookbackDays=30`.\n2. `GET /api/v1/politicians/activity?lookbackDays=30&limit=500`, keeping rows with `transactionType == \"PURCHASE\"`.\n3. `GET /api/v1/analyst/activity?lookbackDays=30&actionTypes=UPGRADE&limit=500` (server-side filter; also accepts a CSV like `UPGRADE,DOWNGRADE,INITIATE`).\n\nAll three are wrapped: read `.data`. **Steps 2 and 3 are paged feeds: one call is one page, not the window.** Both carry `totalCount` for the whole window on the envelope, so keep requesting with `offset` while `offset + len(data) < totalCount`. A 30-day congressional window runs to several hundred rows, which the default page (200) cuts short, and the analyst default page is 50. Reading one page silently drops most of the window and can turn a real overlap into an empty one. On a FREE key both feeds stop at a preview slice, so say the screen covered a partial window rather than reporting \"no convergence\". Intersect the three ticker lists and report names appearing in two or more buckets, ranked by total signal count, with a one-liner each: \"$NVDA: 4 insiders bought, 1 congressional purchase, 2 analyst upgrades (30d).\"\n\n**Start this one at `lookbackDays=30`, not 7.** A 7-day window is too narrow for three slow feeds to overlap: on a representative run it returned 1 cluster-buy ticker, 1 congressional purchase ticker and 21 upgraded tickers, which intersected to **zero** names in two or more buckets. The same three calls at 30 days returned 8, 65 and 45 tickers and produced 7 convergent names. The trap is that no individual bucket was empty at 7 days, so an \"is this bucket empty\" check pass\n\nArchive v0.4.5: 4 files, 17474 bytes\n\nFiles: scripts/sentiment_client.py (11483b), skill-card.md (1793b), SKILL.md (30430b), _meta.json (134b)\n\nArchive v0.4.4: 4 files, 17158 bytes\n\nFiles: scripts/sentiment_client.py (10557b), skill-card.md (2440b), SKILL.md (29811b), _meta.json (134b)\n\nArchive v0.4.3: 4 files, 16949 bytes\n\nFiles: scripts/sentiment_client.py (9741b), skill-card.md (2339b), SKILL.md (30214b), _meta.json (134b)\n\nArchive v0.4.2: 4 files, 16545 bytes\n\nFiles: scripts/sentiment_client.py (9152b), skill-card.md (2634b), SKILL.md (29320b), _meta.json (134b)\n\nArchive v0.4.1: 4 files, 16180 bytes\n\nFiles: scripts/sentiment_client.py (9152b), skill-card.md (2487b), SKILL.md (28451b), _meta.json (134b)\n\nArchive v0.4.0: 4 files, 16131 bytes\n\nFiles: scripts/sentiment_client.py (9152b), skill-card.md (2335b), SKILL.md (28424b), _meta.json (134b)","readmeExcerpt":"Skill: stock-sentiment Owner: thesentitrader Summary: Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"curl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\"},{"language":"bash","snippet":"curl -s -H \"X-SentiSense-API-Key: $SENTISENSE_API_KEY\" \\\n  \"https://app.sentisense.ai/api/v2/metrics/entity/NVDA/metric/sentiment\""},{"language":"python","snippet":"#!/usr/bin/env python3\n\"\"\"Minimal stdlib client for the read-only SentiSense API.\"\"\"\nimport json, os, urllib.parse, urllib.request\n\nAPI_ORIGIN = \"https://app.sentisense.ai\"\n\nclass NoRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        return None\n\ndef sentisense_api_url(path, params=None):\n    url = urllib.parse.urljoin(API_ORIGIN + \"/\", path)\n    parsed = urllib.parse.urlparse(url)\n    if (parsed.scheme != \"https\" or parsed.hostname != \"app.sentisense.ai\"\n            or parsed.netloc != \"app.sentisense.ai\"\n            or parsed.username is not None or parsed.password is not None\n            or parsed.port is not None):\n        raise ValueError(\"API URL must use https://app.sentisense.ai with no credentials or port\")\n    if params:\n        url += (\"&\" if parsed.query else \"?\") + urllib.parse.urlencode(params)\n    return url\n\ndef get(path, **params):\n    url = sentisense_api_url(path, params)\n    req = urllib.request.Request(\n        url, headers={\"X-SentiSense-API-Key\": os.environ[\"SENTISENSE_API_KEY\"]})\n    with urllib.request.build_opener(NoRedirect).open(req, timeout=20) as r:\n        return json.load(r)\n\ndef rows(raw):\n    \"\"\"Wrap-vs-flat: some endpoints return a bare array, others {isPreview, data}.\"\"\"\n    if isinstance(raw, list):\n        return raw\n    if isinstance(raw, dict) and \"data\" in raw:\n        return raw[\"data\"]\n    return raw\n\ndef latest_metric(ticker, slug=\"sentiment\"):\n    \"\"\"Latest reading of any metric series. Read the flat top-level `value`: it is present\n    on every point and holds the scalar, while the nested metricValue is a dict for value\n    metrics and a bare number for count metrics like `mentions`.\"\"\"\n    series = get(f\"/api/v2/metrics/entity/{ticker}/metric/{slug}\")\n    if not series or series[-1].get(\"value\") is None:\n        return None\n    return float(series[-1][\"value\"])"},{"language":"bash","snippet":"python scripts/sentiment_client.py sentiment NVDA\npython scripts/sentiment_client.py mood"},{"language":"text","snippet":"RESOLVE A NAME (only when the user typed a company or fund name, not a symbol)\n  GET /api/v1/kb/entities/search?q={name}&type=company&limit=5\n        Bare array of {name, urlSlug, type, ticker}, best match first. Take the first match with a\n        non-null ticker. Use type=etf for fund names (SPY resolves only there). See Pitfalls.\n\nPEERS (to test whether a mention spike is the ticker's own; see workflow 6)\n  GET /api/v1/stocks/{T}/graph?depth=1&cap=75\n        groups.peers[] is the curated comparable set, as entity slugs that the metric endpoints\n        accept in place of {T}; join a slug to nodes[] for its displayName. Peers from depth=1 only.\n\nSENTIMENT & MOOD\n  GET /api/v2/metrics/entity/{T}/metric/sentiment?startTime={epochMs}&endTime={epochMs}\n        Sentiment polarity time series. Omit params for the server default 7-day window.\n        Bare array; latest scalar is series[-1].value (a float in [-1, 1]). Every series\n        below reads the same way: take the flat top-level value, not the nested metricValue,\n        whose depth differs between value metrics and count metrics.\n  GET /api/v2/metrics/entity/{T}/metric/sentisense\n        The SentiSense Score (unbounded composite; report as-is, never normalize to 0-100).\n        Each daily point also carries metricValue.properties.{bull, bear, directional}: that\n        day's bullish and bearish analyses and their sum. Direction lives here, not in mentions.\n  GET /api/v2/metrics/entity/{T}/metric/mentions\n        Mention-volume time series (how much a ticker is being talked about). Direction-blind.\n  GET /api/v2/metrics/entity/{T}/metric/social_dominance\n        Share-of-conversation time series (a ticker's dominance of the chatter).\n  GET /api/v2/metrics/entity/{E}/metric/app_review_count\n        New Apple App Store reviews per day for a product's iOS app. Products with a\n        tracked app only, so {E} is a product entity slug, never a ticker.\n  GET /api/v2/metrics/entity/{E}/metric/app_rating\n        Mean sta"},{"language":"text","snippet":"GET /api/v1/stocks/ZS/graph?depth=1&cap=75\n  {\"ticker\": \"ZS\", \"root\": \"Zscaler-Inc\", \"depth\": 1, \"truncated\": false,\n   \"groups\": {\"peers\": [\"Cloudflare-Inc\", \"CrowdStrike-Holdings-Inc\", \"Okta-Inc\", \"Palo-Alto-Networks-Inc\"], ...},\n   \"nodes\": [{\"slug\": \"Cloudflare-Inc\", \"displayName\": \"Cloudflare, Inc.\", \"type\": \"COMPANY\"}, ...]}\nGET /api/v2/metrics/entity/ZS/metric/mentions  (spike-day point)\n  {\"timestamp\": 1790308800000, \"metricType\": \"MENTIONS\", \"value\": 66.0,\n   \"metricValue\": {\"type\": \"CountMetricValue\", \"value\": 66, \"count\": 66}}\nGET /api/v2/metrics/entity/ZS/metric/sentisense  (same day, metricValue.properties)\n  {\"bear\": 25.0, \"bull\": 16.0, \"directional\": 41.0}"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: stock-sentiment\ndescription: \"Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a mention spike is good or bad news, read from the ticker's own bullish and bearish coverage. Use for stock sentiment, stock sentiment analysis, is the mood bullish or bearish, market mood today, fear and greed index, smart money tracker, insider buying and analyst upgrades, mention spike, sentiment vs price divergence, pre-earnings sentiment. Read-only. No trading, no purchases, no write operations, no wallet access.\"\nhomepage: https://sentisense.ai\nrequires:\n  env:\n    - SENTISENSE_API_KEY\nprimaryEnv: SENTISENSE_API_KEY\nmetadata:\n  openclaw:\n    requires:\n      env:\n        - SENTISENSE_API_KEY\n    primaryEnv: SENTISENSE_API_KEY\n---\n# Stock Sentiment Skill\n\nThe sentiment and smart-money layer for US equities. A quote skill tells you the price; this skill reads what the market feels about a stock (the SentiSense Score, sentiment polarity, mentions, share of voice), where the smart money is moving (insider, congressional, and institutional flows plus analyst actions), and what the AI read of the tape is (per-stock and market-wide insights, sentiment-tagged news), all through the read-only SentiSense API.\n\nRead-only educational data interface. Output is informational context, never a personalized buy or sell recommendation.\n\n## When to Use\n\nReach for this skill when the question is about perception, positioning, or signal rather than raw price:\n\n- \"What is the sentiment on $NVDA?\" or \"Is the mood on $TSLA bullish or bearish?\"\n- \"What is the smart money doing this week?\" (insider cluster-buys, congressional trades, 13F flows, and analyst upgrades converging on the same tickers).\n- \"What is the overall market mood today, fear or greed?\"\n- \"Is sentiment diverging from price on $COIN?\" (price up while sentiment falls, or the reverse).\n- \"Mentions of $ZS just spiked: is that good or bad news for the stock?\"\n- \"What is the pre-earnings sentiment setup on $AAPL?\"\n- \"What is the AI insight on $MSFT, and what are people saying in the news?\"\n\nFor \"does this change my thesis?\", hand off to the `us-stocks-analysis` skill when available. Pass the ticker, user thesis, horizon, and dated signal disagreements. Return an evidence-led bull/bear assessment and unresolved objections. Hand off only when the user changes the question; do not automatically route back. If the sibling is unavailable, answer the supported part here using a connected tool or the inline REST workflow, state any remaining gap, and never require an install.\n\nDo not use it for order entry, portfolio management, or personalized advice. It has no write, trading, or wallet surface; every endpoint is "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn71ca3nrt3w6w0v3nhv3c4tan82x1ym\",\n  \"slug\": \"stock-sentiment\",\n  \"version\": \"0.6.0\",\n  \"publishedAt\": 1790880486672\n}"},{"path":"skill-card.md","content":"## Description:\n\nProvides read-only sentiment, market-mood, news, and investor-positioning context for US stocks through SentiSense.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[thesentitrader](https://clawhub.ai/user/thesentitrader)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nInvestors and analysts use this skill to review US-stock sentiment, market mood, and insider, congressional, institutional, and analyst activity as informational context, not personalized investment advice.\n\n### Deployment Geography for Use:\n\nGlobal (US equities coverage)\n\n## Known Risks and Mitigations:\n\nRisk: The skill sends a SentiSense API key to SentiSense for data requests.\n\nMitigation: Use a dedicated API key and provide it only to the intended SentiSense service.\n\nRisk: Financial sentiment could be mistaken for investment advice or a trading instruction.\n\nMitigation: Present results as educational context, not personalized buy or sell recommendations.\n\nRisk: Batch data and limited previews can obscure recency or omit relevant activity.\n\nMitigation: State data timestamps and label partial previews without inferring absence from a slice.\n\n## Reference(s):\n\n- [ClawHub stock-sentiment release](https://clawhub.ai/thesentitrader/skills/stock-sentiment)\n- [SentiSense API skill documentation](https://sentisense.ai/skill.md)\n- [SentiSense API key](https://app.sentisense.ai/get-api-key)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Analysis]\n\n**Output Format:** [Plain text or Markdown summary]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Dated market signals; free-tier results may be partial previews.]\n\n## Skill Version(s):\n\n0.6.0 (source: server-resolved ClawHub release)\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."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a mention spike is good or bad news, read from the ticker's own bullish and bearish coverage. Use for stock sentiment, stock sentiment analysis, is the mood bullish or bearish, market mood today, fear and greed index, smart money tracker, insider buying and analyst upgrades, mention spike, sentiment vs price divergence, pre-earnings sentiment. Read-only. No trading, no purchases, no write operations, no wallet access. Skill: stock-sentiment Owner: thesentitrader Summary: Stock sentiment and smart-money positioning for US stocks: the SentiSense Score, sentiment polarity, mention volume and share of voice for any ticker, the fear-to-greed market mood with sector readings, insider cluster buys, congressional purchases and analyst upgrades converging on the same names, 13F holders, AI insights, and a peer-normalized check of whether a","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1419,"uniquenessScore":45,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T22:53:07.892Z","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-09T22:53:07.892Z","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-10T08:45:35.573Z","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"}]}}}