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