Skip to main content

quicknode_sdk/admin/
mod.rs

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