Sentiment forecast

GET /forecasts/sentiment
GetSentimentForecast

Where an asset's sentiment is likely to go over the next 72 hours, as a range of outcomes with their chances. series gives, every 4 hours, the bullish, bearish, neutral, and total posts expected in that step and the sentiment_signal at its end, on the /sentiment scale. chance_higher_24h and chance_higher_72h give the chance the signal ends above current_sentiment_signal; their _confident flags mark when that chance leans clearly one way.

Forecasts update within a minute of new posts about the asset, and at least every 15 minutes. refresh=true recomputes on the latest data in about 5 seconds; if that takes over 20, you get the latest forecast with refreshed: false, charged at 5. Assets without a forecast return status: not_forecastable with a reason; /forecasts/assets lists the assets that have one.

Costs 5 credits, or 10 with refresh=true. not_forecastable answers are free, up to 30 an hour per account; past that this endpoint returns 429 until the hour clears.

Parameters

asset_id string QUERY required

Asset ID from /assets or /search, such as NVDA, GLD, SPX, or bitcoin. Send it with the asset_class returned alongside it.

Example:NVDA

asset_class string QUERY required

Class of the asset named by asset_id: equity (stocks and ETFs), commodity, index, or crypto. Send the asset_class that /assets or /search returned alongside the asset_id.

Allowed values
equitycommodityindexcrypto

Example:equity

refresh boolean QUERY

true recomputes the forecast on the latest data: 10 credits, about 5 seconds. Defaults to false.

Example:false

cURL Example

curl --get \
  --url 'https://nebula-api.hiddensystems.ai/api/v1/public/forecasts/sentiment' \
  --data-urlencode 'asset_id=NVDA' \
  --data-urlencode 'asset_class=equity' \
  --header 'X-API-Key: YOUR_API_KEY'

Responses

200 OK
400 Bad Request
401 Unauthorized
402 Credits used up; upgrade_url links to buying more
403 Forbidden
404 Not Found
405 Method not allowed; the Allow header lists the supported method
429 Too Many Requests
500 Internal Server Error
503 Service Unavailable

Media type application/json

as_of
string (date-time) required

UTC start of the forecast: the end of the last complete 15 minutes it was computed from. Null when the asset has never been scored.

chance_higher_24h
number (double) required

The chance the sentiment signal is higher than current_sentiment_signal in 24 hours, from 0 to 1.

chance_higher_24h_confident
boolean required

True when chance_higher_24h is under 0.3 or over 0.7: the forecast leans clearly one way. Nearer 0.5, read it as no clear direction.

chance_higher_72h
number (double) required

The chance the sentiment signal is higher than current_sentiment_signal in 72 hours, from 0 to 1.

chance_higher_72h_confident
boolean required

True when chance_higher_72h is under 0.3 or over 0.7: the forecast leans clearly one way. Nearer 0.5, read it as no clear direction.

current_sentiment_signal
number (double) required

The sentiment signal at as_of, on the /sentiment scale: bullish minus bearish posts over the trailing 24 hours, recent posts weighted more. Every path starts here, and the chances are measured against it. It can differ slightly from the latest /sentiment point, which is hourly.

horizon_hours
integer required

How far ahead the forecast runs, in hours: always 72.

reason
string required

Why the asset cannot be forecast: too_little_history, stale_data, or missing_price. Null when status is ok.

Allowed values
too_little_historystale_datamissing_pricenull
refreshed
boolean required

True when this request re-ran the forecast (refresh=true and the re-run finished in time); false when it is the latest scheduled forecast.

sample_paths
array<array<number (double)>> required

24 possible paths for the sentiment signal, each aligned with series. Draw them to show the spread of outcomes.

series
array<object> required

The forecast every 4 hours from as_of: 18 steps, oldest first.

series[].bearish_posts
object required

The bearish posts expected in this 4-hour step alone, bots excluded: like /sentiment's interval posts, not its trailing 24-hour counts.

series[].bearish_posts.mean
number (double) required

The expected number of bearish posts in the step.

series[].bearish_posts.p10
number (double) required

The 10th percentile of the bearish posts in the step.

series[].bearish_posts.p25
number (double) required

The 25th percentile of the bearish posts in the step.

series[].bearish_posts.p50
number (double) required

The 50th percentile of the bearish posts in the step.

series[].bearish_posts.p75
number (double) required

The 75th percentile of the bearish posts in the step.

series[].bearish_posts.p90
number (double) required

The 90th percentile of the bearish posts in the step.

series[].bullish_posts
object required

The bullish posts expected in this 4-hour step alone, bots excluded: like /sentiment's interval posts, not its trailing 24-hour counts.

series[].bullish_posts.mean
number (double) required

The expected number of bullish posts in the step.

series[].bullish_posts.p10
number (double) required

The 10th percentile of the bullish posts in the step.

series[].bullish_posts.p25
number (double) required

The 25th percentile of the bullish posts in the step.

series[].bullish_posts.p50
number (double) required

The 50th percentile of the bullish posts in the step.

series[].bullish_posts.p75
number (double) required

The 75th percentile of the bullish posts in the step.

series[].bullish_posts.p90
number (double) required

The 90th percentile of the bullish posts in the step.

series[].neutral_posts
object required

The neutral posts expected in this 4-hour step alone, bots excluded: like /sentiment's interval posts, not its trailing 24-hour counts.

series[].neutral_posts.mean
number (double) required

The expected number of neutral posts in the step.

series[].neutral_posts.p10
number (double) required

The 10th percentile of the neutral posts in the step.

series[].neutral_posts.p25
number (double) required

The 25th percentile of the neutral posts in the step.

