Skip to main content
Everything a coding agent needs to use the Forward Model scores API, on one page. A person makes the key; the agent does the rest.

The contract

How the data behaves

  • One set of scores per trading day. They’re published before the market opens, usually by 9:00 a.m. Eastern. Until the new day is out (early in the morning, on a market holiday, or on the rare late day), a call returns the latest published day. If the new day isn’t out by 9:30 a.m. Eastern, use the latest day and check again later.
  • Check date. The top-level date tells you which trading day you got. Compare it with today’s date in US Eastern time.
  • A day’s scores never change. Cache each answer by (symbol, date) and don’t ask again that day. A repeat call returns the same values.
  • Batch. Ask for up to 50 symbols in one call instead of one call per symbol. When you split a long list into several calls, pass the first answer’s date to the rest, so every batch is the same day.
  • Some symbols aren’t scored. Funds, ETFs, unknown tickers and other non-equities come back under not_scored with a reason, not as an error, and don’t count. A symbol that can’t be a ticker (anything but letters, digits, . and -, up to 10 characters, starting with a letter) fails the whole call with invalid_input.
  • Compare within a day. A score is a position among that day’s scored stocks. Don’t compare one day’s score with another day’s.
  • Show the notice. When you show a score to a person, show this line with it: “Published score, the same for everyone. Not personal investment advice.” Full entries carry it as notice.

How the plan counts

  • A stock counts once per day, Pacific time (midnight to midnight in Los Angeles), whichever day’s score you get. Asking for it again that day, including after the new scores come out, is free. So checking early in the morning wastes nothing.
  • The allowance resets when the plan renews each month. scores_left_words in each answer says, in words, how much is left until then: “plenty left”, “running low” or “none left until your plan renews”. Slow down when it says running low.
  • There’s also a daily limit. It resets at midnight Pacific time.
  • At the limit, the whole call is refused with limit_reached and nothing in it counts. resets_at says when: the next midnight Pacific for the daily limit, or the plan’s renewal for the monthly one (null if not known). Stocks you’ve already seen that day still come back if you ask for only those.

Response schema

Top level: Each entry (detail=full): With detail=short, an entry has only symbol, name, date, score, band, top_pct and summary. detail defaults to full for 1 to 10 symbols (after duplicates are dropped) and short for more. date may be any of the last 21 trading days, the latest included. A day with no published scores, such as today before they’re out, a weekend or a holiday, returns invalid_date.

Errors

Every error is {"error": {"type", "message", "request_id", ...}}. Retry only rate_limited, model_unavailable and server_error, and honour the Retry-After header. There’s no fixed request rate to stay under; calling one batch at a time is plenty. A response that isn’t JSON (for example a gateway error) is a temporary error: retry it like server_error.

A complete client

In an AI assistant instead

If the person wants the scores inside Claude or ChatGPT rather than in code, there’s no key to handle: see Claude and ChatGPT.