apify_rs/lib.rs
1//! Rust client for the [Apify](https://apify.com) platform API.
2//!
3//! Apify is a web scraping and automation cloud platform. Users publish
4//! **Actors** (serverless programs) to the Apify Store, run them with a JSON
5//! input, and retrieve structured results from **Datasets** or **Key-Value
6//! Stores**.
7//!
8//! This crate mirrors the [Apify REST API v2](https://docs.apify.com/api/v2).
9//! It is organised around three primary resources:
10//!
11//! | Resource | What it represents | Typical workflow |
12//! |----------|-------------------|------------------|
13//! | [`actors`](resources::actors::ActorClient) | Scraping / automation programs | Pick an Actor and run it |
14//! | [`tasks`](resources::tasks::TaskClient) | Saved Actor configurations | Create once, run many times |
15//! | [`runs`](resources::runs::RunClient) | Single executions | Poll status, fetch output |
16//!
17//! # Disclaimer
18//!
19//! This project was written with the help of large language models (LLMs).
20//! It is not well tested in production, so please review and test thoroughly
21//! before using it for critical workloads.
22//!
23//! # Quick start
24//!
25//! ```ignore
26//! use apify_rs::ApifyClient;
27//!
28//! let client = ApifyClient::new(std::env::var("APIFY_TOKEN")?);
29//!
30//! // Run a task asynchronously
31//! let run = client.tasks().run("my-task-id", None).await?;
32//! println!("Started run {}", run.id);
33//!
34//! // Poll until the run finishes
35//! let finished = client.runs().wait_for_finish(&run.id, 5, 600).await?;
36//! println!("Run finished with status {:?}", finished.status);
37//! ```
38
39pub mod client;
40pub mod error;
41pub mod instagram;
42pub mod models;
43pub mod resources;
44
45pub use client::HttpClient;
46pub use error::ApifyError;
47pub use resources::{actors::ActorClient, runs::RunClient, tasks::TaskClient};
48
49use reqwest::Method;
50
51/// Root client for the Apify API.
52///
53/// Holds an authenticated [`HttpClient`] and exposes resource-specific
54/// sub-clients ([`TaskClient`], [`RunClient`], [`ActorClient`]).
55///
56/// You normally construct this via [`ApifyClient::new`] or the
57/// [`ApifyClientBuilder`] for more control.
58///
59/// # Example
60/// ```ignore
61/// let apify = ApifyClient::new("your-api-token");
62/// let task = apify.tasks().get("my-task-id").await?;
63/// let run = apify.runs().get("run-id").await?;
64/// ```
65#[derive(Debug, Clone)]
66pub struct ApifyClient {
67 http: client::HttpClient,
68}
69
70impl ApifyClient {
71 /// Create a new client authenticated with an API token.
72 ///
73 /// The token is sent as a `Bearer` header on every request.
74 pub fn new<T: Into<String>>(token: T) -> Self {
75 Self {
76 http: client::HttpClient::new(Some(token.into())),
77 }
78 }
79
80 /// Create an unauthenticated client.
81 ///
82 /// Only a subset of endpoints (public Actors, public datasets) work
83 /// without a token. Most write operations will fail.
84 pub fn new_anonymous() -> Self {
85 Self {
86 http: client::HttpClient::new(None),
87 }
88 }
89
90 /// Override the base URL (default is `https://api.apify.com/v2`).
91 ///
92 /// Useful for integration tests against a mock server or enterprise
93 /// deployments with a custom gateway.
94 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
95 self.http = self.http.with_base_url(url);
96 self
97 }
98
99 /// Override the default HTTP timeout (60 s).
100 pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
101 self.http = self.http.with_timeout(timeout);
102 self
103 }
104
105 /// Return a [`TaskClient`] tied to this client's HTTP transport.
106 pub fn tasks(&self) -> TaskClient<'_> {
107 TaskClient::new(&self.http)
108 }
109
110 /// Return a [`RunClient`] tied to this client's HTTP transport.
111 pub fn runs(&self) -> RunClient<'_> {
112 RunClient::new(&self.http)
113 }
114
115 /// Return an [`ActorClient`] tied to this client's HTTP transport.
116 pub fn actors(&self) -> ActorClient<'_> {
117 ActorClient::new(&self.http)
118 }
119
120 /// Send an arbitrary authenticated request.
121 ///
122 /// Handy when you need an endpoint that is not yet wrapped by a
123 /// resource client (e.g. webhooks, schedules, or new beta endpoints).
124 pub async fn raw_request<T: serde::de::DeserializeOwned>(
125 &self,
126 method: Method,
127 path: &str,
128 body: Option<impl serde::Serialize>,
129 ) -> Result<T, ApifyError> {
130 self.http.request(method, path, body).await
131 }
132}
133
134/// Fluent builder for [`ApifyClient`].
135///
136/// ```ignore
137/// let client = ApifyClientBuilder::new()
138/// .token("xxx")
139/// .timeout(Duration::from_secs(30))
140/// .build();
141/// ```
142pub struct ApifyClientBuilder {
143 token: Option<String>,
144 base_url: Option<String>,
145 timeout: Option<std::time::Duration>,
146}
147
148impl ApifyClientBuilder {
149 /// Start building with no configuration.
150 pub fn new() -> Self {
151 Self {
152 token: None,
153 base_url: None,
154 timeout: None,
155 }
156 }
157
158 /// Set the API token.
159 pub fn token(mut self, token: impl Into<String>) -> Self {
160 self.token = Some(token.into());
161 self
162 }
163
164 /// Set a custom base URL.
165 pub fn base_url(mut self, url: impl Into<String>) -> Self {
166 self.base_url = Some(url.into());
167 self
168 }
169
170 /// Set a request timeout.
171 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
172 self.timeout = Some(timeout);
173 self
174 }
175
176 /// Consume the builder and return an [`ApifyClient`].
177 pub fn build(self) -> ApifyClient {
178 let mut client = ApifyClient::new(self.token.unwrap_or_default());
179 if let Some(url) = self.base_url {
180 client = client.with_base_url(url);
181 }
182 if let Some(timeout) = self.timeout {
183 client = client.with_timeout(timeout);
184 }
185 client
186 }
187}
188
189impl Default for ApifyClientBuilder {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_builder() {
201 let _client = ApifyClientBuilder::new()
202 .token("test-token")
203 .timeout(std::time::Duration::from_secs(30))
204 .build();
205 // Just verify it builds without panic
206 }
207
208 #[test]
209 fn test_new_anonymous() {
210 let _client = ApifyClient::new_anonymous();
211 // Just verify it builds without panic
212 }
213}