series[].neutral_posts.p50
number (double) required

The 50th percentile of the neutral posts in the step.

series[].neutral_posts.p75
number (double) required

The 75th percentile of the neutral posts in the step.

series[].neutral_posts.p90
number (double) required

The 90th percentile of the neutral posts in the step.

series[].sentiment_signal
object required

The sentiment signal at the end of the step, on the /sentiment scale: where the line is likely to be.

series[].sentiment_signal.mean
number (double) required

The expected sentiment signal at the end of the step.

series[].sentiment_signal.p10
number (double) required

The 10th percentile of the sentiment signal at the end of the step.

series[].sentiment_signal.p50
number (double) required

The 50th percentile of the sentiment signal at the end of the step.

series[].sentiment_signal.p90
number (double) required

The 90th percentile of the sentiment signal at the end of the step.

series[].sentiment_signal.quantiles
array<number (double)> required

21 quantiles of the sentiment signal at the end of the step: the 0th, 5th, 10th, ... 100th percentiles, lowest first. Interpolate between them for the chance of landing in any range.

series[].timestamp
string (date-time) required

UTC time the step ends: as_of plus 4, 8, ... 72 hours.

series[].total_posts
object required

The posts (bullish, bearish, and neutral together) expected in this 4-hour step alone, bots excluded: like /sentiment's interval posts, not its trailing 24-hour counts. Its percentiles are of the total itself, so they are not the sums of the three kinds' percentiles.

series[].total_posts.mean
number (double) required

The expected number of posts (bullish, bearish, and neutral together) in the step.

series[].total_posts.p10
number (double) required

The 10th percentile of the posts (bullish, bearish, and neutral together) in the step.

series[].total_posts.p25
number (double) required

The 25th percentile of the posts (bullish, bearish, and neutral together) in the step.

series[].total_posts.p50
number (double) required

The 50th percentile of the posts (bullish, bearish, and neutral together) in the step.

series[].total_posts.p75
number (double) required

The 75th percentile of the posts (bullish, bearish, and neutral together) in the step.

series[].total_posts.p90
number (double) required

The 90th percentile of the posts (bullish, bearish, and neutral together) in the step.

status
string required

ok, or not_forecastable when the asset cannot be forecast (the summary fields are then null and the series empty; reason says why).

Allowed values
oknot_forecastable
turning
integer required

1 while sentiment is turning bullish (the last few hours lean clearly more bullish than the last day), -1 while it is turning bearish, 0 otherwise.

Allowed values
-101null
{
  "as_of": "2026-09-14T09:15:00Z",
  "chance_higher_24h": 0.74,
  "chance_higher_24h_confident": true,
  "chance_higher_72h": 0.61,
  "chance_higher_72h_confident": false,
  "current_sentiment_signal": 128.6,
  "horizon_hours": 72,
  "reason": null,
  "refreshed": false,
  "sample_paths": [
    [
      133.2,
      141.7
    ],
    [
      126.4,
      118.9
    ]
  ],
  "series": [
    {
      "bearish_posts": {
        "mean": 18,
        "p10": 9.9,
        "p25": 13.5,
        "p50": 17.1,
        "p75": 21.6,
        "p90": 27.9
      },
      "bullish_posts": {
        "mean": 46,
        "p10": 25.3,
        "p25": 34.5,
        "p50": 43.7,
        "p75": 55.2,
        "p90": 71.3
      },
      "neutral_posts": {
        "mean": 29,
        "p10": 16,
        "p25": 21.8,
        "p50": 27.6,
        "p75": 34.8,
        "p90": 45
      },
      "sentiment_signal": {
        "mean": 131.4,
        "p10": 92.9,
        "p50": 129.6,
        "p90": 175.5,
        "quantiles": [
          21.6,
          70.2,
          92.9,
          104.8,
          113.1,
          119.4,
          124.2,
          127.6,
          130.5,
          132.7,
          134.5,
          136.4,
          138.7,
          141.3,
          144.9,
          149.2,
          155.1,
          163.8,
          179.4,
          202.6,
          287.3
        ]
      },
      "timestamp": "2026-09-14T13:15:00Z",
      "total_posts": {
        "mean": 93,
        "p10": 51.2,
        "p25": 69.8,
        "p50": 88.4,
        "p75": 111.6,
        "p90": 144.2
      }
    },
    {
      "bearish_posts": {
        "mean": 19,
        "p10": 10.5,
        "p25": 14.3,
        "p50": 18.1,
        "p75": 22.8,
        "p90": 29.5
      },
      "bullish_posts": {
        "mean": 50,
        "p10": 27.5,
        "p25": 37.5,
        "p50": 47.5,
        "p75": 60,
        "p90": 77.5
      },
      "neutral_posts": {
        "mean": 31,
        "p10": 17.1,
        "p25": 23.3,
        "p50": 29.5,
        "p75": 37.2,
        "p90": 48.1
      },
      "sentiment_signal": {
        "mean": 137.6,
        "p10": 99.1,
        "p50": 135.79999999999998,
        "p90": 181.7,
        "quantiles": [
          21.6,
          70.2,
          92.9,
          104.8,
          113.1,
          119.4,
          124.2,
          127.6,
          130.5,
          132.7,
          134.5,
          136.4,
          138.7,
          141.3,
          144.9,
          149.2,
          155.1,
          163.8,
          179.4,
          202.6,
          287.3
        ]
      },
      "timestamp": "2026-09-14T17:15:00Z",
      "total_posts": {
        "mean": 100,
        "p10": 55,
        "p25": 75,
        "p50": 95,
        "p75": 120,
        "p90": 155
      }
    }
  ],
  "status": "ok",
  "turning": 0
}