Rust SDK
Build with the official Forecite Rust SDK.
The official Rust SDK gives you a single typed surface across the Forecite scored
feed, the Verdict Engine, reference data, webhooks, and the realtime stream. It's
fully async, built on tokio + reqwest + tokio-tungstenite.
Beta
The Rust SDK is in beta while we harden the surface ahead of a stable 1.0.
Method names and shapes may still change.
A single Forecite client exposes every resource as a method — feeds_list,
providers_get, score, stream — all authenticated with one API key and
returning Result<_, ForeciteError>.
Quickstart
Add the dependency
cargo add forecite tokio --features tokio/fullCreate a client
Pass your fc_live_… key (get one from the API keys
page). Keep it out of source control.
use forecite::Forecite;
let forecite = Forecite::new(std::env::var("FORECITE_API_KEY")?);Fetch scored feeds
use forecite::{Forecite, FeedsQuery};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let forecite = Forecite::new(std::env::var("FORECITE_API_KEY")?);
let page = forecite
.feeds_list(&FeedsQuery {
symbol: Some("NVDA".into()),
actionability: Some(true),
limit: Some(10),
..Default::default()
})
.await?;
for item in &page.data {
println!("{:?} {:?}", item.title, item.scoring.sentiment_score);
}
Ok(())
}SDK patterns
The client uses consistent patterns for configuration, pagination, and errors across every method.
Response models
Reads deserialize into typed serde structs with snake_case fields matching
the API. A FeedItem carries the source artifact plus its Verdict Engine
scoring block:
pub struct FeedItem {
pub id: String,
pub title: Option<String>,
pub source: String,
pub published_at: String,
pub scoring: Scoring,
pub symbols: Vec<SymbolRef>,
// …
}
pub struct Scoring {
pub actionability: Option<bool>,
pub sentiment_score: Option<i64>, // 0–10 (5 = neutral)
// …
}Environment configuration
Production is the default. Use the builder to target local dev or a custom deployment.
use forecite::Forecite;
let forecite = Forecite::builder(std::env::var("FORECITE_API_KEY")?)
.base_url("http://localhost:3000/api")
.ws_url("ws://localhost:8080")
.build();Pagination
feeds_list returns a FeedList { data, next_cursor }. Pass next_cursor back
as cursor to page; a None cursor means you've reached the end.
let mut cursor: Option<String> = None;
loop {
let page = forecite
.feeds_list(&FeedsQuery { limit: Some(100), cursor: cursor.clone(), ..Default::default() })
.await?;
for item in &page.data {
// handle(item)
}
cursor = page.next_cursor;
if cursor.is_none() {
break;
}
}Error handling
Every call returns Result<_, ForeciteError>. The Api variant carries the HTTP
status and the API's code / message.
use forecite::ForeciteError;
match forecite.feeds_list(&Default::default()).await {
Ok(page) => { /* … */ }
Err(ForeciteError::Api { status: 429, .. }) => { /* retry with backoff */ }
Err(ForeciteError::Api { status: 401, .. }) => { /* bad or missing key */ }
Err(err) => return Err(err.into()),
}Feed data
Read the scored feed — list with filters, or fetch a single item by id.
// Filtered list — actionable NVDA items, most bullish first
let page = forecite
.feeds_list(&FeedsQuery {
symbol: Some("NVDA".into()),
actionability: Some(true),
sentiment_min: Some(7),
..Default::default()
})
.await?;
// One item with full per-symbol detail
let item = forecite.feeds_get("0b3f2c1e-…").await?;FeedsQuery fields: since / until (ISO 8601), symbol, exchange,
aggregator, actionability, sentiment_min / sentiment_max, limit,
cursor.
Discovery
Browse the reference data that powers the feed's filters.
let providers = forecite.providers_list().await?;
let one = forecite.providers_get(&providers[0].id).await?;let sources = forecite.sources_list().await?; // Vec<String>
let activity = forecite.sources_get("globenewswire").await?;let symbols = forecite.symbols_list(Some(500)).await?;
let nvda = forecite.symbols_get("NVDA").await?;Scoring
Run the Verdict Engine on your own text. Pass an
Artifact and optional ScoreOptions.
use forecite::{Artifact, ScoreOptions};
let artifact = Artifact {
r#type: "filing".into(),
title: "Acme cuts FY guide on softening demand".into(),
body: "Acme Corp lowered its full-year revenue outlook…".into(),
related_symbols: Some(vec!["ACME".into()]),
..Default::default()
};
let verdict = forecite
.score(&artifact, Some(&ScoreOptions { model: Some("pro".into()), ..Default::default() }))
.await?;
println!("{}", verdict.actionability.score);Realtime streams
Subscribe to the scored feed over WebSocket. Pull events with next() and match
on the StreamEvent variant.
use forecite::{StreamFilters, StreamEvent};
let filters = StreamFilters {
symbols: Some(vec!["NVDA".into(), "TSLA".into()]),
..Default::default()
};
let mut stream = forecite.stream(&filters, 10).await?;
while let Some(event) = stream.next().await? {
match event {
StreamEvent::Feed { data, snapshot } => {
println!("{} {:?}", if snapshot { "backfill" } else { "live" }, data.title);
}
StreamEvent::Welcome { tier, .. } => println!("connected: {tier}"),
StreamEvent::QuotaExceeded { message, .. } => eprintln!("{message}"),
_ => {}
}
}Account
Inspect the authenticated key and its usage.
let key = forecite.me().await?; // tier, limits
let usage = forecite.usage(14).await?; // daily feed vs. score usageWebhooks
Manage outbound webhook endpoints.
use forecite::UpdateWebhook;
let hook = forecite
.webhooks_create("https://example.com/forecite", Some("prod"))
.await?;
forecite.webhooks_list().await?;
forecite
.webhooks_update(&hook.id, &UpdateWebhook { enabled: Some(false), ..Default::default() })
.await?;
forecite.webhooks_delete(&hook.id).await?;Changelog
0.1.2
- First public beta of the
forecitecrate. - Async client for feeds, scoring, discovery, account, webhooks.
- Realtime feed stream (
StreamEvent) with snapshot replay.