Skip to main content

qcs_api_client_openapi/apis/
endpoints_api.rs

1// Copyright 2026 Rigetti Computing
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*
16 * Rigetti QCS API
17 *
18 * # Introduction  This is the documentation for the Rigetti QCS HTTP API.  You can find out more about Rigetti at [https://rigetti.com](https://rigetti.com), and also interact with QCS via the web at [https://qcs.rigetti.com](https://qcs.rigetti.com).  This API is documented in **OpenAPI format** and so is compatible with the dozens of language-specific client generators available [here](https://github.com/OpenAPITools/openapi-generator) and elsewhere on the web.  # Principles  This API follows REST design principles where appropriate, and otherwise an HTTP RPC paradigm. We adhere to the Google [API Improvement Proposals](https://google.aip.dev/general) where reasonable to provide a consistent, intuitive developer experience. HTTP response codes match their specifications, and error messages fit a common format.  # Authentication  All access to the QCS API requires OAuth2 authentication provided by Okta. You can request access [here](https://www.rigetti.com/get-quantum). Once you have a user account, you can download your access token from QCS [here](https://qcs.rigetti.com/auth/token).   That access token is valid for 24 hours after issuance. The value of `access_token` within the JSON file is the token used for authentication (don't use the entire JSON file).  Authenticate requests using the `Authorization` header and a `Bearer` prefix:  ``` curl --header \"Authorization: Bearer eyJraW...Iow\" ```  # Quantum Processor Access  Access to the quantum processors themselves is not yet provided directly by this HTTP API, but is instead performed over ZeroMQ/[rpcq](https://github.com/rigetti/rpcq). Until that changes, we suggest using [pyquil](https://github.com/rigetti/pyquil) to build and execute quantum programs via the Legacy API.  # Legacy API  Our legacy HTTP API remains accessible at https://forest-server.qcs.rigetti.com, and it shares a source of truth with this API's services. You can use either service with the same user account and means of authentication. We strongly recommend using the API documented here, as the legacy API is on the path to deprecation.
19 *
20 * The version of the OpenAPI document: 2020-07-31
21 * Contact: support@rigetti.com
22 * Generated by: https://openapi-generator.tech
23 */
24
25use super::{ContentType, Error, configuration};
26use crate::{apis::ResponseContent, models};
27use ::qcs_api_client_common::backoff::{
28    BackoffBuilder, ExponentialBackoff, duration_from_io_error, duration_from_reqwest_error,
29    duration_from_response,
30};
31#[cfg(feature = "tracing")]
32use qcs_api_client_common::configuration::tokens::TokenRefresher;
33use qcs_dependencies_client::reqwest::{self, StatusCode};
34use serde::{Deserialize, Serialize};
35
36#[cfg(feature = "clap")]
37#[allow(unused, reason = "not used in all templates, but required in some")]
38use ::{miette::IntoDiagnostic as _, qcs_api_client_common::clap_utils::JsonMaybeStdin};
39
40/// Serialize command-line arguments for [`create_endpoint`]
41#[cfg(feature = "clap")]
42#[derive(Debug, clap::Args)]
43pub struct CreateEndpointClapParams {
44    pub create_endpoint_parameters: JsonMaybeStdin<crate::models::CreateEndpointParameters>,
45}
46
47#[cfg(feature = "clap")]
48impl CreateEndpointClapParams {
49    pub async fn execute(
50        self,
51        configuration: &configuration::Configuration,
52    ) -> Result<models::Endpoint, miette::Error> {
53        let request = self.create_endpoint_parameters.into_inner().into_inner();
54
55        create_endpoint(configuration, request)
56            .await
57            .into_diagnostic()
58    }
59}
60
61/// Serialize command-line arguments for [`list_endpoints`]
62#[cfg(feature = "clap")]
63#[derive(Debug, clap::Args)]
64pub struct ListEndpointsClapParams {
65    /// Filtering logic specified using [rule-engine](https://zerosteiner.github.io/rule-engine/syntax.html) grammar
66    #[arg(long)]
67    pub filter: Option<String>,
68    #[arg(long)]
69    pub page_size: Option<i64>,
70    #[arg(long)]
71    pub page_token: Option<String>,
72}
73
74#[cfg(feature = "clap")]
75impl ListEndpointsClapParams {
76    pub async fn execute(
77        self,
78        configuration: &configuration::Configuration,
79    ) -> Result<models::ListEndpointsResponse, miette::Error> {
80        list_endpoints(
81            configuration,
82            self.filter.as_deref(),
83            self.page_size,
84            self.page_token.as_deref(),
85        )
86        .await
87        .into_diagnostic()
88    }
89}
90
91/// Serialize command-line arguments for [`restart_endpoint`]
92#[cfg(feature = "clap")]
93#[derive(Debug, clap::Args)]
94pub struct RestartEndpointClapParams {
95    #[arg(long)]
96    pub endpoint_id: String,
97    pub restart_endpoint_request: Option<JsonMaybeStdin<crate::models::RestartEndpointRequest>>,
98}
99
100#[cfg(feature = "clap")]
101impl RestartEndpointClapParams {
102    pub async fn execute(
103        self,
104        configuration: &configuration::Configuration,
105    ) -> Result<(), miette::Error> {
106        let request = self
107            .restart_endpoint_request
108            .map(|body| body.into_inner().into_inner());
109
110        restart_endpoint(configuration, self.endpoint_id.as_str(), request)
111            .await
112            .into_diagnostic()
113    }
114}
115
116/// struct for typed errors of method [`create_endpoint`]
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(untagged)]
119pub enum CreateEndpointError {
120    Status400(models::Error),
121    Status404(models::Error),
122    Status422(models::ValidationError),
123    UnknownValue(serde_json::Value),
124}
125
126/// struct for typed errors of method [`list_endpoints`]
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(untagged)]
129pub enum ListEndpointsError {
130    Status422(models::ValidationError),
131    UnknownValue(serde_json::Value),
132}
133
134/// struct for typed errors of method [`restart_endpoint`]
135#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(untagged)]
137pub enum RestartEndpointError {
138    Status403(models::Error),
139    Status422(models::ValidationError),
140    UnknownValue(serde_json::Value),
141}
142
143async fn create_endpoint_inner(
144    configuration: &configuration::Configuration,
145    backoff: &mut ExponentialBackoff,
146    create_endpoint_parameters: crate::models::CreateEndpointParameters,
147) -> Result<models::Endpoint, Error<CreateEndpointError>> {
148    let local_var_configuration = configuration;
149    // add a prefix to parameters to efficiently prevent name collisions
150    let p_body_create_endpoint_parameters = create_endpoint_parameters;
151
152    let local_var_client = &local_var_configuration.client;
153
154    let local_var_uri_str = format!(
155        "{}/v1/endpoints",
156        local_var_configuration.qcs_config.api_url()
157    );
158    let mut local_var_req_builder =
159        local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str());
160
161    #[cfg(feature = "tracing")]
162    {
163        // Ignore parsing errors if the URL is invalid for some reason.
164        // If it is invalid, it will turn up as an error later when actually making the request.
165        let local_var_do_tracing = local_var_uri_str
166            .parse::<::url::Url>()
167            .ok()
168            .is_none_or(|url| {
169                configuration
170                    .qcs_config
171                    .should_trace(&::urlpattern::UrlPatternMatchInput::Url(url))
172            });
173
174        if local_var_do_tracing {
175            ::tracing::debug!(
176                url=%local_var_uri_str,
177                method="POST",
178                "making create_endpoint request",
179            );
180        }
181    }
182
183    // Use the QCS Bearer token if a client OAuthSession is present,
184    // but do not require one when the security schema says it is optional.
185    {
186        use qcs_api_client_common::configuration::TokenError;
187
188        #[allow(
189            clippy::nonminimal_bool,
190            clippy::eq_op,
191            reason = "Logic must be done at runtime since it cannot be handled by the mustache template engine."
192        )]
193        let is_jwt_bearer_optional: bool = false || "JWTBearer" == "JWTBearerOptional";
194
195        let token = local_var_configuration
196            .qcs_config
197            .get_bearer_access_token()
198            .await;
199
200        if is_jwt_bearer_optional && matches!(token, Err(TokenError::NoCredentials)) {
201            // the client is configured without any OAuthSession, but this call does not require one.
202            #[cfg(feature = "tracing")]
203            tracing::debug!(
204                "No client credentials found, but this call does not require authentication."
205            );
206        } else {
207            local_var_req_builder = local_var_req_builder.bearer_auth(token?.secret());
208        }
209    }
210
211    local_var_req_builder = local_var_req_builder.json(&p_body_create_endpoint_parameters);
212
213    let local_var_req = local_var_req_builder.build()?;
214    let local_var_resp = local_var_client.execute(local_var_req).await?;
215
216    let local_var_status = local_var_resp.status();
217    let local_var_raw_content_type = local_var_resp
218        .headers()
219        .get("content-type")
220        .and_then(|v| v.to_str().ok())
221        .unwrap_or("application/octet-stream")
222        .to_string();
223    let local_var_content_type = super::ContentType::from(local_var_raw_content_type.as_str());
224
225    if !local_var_status.is_client_error() && !local_var_status.is_server_error() {
226        let local_var_content = local_var_resp.text().await?;
227        match local_var_content_type {
228            ContentType::Json => serde_path_to_error::deserialize(
229                &mut serde_json::Deserializer::from_str(&local_var_content),
230            )
231            .map_err(Error::from),
232            ContentType::Text => Err(Error::InvalidContentType {
233                content_type: local_var_raw_content_type,
234                return_type: "models::Endpoint",
235            }),
236            ContentType::Unsupported(unknown_type) => Err(Error::InvalidContentType {
237                content_type: unknown_type,
238                return_type: "models::Endpoint",
239            }),
240        }
241    } else {
242        let local_var_retry_delay =
243            duration_from_response(local_var_resp.status(), local_var_resp.headers(), backoff);
244        let local_var_content = local_var_resp.text().await?;
245        let local_var_entity: Option<CreateEndpointError> =
246            serde_json::from_str(&local_var_content).ok();
247        let local_var_error = ResponseContent {
248            status: local_var_status,
249            content: local_var_content,
250            entity: local_var_entity,
251            retry_delay: local_var_retry_delay,
252        };
253        Err(Error::ResponseError(local_var_error))
254    }
255}
256
257/// Create an endpoint associated with your user account.
258pub async fn create_endpoint(
259    configuration: &configuration::Configuration,
260    create_endpoint_parameters: crate::models::CreateEndpointParameters,
261) -> Result<models::Endpoint, Error<CreateEndpointError>> {
262    let mut backoff = configuration.backoff.build();
263    let mut refreshed_credentials = false;
264    let method = reqwest::Method::POST;
265    loop {
266        let result = create_endpoint_inner(
267            configuration,
268            &mut backoff,
269            create_endpoint_parameters.clone(),
270        )
271        .await;
272
273        match result {
274            Ok(result) => return Ok(result),
275            Err(Error::ResponseError(response)) => {
276                if !refreshed_credentials
277                    && matches!(
278                        response.status,
279                        StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED
280                    )
281                {
282                    // Attempt to refresh credentials
283                    match configuration.qcs_config.refresh().await {
284                        Ok(_) => {
285                            refreshed_credentials = true;
286                            continue;
287                        }
288                        Err(::qcs_api_client_common::configuration::TokenError::Write {
289                            error,
290                            oauth_session: _,
291                        }) => {
292                            // Token refresh succeeded but persistence failed
293                            // The token is already in memory and will be used for this request
294                            #[cfg(feature = "tracing")]
295                            tracing::warn!(
296                                "Token refresh succeeded but failed to persist: {}. Continuing with in-memory token.",
297                                error
298                            );
299                            refreshed_credentials = true;
300                            continue;
301                        }
302                        Err(e) => return Err(e.into()),
303                    }
304                } else if let Some(duration) = response.retry_delay {
305                    tokio::time::sleep(duration).await;
306                    continue;
307                }
308
309                return Err(Error::ResponseError(response));
310            }
311            Err(Error::Reqwest(error)) => {
312                if let Some(duration) = duration_from_reqwest_error(&method, &error, &mut backoff) {
313                    tokio::time::sleep(duration).await;
314                    continue;
315                }
316
317                return Err(Error::Reqwest(error));
318            }
319            Err(Error::Io(error)) => {
320                if let Some(duration) = duration_from_io_error(&method, &error, &mut backoff) {
321                    tokio::time::sleep(duration).await;
322                    continue;
323                }
324
325                return Err(Error::Io(error));
326            }
327            Err(error) => return Err(error),
328        }
329    }
330}
331async fn list_endpoints_inner(
332    configuration: &configuration::Configuration,
333    backoff: &mut ExponentialBackoff,
334    filter: Option<&str>,
335    page_size: Option<i64>,
336    page_token: Option<&str>,
337) -> Result<models::ListEndpointsResponse, Error<ListEndpointsError>> {
338    let local_var_configuration = configuration;
339    // add a prefix to parameters to efficiently prevent name collisions
340    let p_query_filter = filter;
341    let p_query_page_size = page_size;
342    let p_query_page_token = page_token;
343
344    let local_var_client = &local_var_configuration.client;
345
346    let local_var_uri_str = format!(
347        "{}/v1/endpoints",
348        local_var_configuration.qcs_config.api_url()
349    );
350    let mut local_var_req_builder =
351        local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str());
352
353    #[cfg(feature = "tracing")]
354    {
355        // Ignore parsing errors if the URL is invalid for some reason.
356        // If it is invalid, it will turn up as an error later when actually making the request.
357        let local_var_do_tracing = local_var_uri_str
358            .parse::<::url::Url>()
359            .ok()
360            .is_none_or(|url| {
361                configuration
362                    .qcs_config
363                    .should_trace(&::urlpattern::UrlPatternMatchInput::Url(url))
364            });
365
366        if local_var_do_tracing {
367            ::tracing::debug!(
368                url=%local_var_uri_str,
369                method="GET",
370                "making list_endpoints request",
371            );
372        }
373    }
374
375    if let Some(ref local_var_str) = p_query_filter {
376        local_var_req_builder =
377            local_var_req_builder.query(&[("filter", &local_var_str.to_string())]);
378    }
379    if let Some(ref local_var_str) = p_query_page_size {
380        local_var_req_builder =
381            local_var_req_builder.query(&[("pageSize", &local_var_str.to_string())]);
382    }
383    if let Some(ref local_var_str) = p_query_page_token {
384        local_var_req_builder =
385            local_var_req_builder.query(&[("pageToken", &local_var_str.to_string())]);
386    }
387
388    // Use the QCS Bearer token if a client OAuthSession is present,
389    // but do not require one when the security schema says it is optional.
390    {
391        use qcs_api_client_common::configuration::TokenError;
392
393        #[allow(
394            clippy::nonminimal_bool,
395            clippy::eq_op,
396            reason = "Logic must be done at runtime since it cannot be handled by the mustache template engine."
397        )]
398        let is_jwt_bearer_optional: bool = false || "JWTBearer" == "JWTBearerOptional";
399
400        let token = local_var_configuration
401            .qcs_config
402            .get_bearer_access_token()
403            .await;
404
405        if is_jwt_bearer_optional && matches!(token, Err(TokenError::NoCredentials)) {
406            // the client is configured without any OAuthSession, but this call does not require one.
407            #[cfg(feature = "tracing")]
408            tracing::debug!(
409                "No client credentials found, but this call does not require authentication."
410            );
411        } else {
412            local_var_req_builder = local_var_req_builder.bearer_auth(token?.secret());
413        }
414    }
415
416    let local_var_req = local_var_req_builder.build()?;
417    let local_var_resp = local_var_client.execute(local_var_req).await?;
418
419    let local_var_status = local_var_resp.status();
420    let local_var_raw_content_type = local_var_resp
421        .headers()
422        .get("content-type")
423        .and_then(|v| v.to_str().ok())
424        .unwrap_or("application/octet-stream")
425        .to_string();
426    let local_var_content_type = super::ContentType::from(local_var_raw_content_type.as_str());
427
428    if !local_var_status.is_client_error() && !local_var_status.is_server_error() {
429        let local_var_content = local_var_resp.text().await?;
430        match local_var_content_type {
431            ContentType::Json => serde_path_to_error::deserialize(
432                &mut serde_json::Deserializer::from_str(&local_var_content),
433            )
434            .map_err(Error::from),
435            ContentType::Text => Err(Error::InvalidContentType {
436                content_type: local_var_raw_content_type,
437                return_type: "models::ListEndpointsResponse",
438            }),
439            ContentType::Unsupported(unknown_type) => Err(Error::InvalidContentType {
440                content_type: unknown_type,
441                return_type: "models::ListEndpointsResponse",
442            }),
443        }
444    } else {
445        let local_var_retry_delay =
446            duration_from_response(local_var_resp.status(), local_var_resp.headers(), backoff);
447        let local_var_content = local_var_resp.text().await?;
448        let local_var_entity: Option<ListEndpointsError> =
449            serde_json::from_str(&local_var_content).ok();
450        let local_var_error = ResponseContent {
451            status: local_var_status,
452            content: local_var_content,
453            entity: local_var_entity,
454            retry_delay: local_var_retry_delay,
455        };
456        Err(Error::ResponseError(local_var_error))
457    }
458}
459
460/// List all endpoints, optionally filtering by attribute.
461pub async fn list_endpoints(
462    configuration: &configuration::Configuration,
463    filter: Option<&str>,
464    page_size: Option<i64>,
465    page_token: Option<&str>,
466) -> Result<models::ListEndpointsResponse, Error<ListEndpointsError>> {
467    let mut backoff = configuration.backoff.build();
468    let mut refreshed_credentials = false;
469    let method = reqwest::Method::GET;
470    loop {
471        let result = list_endpoints_inner(
472            configuration,
473            &mut backoff,
474            filter.clone(),
475            page_size.clone(),
476            page_token.clone(),
477        )
478        .await;
479
480        match result {
481            Ok(result) => return Ok(result),
482            Err(Error::ResponseError(response)) => {
483                if !refreshed_credentials
484                    && matches!(
485                        response.status,
486                        StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED
487                    )
488                {
489                    // Attempt to refresh credentials
490                    match configuration.qcs_config.refresh().await {
491                        Ok(_) => {
492                            refreshed_credentials = true;
493                            continue;
494                        }
495                        Err(::qcs_api_client_common::configuration::TokenError::Write {
496                            error,
497                            oauth_session: _,
498                        }) => {
499                            // Token refresh succeeded but persistence failed
500                            // The token is already in memory and will be used for this request
501                            #[cfg(feature = "tracing")]
502                            tracing::warn!(
503                                "Token refresh succeeded but failed to persist: {}. Continuing with in-memory token.",
504                                error
505                            );
506                            refreshed_credentials = true;
507                            continue;
508                        }
509                        Err(e) => return Err(e.into()),
510                    }
511                } else if let Some(duration) = response.retry_delay {
512                    tokio::time::sleep(duration).await;
513                    continue;
514                }
515
516                return Err(Error::ResponseError(response));
517            }
518            Err(Error::Reqwest(error)) => {
519                if let Some(duration) = duration_from_reqwest_error(&method, &error, &mut backoff) {
520                    tokio::time::sleep(duration).await;
521                    continue;
522                }
523
524                return Err(Error::Reqwest(error));
525            }
526            Err(Error::Io(error)) => {
527                if let Some(duration) = duration_from_io_error(&method, &error, &mut backoff) {
528                    tokio::time::sleep(duration).await;
529                    continue;
530                }
531
532                return Err(Error::Io(error));
533            }
534            Err(error) => return Err(error),
535        }
536    }
537}
538async fn restart_endpoint_inner(
539    configuration: &configuration::Configuration,
540    backoff: &mut ExponentialBackoff,
541    endpoint_id: &str,
542    restart_endpoint_request: Option<crate::models::RestartEndpointRequest>,
543) -> Result<(), Error<RestartEndpointError>> {
544    let local_var_configuration = configuration;
545    // add a prefix to parameters to efficiently prevent name collisions
546    let p_path_endpoint_id = endpoint_id;
547    let p_body_restart_endpoint_request = restart_endpoint_request;
548
549    let local_var_client = &local_var_configuration.client;
550
551    let local_var_uri_str = format!(
552        "{}/v1/endpoints/{endpointId}:restart",
553        local_var_configuration.qcs_config.api_url(),
554        endpointId = crate::apis::urlencode(p_path_endpoint_id)
555    );
556    let mut local_var_req_builder =
557        local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str());
558
559    #[cfg(feature = "tracing")]
560    {
561        // Ignore parsing errors if the URL is invalid for some reason.
562        // If it is invalid, it will turn up as an error later when actually making the request.
563        let local_var_do_tracing = local_var_uri_str
564            .parse::<::url::Url>()
565            .ok()
566            .is_none_or(|url| {
567                configuration
568                    .qcs_config
569                    .should_trace(&::urlpattern::UrlPatternMatchInput::Url(url))
570            });
571
572        if local_var_do_tracing {
573            ::tracing::debug!(
574                url=%local_var_uri_str,
575                method="POST",
576                "making restart_endpoint request",
577            );
578        }
579    }
580
581    // Use the QCS Bearer token if a client OAuthSession is present,
582    // but do not require one when the security schema says it is optional.
583    {
584        use qcs_api_client_common::configuration::TokenError;
585
586        #[allow(
587            clippy::nonminimal_bool,
588            clippy::eq_op,
589            reason = "Logic must be done at runtime since it cannot be handled by the mustache template engine."
590        )]
591        let is_jwt_bearer_optional: bool = false || "JWTBearer" == "JWTBearerOptional";
592
593        let token = local_var_configuration
594            .qcs_config
595            .get_bearer_access_token()
596            .await;
597
598        if is_jwt_bearer_optional && matches!(token, Err(TokenError::NoCredentials)) {
599            // the client is configured without any OAuthSession, but this call does not require one.
600            #[cfg(feature = "tracing")]
601            tracing::debug!(
602                "No client credentials found, but this call does not require authentication."
603            );
604        } else {
605            local_var_req_builder = local_var_req_builder.bearer_auth(token?.secret());
606        }
607    }
608
609    local_var_req_builder = local_var_req_builder.json(&p_body_restart_endpoint_request);
610
611    let local_var_req = local_var_req_builder.build()?;
612    let local_var_resp = local_var_client.execute(local_var_req).await?;
613
614    let local_var_status = local_var_resp.status();
615
616    if !local_var_status.is_client_error() && !local_var_status.is_server_error() {
617        Ok(())
618    } else {
619        let local_var_retry_delay =
620            duration_from_response(local_var_resp.status(), local_var_resp.headers(), backoff);
621        let local_var_content = local_var_resp.text().await?;
622        let local_var_entity: Option<RestartEndpointError> =
623            serde_json::from_str(&local_var_content).ok();
624        let local_var_error = ResponseContent {
625            status: local_var_status,
626            content: local_var_content,
627            entity: local_var_entity,
628            retry_delay: local_var_retry_delay,
629        };
630        Err(Error::ResponseError(local_var_error))
631    }
632}
633
634/// Restart an entire endpoint or a single component within an endpoint.
635pub async fn restart_endpoint(
636    configuration: &configuration::Configuration,
637    endpoint_id: &str,
638    restart_endpoint_request: Option<crate::models::RestartEndpointRequest>,
639) -> Result<(), Error<RestartEndpointError>> {
640    let mut backoff = configuration.backoff.build();
641    let mut refreshed_credentials = false;
642    let method = reqwest::Method::POST;
643    loop {
644        let result = restart_endpoint_inner(
645            configuration,
646            &mut backoff,
647            endpoint_id.clone(),
648            restart_endpoint_request.clone(),
649        )
650        .await;
651
652        match result {
653            Ok(result) => return Ok(result),
654            Err(Error::ResponseError(response)) => {
655                if !refreshed_credentials
656                    && matches!(
657                        response.status,
658                        StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED
659                    )
660                {
661                    // Attempt to refresh credentials
662                    match configuration.qcs_config.refresh().await {
663                        Ok(_) => {
664                            refreshed_credentials = true;
665                            continue;
666                        }
667                        Err(::qcs_api_client_common::configuration::TokenError::Write {
668                            error,
669                            oauth_session: _,
670                        }) => {
671                            // Token refresh succeeded but persistence failed
672                            // The token is already in memory and will be used for this request
673                            #[cfg(feature = "tracing")]
674                            tracing::warn!(
675                                "Token refresh succeeded but failed to persist: {}. Continuing with in-memory token.",
676                                error
677                            );
678                            refreshed_credentials = true;
679                            continue;
680                        }
681                        Err(e) => return Err(e.into()),
682                    }
683                } else if let Some(duration) = response.retry_delay {
684                    tokio::time::sleep(duration).await;
685                    continue;
686                }
687
688                return Err(Error::ResponseError(response));
689            }
690            Err(Error::Reqwest(error)) => {
691                if let Some(duration) = duration_from_reqwest_error(&method, &error, &mut backoff) {
692                    tokio::time::sleep(duration).await;
693                    continue;
694                }
695
696                return Err(Error::Reqwest(error));
697            }
698            Err(Error::Io(error)) => {
699                if let Some(duration) = duration_from_io_error(&method, &error, &mut backoff) {
700                    tokio::time::sleep(duration).await;
701                    continue;
702                }
703
704                return Err(Error::Io(error));
705            }
706            Err(error) => return Err(error),
707        }
708    }
709}