Skip to main content

pagesift_api/types/
response.rs

1//! Firecrawl v1/v2 response types and metadata structures.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::BTreeMap;
6
7/// Explanation when a query-directed crawl completes (`pagesift` field in job status).
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CrawlSummary {
11    /// `coverage_reached`, `saturated`, `limit_reached`, `exhausted`, `cancelled`.
12    pub stop_reason: String,
13    /// Coverage percentage (0.0 to 1.0) when adaptive crawl completes.
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub coverage: Option<f64>,
16    /// Number of distinct evidence blocks gathered for each term.
17    #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
18    pub term_evidence: BTreeMap<String, usize>,
19    /// Actual pages fetched.
20    pub pages: usize,
21}
22
23/// PageSift extensions inside `metadata.pagesift`.
24#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct PagesiftMeta {
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub content_hash: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub raw_hash: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub text_hash: Option<String>,
33    #[serde(skip_serializing_if = "Vec::is_empty", default)]
34    pub redirect_chain: Vec<String>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub etag: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub last_modified: Option<String>,
39    #[serde(skip_serializing_if = "std::ops::Not::not", default)]
40    pub not_modified: bool,
41    /// `http` or `browser`.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub engine: Option<String>,
44    /// `lightpanda`, `moli`, or `chrome`.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub browser_engine: Option<String>,
47    #[serde(skip_serializing_if = "Vec::is_empty", default)]
48    pub warnings: Vec<String>,
49    /// Timing per phase in milliseconds.
50    #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
51    pub timings: BTreeMap<String, f64>,
52    /// Duplicate canonical reference URL.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub duplicate_of: Option<String>,
55    /// Query relevance score (0.0 to 1.0).
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub relevance: Option<f32>,
58    /// Query filter statistics.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub query_filter: Option<QueryFilterStats>,
61    /// JSON-LD blocks found on the page, parsed and passed through unchanged.
62    #[serde(skip_serializing_if = "Vec::is_empty", default)]
63    pub json_ld: Vec<Value>,
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct QueryFilterStats {
69    pub kept_blocks: usize,
70    pub total_blocks: usize,
71    pub tokens_before: usize,
72    pub tokens_after: usize,
73}
74
75/// Firecrawl `changeTracking` response object.
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct ChangeTracking {
79    pub previous_scrape_at: Option<String>,
80    /// `new`, `same`, `changed`, `removed`.
81    pub change_status: String,
82    /// `visible` or `hidden`.
83    pub visibility: String,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub diff: Option<Value>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub json: Option<Value>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub pagesift: Option<Value>,
90}
91
92#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct Metadata {
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub title: Option<String>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub description: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub language: Option<String>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub keywords: Option<String>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub robots: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub favicon: Option<String>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub published_time: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub author: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub modified_time: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub url: Option<String>,
115    #[serde(rename = "sourceURL", skip_serializing_if = "Option::is_none")]
116    pub source_url: Option<String>,
117    pub status_code: u16,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub content_type: Option<String>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub error: Option<String>,
122    pub proxy_used: String,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub cache_state: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub cached_at: Option<String>,
127    #[serde(flatten)]
128    pub extra: BTreeMap<String, Value>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub pagesift: Option<PagesiftMeta>,
131}
132
133#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct Document {
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub markdown: Option<String>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub html: Option<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub raw_html: Option<String>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub links: Option<Vec<String>>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub images: Option<Vec<String>>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub screenshot: Option<String>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub json: Option<Value>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub summary: Option<String>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub actions: Option<Value>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub warning: Option<String>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub change_tracking: Option<ChangeTracking>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub chunks: Option<Vec<DocChunk>>,
160    pub metadata: Metadata,
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164#[serde(rename_all = "camelCase")]
165pub struct DocChunk {
166    pub text: String,
167    pub heading_path: Vec<String>,
168    pub start: usize,
169    pub end: usize,
170    pub tokens: usize,
171}
172
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct MapLink {
175    pub url: String,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub title: Option<String>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub description: Option<String>,
180}
181
182#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct ScrapeResponse {
184    pub success: bool,
185    pub data: Document,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub warning: Option<String>,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct JobStarted {
193    pub success: bool,
194    pub id: String,
195    pub url: String,
196    #[serde(rename = "invalidURLs", skip_serializing_if = "Option::is_none")]
197    pub invalid_urls: Option<Vec<String>>,
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct JobStatus {
203    pub success: bool,
204    pub status: String,
205    pub completed: usize,
206    pub total: usize,
207    pub credits_used: usize,
208    pub expires_at: String,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub next: Option<String>,
211    pub data: Vec<Document>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub pagesift: Option<CrawlSummary>,
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct JobError {
218    pub id: String,
219    pub timestamp: String,
220    pub url: String,
221    pub code: String,
222    pub error: String,
223}
224
225#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct JobErrors {
228    pub errors: Vec<JobError>,
229    pub robots_blocked: Vec<String>,
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub struct MapResponse {
234    pub success: bool,
235    pub links: Vec<MapLink>,
236}