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    /// 每页的 Markdown:只在 `/v2/parse` 的 PDF 请求了 `parsers: [{type: "pdf", pages: true}]` 时给。
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub pages: Option<Vec<PageMarkdown>>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub html: Option<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub raw_html: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub links: Option<Vec<String>>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub images: Option<Vec<String>>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub screenshot: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub json: Option<Value>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub summary: Option<String>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub actions: Option<Value>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub warning: Option<String>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub change_tracking: Option<ChangeTracking>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub chunks: Option<Vec<DocChunk>>,
163    pub metadata: Metadata,
164}
165
166/// Firecrawl `data.pages[]`。
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct PageMarkdown {
170    /// 从 1 开始的页码。
171    pub page_number: usize,
172    pub markdown: String,
173}
174
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct DocChunk {
178    pub text: String,
179    pub heading_path: Vec<String>,
180    pub start: usize,
181    pub end: usize,
182    pub tokens: usize,
183}
184
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct MapLink {
187    pub url: String,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub title: Option<String>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub description: Option<String>,
192}
193
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub struct ScrapeResponse {
196    pub success: bool,
197    pub data: Document,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub warning: Option<String>,
200}
201
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct JobStarted {
205    pub success: bool,
206    pub id: String,
207    pub url: String,
208    #[serde(rename = "invalidURLs", skip_serializing_if = "Option::is_none")]
209    pub invalid_urls: Option<Vec<String>>,
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct JobStatus {
215    pub success: bool,
216    pub status: String,
217    pub completed: usize,
218    pub total: usize,
219    pub credits_used: usize,
220    pub expires_at: String,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub next: Option<String>,
223    pub data: Vec<Document>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub pagesift: Option<CrawlSummary>,
226}
227
228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct JobError {
230    pub id: String,
231    pub timestamp: String,
232    pub url: String,
233    pub code: String,
234    pub error: String,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct JobErrors {
240    pub errors: Vec<JobError>,
241    pub robots_blocked: Vec<String>,
242}
243
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct MapResponse {
246    pub success: bool,
247    pub links: Vec<MapLink>,
248}