libauth_rs/vendor/api/
clerk.rs

1/*!
2 * A rust library for interacting with the Clerk Backend API.
3 *
4 * For more information, the Clerk Backend API is documented at [clerk.com/docs/reference/backend-api](https://clerk.com/docs/reference/backend-api).
5 *
6 * Example:
7 *
8 * ```ignore
9 * use clerk_api::{ClerkClient, CreateUserRequest};
10 *
11 * async fn create_user() {
12 *     // Initialize the Clerk client
13 *     let clerk = ClerkClient::new("sk_test_...".to_string());
14 *
15 *     // Create a new user
16 *     let user = clerk
17 *         .create_user(CreateUserRequest {
18 *             email_address: vec!["user@example.com".to_string()],
19 *             first_name: Some("John".to_string()),
20 *             last_name: Some("Doe".to_string()),
21 *             ..Default::default()
22 *         })
23 *         .await
24 *         .unwrap();
25 *
26 *     println!("Created user: {}", user.id);
27 * }
28 * ```
29 */
30#![allow(clippy::field_reassign_with_default)]
31
32use reqwest::{header, Method, StatusCode, Url};
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::error::Error;
36use std::fmt;
37
38/// Endpoint for the Clerk Backend API
39const ENDPOINT: &str = "https://api.clerk.com/v1/";
40
41/// Custom error type for Clerk API operations
42#[derive(Debug)]
43pub enum ClerkClientError {
44    /// HTTP request error
45    RequestError(reqwest::Error),
46    /// API returned an error status
47    ApiError { status: StatusCode, message: String },
48    /// Invalid header value
49    HeaderError(reqwest::header::InvalidHeaderValue),
50    /// Invalid URL
51    InvalidUrl(String),
52}
53
54impl fmt::Display for ClerkClientError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::RequestError(e) => write!(f, "Request error: {}", e),
58            Self::ApiError { status, message } => write!(f, "API error ({}): {}", status, message),
59            Self::HeaderError(e) => write!(f, "Header error: {}", e),
60            Self::InvalidUrl(msg) => write!(f, "Invalid URL: {}", msg),
61        }
62    }
63}
64
65impl Error for ClerkClientError {}
66
67impl From<reqwest::Error> for ClerkClientError {
68    fn from(err: reqwest::Error) -> Self {
69        Self::RequestError(err)
70    }
71}
72
73impl From<reqwest::header::InvalidHeaderValue> for ClerkClientError {
74    fn from(err: reqwest::header::InvalidHeaderValue) -> Self {
75        Self::HeaderError(err)
76    }
77}
78
79pub type Result<T> = std::result::Result<T, ClerkClientError>;
80
81/// Entrypoint for interacting with the Clerk Backend API
82pub struct ClerkClient {
83    secret_key: String,
84    client: reqwest::Client,
85}
86
87/// Get the secret key from the CLERK_SECRET_KEY env variable
88pub fn secret_key_from_env() -> String {
89    std::env::var("CLERK_SECRET_KEY").unwrap_or_default()
90}
91
92impl ClerkClient {
93    /// Create a new Clerk client struct. It takes a secret key (starts with sk_)
94    pub fn new<K>(secret_key: K) -> Result<Self>
95    where
96        K: ToString,
97    {
98        let client = reqwest::Client::builder()
99            .timeout(std::time::Duration::from_secs(30))
100            .build()?;
101
102        Ok(Self {
103            secret_key: secret_key.to_string(),
104            client,
105        })
106    }
107
108    /// Create a new Clerk client struct from environment variables
109    pub fn new_from_env() -> Result<Self> {
110        ClerkClient::new(secret_key_from_env())
111    }
112
113    /// Get the currently set secret key
114    pub fn get_key(&self) -> &str {
115        &self.secret_key
116    }
117
118    async fn request<B>(
119        &self,
120        method: Method,
121        path: String,
122        body: B,
123        query: Option<Vec<(&str, String)>>,
124    ) -> Result<reqwest::Response>
125    where
126        B: Serialize,
127    {
128        let base = Url::parse(ENDPOINT).map_err(|e| ClerkClientError::InvalidUrl(e.to_string()))?;
129        let url = base
130            .join(&path)
131            .map_err(|e| ClerkClientError::InvalidUrl(e.to_string()))?;
132
133        let bt = format!("Bearer {}", self.secret_key);
134        let bearer = header::HeaderValue::from_str(&bt)?;
135
136        // Set the default headers per Clerk API docs
137        let mut headers = header::HeaderMap::new();
138        headers.append(header::AUTHORIZATION, bearer);
139        headers.append(
140            header::CONTENT_TYPE,
141            header::HeaderValue::from_static("application/json"),
142        );
143        headers.append(
144            header::USER_AGENT,
145            header::HeaderValue::from_static("libauth-rs/0.1.0"),
146        );
147
148        let mut rb = self.client.request(method.clone(), url).headers(headers);
149
150        if let Some(val) = query {
151            rb = rb.query(&val);
152        }
153
154        // Add the body for POST/PATCH/PUT
155        if method != Method::GET && method != Method::DELETE {
156            rb = rb.json(&body);
157        }
158
159        let resp = rb.send().await?;
160        Ok(resp)
161    }
162
163    /// List users
164    /// GET /users
165    pub async fn list_users(&self, params: ListUsersParams) -> Result<Vec<User>> {
166        let mut query_params = vec![];
167
168        if let Some(limit) = params.limit {
169            query_params.push(("limit", limit.to_string()));
170        }
171        if let Some(offset) = params.offset {
172            query_params.push(("offset", offset.to_string()));
173        }
174        if let Some(email_address) = params.email_address {
175            query_params.push(("email_address", email_address));
176        }
177        if let Some(phone_number) = params.phone_number {
178            query_params.push(("phone_number", phone_number));
179        }
180        if let Some(username) = params.username {
181            query_params.push(("username", username));
182        }
183        if let Some(user_id) = params.user_id {
184            query_params.push(("user_id", user_id));
185        }
186        if let Some(order_by) = params.order_by {
187            query_params.push(("order_by", order_by));
188        }
189
190        let resp = self
191            .request(Method::GET, "users".to_string(), (), Some(query_params))
192            .await?;
193
194        if !resp.status().is_success() {
195            let status = resp.status();
196            let body = resp.text().await?;
197            return Err(ClerkClientError::ApiError {
198                status,
199                message: body,
200            });
201        }
202
203        let users: Vec<User> = resp.json().await?;
204        Ok(users)
205    }
206
207    /// Get a user by ID
208    /// GET /users/{user_id}
209    pub async fn get_user(&self, user_id: &str) -> Result<User> {
210        let resp = self
211            .request(Method::GET, format!("users/{}", user_id), (), None)
212            .await?;
213
214        if !resp.status().is_success() {
215            let status = resp.status();
216            let body = resp.text().await?;
217            return Err(ClerkClientError::ApiError {
218                status,
219                message: body,
220            });
221        }
222
223        let user: User = resp.json().await?;
224        Ok(user)
225    }
226
227    /// Create a new user
228    /// POST /users
229    pub async fn create_user(&self, req: CreateUserRequest) -> Result<User> {
230        let resp = self
231            .request(Method::POST, "users".to_string(), req, None)
232            .await?;
233
234        if !resp.status().is_success() {
235            let status = resp.status();
236            let body = resp.text().await?;
237            return Err(ClerkClientError::ApiError {
238                status,
239                message: body,
240            });
241        }
242
243        let user: User = resp.json().await?;
244        Ok(user)
245    }
246
247    /// Update a user
248    /// PATCH /users/{user_id}
249    pub async fn update_user(&self, user_id: &str, req: UpdateUserRequest) -> Result<User> {
250        let resp = self
251            .request(Method::PATCH, format!("users/{}", user_id), req, None)
252            .await?;
253
254        if !resp.status().is_success() {
255            let status = resp.status();
256            let body = resp.text().await?;
257            return Err(ClerkClientError::ApiError {
258                status,
259                message: body,
260            });
261        }
262
263        let user: User = resp.json().await?;
264        Ok(user)
265    }
266
267    /// Delete a user
268    /// DELETE /users/{user_id}
269    pub async fn delete_user(&self, user_id: &str) -> Result<DeletedObject> {
270        let resp = self
271            .request(Method::DELETE, format!("users/{}", user_id), (), None)
272            .await?;
273
274        if !resp.status().is_success() {
275            let status = resp.status();
276            let body = resp.text().await?;
277            return Err(ClerkClientError::ApiError {
278                status,
279                message: body,
280            });
281        }
282
283        let result: DeletedObject = resp.json().await?;
284        Ok(result)
285    }
286
287    /// List sessions
288    /// GET /sessions
289    pub async fn list_sessions(&self, params: ListSessionsParams) -> Result<Vec<Session>> {
290        let mut query_params = vec![];
291
292        if let Some(limit) = params.limit {
293            query_params.push(("limit", limit.to_string()));
294        }
295        if let Some(offset) = params.offset {
296            query_params.push(("offset", offset.to_string()));
297        }
298        if let Some(client_id) = params.client_id {
299            query_params.push(("client_id", client_id));
300        }
301        if let Some(user_id) = params.user_id {
302            query_params.push(("user_id", user_id));
303        }
304        if let Some(status) = params.status {
305            query_params.push(("status", status));
306        }
307
308        let resp = self
309            .request(Method::GET, "sessions".to_string(), (), Some(query_params))
310            .await?;
311
312        if !resp.status().is_success() {
313            let status = resp.status();
314            let body = resp.text().await?;
315            return Err(ClerkClientError::ApiError {
316                status,
317                message: body,
318            });
319        }
320
321        let sessions: Vec<Session> = resp.json().await?;
322        Ok(sessions)
323    }
324
325    /// Get a session by ID
326    /// GET /sessions/{session_id}
327    pub async fn get_session(&self, session_id: &str) -> Result<Session> {
328        let resp = self
329            .request(Method::GET, format!("sessions/{}", session_id), (), None)
330            .await?;
331
332        if !resp.status().is_success() {
333            let status = resp.status();
334            let body = resp.text().await?;
335            return Err(ClerkClientError::ApiError {
336                status,
337                message: body,
338            });
339        }
340
341        let session: Session = resp.json().await?;
342        Ok(session)
343    }
344
345    /// Verify a session token
346    /// POST /sessions/{session_id}/verify
347    pub async fn verify_session(&self, session_id: &str) -> Result<Session> {
348        let resp = self
349            .request(
350                Method::POST,
351                format!("sessions/{}/verify", session_id),
352                (),
353                None,
354            )
355            .await?;
356
357        if !resp.status().is_success() {
358            let status = resp.status();
359            let body = resp.text().await?;
360            return Err(ClerkClientError::ApiError {
361                status,
362                message: body,
363            });
364        }
365
366        let session: Session = resp.json().await?;
367        Ok(session)
368    }
369
370    /// Revoke a session
371    /// POST /sessions/{session_id}/revoke
372    pub async fn revoke_session(&self, session_id: &str) -> Result<Session> {
373        let resp = self
374            .request(
375                Method::POST,
376                format!("sessions/{}/revoke", session_id),
377                (),
378                None,
379            )
380            .await?;
381
382        if !resp.status().is_success() {
383            let status = resp.status();
384            let body = resp.text().await?;
385            return Err(ClerkClientError::ApiError {
386                status,
387                message: body,
388            });
389        }
390
391        let session: Session = resp.json().await?;
392        Ok(session)
393    }
394
395    /// List organizations
396    /// GET /organizations
397    pub async fn list_organizations(
398        &self,
399        params: ListOrganizationsParams,
400    ) -> Result<Vec<Organization>> {
401        let mut query_params = vec![];
402
403        if let Some(limit) = params.limit {
404            query_params.push(("limit", limit.to_string()));
405        }
406        if let Some(offset) = params.offset {
407            query_params.push(("offset", offset.to_string()));
408        }
409        if let Some(query) = params.query {
410            query_params.push(("query", query));
411        }
412
413        let resp = self
414            .request(
415                Method::GET,
416                "organizations".to_string(),
417                (),
418                Some(query_params),
419            )
420            .await?;
421
422        if !resp.status().is_success() {
423            let status = resp.status();
424            let body = resp.text().await?;
425            return Err(ClerkClientError::ApiError {
426                status,
427                message: body,
428            });
429        }
430
431        let orgs: Vec<Organization> = resp.json().await?;
432        Ok(orgs)
433    }
434
435    /// Get an organization by ID
436    /// GET /organizations/{organization_id}
437    pub async fn get_organization(&self, organization_id: &str) -> Result<Organization> {
438        let resp = self
439            .request(
440                Method::GET,
441                format!("organizations/{}", organization_id),
442                (),
443                None,
444            )
445            .await?;
446
447        if !resp.status().is_success() {
448            let status = resp.status();
449            let body = resp.text().await?;
450            return Err(ClerkClientError::ApiError {
451                status,
452                message: body,
453            });
454        }
455
456        let org: Organization = resp.json().await?;
457        Ok(org)
458    }
459
460    /// Create an organization
461    /// POST /organizations
462    pub async fn create_organization(
463        &self,
464        req: CreateOrganizationRequest,
465    ) -> Result<Organization> {
466        let resp = self
467            .request(Method::POST, "organizations".to_string(), req, None)
468            .await?;
469
470        if !resp.status().is_success() {
471            let status = resp.status();
472            let body = resp.text().await?;
473            return Err(ClerkClientError::ApiError {
474                status,
475                message: body,
476            });
477        }
478
479        let org: Organization = resp.json().await?;
480        Ok(org)
481    }
482
483    /// Update an organization
484    /// PATCH /organizations/{organization_id}
485    pub async fn update_organization(
486        &self,
487        organization_id: &str,
488        req: UpdateOrganizationRequest,
489    ) -> Result<Organization> {
490        let resp = self
491            .request(
492                Method::PATCH,
493                format!("organizations/{}", organization_id),
494                req,
495                None,
496            )
497            .await?;
498
499        if !resp.status().is_success() {
500            let status = resp.status();
501            let body = resp.text().await?;
502            return Err(ClerkClientError::ApiError {
503                status,
504                message: body,
505            });
506        }
507
508        let org: Organization = resp.json().await?;
509        Ok(org)
510    }
511
512    /// Delete an organization
513    /// DELETE /organizations/{organization_id}
514    pub async fn delete_organization(&self, organization_id: &str) -> Result<DeletedObject> {
515        let resp = self
516            .request(
517                Method::DELETE,
518                format!("organizations/{}", organization_id),
519                (),
520                None,
521            )
522            .await?;
523
524        if !resp.status().is_success() {
525            let status = resp.status();
526            let body = resp.text().await?;
527            return Err(ClerkClientError::ApiError {
528                status,
529                message: body,
530            });
531        }
532
533        let result: DeletedObject = resp.json().await?;
534        Ok(result)
535    }
536}
537
538// ============================================================================
539// User Types
540// ============================================================================
541
542/// Parameters for listing users
543#[derive(Debug, Default, Clone)]
544pub struct ListUsersParams {
545    pub limit: Option<u32>,
546    pub offset: Option<u32>,
547    pub email_address: Option<String>,
548    pub phone_number: Option<String>,
549    pub username: Option<String>,
550    pub user_id: Option<String>,
551    pub order_by: Option<String>,
552}
553
554/// Request to create a new user
555#[derive(Debug, Default, Clone, Serialize, Deserialize)]
556pub struct CreateUserRequest {
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub email_address: Option<Vec<String>>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub phone_number: Option<Vec<String>>,
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub username: Option<String>,
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub password: Option<String>,
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub first_name: Option<String>,
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub last_name: Option<String>,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub external_id: Option<String>,
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub public_metadata: Option<HashMap<String, serde_json::Value>>,
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub private_metadata: Option<HashMap<String, serde_json::Value>>,
575    #[serde(skip_serializing_if = "Option::is_none")]
576    pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
577}
578
579/// Request to update a user
580#[derive(Debug, Default, Clone, Serialize, Deserialize)]
581pub struct UpdateUserRequest {
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub first_name: Option<String>,
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub last_name: Option<String>,
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub username: Option<String>,
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub primary_email_address_id: Option<String>,
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub primary_phone_number_id: Option<String>,
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub public_metadata: Option<HashMap<String, serde_json::Value>>,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub private_metadata: Option<HashMap<String, serde_json::Value>>,
596    #[serde(skip_serializing_if = "Option::is_none")]
597    pub unsafe_metadata: Option<HashMap<String, serde_json::Value>>,
598}
599
600/// A Clerk user object
601#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct User {
603    pub id: String,
604    #[serde(default, skip_serializing_if = "Option::is_none")]
605    pub object: Option<String>,
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub username: Option<String>,
608    #[serde(default, skip_serializing_if = "Option::is_none")]
609    pub first_name: Option<String>,
610    #[serde(default, skip_serializing_if = "Option::is_none")]
611    pub last_name: Option<String>,
612    #[serde(default, skip_serializing_if = "Option::is_none")]
613    pub image_url: Option<String>,
614    #[serde(default, skip_serializing_if = "Option::is_none")]
615    pub has_image: Option<bool>,
616    #[serde(default, skip_serializing_if = "Option::is_none")]
617    pub primary_email_address_id: Option<String>,
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub primary_phone_number_id: Option<String>,
620    #[serde(default, skip_serializing_if = "Option::is_none")]
621    pub primary_web3_wallet_id: Option<String>,
622    #[serde(default)]
623    pub email_addresses: Vec<EmailAddress>,
624    #[serde(default)]
625    pub phone_numbers: Vec<PhoneNumber>,
626    #[serde(default)]
627    pub web3_wallets: Vec<Web3Wallet>,
628    #[serde(default, skip_serializing_if = "Option::is_none")]
629    pub external_id: Option<String>,
630    #[serde(default)]
631    pub public_metadata: HashMap<String, serde_json::Value>,
632    #[serde(default)]
633    pub private_metadata: HashMap<String, serde_json::Value>,
634    #[serde(default)]
635    pub unsafe_metadata: HashMap<String, serde_json::Value>,
636    pub created_at: i64,
637    pub updated_at: i64,
638    #[serde(default, skip_serializing_if = "Option::is_none")]
639    pub last_sign_in_at: Option<i64>,
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub banned: Option<bool>,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    pub locked: Option<bool>,
644}
645
646/// Email address object
647#[derive(Debug, Clone, Serialize, Deserialize)]
648pub struct EmailAddress {
649    pub id: String,
650    #[serde(default, skip_serializing_if = "Option::is_none")]
651    pub object: Option<String>,
652    pub email_address: String,
653    #[serde(default, skip_serializing_if = "Option::is_none")]
654    pub verification: Option<Verification>,
655    #[serde(default, skip_serializing_if = "Option::is_none")]
656    pub linked_to: Option<Vec<LinkedIdentity>>,
657}
658
659/// Phone number object
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct PhoneNumber {
662    pub id: String,
663    #[serde(default, skip_serializing_if = "Option::is_none")]
664    pub object: Option<String>,
665    pub phone_number: String,
666    #[serde(default, skip_serializing_if = "Option::is_none")]
667    pub verification: Option<Verification>,
668    #[serde(default, skip_serializing_if = "Option::is_none")]
669    pub linked_to: Option<Vec<LinkedIdentity>>,
670}
671
672/// Web3 wallet object
673#[derive(Debug, Clone, Serialize, Deserialize)]
674pub struct Web3Wallet {
675    pub id: String,
676    #[serde(default, skip_serializing_if = "Option::is_none")]
677    pub object: Option<String>,
678    pub web3_wallet: String,
679    #[serde(default, skip_serializing_if = "Option::is_none")]
680    pub verification: Option<Verification>,
681}
682
683/// Verification status
684#[derive(Debug, Clone, Serialize, Deserialize)]
685pub struct Verification {
686    pub status: String,
687    #[serde(default, skip_serializing_if = "Option::is_none")]
688    pub strategy: Option<String>,
689    #[serde(default, skip_serializing_if = "Option::is_none")]
690    pub attempts: Option<i32>,
691    #[serde(default, skip_serializing_if = "Option::is_none")]
692    pub expire_at: Option<i64>,
693}
694
695/// Linked identity
696#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct LinkedIdentity {
698    pub id: String,
699    #[serde(rename = "type")]
700    pub identity_type: String,
701}
702
703// ============================================================================
704// Session Types
705// ============================================================================
706
707/// Parameters for listing sessions
708#[derive(Debug, Default, Clone)]
709pub struct ListSessionsParams {
710    pub limit: Option<u32>,
711    pub offset: Option<u32>,
712    pub client_id: Option<String>,
713    pub user_id: Option<String>,
714    pub status: Option<String>,
715}
716
717/// A Clerk session object
718#[derive(Debug, Clone, Serialize, Deserialize)]
719pub struct Session {
720    pub id: String,
721    #[serde(default, skip_serializing_if = "Option::is_none")]
722    pub object: Option<String>,
723    pub client_id: String,
724    pub user_id: String,
725    pub status: String,
726    #[serde(default, skip_serializing_if = "Option::is_none")]
727    pub last_active_at: Option<i64>,
728    #[serde(default, skip_serializing_if = "Option::is_none")]
729    pub expire_at: Option<i64>,
730    #[serde(default, skip_serializing_if = "Option::is_none")]
731    pub abandon_at: Option<i64>,
732    pub created_at: i64,
733    pub updated_at: i64,
734}
735
736// ============================================================================
737// Organization Types
738// ============================================================================
739
740/// Parameters for listing organizations
741#[derive(Debug, Default, Clone)]
742pub struct ListOrganizationsParams {
743    pub limit: Option<u32>,
744    pub offset: Option<u32>,
745    pub query: Option<String>,
746}
747
748/// Request to create an organization
749#[derive(Debug, Default, Clone, Serialize, Deserialize)]
750pub struct CreateOrganizationRequest {
751    pub name: String,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub slug: Option<String>,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    pub created_by: Option<String>,
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub public_metadata: Option<HashMap<String, serde_json::Value>>,
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub private_metadata: Option<HashMap<String, serde_json::Value>>,
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub max_allowed_memberships: Option<i32>,
762}
763
764/// Request to update an organization
765#[derive(Debug, Default, Clone, Serialize, Deserialize)]
766pub struct UpdateOrganizationRequest {
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub name: Option<String>,
769    #[serde(skip_serializing_if = "Option::is_none")]
770    pub slug: Option<String>,
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub public_metadata: Option<HashMap<String, serde_json::Value>>,
773    #[serde(skip_serializing_if = "Option::is_none")]
774    pub private_metadata: Option<HashMap<String, serde_json::Value>>,
775    #[serde(skip_serializing_if = "Option::is_none")]
776    pub max_allowed_memberships: Option<i32>,
777}
778
779/// A Clerk organization object
780#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct Organization {
782    pub id: String,
783    #[serde(default, skip_serializing_if = "Option::is_none")]
784    pub object: Option<String>,
785    pub name: String,
786    pub slug: String,
787    #[serde(default, skip_serializing_if = "Option::is_none")]
788    pub image_url: Option<String>,
789    #[serde(default, skip_serializing_if = "Option::is_none")]
790    pub has_image: Option<bool>,
791    #[serde(default, skip_serializing_if = "Option::is_none")]
792    pub members_count: Option<i32>,
793    #[serde(default, skip_serializing_if = "Option::is_none")]
794    pub pending_invitations_count: Option<i32>,
795    #[serde(default, skip_serializing_if = "Option::is_none")]
796    pub max_allowed_memberships: Option<i32>,
797    #[serde(default)]
798    pub public_metadata: HashMap<String, serde_json::Value>,
799    #[serde(default)]
800    pub private_metadata: HashMap<String, serde_json::Value>,
801    pub created_at: i64,
802    pub updated_at: i64,
803}
804
805// ============================================================================
806// Common Types
807// ============================================================================
808
809/// Response when an object is deleted
810#[derive(Debug, Clone, Serialize, Deserialize)]
811pub struct DeletedObject {
812    #[serde(default, skip_serializing_if = "Option::is_none")]
813    pub object: Option<String>,
814    pub id: String,
815    #[serde(default, skip_serializing_if = "Option::is_none")]
816    pub deleted: Option<bool>,
817}
818
819/// Error response from Clerk API
820#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct ClerkError {
822    #[serde(default)]
823    pub errors: Vec<ClerkErrorDetail>,
824    #[serde(default, skip_serializing_if = "Option::is_none")]
825    pub clerk_trace_id: Option<String>,
826}
827
828/// Individual error detail
829#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct ClerkErrorDetail {
831    pub message: String,
832    #[serde(default, skip_serializing_if = "Option::is_none")]
833    pub long_message: Option<String>,
834    pub code: String,
835    #[serde(default, skip_serializing_if = "Option::is_none")]
836    pub meta: Option<HashMap<String, serde_json::Value>>,
837}