Skip to main content

quicknode_sdk/admin/
mod.rs

1pub mod billing;
2pub mod bulk;
3pub mod chains;
4pub mod endpoint_metrics;
5pub mod endpoint_rate_limits;
6pub mod endpoint_security;
7pub mod endpoints;
8pub mod logs;
9pub mod tags;
10pub mod teams;
11pub mod usage;
12
13pub use billing::{
14    Invoice, InvoiceLine, ListInvoicesData, ListInvoicesResponse, ListPaymentsData,
15    ListPaymentsResponse, Payment,
16};
17pub use bulk::{
18    BulkAddTagData, BulkAddTagRequest, BulkAddTagResponse, BulkOperationResult, BulkRemoveTagData,
19    BulkRemoveTagRequest, BulkRemoveTagResponse, BulkTag, BulkUpdateEndpointStatusData,
20    BulkUpdateEndpointStatusRequest, BulkUpdateEndpointStatusResponse,
21};
22pub use chains::{Chain, ChainNetwork, ListChainsResponse};
23pub use endpoint_metrics::{
24    EndpointMetric, GetAccountMetricsRequest, GetAccountMetricsResponse, GetEndpointMetricsRequest,
25    GetEndpointMetricsResponse,
26};
27pub use endpoint_rate_limits::{
28    CreateMethodRateLimitRequest, CreateMethodRateLimitResponse, GetMethodRateLimitsData,
29    GetMethodRateLimitsResponse, MethodRateLimiter, RateLimitSettings,
30    UpdateMethodRateLimitRequest, UpdateMethodRateLimitResponse, UpdateRateLimitsRequest,
31};
32pub use endpoint_security::{
33    CreateDomainMaskRequest, CreateIpRequest, CreateJwtRequest,
34    CreateOrUpdateIpCustomHeaderRequest, CreateOrUpdateIpCustomHeaderResponse,
35    CreateReferrerRequest, CreateRequestFilterData, CreateRequestFilterRequest,
36    CreateRequestFilterResponse, DeleteBoolResponse, GetSecurityOptionsResponse,
37    IpCustomHeaderData, SecurityOption, SecurityOptionsUpdate, UpdateRequestFilterRequest,
38    UpdateSecurityOptionsRequest, UpdateSecurityOptionsResponse,
39};
40pub use endpoints::{
41    CreateEndpointRequest, CreateEndpointResponse, CreateTagRequest, Endpoint, EndpointDomainMask,
42    EndpointIp, EndpointIpCustomHeaderOption, EndpointJwt, EndpointRateLimits, EndpointReferrer,
43    EndpointRequestFilter, EndpointSecurity, EndpointSecurityOptions, EndpointTag, EndpointToken,
44    GetEndpointSecurityResponse, GetEndpointsRequest, GetEndpointsResponse, Pagination,
45    ShowEndpointResponse, SingleEndpoint, UpdateEndpointRequest, UpdateEndpointStatusRequest,
46    UpdateEndpointStatusResponse,
47};
48pub use logs::{
49    EndpointLog, GetEndpointLogsRequest, GetEndpointLogsResponse, GetLogDetailsResponse, LogDetails,
50};
51pub use tags::{
52    AccountTag, DeleteAccountTagData, DeleteAccountTagResponse, ListTagsData, ListTagsResponse,
53    RenameTagRequest, RenameTagResponse,
54};
55pub use teams::{
56    CreateTeamData, CreateTeamRequest, CreateTeamResponse, DeleteTeamData, DeleteTeamResponse,
57    GetTeamResponse, InviteTeamMemberRequest, InviteTeamMemberResponse, ListTeamEndpointsResponse,
58    ListTeamsResponse, RemoveTeamMemberRequest, RemoveTeamMemberResponse, ResendTeamInviteResponse,
59    TeamDetail, TeamEndpoint, TeamMessageData, TeamSummary, TeamUser, UpdateTeamEndpointsData,
60    UpdateTeamEndpointsRequest, UpdateTeamEndpointsResponse,
61};
62
63pub use usage::{
64    ChainUsage, EndpointUsage, GetUsageByChainResponse, GetUsageByEndpointResponse,
65    GetUsageByMethodResponse, GetUsageByTagResponse, GetUsageRequest, GetUsageResponse,
66    MethodUsage, TagUsage, UsageByChainData, UsageByEndpointData, UsageByMethodData,
67    UsageByTagData, UsageData,
68};
69
70use crate::{config::AdminConfig, errors::SdkError, SdkConfig};
71
72const ADMIN_BASE_URL: &str = "https://api.quicknode.com/v0/";
73
74pub(crate) struct ResolvedAdminConfig {
75    pub(crate) base_url: reqwest::Url,
76}
77
78impl ResolvedAdminConfig {
79    pub(crate) fn from_config(config: Option<&AdminConfig>) -> Result<Self, SdkError> {
80        let url_str = config
81            .and_then(|a| a.base_url.as_deref())
82            .unwrap_or(ADMIN_BASE_URL);
83        let mut base_url = reqwest::Url::parse(url_str)?;
84        if !base_url.path().ends_with('/') {
85            base_url.set_path(&format!("{}/", base_url.path()));
86        }
87        Ok(Self { base_url })
88    }
89}
90
91/// Client for the QuickNode Admin API. Manage endpoints, tags, teams, billing,
92/// usage/metrics, security, and rate limits on the account.
93#[derive(Debug, Clone)]
94pub struct AdminApiClient {
95    config: SdkConfig,
96}
97
98impl AdminApiClient {
99    pub fn new(config: SdkConfig) -> Self {
100        Self { config }
101    }
102
103    /// Returns a paginated list of endpoints on the account. Supports searching
104    /// by subdomain or label, filtering by networks, statuses, labels, and
105    /// tags, and sorting. The response includes endpoint metadata (id, label,
106    /// status, chain/network, HTTP and WebSocket URLs, tags) plus
107    /// total/limit/offset pagination info.
108    pub async fn get_endpoints(
109        &self,
110        params: &GetEndpointsRequest,
111    ) -> Result<GetEndpointsResponse, SdkError> {
112        let url = self.config.admin().base_url.join("endpoints")?;
113        // Build query manually: serde_urlencoded (used by reqwest's .query())
114        // rejects Vec<T> fields, but the API expects array params like
115        // networks[]=mainnet for the filter/list query string.
116        let query = endpoints_query(params);
117        let resp = self
118            .config
119            .http_client()
120            .get(url)
121            .query(&query)
122            .send()
123            .await
124            .map_err(SdkError::Http)?;
125
126        let status = resp.status();
127        let body = resp.text().await.map_err(SdkError::Http)?;
128
129        if !status.is_success() {
130            return Err(SdkError::Api { status, body });
131        }
132        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
133    }
134
135    /// Creates a new endpoint for a given blockchain and network. Requires
136    /// `chain` and `network`; returns the new endpoint with its HTTP and
137    /// WebSocket URLs, default security configuration (tokens, JWTs, IPs,
138    /// domain masks, CORS), and rate limits.
139    pub async fn create_endpoint(
140        &self,
141        params: &CreateEndpointRequest,
142    ) -> Result<CreateEndpointResponse, SdkError> {
143        let url = self.config.admin().base_url.join("endpoints")?;
144        let resp = self
145            .config
146            .http_client()
147            .post(url)
148            .json(params)
149            .send()
150            .await
151            .map_err(SdkError::Http)?;
152
153        let status = resp.status();
154        let body = resp.text().await.map_err(SdkError::Http)?;
155
156        if !status.is_success() {
157            return Err(SdkError::Api { status, body });
158        }
159        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
160    }
161
162    /// Returns details for a specific endpoint by ID.
163    pub async fn show_endpoint(&self, id: &str) -> Result<ShowEndpointResponse, SdkError> {
164        let url = self
165            .config
166            .admin()
167            .base_url
168            .join(&format!("endpoints/{}", id))?;
169        let resp = self
170            .config
171            .http_client()
172            .get(url)
173            .send()
174            .await
175            .map_err(SdkError::Http)?;
176
177        let status = resp.status();
178        let body = resp.text().await.map_err(SdkError::Http)?;
179
180        if !status.is_success() {
181            return Err(SdkError::Api { status, body });
182        }
183        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
184    }
185
186    /// Updates editable fields on an endpoint (e.g. its label). Returns a
187    /// boolean indicating whether the update succeeded.
188    pub async fn update_endpoint(
189        &self,
190        id: &str,
191        params: &UpdateEndpointRequest,
192    ) -> Result<(), SdkError> {
193        let url = self
194            .config
195            .admin()
196            .base_url
197            .join(&format!("endpoints/{}", id))?;
198        let resp = self
199            .config
200            .http_client()
201            .patch(url)
202            .json(params)
203            .send()
204            .await
205            .map_err(SdkError::Http)?;
206
207        let status = resp.status();
208        let body = resp.text().await.map_err(SdkError::Http)?;
209
210        if !status.is_success() {
211            return Err(SdkError::Api { status, body });
212        }
213        Ok(())
214    }
215
216    /// Archives an endpoint. The API uses `DELETE` but the effect is archival
217    /// rather than permanent deletion.
218    pub async fn archive_endpoint(&self, id: &str) -> Result<(), SdkError> {
219        let url = self
220            .config
221            .admin()
222            .base_url
223            .join(&format!("endpoints/{}", id))?;
224        let resp = self
225            .config
226            .http_client()
227            .delete(url)
228            .send()
229            .await
230            .map_err(SdkError::Http)?;
231
232        let status = resp.status();
233        let body = resp.text().await.map_err(SdkError::Http)?;
234
235        if !status.is_success() {
236            return Err(SdkError::Api { status, body });
237        }
238        Ok(())
239    }
240
241    /// Pauses or unpauses an endpoint by setting its status to `active` or
242    /// `paused`.
243    pub async fn update_endpoint_status(
244        &self,
245        id: &str,
246        params: &UpdateEndpointStatusRequest,
247    ) -> Result<UpdateEndpointStatusResponse, SdkError> {
248        let url = self
249            .config
250            .admin()
251            .base_url
252            .join(&format!("endpoints/{}/status", id))?;
253        let resp = self
254            .config
255            .http_client()
256            .patch(url)
257            .json(params)
258            .send()
259            .await
260            .map_err(SdkError::Http)?;
261
262        let status = resp.status();
263        let body = resp.text().await.map_err(SdkError::Http)?;
264
265        if !status.is_success() {
266            return Err(SdkError::Api { status, body });
267        }
268        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
269    }
270
271    /// Creates a new tag on a specific endpoint from a label. Returns the new
272    /// tag with its id, account info, and timestamps.
273    pub async fn create_tag(&self, id: &str, params: &CreateTagRequest) -> Result<(), SdkError> {
274        let url = self
275            .config
276            .admin()
277            .base_url
278            .join(&format!("endpoints/{}/tags", id))?;
279        let resp = self
280            .config
281            .http_client()
282            .post(url)
283            .json(params)
284            .send()
285            .await
286            .map_err(SdkError::Http)?;
287
288        let status = resp.status();
289        let body = resp.text().await.map_err(SdkError::Http)?;
290
291        if !status.is_success() {
292            return Err(SdkError::Api { status, body });
293        }
294        Ok(())
295    }
296
297    /// Removes a tag from a specific endpoint by tag id.
298    pub async fn delete_tag(&self, id: &str, tag_id: &str) -> Result<(), SdkError> {
299        let url = self
300            .config
301            .admin()
302            .base_url
303            .join(&format!("endpoints/{}/tags/{}", id, tag_id))?;
304        let resp = self
305            .config
306            .http_client()
307            .delete(url)
308            .send()
309            .await
310            .map_err(SdkError::Http)?;
311
312        let status = resp.status();
313        let body = resp.text().await.map_err(SdkError::Http)?;
314
315        if !status.is_success() {
316            return Err(SdkError::Api { status, body });
317        }
318        Ok(())
319    }
320
321    /// Returns all teams on the account. Each team includes its id, name,
322    /// member count, and member details (roles, contact info, account status).
323    pub async fn list_teams(&self) -> Result<ListTeamsResponse, SdkError> {
324        let url = self.config.admin().base_url.join("teams")?;
325        let resp = self
326            .config
327            .http_client()
328            .get(url)
329            .send()
330            .await
331            .map_err(SdkError::Http)?;
332
333        let status = resp.status();
334        let body = resp.text().await.map_err(SdkError::Http)?;
335
336        if !status.is_success() {
337            return Err(SdkError::Api { status, body });
338        }
339        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
340    }
341
342    /// Creates a new team. Requires a `name`; returns the new team with its
343    /// id, name, default role, and member count.
344    pub async fn create_team(
345        &self,
346        params: &CreateTeamRequest,
347    ) -> Result<CreateTeamResponse, SdkError> {
348        let url = self.config.admin().base_url.join("teams")?;
349        let resp = self
350            .config
351            .http_client()
352            .post(url)
353            .json(params)
354            .send()
355            .await
356            .map_err(SdkError::Http)?;
357
358        let status = resp.status();
359        let body = resp.text().await.map_err(SdkError::Http)?;
360
361        if !status.is_success() {
362            return Err(SdkError::Api { status, body });
363        }
364        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
365    }
366
367    /// Returns a specific team by id, including active members with their
368    /// roles and contact info plus any pending invites.
369    pub async fn get_team(&self, id: i64) -> Result<GetTeamResponse, SdkError> {
370        let url = self
371            .config
372            .admin()
373            .base_url
374            .join(&format!("teams/{}", id))?;
375        let resp = self
376            .config
377            .http_client()
378            .get(url)
379            .send()
380            .await
381            .map_err(SdkError::Http)?;
382
383        let status = resp.status();
384        let body = resp.text().await.map_err(SdkError::Http)?;
385
386        if !status.is_success() {
387            return Err(SdkError::Api { status, body });
388        }
389        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
390    }
391
392    /// Deletes a team by id. The team must have no members before it can be
393    /// deleted.
394    pub async fn delete_team(&self, id: i64) -> Result<DeleteTeamResponse, SdkError> {
395        let url = self
396            .config
397            .admin()
398            .base_url
399            .join(&format!("teams/{}", id))?;
400        let resp = self
401            .config
402            .http_client()
403            .delete(url)
404            .send()
405            .await
406            .map_err(SdkError::Http)?;
407
408        let status = resp.status();
409        let body = resp.text().await.map_err(SdkError::Http)?;
410
411        if !status.is_success() {
412            return Err(SdkError::Api { status, body });
413        }
414        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
415    }
416
417    /// Returns the endpoints accessible to a given team. Each entry includes
418    /// the endpoint id, subdomain, chain, and network.
419    pub async fn list_team_endpoints(
420        &self,
421        id: i64,
422    ) -> Result<ListTeamEndpointsResponse, SdkError> {
423        let url = self
424            .config
425            .admin()
426            .base_url
427            .join(&format!("teams/{}/endpoints", id))?;
428        let resp = self
429            .config
430            .http_client()
431            .get(url)
432            .send()
433            .await
434            .map_err(SdkError::Http)?;
435
436        let status = resp.status();
437        let body = resp.text().await.map_err(SdkError::Http)?;
438
439        if !status.is_success() {
440            return Err(SdkError::Api { status, body });
441        }
442        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
443    }
444
445    /// Assigns or unassigns endpoints for a team. Pass an array of endpoint ids
446    /// to set the team's accessible endpoints; pass an empty array to remove
447    /// all associations.
448    pub async fn update_team_endpoints(
449        &self,
450        id: i64,
451        params: &UpdateTeamEndpointsRequest,
452    ) -> Result<UpdateTeamEndpointsResponse, SdkError> {
453        let url = self
454            .config
455            .admin()
456            .base_url
457            .join(&format!("teams/{}/endpoints", id))?;
458        let resp = self
459            .config
460            .http_client()
461            .patch(url)
462            .json(params)
463            .send()
464            .await
465            .map_err(SdkError::Http)?;
466
467        let status = resp.status();
468        let body = resp.text().await.map_err(SdkError::Http)?;
469
470        if !status.is_success() {
471            return Err(SdkError::Api { status, body });
472        }
473        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
474    }
475
476    /// Invites a user to a team by email. For new users, `full_name` and
477    /// `role` (`admin`, `viewer`, or `billing`) are also required. Returns the
478    /// invited user's profile and invitation status.
479    pub async fn invite_team_member(
480        &self,
481        id: i64,
482        params: &InviteTeamMemberRequest,
483    ) -> Result<InviteTeamMemberResponse, SdkError> {
484        let url = self
485            .config
486            .admin()
487            .base_url
488            .join(&format!("teams/{}/members", id))?;
489        let resp = self
490            .config
491            .http_client()
492            .post(url)
493            .json(params)
494            .send()
495            .await
496            .map_err(SdkError::Http)?;
497
498        let status = resp.status();
499        let body = resp.text().await.map_err(SdkError::Http)?;
500
501        if !status.is_success() {
502            return Err(SdkError::Api { status, body });
503        }
504        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
505    }
506
507    /// Removes a user from a team by team id and user id.
508    pub async fn remove_team_member(
509        &self,
510        id: i64,
511        user_id: i64,
512        params: &RemoveTeamMemberRequest,
513    ) -> Result<RemoveTeamMemberResponse, SdkError> {
514        let url = self
515            .config
516            .admin()
517            .base_url
518            .join(&format!("teams/{}/members/{}", id, user_id))?;
519        let resp = self
520            .config
521            .http_client()
522            .delete(url)
523            .json(params)
524            .send()
525            .await
526            .map_err(SdkError::Http)?;
527
528        let status = resp.status();
529        let body = resp.text().await.map_err(SdkError::Http)?;
530
531        if !status.is_success() {
532            return Err(SdkError::Api { status, body });
533        }
534        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
535    }
536
537    /// Resends the invitation email to a pending team member, identified by
538    /// team id and user id.
539    pub async fn resend_team_invite(
540        &self,
541        id: i64,
542        user_id: i64,
543    ) -> Result<ResendTeamInviteResponse, SdkError> {
544        let url = self
545            .config
546            .admin()
547            .base_url
548            .join(&format!("teams/{}/members/{}/resend_invite", id, user_id))?;
549        let resp = self
550            .config
551            .http_client()
552            .post(url)
553            .send()
554            .await
555            .map_err(SdkError::Http)?;
556
557        let status = resp.status();
558        let body = resp.text().await.map_err(SdkError::Http)?;
559
560        if !status.is_success() {
561            return Err(SdkError::Api { status, body });
562        }
563        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
564    }
565
566    /// Returns account RPC usage totals for an optional time range. The
567    /// response includes `credits_used`, `credits_remaining`, the account
568    /// `limit`, any `overages`, and the queried time window.
569    pub async fn get_usage(&self, params: &GetUsageRequest) -> Result<GetUsageResponse, SdkError> {
570        let url = self.config.admin().base_url.join("usage/rpc")?;
571        let resp = self
572            .config
573            .http_client()
574            .get(url)
575            .query(params)
576            .send()
577            .await
578            .map_err(SdkError::Http)?;
579
580        let status = resp.status();
581        let body = resp.text().await.map_err(SdkError::Http)?;
582
583        if !status.is_success() {
584            return Err(SdkError::Api { status, body });
585        }
586        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
587    }
588
589    /// Returns RPC usage broken down per endpoint over an optional time range.
590    /// Each entry includes endpoint metadata, aggregate `credits_used` and
591    /// `requests`, and a per-method credit breakdown.
592    pub async fn get_usage_by_endpoint(
593        &self,
594        params: &GetUsageRequest,
595    ) -> Result<GetUsageByEndpointResponse, SdkError> {
596        let url = self.config.admin().base_url.join("usage/rpc/by-endpoint")?;
597        let resp = self
598            .config
599            .http_client()
600            .get(url)
601            .query(params)
602            .send()
603            .await
604            .map_err(SdkError::Http)?;
605
606        let status = resp.status();
607        let body = resp.text().await.map_err(SdkError::Http)?;
608
609        if !status.is_success() {
610            return Err(SdkError::Api { status, body });
611        }
612        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
613    }
614
615    /// Returns RPC usage grouped by method over an optional time range. Each
616    /// entry includes the method name, credits consumed, and archival status.
617    /// Ranges longer than one week are rounded to midnight UTC.
618    pub async fn get_usage_by_method(
619        &self,
620        params: &GetUsageRequest,
621    ) -> Result<GetUsageByMethodResponse, SdkError> {
622        let url = self.config.admin().base_url.join("usage/rpc/by-method")?;
623        let resp = self
624            .config
625            .http_client()
626            .get(url)
627            .query(params)
628            .send()
629            .await
630            .map_err(SdkError::Http)?;
631
632        let status = resp.status();
633        let body = resp.text().await.map_err(SdkError::Http)?;
634
635        if !status.is_success() {
636            return Err(SdkError::Api { status, body });
637        }
638        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
639    }
640
641    /// Returns RPC usage grouped by chain over an optional time range. Each
642    /// entry includes the chain and its credit consumption.
643    pub async fn get_usage_by_chain(
644        &self,
645        params: &GetUsageRequest,
646    ) -> Result<GetUsageByChainResponse, SdkError> {
647        let url = self.config.admin().base_url.join("usage/rpc/by-chain")?;
648        let resp = self
649            .config
650            .http_client()
651            .get(url)
652            .query(params)
653            .send()
654            .await
655            .map_err(SdkError::Http)?;
656
657        let status = resp.status();
658        let body = resp.text().await.map_err(SdkError::Http)?;
659
660        if !status.is_success() {
661            return Err(SdkError::Api { status, body });
662        }
663        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
664    }
665
666    /// Returns activity logs for a specific endpoint. Supports filtering by
667    /// timestamp range and pagination. Each log entry includes timestamp,
668    /// HTTP method, network, status code, and error data; full request/response
669    /// bodies can be included when requested.
670    pub async fn get_endpoint_logs(
671        &self,
672        id: &str,
673        params: &GetEndpointLogsRequest,
674    ) -> Result<GetEndpointLogsResponse, SdkError> {
675        let url = self
676            .config
677            .admin()
678            .base_url
679            .join(&format!("endpoints/{}/logs", id))?;
680        let resp = self
681            .config
682            .http_client()
683            .get(url)
684            .query(params)
685            .send()
686            .await
687            .map_err(SdkError::Http)?;
688
689        let status = resp.status();
690        let body = resp.text().await.map_err(SdkError::Http)?;
691
692        if !status.is_success() {
693            return Err(SdkError::Api { status, body });
694        }
695        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
696    }
697
698    /// Returns the raw request and response payloads for a specific log entry
699    /// on an endpoint, identified by request UUID. Both payloads are
700    /// JSON-encoded strings and are truncated at 2KB.
701    pub async fn get_log_details(
702        &self,
703        id: &str,
704        request_id: &str,
705    ) -> Result<GetLogDetailsResponse, SdkError> {
706        let url = self
707            .config
708            .admin()
709            .base_url
710            .join(&format!("endpoints/{}/log_details", id))?;
711        let resp = self
712            .config
713            .http_client()
714            .get(url)
715            .query(&[("request_id", request_id)])
716            .send()
717            .await
718            .map_err(SdkError::Http)?;
719
720        let status = resp.status();
721        let body = resp.text().await.map_err(SdkError::Http)?;
722
723        if !status.is_success() {
724            return Err(SdkError::Api { status, body });
725        }
726        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
727    }
728
729    /// Returns the security options for an endpoint — an object of security
730    /// feature toggles with their current enabled/disabled status.
731    pub async fn get_security_options(
732        &self,
733        id: &str,
734    ) -> Result<GetSecurityOptionsResponse, SdkError> {
735        let url = self
736            .config
737            .admin()
738            .base_url
739            .join(&format!("endpoints/{}/security_options", id))?;
740        let resp = self
741            .config
742            .http_client()
743            .get(url)
744            .send()
745            .await
746            .map_err(SdkError::Http)?;
747
748        let status = resp.status();
749        let body = resp.text().await.map_err(SdkError::Http)?;
750
751        if !status.is_success() {
752            return Err(SdkError::Api { status, body });
753        }
754        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
755    }
756
757    /// Updates which security features are enabled on an endpoint. Each option
758    /// in the submitted object can be toggled `enabled` or `disabled` —
759    /// examples include token auth, JWT validation, IP restrictions, CORS,
760    /// HSTS, referrer validation, and domain masking.
761    pub async fn update_security_options(
762        &self,
763        id: &str,
764        params: &UpdateSecurityOptionsRequest,
765    ) -> Result<UpdateSecurityOptionsResponse, SdkError> {
766        let url = self
767            .config
768            .admin()
769            .base_url
770            .join(&format!("endpoints/{}/security_options", id))?;
771        let resp = self
772            .config
773            .http_client()
774            .patch(url)
775            .json(params)
776            .send()
777            .await
778            .map_err(SdkError::Http)?;
779
780        let status = resp.status();
781        let body = resp.text().await.map_err(SdkError::Http)?;
782
783        if !status.is_success() {
784            return Err(SdkError::Api { status, body });
785        }
786        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
787    }
788
789    /// Generates a new authentication token for an endpoint.
790    pub async fn create_token(&self, id: &str) -> Result<(), SdkError> {
791        let url = self
792            .config
793            .admin()
794            .base_url
795            .join(&format!("endpoints/{}/security/tokens", id))?;
796        let resp = self
797            .config
798            .http_client()
799            .post(url)
800            .send()
801            .await
802            .map_err(SdkError::Http)?;
803
804        let status = resp.status();
805        let body = resp.text().await.map_err(SdkError::Http)?;
806
807        if !status.is_success() {
808            return Err(SdkError::Api { status, body });
809        }
810        Ok(())
811    }
812
813    /// Revokes a token on an endpoint by token id.
814    pub async fn delete_token(
815        &self,
816        id: &str,
817        token_id: &str,
818    ) -> Result<DeleteBoolResponse, SdkError> {
819        let url = self
820            .config
821            .admin()
822            .base_url
823            .join(&format!("endpoints/{}/security/tokens/{}", id, token_id))?;
824        let resp = self
825            .config
826            .http_client()
827            .delete(url)
828            .send()
829            .await
830            .map_err(SdkError::Http)?;
831
832        let status = resp.status();
833        let body = resp.text().await.map_err(SdkError::Http)?;
834
835        if !status.is_success() {
836            return Err(SdkError::Api { status, body });
837        }
838        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
839    }
840
841    /// Adds a referrer to an endpoint's security settings, specifying which
842    /// external URL or domain is permitted to call the endpoint.
843    pub async fn create_referrer(
844        &self,
845        id: &str,
846        params: &CreateReferrerRequest,
847    ) -> Result<(), SdkError> {
848        let url = self
849            .config
850            .admin()
851            .base_url
852            .join(&format!("endpoints/{}/security/referrers", id))?;
853        let resp = self
854            .config
855            .http_client()
856            .post(url)
857            .json(params)
858            .send()
859            .await
860            .map_err(SdkError::Http)?;
861
862        let status = resp.status();
863        let body = resp.text().await.map_err(SdkError::Http)?;
864
865        if !status.is_success() {
866            return Err(SdkError::Api { status, body });
867        }
868        Ok(())
869    }
870
871    /// Removes a referrer from an endpoint's security settings by referrer id.
872    pub async fn delete_referrer(
873        &self,
874        id: &str,
875        referrer_id: &str,
876    ) -> Result<DeleteBoolResponse, SdkError> {
877        let url = self.config.admin().base_url.join(&format!(
878            "endpoints/{}/security/referrers/{}",
879            id, referrer_id
880        ))?;
881        let resp = self
882            .config
883            .http_client()
884            .delete(url)
885            .send()
886            .await
887            .map_err(SdkError::Http)?;
888
889        let status = resp.status();
890        let body = resp.text().await.map_err(SdkError::Http)?;
891
892        if !status.is_success() {
893            return Err(SdkError::Api { status, body });
894        }
895        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
896    }
897
898    /// Adds an IP address to an endpoint's security whitelist.
899    pub async fn create_ip(&self, id: &str, params: &CreateIpRequest) -> Result<(), SdkError> {
900        let url = self
901            .config
902            .admin()
903            .base_url
904            .join(&format!("endpoints/{}/security/ips", id))?;
905        let resp = self
906            .config
907            .http_client()
908            .post(url)
909            .json(params)
910            .send()
911            .await
912            .map_err(SdkError::Http)?;
913
914        let status = resp.status();
915        let body = resp.text().await.map_err(SdkError::Http)?;
916
917        if !status.is_success() {
918            return Err(SdkError::Api { status, body });
919        }
920        Ok(())
921    }
922
923    /// Removes an IP address from an endpoint's security whitelist by ip id.
924    pub async fn delete_ip(&self, id: &str, ip_id: &str) -> Result<DeleteBoolResponse, SdkError> {
925        let url = self
926            .config
927            .admin()
928            .base_url
929            .join(&format!("endpoints/{}/security/ips/{}", id, ip_id))?;
930        let resp = self
931            .config
932            .http_client()
933            .delete(url)
934            .send()
935            .await
936            .map_err(SdkError::Http)?;
937
938        let status = resp.status();
939        let body = resp.text().await.map_err(SdkError::Http)?;
940
941        if !status.is_success() {
942            return Err(SdkError::Api { status, body });
943        }
944        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
945    }
946
947    /// Adds a domain mask to an endpoint — a custom domain used to hide the
948    /// endpoint's QuickNode URL so requests can be routed through your own
949    /// domain.
950    pub async fn create_domain_mask(
951        &self,
952        id: &str,
953        params: &CreateDomainMaskRequest,
954    ) -> Result<(), SdkError> {
955        let url = self
956            .config
957            .admin()
958            .base_url
959            .join(&format!("endpoints/{}/security/domain_masks", id))?;
960        let resp = self
961            .config
962            .http_client()
963            .post(url)
964            .json(params)
965            .send()
966            .await
967            .map_err(SdkError::Http)?;
968
969        let status = resp.status();
970        let body = resp.text().await.map_err(SdkError::Http)?;
971
972        if !status.is_success() {
973            return Err(SdkError::Api { status, body });
974        }
975        Ok(())
976    }
977
978    /// Removes a domain mask from an endpoint by domain mask id.
979    pub async fn delete_domain_mask(
980        &self,
981        id: &str,
982        domain_mask_id: &str,
983    ) -> Result<DeleteBoolResponse, SdkError> {
984        let url = self.config.admin().base_url.join(&format!(
985            "endpoints/{}/security/domain_masks/{}",
986            id, domain_mask_id
987        ))?;
988        let resp = self
989            .config
990            .http_client()
991            .delete(url)
992            .send()
993            .await
994            .map_err(SdkError::Http)?;
995
996        let status = resp.status();
997        let body = resp.text().await.map_err(SdkError::Http)?;
998
999        if !status.is_success() {
1000            return Err(SdkError::Api { status, body });
1001        }
1002        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1003    }
1004
1005    /// Creates a new JWT for endpoint authentication. Accepts a public key,
1006    /// key id (`kid`), and token name.
1007    pub async fn create_jwt(&self, id: &str, params: &CreateJwtRequest) -> Result<(), SdkError> {
1008        let url = self
1009            .config
1010            .admin()
1011            .base_url
1012            .join(&format!("endpoints/{}/security/jwts", id))?;
1013        let resp = self
1014            .config
1015            .http_client()
1016            .post(url)
1017            .json(params)
1018            .send()
1019            .await
1020            .map_err(SdkError::Http)?;
1021
1022        let status = resp.status();
1023        let body = resp.text().await.map_err(SdkError::Http)?;
1024
1025        if !status.is_success() {
1026            return Err(SdkError::Api { status, body });
1027        }
1028        Ok(())
1029    }
1030
1031    /// Removes a JWT from an endpoint's security configuration by jwt id,
1032    /// revoking its access.
1033    pub async fn delete_jwt(&self, id: &str, jwt_id: &str) -> Result<(), SdkError> {
1034        let url = self
1035            .config
1036            .admin()
1037            .base_url
1038            .join(&format!("endpoints/{}/security/jwts/{}", id, jwt_id))?;
1039        let resp = self
1040            .config
1041            .http_client()
1042            .delete(url)
1043            .send()
1044            .await
1045            .map_err(SdkError::Http)?;
1046
1047        let status = resp.status();
1048        let body = resp.text().await.map_err(SdkError::Http)?;
1049
1050        if !status.is_success() {
1051            return Err(SdkError::Api { status, body });
1052        }
1053        Ok(())
1054    }
1055
1056    /// Creates a request filter on an endpoint — a method whitelist that
1057    /// restricts which RPC methods may be called. Accepts an array of method
1058    /// names; other methods are blocked.
1059    pub async fn create_request_filter(
1060        &self,
1061        id: &str,
1062        params: &CreateRequestFilterRequest,
1063    ) -> Result<CreateRequestFilterResponse, SdkError> {
1064        let url = self
1065            .config
1066            .admin()
1067            .base_url
1068            .join(&format!("endpoints/{}/security/request_filters", id))?;
1069        let resp = self
1070            .config
1071            .http_client()
1072            .post(url)
1073            .json(params)
1074            .send()
1075            .await
1076            .map_err(SdkError::Http)?;
1077
1078        let status = resp.status();
1079        let body = resp.text().await.map_err(SdkError::Http)?;
1080
1081        if !status.is_success() {
1082            return Err(SdkError::Api { status, body });
1083        }
1084        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1085    }
1086
1087    /// Updates an existing request filter on an endpoint, replacing the
1088    /// whitelisted method list.
1089    pub async fn update_request_filter(
1090        &self,
1091        id: &str,
1092        request_filter_id: &str,
1093        params: &UpdateRequestFilterRequest,
1094    ) -> Result<(), SdkError> {
1095        let url = self.config.admin().base_url.join(&format!(
1096            "endpoints/{}/security/request_filters/{}",
1097            id, request_filter_id
1098        ))?;
1099        let resp = self
1100            .config
1101            .http_client()
1102            .put(url)
1103            .json(params)
1104            .send()
1105            .await
1106            .map_err(SdkError::Http)?;
1107
1108        let status = resp.status();
1109        let body = resp.text().await.map_err(SdkError::Http)?;
1110
1111        if !status.is_success() {
1112            return Err(SdkError::Api { status, body });
1113        }
1114        Ok(())
1115    }
1116
1117    /// Removes a request filter from an endpoint's security configuration by
1118    /// request filter id.
1119    pub async fn delete_request_filter(
1120        &self,
1121        id: &str,
1122        request_filter_id: &str,
1123    ) -> Result<(), SdkError> {
1124        let url = self.config.admin().base_url.join(&format!(
1125            "endpoints/{}/security/request_filters/{}",
1126            id, request_filter_id
1127        ))?;
1128        let resp = self
1129            .config
1130            .http_client()
1131            .delete(url)
1132            .send()
1133            .await
1134            .map_err(SdkError::Http)?;
1135
1136        let status = resp.status();
1137        let body = resp.text().await.map_err(SdkError::Http)?;
1138
1139        if !status.is_success() {
1140            return Err(SdkError::Api { status, body });
1141        }
1142        Ok(())
1143    }
1144
1145    /// Enables multichain functionality on an endpoint, allowing a single
1146    /// endpoint to serve multiple chains.
1147    pub async fn enable_multichain(&self, id: &str) -> Result<(), SdkError> {
1148        let url = self
1149            .config
1150            .admin()
1151            .base_url
1152            .join(&format!("endpoints/{}/enable_multichain", id))?;
1153        let resp = self
1154            .config
1155            .http_client()
1156            .post(url)
1157            .send()
1158            .await
1159            .map_err(SdkError::Http)?;
1160
1161        let status = resp.status();
1162        let body = resp.text().await.map_err(SdkError::Http)?;
1163
1164        if !status.is_success() {
1165            return Err(SdkError::Api { status, body });
1166        }
1167        Ok(())
1168    }
1169
1170    /// Disables multichain functionality on an endpoint.
1171    pub async fn disable_multichain(&self, id: &str) -> Result<(), SdkError> {
1172        let url = self
1173            .config
1174            .admin()
1175            .base_url
1176            .join(&format!("endpoints/{}/disable_multichain", id))?;
1177        let resp = self
1178            .config
1179            .http_client()
1180            .post(url)
1181            .send()
1182            .await
1183            .map_err(SdkError::Http)?;
1184
1185        let status = resp.status();
1186        let body = resp.text().await.map_err(SdkError::Http)?;
1187
1188        if !status.is_success() {
1189            return Err(SdkError::Api { status, body });
1190        }
1191        Ok(())
1192    }
1193
1194    /// Sets the custom HTTP header used to identify the client IP for an
1195    /// endpoint (for example, `X-Forwarded-For`). This header is used by
1196    /// IP-based security features to resolve the real client address when
1197    /// requests are proxied.
1198    pub async fn create_or_update_ip_custom_header(
1199        &self,
1200        id: &str,
1201        params: &CreateOrUpdateIpCustomHeaderRequest,
1202    ) -> Result<CreateOrUpdateIpCustomHeaderResponse, SdkError> {
1203        let url = self
1204            .config
1205            .admin()
1206            .base_url
1207            .join(&format!("endpoints/{}/ip_custom_header", id))?;
1208        let resp = self
1209            .config
1210            .http_client()
1211            .patch(url)
1212            .json(params)
1213            .send()
1214            .await
1215            .map_err(SdkError::Http)?;
1216
1217        let status = resp.status();
1218        let body = resp.text().await.map_err(SdkError::Http)?;
1219
1220        if !status.is_success() {
1221            return Err(SdkError::Api { status, body });
1222        }
1223        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1224    }
1225
1226    /// Removes the custom IP header configuration from an endpoint.
1227    pub async fn delete_ip_custom_header(&self, id: &str) -> Result<DeleteBoolResponse, SdkError> {
1228        let url = self
1229            .config
1230            .admin()
1231            .base_url
1232            .join(&format!("endpoints/{}/ip_custom_header", id))?;
1233        let resp = self
1234            .config
1235            .http_client()
1236            .delete(url)
1237            .send()
1238            .await
1239            .map_err(SdkError::Http)?;
1240
1241        let status = resp.status();
1242        let body = resp.text().await.map_err(SdkError::Http)?;
1243
1244        if !status.is_success() {
1245            return Err(SdkError::Api { status, body });
1246        }
1247        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1248    }
1249
1250    /// Returns the method rate limits configured on an endpoint, including
1251    /// each limiter's interval, methods, rate, and status.
1252    pub async fn get_method_rate_limits(
1253        &self,
1254        id: &str,
1255    ) -> Result<GetMethodRateLimitsResponse, SdkError> {
1256        let url = self
1257            .config
1258            .admin()
1259            .base_url
1260            .join(&format!("endpoints/{}/method-rate-limits", id))?;
1261        let resp = self
1262            .config
1263            .http_client()
1264            .get(url)
1265            .send()
1266            .await
1267            .map_err(SdkError::Http)?;
1268
1269        let status = resp.status();
1270        let body = resp.text().await.map_err(SdkError::Http)?;
1271
1272        if !status.is_success() {
1273            return Err(SdkError::Api { status, body });
1274        }
1275        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1276    }
1277
1278    /// Creates a per-method rate limit on an endpoint. A method rate limit
1279    /// caps specific RPC methods rather than the endpoint as a whole, defined
1280    /// by an `interval` (e.g. `second`), the target `methods`, and a `rate`.
1281    pub async fn create_method_rate_limit(
1282        &self,
1283        id: &str,
1284        params: &CreateMethodRateLimitRequest,
1285    ) -> Result<CreateMethodRateLimitResponse, SdkError> {
1286        let url = self
1287            .config
1288            .admin()
1289            .base_url
1290            .join(&format!("endpoints/{}/method-rate-limits", id))?;
1291        let resp = self
1292            .config
1293            .http_client()
1294            .post(url)
1295            .json(params)
1296            .send()
1297            .await
1298            .map_err(SdkError::Http)?;
1299
1300        let status = resp.status();
1301        let body = resp.text().await.map_err(SdkError::Http)?;
1302
1303        if !status.is_success() {
1304            return Err(SdkError::Api { status, body });
1305        }
1306        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1307    }
1308
1309    /// Updates an existing method rate limit on an endpoint. Accepts the
1310    /// methods to apply the limit to, the desired `status`, and the `rate`.
1311    pub async fn update_method_rate_limit(
1312        &self,
1313        id: &str,
1314        method_rate_limit_id: &str,
1315        params: &UpdateMethodRateLimitRequest,
1316    ) -> Result<UpdateMethodRateLimitResponse, SdkError> {
1317        let url = self.config.admin().base_url.join(&format!(
1318            "endpoints/{}/method-rate-limits/{}",
1319            id, method_rate_limit_id
1320        ))?;
1321        let resp = self
1322            .config
1323            .http_client()
1324            .patch(url)
1325            .json(params)
1326            .send()
1327            .await
1328            .map_err(SdkError::Http)?;
1329
1330        let status = resp.status();
1331        let body = resp.text().await.map_err(SdkError::Http)?;
1332
1333        if !status.is_success() {
1334            return Err(SdkError::Api { status, body });
1335        }
1336        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1337    }
1338
1339    /// Removes a method rate limit from an endpoint by method rate limit id.
1340    pub async fn delete_method_rate_limit(
1341        &self,
1342        id: &str,
1343        method_rate_limit_id: &str,
1344    ) -> Result<(), SdkError> {
1345        let url = self.config.admin().base_url.join(&format!(
1346            "endpoints/{}/method-rate-limits/{}",
1347            id, method_rate_limit_id
1348        ))?;
1349        let resp = self
1350            .config
1351            .http_client()
1352            .delete(url)
1353            .send()
1354            .await
1355            .map_err(SdkError::Http)?;
1356
1357        let status = resp.status();
1358        let body = resp.text().await.map_err(SdkError::Http)?;
1359
1360        if !status.is_success() {
1361            return Err(SdkError::Api { status, body });
1362        }
1363        Ok(())
1364    }
1365
1366    /// Updates the overall rate limits on an endpoint. Accepts `rps`
1367    /// (requests per second), `rpm` (requests per minute), and `rpd` (requests
1368    /// per day).
1369    pub async fn update_rate_limits(
1370        &self,
1371        id: &str,
1372        params: &UpdateRateLimitsRequest,
1373    ) -> Result<(), SdkError> {
1374        let url = self
1375            .config
1376            .admin()
1377            .base_url
1378            .join(&format!("endpoints/{}/rate-limits", id))?;
1379        let resp = self
1380            .config
1381            .http_client()
1382            .put(url)
1383            .json(params)
1384            .send()
1385            .await
1386            .map_err(SdkError::Http)?;
1387
1388        let status = resp.status();
1389        let body = resp.text().await.map_err(SdkError::Http)?;
1390
1391        if !status.is_success() {
1392            return Err(SdkError::Api { status, body });
1393        }
1394        Ok(())
1395    }
1396
1397    /// Returns time-series metrics for a specific endpoint. Requires a
1398    /// `period` (`hour`, `day`, `week`, or `month`) and a metric type such as
1399    /// `method_calls_over_time` or `response_status_breakdown`.
1400    pub async fn get_endpoint_metrics(
1401        &self,
1402        id: &str,
1403        params: &GetEndpointMetricsRequest,
1404    ) -> Result<GetEndpointMetricsResponse, SdkError> {
1405        let url = self
1406            .config
1407            .admin()
1408            .base_url
1409            .join(&format!("endpoints/{}/metrics", id))?;
1410        let resp = self
1411            .config
1412            .http_client()
1413            .get(url)
1414            .query(params)
1415            .send()
1416            .await
1417            .map_err(SdkError::Http)?;
1418
1419        let status = resp.status();
1420        let body = resp.text().await.map_err(SdkError::Http)?;
1421
1422        if !status.is_success() {
1423            return Err(SdkError::Api { status, body });
1424        }
1425        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1426    }
1427
1428    /// Returns aggregated metrics across all endpoints on the account. Accepts
1429    /// a `period` (`hour`, `day`, `week`, or `month`) and a metric type such
1430    /// as `method_calls_over_time` or `credits_over_time`.
1431    pub async fn get_account_metrics(
1432        &self,
1433        params: &GetAccountMetricsRequest,
1434    ) -> Result<GetAccountMetricsResponse, SdkError> {
1435        let url = self.config.admin().base_url.join("metrics")?;
1436        let resp = self
1437            .config
1438            .http_client()
1439            .get(url)
1440            .query(params)
1441            .send()
1442            .await
1443            .map_err(SdkError::Http)?;
1444
1445        let status = resp.status();
1446        let body = resp.text().await.map_err(SdkError::Http)?;
1447
1448        if !status.is_success() {
1449            return Err(SdkError::Api { status, body });
1450        }
1451        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1452    }
1453
1454    /// Returns all chains supported by QuickNode along with their networks.
1455    /// Each entry includes the chain slug and its network slugs and names.
1456    pub async fn list_chains(&self) -> Result<ListChainsResponse, SdkError> {
1457        let url = self.config.admin().base_url.join("chains")?;
1458        let resp = self
1459            .config
1460            .http_client()
1461            .get(url)
1462            .send()
1463            .await
1464            .map_err(SdkError::Http)?;
1465
1466        let status = resp.status();
1467        let body = resp.text().await.map_err(SdkError::Http)?;
1468
1469        if !status.is_success() {
1470            return Err(SdkError::Api { status, body });
1471        }
1472        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1473    }
1474
1475    /// Returns the account's invoices, including id, status, billing reason,
1476    /// amounts due and paid, line items with descriptions and billing periods,
1477    /// and creation timestamps.
1478    pub async fn list_invoices(&self) -> Result<ListInvoicesResponse, SdkError> {
1479        let url = self.config.admin().base_url.join("billing/invoices")?;
1480        let resp = self
1481            .config
1482            .http_client()
1483            .get(url)
1484            .send()
1485            .await
1486            .map_err(SdkError::Http)?;
1487
1488        let status = resp.status();
1489        let body = resp.text().await.map_err(SdkError::Http)?;
1490
1491        if !status.is_success() {
1492            return Err(SdkError::Api { status, body });
1493        }
1494        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1495    }
1496
1497    /// Returns all payments on the account, including amount, status, card
1498    /// last-four, timestamp, currency, and marketplace spending.
1499    pub async fn list_payments(&self) -> Result<ListPaymentsResponse, SdkError> {
1500        let url = self.config.admin().base_url.join("billing/payments")?;
1501        let resp = self
1502            .config
1503            .http_client()
1504            .get(url)
1505            .send()
1506            .await
1507            .map_err(SdkError::Http)?;
1508
1509        let status = resp.status();
1510        let body = resp.text().await.map_err(SdkError::Http)?;
1511
1512        if !status.is_success() {
1513            return Err(SdkError::Api { status, body });
1514        }
1515        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1516    }
1517
1518    /// Pauses or unpauses multiple endpoints in a single call. Accepts an
1519    /// array of endpoint ids and a target status (`active` or `paused`);
1520    /// returns per-endpoint success/failure results plus totals.
1521    pub async fn bulk_update_endpoint_status(
1522        &self,
1523        params: &BulkUpdateEndpointStatusRequest,
1524    ) -> Result<BulkUpdateEndpointStatusResponse, SdkError> {
1525        if params.ids.is_empty() {
1526            return Err(SdkError::Config(
1527                "bulk_update_endpoint_status requires at least one id".into(),
1528            ));
1529        }
1530        let url = self.config.admin().base_url.join("endpoints/bulk/status")?;
1531        let resp = self
1532            .config
1533            .http_client()
1534            .post(url)
1535            .json(params)
1536            .send()
1537            .await
1538            .map_err(SdkError::Http)?;
1539
1540        let status = resp.status();
1541        let body = resp.text().await.map_err(SdkError::Http)?;
1542
1543        if !status.is_success() {
1544            return Err(SdkError::Api { status, body });
1545        }
1546        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1547    }
1548
1549    /// Applies a single tag label to multiple endpoints in one call. Returns
1550    /// totals for affected endpoints, successes, and failures, plus the tag
1551    /// that was applied.
1552    pub async fn bulk_add_tag(
1553        &self,
1554        params: &BulkAddTagRequest,
1555    ) -> Result<BulkAddTagResponse, SdkError> {
1556        if params.ids.is_empty() {
1557            return Err(SdkError::Config(
1558                "bulk_add_tag requires at least one id".into(),
1559            ));
1560        }
1561        let url = self.config.admin().base_url.join("endpoints/bulk/tags")?;
1562        let resp = self
1563            .config
1564            .http_client()
1565            .post(url)
1566            .json(params)
1567            .send()
1568            .await
1569            .map_err(SdkError::Http)?;
1570
1571        let status = resp.status();
1572        let body = resp.text().await.map_err(SdkError::Http)?;
1573
1574        if !status.is_success() {
1575            return Err(SdkError::Api { status, body });
1576        }
1577        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1578    }
1579
1580    /// Removes a tag from multiple endpoints in one call, identified by an
1581    /// array of endpoint ids and a tag id.
1582    pub async fn bulk_remove_tag(
1583        &self,
1584        params: &BulkRemoveTagRequest,
1585    ) -> Result<BulkRemoveTagResponse, SdkError> {
1586        // Empty ids on a DELETE-with-body is high blast radius: some proxies
1587        // strip DELETE bodies, and an empty batch could be misinterpreted by
1588        // the server. Fail fast client-side before firing the request.
1589        if params.ids.is_empty() {
1590            return Err(SdkError::Config(
1591                "bulk_remove_tag requires at least one id".into(),
1592            ));
1593        }
1594        let url = self.config.admin().base_url.join("endpoints/bulk/tags")?;
1595        let resp = self
1596            .config
1597            .http_client()
1598            .delete(url)
1599            .json(params)
1600            .send()
1601            .await
1602            .map_err(SdkError::Http)?;
1603
1604        let status = resp.status();
1605        let body = resp.text().await.map_err(SdkError::Http)?;
1606
1607        if !status.is_success() {
1608            return Err(SdkError::Api { status, body });
1609        }
1610        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1611    }
1612
1613    /// Returns all account-level tags, including tags with zero associated
1614    /// endpoints. Each tag includes its id, label, and endpoint usage count.
1615    pub async fn list_tags(&self) -> Result<ListTagsResponse, SdkError> {
1616        let url = self.config.admin().base_url.join("endpoints/tags")?;
1617        let resp = self
1618            .config
1619            .http_client()
1620            .get(url)
1621            .send()
1622            .await
1623            .map_err(SdkError::Http)?;
1624
1625        let status = resp.status();
1626        let body = resp.text().await.map_err(SdkError::Http)?;
1627
1628        if !status.is_success() {
1629            return Err(SdkError::Api { status, body });
1630        }
1631        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1632    }
1633
1634    /// Updates the label of an account tag. Because the tag is shared across
1635    /// endpoints, all associated endpoints reflect the new label immediately.
1636    pub async fn rename_tag(
1637        &self,
1638        id: i32,
1639        params: &RenameTagRequest,
1640    ) -> Result<RenameTagResponse, SdkError> {
1641        let url = self
1642            .config
1643            .admin()
1644            .base_url
1645            .join(&format!("endpoints/tags/{}", id))?;
1646        let resp = self
1647            .config
1648            .http_client()
1649            .patch(url)
1650            .json(params)
1651            .send()
1652            .await
1653            .map_err(SdkError::Http)?;
1654
1655        let status = resp.status();
1656        let body = resp.text().await.map_err(SdkError::Http)?;
1657
1658        if !status.is_success() {
1659            return Err(SdkError::Api { status, body });
1660        }
1661        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1662    }
1663
1664    // Named delete_account_tag to avoid collision with the existing per-endpoint
1665    // delete_tag(id, tag_id). OpenAPI reuses the deleteTag operationId for both.
1666    /// Deletes an account-level tag. The tag must first be removed from all
1667    /// endpoints before it can be deleted.
1668    pub async fn delete_account_tag(&self, id: i32) -> Result<DeleteAccountTagResponse, SdkError> {
1669        let url = self
1670            .config
1671            .admin()
1672            .base_url
1673            .join(&format!("endpoints/tags/{}", id))?;
1674        let resp = self
1675            .config
1676            .http_client()
1677            .delete(url)
1678            .send()
1679            .await
1680            .map_err(SdkError::Http)?;
1681
1682        let status = resp.status();
1683        let body = resp.text().await.map_err(SdkError::Http)?;
1684
1685        if !status.is_success() {
1686            return Err(SdkError::Api { status, body });
1687        }
1688        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1689    }
1690
1691    /// Returns RPC usage grouped by endpoint tag over an optional time range.
1692    /// Each entry includes the tag id, label, credits consumed, and request
1693    /// count.
1694    pub async fn get_usage_by_tag(
1695        &self,
1696        params: &GetUsageRequest,
1697    ) -> Result<GetUsageByTagResponse, SdkError> {
1698        let url = self.config.admin().base_url.join("usage/rpc/by-tag")?;
1699        let resp = self
1700            .config
1701            .http_client()
1702            .get(url)
1703            .query(params)
1704            .send()
1705            .await
1706            .map_err(SdkError::Http)?;
1707
1708        let status = resp.status();
1709        let body = resp.text().await.map_err(SdkError::Http)?;
1710
1711        if !status.is_success() {
1712            return Err(SdkError::Api { status, body });
1713        }
1714        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1715    }
1716
1717    /// Returns the full security configuration for an endpoint in a single
1718    /// call, without loading the entire endpoint object. The response includes
1719    /// tokens, JWTs, referrers, domain masks, IPs, and a security options
1720    /// object describing which features are enabled.
1721    pub async fn get_endpoint_security(
1722        &self,
1723        id: &str,
1724    ) -> Result<GetEndpointSecurityResponse, SdkError> {
1725        let url = self
1726            .config
1727            .admin()
1728            .base_url
1729            .join(&format!("endpoints/{}/security", id))?;
1730        let resp = self
1731            .config
1732            .http_client()
1733            .get(url)
1734            .send()
1735            .await
1736            .map_err(SdkError::Http)?;
1737
1738        let status = resp.status();
1739        let body = resp.text().await.map_err(SdkError::Http)?;
1740
1741        if !status.is_success() {
1742            return Err(SdkError::Api { status, body });
1743        }
1744        serde_json::from_str(&body).map_err(|source| SdkError::Decode { source, body })
1745    }
1746}
1747
1748fn endpoints_query(params: &GetEndpointsRequest) -> Vec<(&'static str, String)> {
1749    let mut q: Vec<(&'static str, String)> = Vec::new();
1750    if let Some(v) = params.limit {
1751        q.push(("limit", v.to_string()));
1752    }
1753    if let Some(v) = params.offset {
1754        q.push(("offset", v.to_string()));
1755    }
1756    if let Some(ref v) = params.search {
1757        q.push(("search", v.clone()));
1758    }
1759    if let Some(ref v) = params.sort_by {
1760        q.push(("sort_by", v.clone()));
1761    }
1762    if let Some(ref v) = params.sort_direction {
1763        q.push(("sort_direction", v.clone()));
1764    }
1765    if let Some(ref list) = params.networks {
1766        for item in list {
1767            q.push(("networks[]", item.clone()));
1768        }
1769    }
1770    if let Some(ref list) = params.statuses {
1771        for item in list {
1772            q.push(("statuses[]", item.clone()));
1773        }
1774    }
1775    if let Some(ref list) = params.labels {
1776        for item in list {
1777            q.push(("labels[]", item.clone()));
1778        }
1779    }
1780    if let Some(v) = params.dedicated {
1781        q.push(("dedicated", v.to_string()));
1782    }
1783    if let Some(v) = params.is_flat_rate {
1784        q.push(("is_flat_rate", v.to_string()));
1785    }
1786    if let Some(ref list) = params.tag_ids {
1787        for item in list {
1788            q.push(("tag_ids[]", item.to_string()));
1789        }
1790    }
1791    if let Some(ref list) = params.tag_labels {
1792        for item in list {
1793            q.push(("tag_labels[]", item.clone()));
1794        }
1795    }
1796    q
1797}
1798
1799#[cfg(test)]
1800#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1801mod tests {
1802    use super::*;
1803    use crate::{AdminConfig, QuickNodeSdk, SdkFullConfig};
1804    use wiremock::matchers::{method, path, query_param};
1805    use wiremock::{Mock, MockServer, ResponseTemplate};
1806
1807    fn make_sdk(base_url: String) -> QuickNodeSdk {
1808        QuickNodeSdk::new(&SdkFullConfig {
1809            api_key: "test-key".to_string(),
1810            http: None,
1811            admin: Some(AdminConfig {
1812                base_url: Some(base_url),
1813            }),
1814            streams: None,
1815            webhooks: None,
1816            kvstore: None,
1817        })
1818        .unwrap()
1819    }
1820
1821    #[tokio::test]
1822    async fn get_endpoints_success() {
1823        let server = MockServer::start().await;
1824
1825        Mock::given(method("GET"))
1826            .and(path("/endpoints"))
1827            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1828                "data": [
1829                    {
1830                        "id": "abc123",
1831                        "name": "aged-intensive-patron",
1832                        "label": "My Endpoint",
1833                        "status": "active",
1834                        "chain": "ethereum",
1835                        "network": "mainnet",
1836                        "is_dedicated": false,
1837                        "is_flat_rate": true,
1838                        "http_url": "https://example.quicknode.pro/abc123",
1839                        "wss_url": null,
1840                        "tags": []
1841                    }
1842                ],
1843                "pagination": {
1844                    "total": 1,
1845                    "limit": 20,
1846                    "offset": 0
1847                },
1848                "error": null
1849            })))
1850            .mount(&server)
1851            .await;
1852
1853        let sdk = make_sdk(format!("{}/", server.uri()));
1854        let resp = sdk
1855            .admin
1856            .get_endpoints(&GetEndpointsRequest::default())
1857            .await
1858            .unwrap();
1859
1860        assert_eq!(resp.data.len(), 1);
1861        assert_eq!(resp.data[0].id, "abc123");
1862        assert_eq!(resp.data[0].name, "aged-intensive-patron");
1863        assert_eq!(resp.data[0].status, "active");
1864        assert_eq!(resp.data[0].chain, "ethereum");
1865        assert!(!resp.data[0].is_dedicated);
1866        assert!(resp.data[0].is_flat_rate);
1867        let pagination = resp.pagination.expect("pagination present");
1868        assert_eq!(pagination.total, 1);
1869        assert_eq!(pagination.limit, 20);
1870        assert_eq!(pagination.offset, 0);
1871    }
1872
1873    #[tokio::test]
1874    async fn get_endpoints_sends_search_and_filter_params() {
1875        let server = MockServer::start().await;
1876
1877        Mock::given(method("GET"))
1878            .and(path("/endpoints"))
1879            .and(query_param("search", "intensive"))
1880            .and(query_param("networks[]", "mainnet"))
1881            .and(query_param("statuses[]", "active"))
1882            .and(query_param("dedicated", "true"))
1883            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1884                "data": [],
1885                "error": null
1886            })))
1887            .mount(&server)
1888            .await;
1889
1890        let sdk = make_sdk(format!("{}/", server.uri()));
1891        let params = GetEndpointsRequest {
1892            search: Some("intensive".to_string()),
1893            networks: Some(vec!["mainnet".to_string()]),
1894            statuses: Some(vec!["active".to_string()]),
1895            dedicated: Some(true),
1896            ..Default::default()
1897        };
1898        let resp = sdk.admin.get_endpoints(&params).await.unwrap();
1899
1900        assert_eq!(resp.data.len(), 0);
1901    }
1902
1903    #[tokio::test]
1904    async fn get_endpoints_api_error() {
1905        let server = MockServer::start().await;
1906
1907        Mock::given(method("GET"))
1908            .and(path("/endpoints"))
1909            .respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
1910            .mount(&server)
1911            .await;
1912
1913        let sdk = make_sdk(format!("{}/", server.uri()));
1914        let err = sdk
1915            .admin
1916            .get_endpoints(&GetEndpointsRequest::default())
1917            .await
1918            .unwrap_err();
1919
1920        match err {
1921            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 401),
1922            other => panic!("expected SdkError::Api, got {:?}", other),
1923        }
1924    }
1925
1926    #[tokio::test]
1927    async fn get_endpoints_sends_query_params() {
1928        let server = MockServer::start().await;
1929
1930        Mock::given(method("GET"))
1931            .and(path("/endpoints"))
1932            .and(query_param("limit", "10"))
1933            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1934                "data": [],
1935                "error": null
1936            })))
1937            .mount(&server)
1938            .await;
1939
1940        let sdk = make_sdk(format!("{}/", server.uri()));
1941        let params = GetEndpointsRequest {
1942            limit: Some(10),
1943            ..Default::default()
1944        };
1945        let resp = sdk.admin.get_endpoints(&params).await.unwrap();
1946
1947        assert_eq!(resp.data.len(), 0);
1948    }
1949
1950    #[tokio::test]
1951    async fn get_endpoints_base_url_without_trailing_slash() {
1952        let server = MockServer::start().await;
1953
1954        Mock::given(method("GET"))
1955            .and(path("/endpoints"))
1956            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1957                "data": [],
1958                "error": null
1959            })))
1960            .mount(&server)
1961            .await;
1962
1963        let base_url_no_slash = server.uri();
1964        let sdk = make_sdk(base_url_no_slash);
1965        let resp = sdk
1966            .admin
1967            .get_endpoints(&GetEndpointsRequest::default())
1968            .await
1969            .unwrap();
1970
1971        assert_eq!(resp.data.len(), 0);
1972    }
1973
1974    #[tokio::test]
1975    async fn create_endpoint_success() {
1976        let server = MockServer::start().await;
1977
1978        Mock::given(method("POST"))
1979            .and(path("/endpoints"))
1980            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
1981                "data": {
1982                    "id": "ep123",
1983                    "label": null,
1984                    "status": "active",
1985                    "chain": "ethereum",
1986                    "network": "mainnet",
1987                    "http_url": "https://example.quicknode.pro/ep123",
1988                    "wss_url": null,
1989                    "security": {
1990                        "options": { "tokens": true, "jwts": false, "domainMasks": false, "ips": false, "referrers": false, "requestFilters": false },
1991                        "tokens": [{"id": "tok1", "token": "abc123"}],
1992                        "jwts": null,
1993                        "referrers": null,
1994                        "domain_masks": null,
1995                        "ips": null,
1996                        "request_filters": null
1997                    },
1998                    "rate_limits": null,
1999                    "tags": []
2000                },
2001                "error": null
2002            })))
2003            .mount(&server)
2004            .await;
2005
2006        let sdk = make_sdk(format!("{}/", server.uri()));
2007        let resp = sdk
2008            .admin
2009            .create_endpoint(&CreateEndpointRequest::default())
2010            .await
2011            .unwrap();
2012
2013        assert_eq!(resp.data.id, "ep123");
2014        assert_eq!(resp.data.chain, "ethereum");
2015        assert_eq!(resp.data.network, "mainnet");
2016        let security = resp.data.security.unwrap();
2017        assert!(security.tokens.unwrap().len() == 1);
2018        assert!(security.jwts.is_none());
2019    }
2020
2021    #[tokio::test]
2022    async fn create_endpoint_api_error() {
2023        let server = MockServer::start().await;
2024
2025        Mock::given(method("POST"))
2026            .and(path("/endpoints"))
2027            .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
2028            .mount(&server)
2029            .await;
2030
2031        let sdk = make_sdk(format!("{}/", server.uri()));
2032        let err = sdk
2033            .admin
2034            .create_endpoint(&CreateEndpointRequest::default())
2035            .await
2036            .unwrap_err();
2037
2038        match err {
2039            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 400),
2040            other => panic!("expected SdkError::Api, got {:?}", other),
2041        }
2042    }
2043
2044    #[tokio::test]
2045    async fn create_endpoint_sends_body() {
2046        use wiremock::matchers::body_json;
2047
2048        let server = MockServer::start().await;
2049
2050        Mock::given(method("POST"))
2051            .and(path("/endpoints"))
2052            .and(body_json(serde_json::json!({
2053                "chain": "solana",
2054                "network": "mainnet-beta"
2055            })))
2056            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2057                "data": {
2058                    "id": "ep456",
2059                    "label": null,
2060                    "status": "active",
2061                    "chain": "solana",
2062                    "network": "mainnet-beta",
2063                    "http_url": "https://example.quicknode.pro/ep456",
2064                    "wss_url": null,
2065                    "security": null,
2066                    "rate_limits": null,
2067                    "tags": []
2068                },
2069                "error": null
2070            })))
2071            .mount(&server)
2072            .await;
2073
2074        let sdk = make_sdk(format!("{}/", server.uri()));
2075        let params = CreateEndpointRequest {
2076            chain: Some("solana".to_string()),
2077            network: Some("mainnet-beta".to_string()),
2078        };
2079        let resp = sdk.admin.create_endpoint(&params).await.unwrap();
2080
2081        assert_eq!(resp.data.id, "ep456");
2082        assert_eq!(resp.data.chain, "solana");
2083    }
2084
2085    #[tokio::test]
2086    async fn show_endpoint_success() {
2087        let server = MockServer::start().await;
2088
2089        Mock::given(method("GET"))
2090            .and(path("/endpoints/ep123"))
2091            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2092                "data": {
2093                    "id": "ep123",
2094                    "label": null,
2095                    "status": "active",
2096                    "chain": "ethereum",
2097                    "network": "mainnet",
2098                    "http_url": "https://example.quicknode.pro/ep123",
2099                    "wss_url": null,
2100                    "security": null,
2101                    "rate_limits": null,
2102                    "tags": []
2103                },
2104                "error": null
2105            })))
2106            .mount(&server)
2107            .await;
2108
2109        let sdk = make_sdk(format!("{}/", server.uri()));
2110        let resp = sdk.admin.show_endpoint("ep123").await.unwrap();
2111        assert_eq!(resp.data.unwrap().id, "ep123");
2112    }
2113
2114    #[tokio::test]
2115    async fn show_endpoint_api_error() {
2116        let server = MockServer::start().await;
2117
2118        Mock::given(method("GET"))
2119            .and(path("/endpoints/ep123"))
2120            .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
2121            .mount(&server)
2122            .await;
2123
2124        let sdk = make_sdk(format!("{}/", server.uri()));
2125        let err = sdk.admin.show_endpoint("ep123").await.unwrap_err();
2126        match err {
2127            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 404),
2128            other => panic!("expected SdkError::Api, got {:?}", other),
2129        }
2130    }
2131
2132    #[tokio::test]
2133    async fn update_endpoint_success() {
2134        let server = MockServer::start().await;
2135
2136        Mock::given(method("PATCH"))
2137            .and(path("/endpoints/ep123"))
2138            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2139            .mount(&server)
2140            .await;
2141
2142        let sdk = make_sdk(format!("{}/", server.uri()));
2143        sdk.admin
2144            .update_endpoint(
2145                "ep123",
2146                &UpdateEndpointRequest {
2147                    label: Some("New Name".to_string()),
2148                },
2149            )
2150            .await
2151            .unwrap();
2152    }
2153
2154    #[tokio::test]
2155    async fn archive_endpoint_success() {
2156        let server = MockServer::start().await;
2157
2158        Mock::given(method("DELETE"))
2159            .and(path("/endpoints/ep123"))
2160            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2161            .mount(&server)
2162            .await;
2163
2164        let sdk = make_sdk(format!("{}/", server.uri()));
2165        sdk.admin.archive_endpoint("ep123").await.unwrap();
2166    }
2167
2168    #[tokio::test]
2169    async fn update_endpoint_status_success() {
2170        let server = MockServer::start().await;
2171
2172        Mock::given(method("PATCH"))
2173            .and(path("/endpoints/ep123/status"))
2174            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2175                "data": "paused",
2176                "error": null
2177            })))
2178            .mount(&server)
2179            .await;
2180
2181        let sdk = make_sdk(format!("{}/", server.uri()));
2182        let resp = sdk
2183            .admin
2184            .update_endpoint_status(
2185                "ep123",
2186                &UpdateEndpointStatusRequest {
2187                    status: "paused".to_string(),
2188                },
2189            )
2190            .await
2191            .unwrap();
2192        assert_eq!(resp.data.unwrap(), "paused");
2193    }
2194
2195    #[tokio::test]
2196    async fn create_tag_success() {
2197        let server = MockServer::start().await;
2198
2199        Mock::given(method("POST"))
2200            .and(path("/endpoints/ep123/tags"))
2201            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2202            .mount(&server)
2203            .await;
2204
2205        let sdk = make_sdk(format!("{}/", server.uri()));
2206        sdk.admin
2207            .create_tag(
2208                "ep123",
2209                &CreateTagRequest {
2210                    label: Some("my-tag".to_string()),
2211                },
2212            )
2213            .await
2214            .unwrap();
2215    }
2216
2217    #[tokio::test]
2218    async fn delete_tag_success() {
2219        let server = MockServer::start().await;
2220
2221        Mock::given(method("DELETE"))
2222            .and(path("/endpoints/ep123/tags/tag456"))
2223            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2224            .mount(&server)
2225            .await;
2226
2227        let sdk = make_sdk(format!("{}/", server.uri()));
2228        sdk.admin.delete_tag("ep123", "tag456").await.unwrap();
2229    }
2230
2231    #[tokio::test]
2232    async fn get_usage_success() {
2233        let server = MockServer::start().await;
2234
2235        Mock::given(method("GET"))
2236            .and(path("/usage/rpc"))
2237            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2238                "data": {
2239                    "credits_used": 5000,
2240                    "credits_remaining": 95000,
2241                    "limit": 100000,
2242                    "overages": null,
2243                    "start_time": 1700000000,
2244                    "end_time": 1702592000
2245                },
2246                "error": null
2247            })))
2248            .mount(&server)
2249            .await;
2250
2251        let sdk = make_sdk(format!("{}/", server.uri()));
2252        let resp = sdk
2253            .admin
2254            .get_usage(&GetUsageRequest::default())
2255            .await
2256            .unwrap();
2257        assert_eq!(resp.data.unwrap().credits_used, 5000);
2258    }
2259
2260    #[tokio::test]
2261    async fn get_usage_by_endpoint_success() {
2262        let server = MockServer::start().await;
2263
2264        Mock::given(method("GET"))
2265            .and(path("/usage/rpc/by-endpoint"))
2266            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2267                "data": {
2268                    "endpoints": [{"name": "ep1", "chain": "eth", "network": "mainnet", "status": "active", "credits_used": 100, "label": null, "methods_breakdown": [], "requests": 50}],
2269                    "start_time": 1700000000,
2270                    "end_time": 1702592000
2271                },
2272                "error": null
2273            })))
2274            .mount(&server)
2275            .await;
2276
2277        let sdk = make_sdk(format!("{}/", server.uri()));
2278        let resp = sdk
2279            .admin
2280            .get_usage_by_endpoint(&GetUsageRequest::default())
2281            .await
2282            .unwrap();
2283        assert_eq!(resp.data.unwrap().endpoints.len(), 1);
2284    }
2285
2286    #[tokio::test]
2287    async fn get_usage_by_chain_success() {
2288        let server = MockServer::start().await;
2289
2290        Mock::given(method("GET"))
2291            .and(path("/usage/rpc/by-chain"))
2292            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2293                "data": {
2294                    "chains": [{"name": "ethereum", "credits_used": 1000}],
2295                    "start_time": 1700000000,
2296                    "end_time": 1702592000
2297                },
2298                "error": null
2299            })))
2300            .mount(&server)
2301            .await;
2302
2303        let sdk = make_sdk(format!("{}/", server.uri()));
2304        let resp = sdk
2305            .admin
2306            .get_usage_by_chain(&GetUsageRequest::default())
2307            .await
2308            .unwrap();
2309        assert_eq!(resp.data.unwrap().chains[0].name, "ethereum");
2310    }
2311
2312    #[tokio::test]
2313    async fn get_endpoint_logs_success() {
2314        let server = MockServer::start().await;
2315
2316        Mock::given(method("GET"))
2317            .and(path("/endpoints/ep123/logs"))
2318            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2319                "data": [
2320                    {
2321                        "timestamp": "2025-04-29T12:39:25.543Z",
2322                        "method": "eth_call",
2323                        "network": "mainnet",
2324                        "http_method": "POST",
2325                        "status": 200,
2326                        "error_code": null,
2327                        "url": "/",
2328                        "request_id": "abc-123",
2329                        "details": null
2330                    }
2331                ],
2332                "next_at": null
2333            })))
2334            .mount(&server)
2335            .await;
2336
2337        let sdk = make_sdk(format!("{}/", server.uri()));
2338        let params = GetEndpointLogsRequest {
2339            from: "2025-04-29T00:00:00Z".to_string(),
2340            to: "2025-04-29T23:59:59Z".to_string(),
2341            ..Default::default()
2342        };
2343        let resp = sdk.admin.get_endpoint_logs("ep123", &params).await.unwrap();
2344        assert_eq!(resp.data.len(), 1);
2345        assert_eq!(resp.data[0].method.as_deref(), Some("eth_call"));
2346    }
2347
2348    #[tokio::test]
2349    async fn get_log_details_success() {
2350        let server = MockServer::start().await;
2351
2352        Mock::given(method("GET"))
2353            .and(path("/endpoints/ep123/log_details"))
2354            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2355                "data": {
2356                    "request": "{\"method\":\"eth_call\"}",
2357                    "response": "{\"result\":\"0x1\"}"
2358                }
2359            })))
2360            .mount(&server)
2361            .await;
2362
2363        let sdk = make_sdk(format!("{}/", server.uri()));
2364        let resp = sdk.admin.get_log_details("ep123", "abc-123").await.unwrap();
2365        assert!(resp.data.is_some());
2366    }
2367
2368    #[tokio::test]
2369    async fn get_security_options_success() {
2370        let server = MockServer::start().await;
2371
2372        Mock::given(method("GET"))
2373            .and(path("/endpoints/ep123/security_options"))
2374            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2375                "data": [{"option": "tokens", "status": "enabled", "value": null}],
2376                "error": null
2377            })))
2378            .mount(&server)
2379            .await;
2380
2381        let sdk = make_sdk(format!("{}/", server.uri()));
2382        let resp = sdk.admin.get_security_options("ep123").await.unwrap();
2383        assert_eq!(resp.data.len(), 1);
2384        assert_eq!(resp.data[0].option, "tokens");
2385    }
2386
2387    #[tokio::test]
2388    async fn update_security_options_success() {
2389        let server = MockServer::start().await;
2390
2391        Mock::given(method("PATCH"))
2392            .and(path("/endpoints/ep123/security_options"))
2393            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2394                "data": [{"option": "tokens", "status": "disabled", "value": null}],
2395                "error": null
2396            })))
2397            .mount(&server)
2398            .await;
2399
2400        let sdk = make_sdk(format!("{}/", server.uri()));
2401        let params = UpdateSecurityOptionsRequest {
2402            options: SecurityOptionsUpdate {
2403                tokens: Some("disabled".to_string()),
2404                ..Default::default()
2405            },
2406        };
2407        let resp = sdk
2408            .admin
2409            .update_security_options("ep123", &params)
2410            .await
2411            .unwrap();
2412        assert_eq!(resp.data[0].status, "disabled");
2413    }
2414
2415    #[tokio::test]
2416    async fn create_token_success() {
2417        let server = MockServer::start().await;
2418
2419        Mock::given(method("POST"))
2420            .and(path("/endpoints/ep123/security/tokens"))
2421            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2422            .mount(&server)
2423            .await;
2424
2425        let sdk = make_sdk(format!("{}/", server.uri()));
2426        sdk.admin.create_token("ep123").await.unwrap();
2427    }
2428
2429    #[tokio::test]
2430    async fn delete_token_success() {
2431        let server = MockServer::start().await;
2432
2433        Mock::given(method("DELETE"))
2434            .and(path("/endpoints/ep123/security/tokens/tok1"))
2435            .respond_with(
2436                ResponseTemplate::new(200)
2437                    .set_body_json(serde_json::json!({"data": true, "error": null})),
2438            )
2439            .mount(&server)
2440            .await;
2441
2442        let sdk = make_sdk(format!("{}/", server.uri()));
2443        let resp = sdk.admin.delete_token("ep123", "tok1").await.unwrap();
2444        assert_eq!(resp.data, Some(true));
2445    }
2446
2447    #[tokio::test]
2448    async fn create_referrer_success() {
2449        let server = MockServer::start().await;
2450
2451        Mock::given(method("POST"))
2452            .and(path("/endpoints/ep123/security/referrers"))
2453            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2454            .mount(&server)
2455            .await;
2456
2457        let sdk = make_sdk(format!("{}/", server.uri()));
2458        sdk.admin
2459            .create_referrer(
2460                "ep123",
2461                &CreateReferrerRequest {
2462                    referrer: Some("example.com".to_string()),
2463                },
2464            )
2465            .await
2466            .unwrap();
2467    }
2468
2469    #[tokio::test]
2470    async fn enable_disable_multichain_success() {
2471        let server = MockServer::start().await;
2472
2473        Mock::given(method("POST"))
2474            .and(path("/endpoints/ep123/enable_multichain"))
2475            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2476            .mount(&server)
2477            .await;
2478
2479        Mock::given(method("POST"))
2480            .and(path("/endpoints/ep123/disable_multichain"))
2481            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2482            .mount(&server)
2483            .await;
2484
2485        let sdk = make_sdk(format!("{}/", server.uri()));
2486        sdk.admin.enable_multichain("ep123").await.unwrap();
2487        sdk.admin.disable_multichain("ep123").await.unwrap();
2488    }
2489
2490    #[tokio::test]
2491    async fn create_or_update_ip_custom_header_success() {
2492        let server = MockServer::start().await;
2493
2494        Mock::given(method("PATCH"))
2495            .and(path("/endpoints/ep123/ip_custom_header"))
2496            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2497                "data": {"header_name": "CF-Connecting-IP"},
2498                "error": null
2499            })))
2500            .mount(&server)
2501            .await;
2502
2503        let sdk = make_sdk(format!("{}/", server.uri()));
2504        let params = CreateOrUpdateIpCustomHeaderRequest {
2505            header_name: "CF-Connecting-IP".to_string(),
2506        };
2507        let resp = sdk
2508            .admin
2509            .create_or_update_ip_custom_header("ep123", &params)
2510            .await
2511            .unwrap();
2512        assert_eq!(resp.data.unwrap().header_name, "CF-Connecting-IP");
2513    }
2514
2515    #[tokio::test]
2516    async fn get_method_rate_limits_success() {
2517        let server = MockServer::start().await;
2518
2519        Mock::given(method("GET"))
2520            .and(path("/endpoints/ep123/method-rate-limits"))
2521            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2522                "data": {
2523                    "rate_limiters": [
2524                        {"id": "rl1", "interval": "second", "methods": ["eth_call"], "rate": 10, "status": "enabled", "created": "2024-01-01T00:00:00Z"}
2525                    ]
2526                },
2527                "error": null
2528            })))
2529            .mount(&server)
2530            .await;
2531
2532        let sdk = make_sdk(format!("{}/", server.uri()));
2533        let resp = sdk.admin.get_method_rate_limits("ep123").await.unwrap();
2534        assert_eq!(resp.data.unwrap().rate_limiters.len(), 1);
2535    }
2536
2537    #[tokio::test]
2538    async fn create_method_rate_limit_success() {
2539        let server = MockServer::start().await;
2540
2541        Mock::given(method("POST"))
2542            .and(path("/endpoints/ep123/method-rate-limits"))
2543            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2544                "data": {"id": "rl1", "interval": "second", "methods": ["eth_call"], "rate": 10, "status": "enabled", "created": "2024-01-01T00:00:00Z"},
2545                "error": null
2546            })))
2547            .mount(&server)
2548            .await;
2549
2550        let sdk = make_sdk(format!("{}/", server.uri()));
2551        let params = CreateMethodRateLimitRequest {
2552            interval: "second".to_string(),
2553            methods: vec!["eth_call".to_string()],
2554            rate: 10,
2555        };
2556        let resp = sdk
2557            .admin
2558            .create_method_rate_limit("ep123", &params)
2559            .await
2560            .unwrap();
2561        assert_eq!(resp.data.unwrap().id, "rl1");
2562    }
2563
2564    #[tokio::test]
2565    async fn update_method_rate_limit_success() {
2566        let server = MockServer::start().await;
2567
2568        Mock::given(method("PATCH"))
2569            .and(path("/endpoints/ep123/method-rate-limits/rl1"))
2570            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2571                "data": {"id": "rl1", "interval": "day", "methods": ["eth_call"], "rate": 30, "status": "enabled", "created": "2024-01-01T00:00:00Z"},
2572                "error": null
2573            })))
2574            .mount(&server)
2575            .await;
2576
2577        let sdk = make_sdk(format!("{}/", server.uri()));
2578        let params = UpdateMethodRateLimitRequest {
2579            rate: Some(30),
2580            ..Default::default()
2581        };
2582        let resp = sdk
2583            .admin
2584            .update_method_rate_limit("ep123", "rl1", &params)
2585            .await
2586            .unwrap();
2587        assert_eq!(resp.data.unwrap().rate, 30);
2588    }
2589
2590    #[tokio::test]
2591    async fn delete_method_rate_limit_success() {
2592        let server = MockServer::start().await;
2593
2594        Mock::given(method("DELETE"))
2595            .and(path("/endpoints/ep123/method-rate-limits/rl1"))
2596            .respond_with(
2597                ResponseTemplate::new(200)
2598                    .set_body_json(serde_json::json!({"data": "deleted", "error": null})),
2599            )
2600            .mount(&server)
2601            .await;
2602
2603        let sdk = make_sdk(format!("{}/", server.uri()));
2604        sdk.admin
2605            .delete_method_rate_limit("ep123", "rl1")
2606            .await
2607            .unwrap();
2608    }
2609
2610    #[tokio::test]
2611    async fn update_rate_limits_success() {
2612        let server = MockServer::start().await;
2613
2614        Mock::given(method("PUT"))
2615            .and(path("/endpoints/ep123/rate-limits"))
2616            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({})))
2617            .mount(&server)
2618            .await;
2619
2620        let sdk = make_sdk(format!("{}/", server.uri()));
2621        let params = UpdateRateLimitsRequest {
2622            rate_limits: RateLimitSettings {
2623                rps: Some(100),
2624                rpm: None,
2625                rpd: None,
2626            },
2627        };
2628        sdk.admin
2629            .update_rate_limits("ep123", &params)
2630            .await
2631            .unwrap();
2632    }
2633
2634    #[tokio::test]
2635    async fn get_endpoint_metrics_success() {
2636        let server = MockServer::start().await;
2637
2638        Mock::given(method("GET"))
2639            .and(path("/endpoints/ep123/metrics"))
2640            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2641                "data": [{"data": [[1700000000, 42]], "tag": "mainnet"}],
2642                "error": null
2643            })))
2644            .mount(&server)
2645            .await;
2646
2647        let sdk = make_sdk(format!("{}/", server.uri()));
2648        let params = GetEndpointMetricsRequest {
2649            period: "day".to_string(),
2650            metric: "credits_over_time".to_string(),
2651        };
2652        let resp = sdk
2653            .admin
2654            .get_endpoint_metrics("ep123", &params)
2655            .await
2656            .unwrap();
2657        assert_eq!(resp.data.len(), 1);
2658        assert_eq!(resp.data[0].tag, "mainnet");
2659    }
2660
2661    #[tokio::test]
2662    async fn get_account_metrics_success() {
2663        let server = MockServer::start().await;
2664
2665        Mock::given(method("GET"))
2666            .and(path("/metrics"))
2667            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2668                "data": [{"data": [[1700000000, 100]], "tag": "total"}],
2669                "error": null
2670            })))
2671            .mount(&server)
2672            .await;
2673
2674        let sdk = make_sdk(format!("{}/", server.uri()));
2675        let params = GetAccountMetricsRequest {
2676            period: "week".to_string(),
2677            metric: "credits_over_time".to_string(),
2678            percentile: None,
2679        };
2680        let resp = sdk.admin.get_account_metrics(&params).await.unwrap();
2681        assert_eq!(resp.data.len(), 1);
2682    }
2683
2684    #[tokio::test]
2685    async fn list_chains_success() {
2686        let server = MockServer::start().await;
2687
2688        Mock::given(method("GET"))
2689            .and(path("/chains"))
2690            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2691                "data": [
2692                    {
2693                        "slug": "eth",
2694                        "networks": [{"slug": "mainnet", "name": "Ethereum Mainnet", "chain_id": 1}],
2695                        "is_select_chain": true
2696                    }
2697                ],
2698                "error": null
2699            })))
2700            .mount(&server)
2701            .await;
2702
2703        let sdk = make_sdk(format!("{}/", server.uri()));
2704        let resp = sdk.admin.list_chains().await.unwrap();
2705        assert_eq!(resp.data.len(), 1);
2706        assert_eq!(resp.data[0].slug, "eth");
2707        assert_eq!(resp.data[0].networks[0].chain_id, Some(1));
2708    }
2709
2710    #[tokio::test]
2711    async fn list_invoices_success() {
2712        let server = MockServer::start().await;
2713
2714        Mock::given(method("GET"))
2715            .and(path("/billing/invoices"))
2716            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2717                "data": {
2718                    "invoices": [
2719                        {
2720                            "id": "inv123",
2721                            "status": "paid",
2722                            "billing_reason": "subscription",
2723                            "lines": [{"description": "Pro plan", "amount": 4900}],
2724                            "amount_due": 4900,
2725                            "amount_paid": 4900,
2726                            "period_start": 1700000000,
2727                            "period_end": 1702592000,
2728                            "created": 1700000000,
2729                            "subtotal": 4900
2730                        }
2731                    ]
2732                },
2733                "error": null
2734            })))
2735            .mount(&server)
2736            .await;
2737
2738        let sdk = make_sdk(format!("{}/", server.uri()));
2739        let resp = sdk.admin.list_invoices().await.unwrap();
2740        let data = resp.data.unwrap();
2741        assert_eq!(data.invoices.len(), 1);
2742        assert_eq!(data.invoices[0].id, "inv123");
2743    }
2744
2745    #[tokio::test]
2746    async fn list_invoices_api_error() {
2747        let server = MockServer::start().await;
2748
2749        Mock::given(method("GET"))
2750            .and(path("/billing/invoices"))
2751            .respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
2752            .mount(&server)
2753            .await;
2754
2755        let sdk = make_sdk(format!("{}/", server.uri()));
2756        let err = sdk.admin.list_invoices().await.unwrap_err();
2757        match err {
2758            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 401),
2759            other => panic!("expected SdkError::Api, got {:?}", other),
2760        }
2761    }
2762
2763    #[tokio::test]
2764    async fn list_payments_success() {
2765        let server = MockServer::start().await;
2766
2767        Mock::given(method("GET"))
2768            .and(path("/billing/payments"))
2769            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2770                "data": {
2771                    "payments": [
2772                        {
2773                            "amount": "49.00",
2774                            "card_last_4": "4242",
2775                            "created_at": "2024-01-01T00:00:00Z",
2776                            "currency": "usd",
2777                            "status": "succeeded",
2778                            "marketplace_amount": "9.0"
2779                        }
2780                    ]
2781                },
2782                "error": null
2783            })))
2784            .mount(&server)
2785            .await;
2786
2787        let sdk = make_sdk(format!("{}/", server.uri()));
2788        let resp = sdk.admin.list_payments().await.unwrap();
2789        let data = resp.data.unwrap();
2790        assert_eq!(data.payments.len(), 1);
2791        assert_eq!(data.payments[0].currency, "usd");
2792    }
2793
2794    #[tokio::test]
2795    async fn list_teams_success() {
2796        let server = MockServer::start().await;
2797
2798        Mock::given(method("GET"))
2799            .and(path("/teams"))
2800            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2801                "data": [{"id": 1, "name": "Engineering", "members_count": 5, "users": []}],
2802                "error": null
2803            })))
2804            .mount(&server)
2805            .await;
2806
2807        let sdk = make_sdk(format!("{}/", server.uri()));
2808        let resp = sdk.admin.list_teams().await.unwrap();
2809        assert_eq!(resp.data.len(), 1);
2810        assert_eq!(resp.data[0].name, "Engineering");
2811    }
2812
2813    #[tokio::test]
2814    async fn list_teams_api_error() {
2815        let server = MockServer::start().await;
2816
2817        Mock::given(method("GET"))
2818            .and(path("/teams"))
2819            .respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
2820            .mount(&server)
2821            .await;
2822
2823        let sdk = make_sdk(format!("{}/", server.uri()));
2824        let err = sdk.admin.list_teams().await.unwrap_err();
2825        match err {
2826            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 401),
2827            other => panic!("expected SdkError::Api, got {:?}", other),
2828        }
2829    }
2830
2831    #[tokio::test]
2832    async fn create_team_success() {
2833        let server = MockServer::start().await;
2834
2835        Mock::given(method("POST"))
2836            .and(path("/teams"))
2837            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2838                "data": {"id": 42, "name": "New Team", "default_role": null, "members_count": 0},
2839                "error": null
2840            })))
2841            .mount(&server)
2842            .await;
2843
2844        let sdk = make_sdk(format!("{}/", server.uri()));
2845        let params = CreateTeamRequest {
2846            name: "New Team".to_string(),
2847        };
2848        let resp = sdk.admin.create_team(&params).await.unwrap();
2849        assert_eq!(resp.data.unwrap().id, 42);
2850    }
2851
2852    #[tokio::test]
2853    async fn get_team_success() {
2854        let server = MockServer::start().await;
2855
2856        Mock::given(method("GET"))
2857            .and(path("/teams/1"))
2858            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2859                "data": {
2860                    "id": 1,
2861                    "name": "Engineering",
2862                    "default_role": "member",
2863                    "members_count": 3,
2864                    "users": [],
2865                    "pending_invites": []
2866                },
2867                "error": null
2868            })))
2869            .mount(&server)
2870            .await;
2871
2872        let sdk = make_sdk(format!("{}/", server.uri()));
2873        let resp = sdk.admin.get_team(1).await.unwrap();
2874        assert_eq!(resp.data.unwrap().name, "Engineering");
2875    }
2876
2877    #[tokio::test]
2878    async fn delete_team_success() {
2879        let server = MockServer::start().await;
2880
2881        Mock::given(method("DELETE"))
2882            .and(path("/teams/1"))
2883            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2884                "data": {"message": "Team deleted"},
2885                "error": null
2886            })))
2887            .mount(&server)
2888            .await;
2889
2890        let sdk = make_sdk(format!("{}/", server.uri()));
2891        let resp = sdk.admin.delete_team(1).await.unwrap();
2892        assert!(resp.data.is_some());
2893    }
2894
2895    #[tokio::test]
2896    async fn list_team_endpoints_success() {
2897        let server = MockServer::start().await;
2898
2899        Mock::given(method("GET"))
2900            .and(path("/teams/1/endpoints"))
2901            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2902                "data": [{"id": 10, "subdomain": "abc123", "chain": "ethereum", "network": "mainnet"}],
2903                "error": null
2904            })))
2905            .mount(&server)
2906            .await;
2907
2908        let sdk = make_sdk(format!("{}/", server.uri()));
2909        let resp = sdk.admin.list_team_endpoints(1).await.unwrap();
2910        assert_eq!(resp.data.len(), 1);
2911        assert_eq!(resp.data[0].subdomain, "abc123");
2912    }
2913
2914    #[tokio::test]
2915    async fn update_team_endpoints_success() {
2916        let server = MockServer::start().await;
2917
2918        Mock::given(method("PATCH"))
2919            .and(path("/teams/1/endpoints"))
2920            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2921                "data": {"success": true},
2922                "error": null
2923            })))
2924            .mount(&server)
2925            .await;
2926
2927        let sdk = make_sdk(format!("{}/", server.uri()));
2928        let params = UpdateTeamEndpointsRequest {
2929            endpoint_ids: vec!["ep1".to_string()],
2930        };
2931        let resp = sdk.admin.update_team_endpoints(1, &params).await.unwrap();
2932        assert!(resp.data.unwrap().success.unwrap());
2933    }
2934
2935    #[tokio::test]
2936    async fn invite_team_member_success() {
2937        let server = MockServer::start().await;
2938
2939        Mock::given(method("POST"))
2940            .and(path("/teams/1/members"))
2941            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2942                "data": {
2943                    "id": 99,
2944                    "email": "user@example.com",
2945                    "full_name": null,
2946                    "role": "member",
2947                    "status": "pending",
2948                    "created_at": null,
2949                    "photo_url": null,
2950                    "account_primary_user": null
2951                },
2952                "error": null
2953            })))
2954            .mount(&server)
2955            .await;
2956
2957        let sdk = make_sdk(format!("{}/", server.uri()));
2958        let params = InviteTeamMemberRequest {
2959            email: "user@example.com".to_string(),
2960            full_name: None,
2961            role: None,
2962        };
2963        let resp = sdk.admin.invite_team_member(1, &params).await.unwrap();
2964        assert_eq!(resp.data.unwrap().email, "user@example.com");
2965    }
2966
2967    #[tokio::test]
2968    async fn remove_team_member_success() {
2969        let server = MockServer::start().await;
2970
2971        Mock::given(method("DELETE"))
2972            .and(path("/teams/1/members/99"))
2973            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2974                "data": {"message": "Member removed"},
2975                "error": null
2976            })))
2977            .mount(&server)
2978            .await;
2979
2980        let sdk = make_sdk(format!("{}/", server.uri()));
2981        let params = RemoveTeamMemberRequest { destroy_user: None };
2982        let resp = sdk.admin.remove_team_member(1, 99, &params).await.unwrap();
2983        assert!(resp.data.is_some());
2984    }
2985
2986    #[tokio::test]
2987    async fn resend_team_invite_success() {
2988        let server = MockServer::start().await;
2989
2990        Mock::given(method("POST"))
2991            .and(path("/teams/1/members/99/resend_invite"))
2992            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
2993                "data": {"message": "Invite resent"},
2994                "error": null
2995            })))
2996            .mount(&server)
2997            .await;
2998
2999        let sdk = make_sdk(format!("{}/", server.uri()));
3000        let resp = sdk.admin.resend_team_invite(1, 99).await.unwrap();
3001        assert!(resp.data.is_some());
3002    }
3003
3004    #[tokio::test]
3005    async fn bulk_update_endpoint_status_success() {
3006        let server = MockServer::start().await;
3007        Mock::given(method("POST"))
3008            .and(path("/endpoints/bulk/status"))
3009            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3010                "data": {
3011                    "total": 2,
3012                    "updated_count": 2,
3013                    "failed_count": 0,
3014                    "results": [
3015                        { "id": "a", "success": true },
3016                        { "id": "b", "success": true }
3017                    ]
3018                }
3019            })))
3020            .mount(&server)
3021            .await;
3022
3023        let sdk = make_sdk(format!("{}/", server.uri()));
3024        let params = BulkUpdateEndpointStatusRequest {
3025            ids: vec!["a".to_string(), "b".to_string()],
3026            status: "paused".to_string(),
3027        };
3028        let resp = sdk
3029            .admin
3030            .bulk_update_endpoint_status(&params)
3031            .await
3032            .unwrap();
3033        let data = resp.data.expect("data present");
3034        assert_eq!(data.total, 2);
3035        assert_eq!(data.updated_count, 2);
3036        assert_eq!(data.results.len(), 2);
3037    }
3038
3039    #[tokio::test]
3040    async fn bulk_update_endpoint_status_api_error() {
3041        let server = MockServer::start().await;
3042        Mock::given(method("POST"))
3043            .and(path("/endpoints/bulk/status"))
3044            .respond_with(ResponseTemplate::new(400).set_body_string("bad request"))
3045            .mount(&server)
3046            .await;
3047
3048        let sdk = make_sdk(format!("{}/", server.uri()));
3049        let params = BulkUpdateEndpointStatusRequest {
3050            ids: vec!["a".to_string()],
3051            status: "paused".to_string(),
3052        };
3053        let err = sdk
3054            .admin
3055            .bulk_update_endpoint_status(&params)
3056            .await
3057            .unwrap_err();
3058        match err {
3059            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 400),
3060            other => panic!("expected Api, got {:?}", other),
3061        }
3062    }
3063
3064    #[tokio::test]
3065    async fn bulk_add_tag_success() {
3066        let server = MockServer::start().await;
3067        Mock::given(method("POST"))
3068            .and(path("/endpoints/bulk/tags"))
3069            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3070                "data": {
3071                    "total": 1,
3072                    "updated_count": 1,
3073                    "failed_count": 0,
3074                    "results": [{ "id": "a", "success": true }],
3075                    "tag": { "tag_id": 7, "label": "prod" }
3076                }
3077            })))
3078            .mount(&server)
3079            .await;
3080
3081        let sdk = make_sdk(format!("{}/", server.uri()));
3082        let params = BulkAddTagRequest {
3083            ids: vec!["a".to_string()],
3084            label: "prod".to_string(),
3085        };
3086        let resp = sdk.admin.bulk_add_tag(&params).await.unwrap();
3087        let data = resp.data.expect("data present");
3088        assert_eq!(data.tag.tag_id, 7);
3089        assert_eq!(data.tag.label, "prod");
3090    }
3091
3092    #[tokio::test]
3093    async fn bulk_remove_tag_success() {
3094        let server = MockServer::start().await;
3095        Mock::given(method("DELETE"))
3096            .and(path("/endpoints/bulk/tags"))
3097            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3098                "data": {
3099                    "total": 2,
3100                    "updated_count": 2,
3101                    "failed_count": 0,
3102                    "results": [
3103                        { "id": "a", "success": true },
3104                        { "id": "b", "success": true }
3105                    ]
3106                }
3107            })))
3108            .mount(&server)
3109            .await;
3110
3111        let sdk = make_sdk(format!("{}/", server.uri()));
3112        let params = BulkRemoveTagRequest {
3113            ids: vec!["a".to_string(), "b".to_string()],
3114            tag_id: 42,
3115        };
3116        let resp = sdk.admin.bulk_remove_tag(&params).await.unwrap();
3117        assert_eq!(resp.data.expect("data").updated_count, 2);
3118    }
3119
3120    #[test]
3121    fn endpoint_token_debug_is_redacted() {
3122        let t = EndpointToken {
3123            id: "tok_1".to_string(),
3124            token: "super-secret".to_string(),
3125        };
3126        let dbg = format!("{t:?}");
3127        assert!(dbg.contains("tok_1"));
3128        assert!(!dbg.contains("super-secret"));
3129        assert!(dbg.contains("[redacted]"));
3130    }
3131
3132    #[test]
3133    fn endpoint_jwt_debug_redacts_public_key() {
3134        let j = EndpointJwt {
3135            id: "jwt_1".to_string(),
3136            public_key: "-----BEGIN PUBLIC KEY-----\nAAAA\n-----END PUBLIC KEY-----".to_string(),
3137            kid: "kid1".to_string(),
3138            name: "myjwt".to_string(),
3139        };
3140        let dbg = format!("{j:?}");
3141        assert!(dbg.contains("jwt_1"));
3142        assert!(dbg.contains("kid1"));
3143        assert!(!dbg.contains("BEGIN PUBLIC KEY"));
3144        assert!(dbg.contains("[redacted]"));
3145    }
3146
3147    #[tokio::test]
3148    async fn bulk_methods_reject_empty_ids() {
3149        // No MockServer: the guards must fail before any HTTP request fires.
3150        let sdk = make_sdk("http://127.0.0.1:1/".to_string());
3151
3152        let err = sdk
3153            .admin
3154            .bulk_update_endpoint_status(&BulkUpdateEndpointStatusRequest {
3155                ids: vec![],
3156                status: "paused".to_string(),
3157            })
3158            .await
3159            .unwrap_err();
3160        assert!(matches!(err, SdkError::Config(_)));
3161
3162        let err = sdk
3163            .admin
3164            .bulk_add_tag(&BulkAddTagRequest {
3165                ids: vec![],
3166                label: "x".to_string(),
3167            })
3168            .await
3169            .unwrap_err();
3170        assert!(matches!(err, SdkError::Config(_)));
3171
3172        let err = sdk
3173            .admin
3174            .bulk_remove_tag(&BulkRemoveTagRequest {
3175                ids: vec![],
3176                tag_id: 1,
3177            })
3178            .await
3179            .unwrap_err();
3180        assert!(matches!(err, SdkError::Config(_)));
3181    }
3182
3183    #[tokio::test]
3184    async fn list_tags_success() {
3185        let server = MockServer::start().await;
3186        Mock::given(method("GET"))
3187            .and(path("/endpoints/tags"))
3188            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3189                "data": {
3190                    "tags": [
3191                        { "id": 1, "label": "prod", "usage_count": 3 },
3192                        { "id": 2, "label": "staging", "usage_count": 0 }
3193                    ]
3194                },
3195                "error": null
3196            })))
3197            .mount(&server)
3198            .await;
3199
3200        let sdk = make_sdk(format!("{}/", server.uri()));
3201        let resp = sdk.admin.list_tags().await.unwrap();
3202        let data = resp.data.expect("data present");
3203        assert_eq!(data.tags.len(), 2);
3204        assert_eq!(data.tags[0].label, "prod");
3205        assert_eq!(data.tags[1].usage_count, 0);
3206    }
3207
3208    #[tokio::test]
3209    async fn list_tags_api_error() {
3210        let server = MockServer::start().await;
3211        Mock::given(method("GET"))
3212            .and(path("/endpoints/tags"))
3213            .respond_with(ResponseTemplate::new(500).set_body_string("oops"))
3214            .mount(&server)
3215            .await;
3216
3217        let sdk = make_sdk(format!("{}/", server.uri()));
3218        let err = sdk.admin.list_tags().await.unwrap_err();
3219        match err {
3220            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 500),
3221            other => panic!("expected Api, got {:?}", other),
3222        }
3223    }
3224
3225    #[tokio::test]
3226    async fn rename_tag_success() {
3227        let server = MockServer::start().await;
3228        Mock::given(method("PATCH"))
3229            .and(path("/endpoints/tags/7"))
3230            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3231                "data": { "id": 7, "label": "prod-v2", "usage_count": 3 },
3232                "error": null
3233            })))
3234            .mount(&server)
3235            .await;
3236
3237        let sdk = make_sdk(format!("{}/", server.uri()));
3238        let params = RenameTagRequest {
3239            label: "prod-v2".to_string(),
3240        };
3241        let resp = sdk.admin.rename_tag(7, &params).await.unwrap();
3242        let tag = resp.data.expect("tag present");
3243        assert_eq!(tag.id, 7);
3244        assert_eq!(tag.label, "prod-v2");
3245    }
3246
3247    #[tokio::test]
3248    async fn delete_account_tag_success() {
3249        let server = MockServer::start().await;
3250        Mock::given(method("DELETE"))
3251            .and(path("/endpoints/tags/7"))
3252            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3253                "data": { "success": true },
3254                "error": null
3255            })))
3256            .mount(&server)
3257            .await;
3258
3259        let sdk = make_sdk(format!("{}/", server.uri()));
3260        let resp = sdk.admin.delete_account_tag(7).await.unwrap();
3261        assert!(resp.data.expect("data").success);
3262    }
3263
3264    #[tokio::test]
3265    async fn delete_account_tag_still_in_use() {
3266        let server = MockServer::start().await;
3267        Mock::given(method("DELETE"))
3268            .and(path("/endpoints/tags/7"))
3269            .respond_with(ResponseTemplate::new(400).set_body_string("tag still in use"))
3270            .mount(&server)
3271            .await;
3272
3273        let sdk = make_sdk(format!("{}/", server.uri()));
3274        let err = sdk.admin.delete_account_tag(7).await.unwrap_err();
3275        match err {
3276            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 400),
3277            other => panic!("expected Api, got {:?}", other),
3278        }
3279    }
3280
3281    #[tokio::test]
3282    async fn get_usage_by_tag_success() {
3283        let server = MockServer::start().await;
3284        Mock::given(method("GET"))
3285            .and(path("/usage/rpc/by-tag"))
3286            .and(query_param("start_time", "1700000000"))
3287            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3288                "data": {
3289                    "tags": [
3290                        { "tag_id": 1, "label": "prod", "credits_used": 1234, "requests": 10 },
3291                        { "tag_id": null, "label": "untagged", "credits_used": 50, "requests": 2 }
3292                    ],
3293                    "start_time": 1700000000,
3294                    "end_time": 1700003600
3295                },
3296                "error": null
3297            })))
3298            .mount(&server)
3299            .await;
3300
3301        let sdk = make_sdk(format!("{}/", server.uri()));
3302        let params = GetUsageRequest {
3303            start_time: Some(1_700_000_000),
3304            ..Default::default()
3305        };
3306        let resp = sdk.admin.get_usage_by_tag(&params).await.unwrap();
3307        let data = resp.data.expect("data present");
3308        assert_eq!(data.tags.len(), 2);
3309        assert_eq!(data.tags[0].tag_id, Some(1));
3310        assert_eq!(data.tags[1].tag_id, None);
3311        assert_eq!(data.tags[1].label, "untagged");
3312    }
3313
3314    #[tokio::test]
3315    async fn get_endpoint_security_success() {
3316        let server = MockServer::start().await;
3317        Mock::given(method("GET"))
3318            .and(path("/endpoints/abc123/security"))
3319            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
3320                "data": {
3321                    "options": { "tokens": true, "ips": false },
3322                    "tokens": [{ "id": "tok_1", "token": "secret" }],
3323                    "jwts": [],
3324                    "referrers": [],
3325                    "domain_masks": [],
3326                    "ips": [],
3327                    "request_filters": []
3328                },
3329                "error": null
3330            })))
3331            .mount(&server)
3332            .await;
3333
3334        let sdk = make_sdk(format!("{}/", server.uri()));
3335        let resp = sdk.admin.get_endpoint_security("abc123").await.unwrap();
3336        let data = resp.data.expect("data present");
3337        let tokens = data.tokens.expect("tokens present");
3338        assert_eq!(tokens.len(), 1);
3339        assert_eq!(tokens[0].id, "tok_1");
3340    }
3341
3342    #[tokio::test]
3343    async fn get_endpoint_security_not_found() {
3344        let server = MockServer::start().await;
3345        Mock::given(method("GET"))
3346            .and(path("/endpoints/missing/security"))
3347            .respond_with(ResponseTemplate::new(404).set_body_string("not found"))
3348            .mount(&server)
3349            .await;
3350
3351        let sdk = make_sdk(format!("{}/", server.uri()));
3352        let err = sdk
3353            .admin
3354            .get_endpoint_security("missing")
3355            .await
3356            .unwrap_err();
3357        match err {
3358            SdkError::Api { status, .. } => assert_eq!(status.as_u16(), 404),
3359            other => panic!("expected Api, got {:?}", other),
3360        }
3361    }
3362
3363    #[test]
3364    fn negative_timeout_secs_returns_error() {
3365        use crate::{HttpConfig, SdkConfig, SdkFullConfig};
3366        let result = SdkConfig::new(&SdkFullConfig {
3367            api_key: "test-key".to_string(),
3368            http: Some(HttpConfig {
3369                timeout_secs: Some(-1),
3370                pool_max_idle_per_host: None,
3371            }),
3372            admin: None,
3373            streams: None,
3374            webhooks: None,
3375            kvstore: None,
3376        });
3377        assert!(matches!(result, Err(crate::errors::SdkError::Config(_))));
3378    }
3379}