Skip to main content

hf_hub/
users.rs

1//! Users and organizations: identity, profile lookup, and social listings.
2//!
3//! This module exposes the [`User`], [`OrgMembership`], and [`Organization`]
4//! types and the corresponding [`HFClient`] methods:
5//!
6//! - [`HFClient::whoami`] — identify the caller and verify that the current token is valid.
7//! - [`HFClient::user_overview`] / [`HFClient::organization_overview`] — fetch a public profile by username or
8//!   organization name.
9//! - [`HFClient::list_user_followers`] / [`HFClient::list_user_following`] / [`HFClient::list_organization_members`] —
10//!   paginated listings that yield [`User`] entries one page at a time.
11
12use bon::bon;
13use futures::Stream;
14use serde::Deserialize;
15use url::Url;
16
17use crate::client::HFClient;
18use crate::error::HFResult;
19use crate::retry;
20
21/// A Hugging Face Hub user account.
22///
23/// Returned by [`HFClient::whoami`] and the various user-lookup endpoints.
24/// Only [`username`](Self::username) is guaranteed to be set; the remaining
25/// fields are populated for the authenticated caller's own `whoami` response
26/// or when the field is publicly visible on the target user's profile.
27#[derive(Debug, Clone, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct User {
30    /// Hub handle (slug) of the user — the name used in URLs such as
31    /// `https://huggingface.co/<username>`.
32    #[serde(alias = "login", alias = "user", alias = "name")]
33    pub username: String,
34    /// Display name as shown on the user's profile, when set.
35    pub fullname: Option<String>,
36    /// URL to the user's avatar image.
37    pub avatar_url: Option<String>,
38    /// Account type, typically `"user"` or `"org"`.
39    #[serde(rename = "type")]
40    pub user_type: Option<String>,
41    /// Free-text bio shown on the user's profile.
42    pub details: Option<String>,
43    /// Whether the authenticated caller follows this user.
44    pub is_following: Option<bool>,
45    /// Whether the user is on a Pro plan.
46    pub is_pro: Option<bool>,
47    /// Number of models created by the user.
48    pub num_models: Option<u64>,
49    /// Number of datasets created by the user.
50    pub num_datasets: Option<u64>,
51    /// Number of Spaces created by the user.
52    pub num_spaces: Option<u64>,
53    /// Number of discussions initiated by the user.
54    pub num_discussions: Option<u64>,
55    /// Number of papers authored by the user.
56    pub num_papers: Option<u64>,
57    /// Upvotes the user has received.
58    pub num_upvotes: Option<u64>,
59    /// Likes the user has given.
60    pub num_likes: Option<u64>,
61    /// Number of users this user is following.
62    pub num_following: Option<u64>,
63    /// Number of users following this user.
64    pub num_followers: Option<u64>,
65    /// Email address — only returned by `whoami` for the authenticated user.
66    pub email: Option<String>,
67    /// Whether the email has been verified — only returned by `whoami`.
68    pub email_verified: Option<bool>,
69    /// Billing plan identifier — only returned by `whoami`.
70    pub plan: Option<String>,
71    /// Whether the account has a valid payment method — only returned by `whoami`.
72    pub can_pay: Option<bool>,
73    /// Organizations the authenticated user belongs to. Only populated by
74    /// `whoami` for the caller themselves.
75    pub orgs: Option<Vec<OrgMembership>>,
76}
77
78/// Summary entry for an organization the authenticated user belongs to.
79///
80/// Returned inside [`User::orgs`]. This is a lighter-weight shape than
81/// [`Organization`] — use [`HFClient::organization_overview`] to fetch the
82/// full record by name.
83#[derive(Debug, Clone, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OrgMembership {
86    /// Hub handle (slug) of the organization.
87    pub name: Option<String>,
88    /// Display name as shown on the organization's profile.
89    pub fullname: Option<String>,
90    /// URL to the organization's avatar image.
91    pub avatar_url: Option<String>,
92}
93
94/// A Hugging Face Hub organization.
95///
96/// Returned by [`HFClient::organization_overview`].
97#[derive(Debug, Clone, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Organization {
100    /// Hub handle (slug) of the organization — the name used in URLs such as
101    /// `https://huggingface.co/<name>`.
102    pub name: String,
103    /// Display name as shown on the organization's profile, when set.
104    pub fullname: Option<String>,
105    /// URL to the organization's avatar image.
106    pub avatar_url: Option<String>,
107    /// Account type, typically `"org"`.
108    #[serde(rename = "type")]
109    pub org_type: Option<String>,
110    /// Free-text description shown on the organization's profile.
111    pub details: Option<String>,
112    /// Whether the organization is verified.
113    pub is_verified: Option<bool>,
114    /// Whether the authenticated caller follows this organization.
115    pub is_following: Option<bool>,
116    /// Number of members in the organization.
117    pub num_users: Option<u64>,
118    /// Number of models owned by the organization.
119    pub num_models: Option<u64>,
120    /// Number of Spaces owned by the organization.
121    pub num_spaces: Option<u64>,
122    /// Number of datasets owned by the organization.
123    pub num_datasets: Option<u64>,
124    /// Number of followers of the organization.
125    pub num_followers: Option<u64>,
126    /// Number of papers authored by the organization.
127    pub num_papers: Option<u64>,
128    /// Plan identifier (e.g., `"enterprise"`, `"team"`).
129    pub plan: Option<String>,
130}
131
132#[bon]
133impl HFClient {
134    /// Fetch the profile of the user that owns the current token.
135    ///
136    /// Returns the authenticated [`User`], including private fields like
137    /// [`email`](User::email) and the caller's [`orgs`](User::orgs) list. Fails with
138    /// [`HFError::AuthRequired`](crate::HFError::AuthRequired) if no valid token is configured.
139    ///
140    /// Endpoint: `GET /api/whoami-v2`.
141    #[builder(finish_fn = send, derive(Debug, Clone))]
142    pub async fn whoami(&self) -> HFResult<User> {
143        let url = format!("{}/api/whoami-v2", self.endpoint());
144        let headers = self.auth_headers();
145        let response =
146            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
147        let response = self
148            .check_response(response, None, crate::error::NotFoundContext::Generic)
149            .await?;
150        Ok(response.json().await?)
151    }
152
153    /// Fetch the public profile of a user by Hub handle.
154    ///
155    /// Endpoint: `GET /api/users/{username}/overview`.
156    ///
157    /// # Parameters
158    ///
159    /// - `username` (required): Hub handle (slug) of the user.
160    #[builder(finish_fn = send, derive(Debug, Clone))]
161    pub async fn user_overview(
162        &self,
163        /// Hub handle (slug) of the user.
164        username: &str,
165    ) -> HFResult<User> {
166        let url = format!("{}/api/users/{}/overview", self.endpoint(), username);
167        let headers = self.auth_headers();
168        let response =
169            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
170        let response = self
171            .check_response(response, None, crate::error::NotFoundContext::Generic)
172            .await?;
173        Ok(response.json().await?)
174    }
175
176    /// Fetch the public profile of an organization by Hub handle.
177    ///
178    /// Endpoint: `GET /api/organizations/{organization}/overview`.
179    ///
180    /// # Parameters
181    ///
182    /// - `organization` (required): Hub handle (slug) of the organization.
183    #[builder(finish_fn = send, derive(Debug, Clone))]
184    pub async fn organization_overview(
185        &self,
186        /// Hub handle (slug) of the organization.
187        organization: &str,
188    ) -> HFResult<Organization> {
189        let url = format!("{}/api/organizations/{}/overview", self.endpoint(), organization);
190        let headers = self.auth_headers();
191        let response =
192            retry::retry(self.retry_config(), || self.http_client().get(&url).headers(headers.clone()).send()).await?;
193        let response = self
194            .check_response(response, None, crate::error::NotFoundContext::Generic)
195            .await?;
196        Ok(response.json().await?)
197    }
198
199    /// Stream the followers of a user.
200    ///
201    /// Endpoint: `GET /api/users/{username}/followers`.
202    ///
203    /// # Parameters
204    ///
205    /// - `username` (required): Hub handle of the user.
206    /// - `limit`: cap on the total number of items yielded.
207    #[builder(finish_fn = send, derive(Debug, Clone))]
208    pub fn list_user_followers(
209        &self,
210        /// Hub handle of the user.
211        #[builder(into)]
212        username: String,
213        /// Cap on the total number of items yielded.
214        limit: Option<usize>,
215    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
216        let url = Url::parse(&format!("{}/api/users/{}/followers", self.endpoint(), username))?;
217        Ok(self.paginate(url, vec![], limit))
218    }
219
220    /// Stream the users that a user is following.
221    ///
222    /// Endpoint: `GET /api/users/{username}/following`.
223    ///
224    /// # Parameters
225    ///
226    /// - `username` (required): Hub handle of the user.
227    /// - `limit`: cap on the total number of items yielded.
228    #[builder(finish_fn = send, derive(Debug, Clone))]
229    pub fn list_user_following(
230        &self,
231        /// Hub handle of the user.
232        #[builder(into)]
233        username: String,
234        /// Cap on the total number of items yielded.
235        limit: Option<usize>,
236    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
237        let url = Url::parse(&format!("{}/api/users/{}/following", self.endpoint(), username))?;
238        Ok(self.paginate(url, vec![], limit))
239    }
240
241    /// Stream the members of an organization.
242    ///
243    /// Endpoint: `GET /api/organizations/{organization}/members`.
244    ///
245    /// # Parameters
246    ///
247    /// - `organization` (required): Hub handle of the organization.
248    /// - `limit`: cap on the total number of items yielded.
249    #[builder(finish_fn = send, derive(Debug, Clone))]
250    pub fn list_organization_members(
251        &self,
252        /// Hub handle of the organization.
253        #[builder(into)]
254        organization: String,
255        /// Cap on the total number of items yielded.
256        limit: Option<usize>,
257    ) -> HFResult<impl Stream<Item = HFResult<User>> + '_> {
258        let url = Url::parse(&format!("{}/api/organizations/{}/members", self.endpoint(), organization))?;
259        Ok(self.paginate(url, vec![], limit))
260    }
261}
262
263#[cfg(feature = "blocking")]
264#[bon]
265impl crate::blocking::HFClientSync {
266    /// Blocking counterpart of [`HFClient::whoami`].
267    #[builder(finish_fn = send, derive(Debug, Clone))]
268    pub fn whoami(&self) -> HFResult<User> {
269        self.runtime.block_on(self.inner.whoami().send())
270    }
271
272    /// Blocking counterpart of [`HFClient::user_overview`]. See the async method for parameters
273    /// and behavior.
274    #[builder(finish_fn = send, derive(Debug, Clone))]
275    pub fn user_overview(&self, username: &str) -> HFResult<User> {
276        self.runtime.block_on(self.inner.user_overview().username(username).send())
277    }
278
279    /// Blocking counterpart of [`HFClient::organization_overview`]. See the async method for
280    /// parameters and behavior.
281    #[builder(finish_fn = send, derive(Debug, Clone))]
282    pub fn organization_overview(&self, organization: &str) -> HFResult<Organization> {
283        self.runtime
284            .block_on(self.inner.organization_overview().organization(organization).send())
285    }
286
287    /// Blocking counterpart of [`HFClient::list_user_followers`]. Collects the stream into a
288    /// `Vec<User>`. See the async method for parameters and behavior.
289    #[builder(finish_fn = send, derive(Debug, Clone))]
290    pub fn list_user_followers(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
291        use futures::StreamExt;
292        self.runtime.block_on(async move {
293            let stream = self.inner.list_user_followers().username(username).maybe_limit(limit).send()?;
294            futures::pin_mut!(stream);
295            let mut items = Vec::new();
296            while let Some(item) = stream.next().await {
297                items.push(item?);
298            }
299            Ok(items)
300        })
301    }
302
303    /// Blocking counterpart of [`HFClient::list_user_following`]. Collects the stream into a
304    /// `Vec<User>`. See the async method for parameters and behavior.
305    #[builder(finish_fn = send, derive(Debug, Clone))]
306    pub fn list_user_following(&self, #[builder(into)] username: String, limit: Option<usize>) -> HFResult<Vec<User>> {
307        use futures::StreamExt;
308        self.runtime.block_on(async move {
309            let stream = self.inner.list_user_following().username(username).maybe_limit(limit).send()?;
310            futures::pin_mut!(stream);
311            let mut items = Vec::new();
312            while let Some(item) = stream.next().await {
313                items.push(item?);
314            }
315            Ok(items)
316        })
317    }
318
319    /// Blocking counterpart of [`HFClient::list_organization_members`]. Collects the stream into a
320    /// `Vec<User>`. See the async method for parameters and behavior.
321    #[builder(finish_fn = send, derive(Debug, Clone))]
322    pub fn list_organization_members(
323        &self,
324        #[builder(into)] organization: String,
325        limit: Option<usize>,
326    ) -> HFResult<Vec<User>> {
327        use futures::StreamExt;
328        self.runtime.block_on(async move {
329            let stream = self
330                .inner
331                .list_organization_members()
332                .organization(organization)
333                .maybe_limit(limit)
334                .send()?;
335            futures::pin_mut!(stream);
336            let mut items = Vec::new();
337            while let Some(item) = stream.next().await {
338                items.push(item?);
339            }
340            Ok(items)
341        })
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::{Organization, User};
348
349    #[test]
350    fn test_user_full_profile() {
351        let json = r#"{
352            "user":"alice",
353            "fullname":"Alice Anderson",
354            "avatarUrl":"https://example/a.png",
355            "type":"user",
356            "details":"researcher",
357            "isFollowing":true,
358            "isPro":false,
359            "numModels":12,
360            "numDatasets":3,
361            "numSpaces":1,
362            "numDiscussions":4,
363            "numPapers":2,
364            "numUpvotes":5,
365            "numLikes":6,
366            "numFollowing":7,
367            "numFollowers":8
368        }"#;
369        let user: User = serde_json::from_str(json).unwrap();
370        assert_eq!(user.username, "alice");
371        assert_eq!(user.details.as_deref(), Some("researcher"));
372        assert_eq!(user.is_following, Some(true));
373        assert_eq!(user.num_models, Some(12));
374        assert_eq!(user.num_datasets, Some(3));
375        assert_eq!(user.num_spaces, Some(1));
376        assert_eq!(user.num_discussions, Some(4));
377        assert_eq!(user.num_papers, Some(2));
378        assert_eq!(user.num_upvotes, Some(5));
379        assert_eq!(user.num_likes, Some(6));
380        assert_eq!(user.num_following, Some(7));
381        assert_eq!(user.num_followers, Some(8));
382    }
383
384    #[test]
385    fn test_organization_full_profile() {
386        let json = r#"{
387            "name":"acme",
388            "fullname":"Acme Corp",
389            "avatarUrl":"https://example/o.png",
390            "type":"org",
391            "details":"description",
392            "isVerified":true,
393            "isFollowing":false,
394            "numUsers":42,
395            "numModels":7,
396            "numSpaces":2,
397            "numDatasets":3,
398            "numFollowers":100,
399            "numPapers":5,
400            "plan":"enterprise"
401        }"#;
402        let org: Organization = serde_json::from_str(json).unwrap();
403        assert_eq!(org.name, "acme");
404        assert_eq!(org.details.as_deref(), Some("description"));
405        assert_eq!(org.is_verified, Some(true));
406        assert_eq!(org.is_following, Some(false));
407        assert_eq!(org.num_users, Some(42));
408        assert_eq!(org.num_models, Some(7));
409        assert_eq!(org.num_spaces, Some(2));
410        assert_eq!(org.num_datasets, Some(3));
411        assert_eq!(org.num_followers, Some(100));
412        assert_eq!(org.num_papers, Some(5));
413        assert_eq!(org.plan.as_deref(), Some("enterprise"));
414    }
415}