ionq_core.exceptions

Structured exceptions for the IonQ API client.

All exceptions inherit from IonQError. The hierarchy is:

IonQError
+-- APIConnectionError          # network / DNS failures
|   +-- APITimeoutError         # request timed out
+-- APIError                    # HTTP 4xx / 5xx responses
|   +-- BadRequestError         # 400
|   +-- AuthenticationError     # 401
|   +-- PermissionDeniedError   # 403
|   +-- NotFoundError           # 404
|   +-- RateLimitError          # 429 (includes retry_after)
|   +-- ServerError             # 5xx
Example:
from ionq_core import IonQClient, RateLimitError, AuthenticationError

client = IonQClient()
try:
    job = create_job.sync(client=client, body=payload)
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
  1# SPDX-FileCopyrightText: 2026 IonQ, Inc.
  2# SPDX-License-Identifier: Apache-2.0
  3
  4"""Structured exceptions for the IonQ API client.
  5
  6All exceptions inherit from `IonQError`. The hierarchy is:
  7
  8```
  9IonQError
 10+-- APIConnectionError          # network / DNS failures
 11|   +-- APITimeoutError         # request timed out
 12+-- APIError                    # HTTP 4xx / 5xx responses
 13|   +-- BadRequestError         # 400
 14|   +-- AuthenticationError     # 401
 15|   +-- PermissionDeniedError   # 403
 16|   +-- NotFoundError           # 404
 17|   +-- RateLimitError          # 429 (includes retry_after)
 18|   +-- ServerError             # 5xx
 19```
 20
 21Example:
 22    ```python
 23    from ionq_core import IonQClient, RateLimitError, AuthenticationError
 24
 25    client = IonQClient()
 26    try:
 27        job = create_job.sync(client=client, body=payload)
 28    except AuthenticationError:
 29        print("Invalid API key")
 30    except RateLimitError as e:
 31        print(f"Rate limited, retry after {e.retry_after}s")
 32    ```
 33"""
 34
 35__all__ = [
 36    "APIConnectionError",
 37    "APIError",
 38    "APITimeoutError",
 39    "AuthenticationError",
 40    "BadRequestError",
 41    "IonQError",
 42    "NotFoundError",
 43    "PermissionDeniedError",
 44    "RateLimitError",
 45    "ServerError",
 46]
 47
 48
 49class IonQError(Exception):
 50    """Base exception for all IonQ errors.
 51
 52    Catch this to handle any error raised by the library, including connection
 53    failures, API errors, polling timeouts, and job failures.
 54    """
 55
 56
 57class APIConnectionError(IonQError):
 58    """Raised when a connection to the IonQ API cannot be established.
 59
 60    This covers DNS resolution failures, refused connections, and other
 61    network-level errors. The original ``httpx`` exception is chained
 62    via ``__cause__``.
 63    """
 64
 65
 66class APITimeoutError(APIConnectionError):
 67    """Raised when a request to the IonQ API times out.
 68
 69    Inherits from `APIConnectionError` so that catching connection errors
 70    also catches timeouts.
 71    """
 72
 73
 74class APIError(IonQError):
 75    """Raised when the IonQ API returns an HTTP error response (4xx or 5xx).
 76
 77    Attributes:
 78        status_code: The HTTP status code.
 79        body: The parsed response body (``dict`` if JSON, ``str`` otherwise,
 80            or ``None`` if the body could not be read).
 81        message: A human-readable error message extracted from the response,
 82            or a default ``"HTTP <status>"`` string.
 83        request_id: The ``x-request-id`` header from the response, useful for
 84            contacting IonQ support about a specific request.
 85    """
 86
 87    def __init__(
 88        self,
 89        status_code: int,
 90        body: dict | str | None = None,
 91        message: str | None = None,
 92        *,
 93        request_id: str | None = None,
 94    ) -> None:
 95        self.status_code = status_code
 96        self.body = body
 97        self.request_id = request_id
 98        self.message = message or f"HTTP {status_code}"
 99        super().__init__(self.message)
100
101
102class AuthenticationError(APIError):
103    """Raised on ``401 Unauthorized``.
104
105    Typically means the API key is missing, invalid, or revoked.
106    """
107
108
109class PermissionDeniedError(APIError):
110    """Raised on ``403 Forbidden``.
111
112    The API key is valid but lacks permission for the requested operation.
113    """
114
115
116class NotFoundError(APIError):
117    """Raised on ``404 Not Found``.
118
119    The requested resource (job, session, backend, etc.) does not exist.
120    """
121
122
123class BadRequestError(APIError):
124    """Raised on ``400 Bad Request``.
125
126    The request body or query parameters failed server-side validation.
127    Inspect ``body`` for details.
128    """
129
130
131class RateLimitError(APIError):
132    """Raised on ``429 Too Many Requests``.
133
134    The client has exceeded the API rate limit. The ``retry_after`` attribute
135    indicates how many seconds to wait before retrying, if the server provided
136    a ``Retry-After`` header.
137
138    Attributes:
139        retry_after: Seconds to wait before retrying, or ``None`` if the
140            server did not include a ``Retry-After`` header.
141    """
142
143    def __init__(
144        self,
145        status_code: int = 429,
146        body: dict | str | None = None,
147        message: str | None = None,
148        retry_after: float | None = None,
149        *,
150        request_id: str | None = None,
151    ) -> None:
152        super().__init__(status_code, body, message, request_id=request_id)
153        self.retry_after = retry_after
154
155
156class ServerError(APIError):
157    """Raised on ``5xx`` server errors.
158
159    These are typically transient and are automatically retried by the default
160    transport (see `IonQClient`).
161    """
162
163
164_STATUS_TO_EXCEPTION: dict[int, type[APIError]] = {
165    400: BadRequestError,
166    401: AuthenticationError,
167    403: PermissionDeniedError,
168    404: NotFoundError,
169    429: RateLimitError,
170}
171
172
173def raise_for_status(
174    status_code: int,
175    body: dict | str | None = None,
176    retry_after: float | None = None,
177    message: str | None = None,
178    *,
179    request_id: str | None = None,
180) -> None:
181    """Raise an appropriate `APIError` subclass for an HTTP error status.
182
183    Does nothing for status codes below 400. For 4xx codes, raises the
184    specific subclass (e.g. `AuthenticationError` for 401). For 5xx codes
185    or unrecognized 4xx codes, raises `ServerError` or `APIError` respectively.
186
187    Args:
188        status_code: The HTTP status code.
189        body: The parsed response body.
190        retry_after: Value from the ``Retry-After`` header, if present.
191        message: A human-readable error message.
192        request_id: The ``x-request-id`` response header.
193
194    Raises:
195        BadRequestError: On 400.
196        AuthenticationError: On 401.
197        PermissionDeniedError: On 403.
198        NotFoundError: On 404.
199        RateLimitError: On 429.
200        ServerError: On 5xx.
201        APIError: On other 4xx codes.
202    """
203    if status_code < 400:
204        return
205    exc_cls = _STATUS_TO_EXCEPTION.get(status_code, ServerError if status_code >= 500 else APIError)
206    if exc_cls is RateLimitError:
207        raise RateLimitError(status_code, body, message, retry_after, request_id=request_id)
208    raise exc_cls(status_code, body, message, request_id=request_id)
class APIConnectionError(IonQError):
58class APIConnectionError(IonQError):
59    """Raised when a connection to the IonQ API cannot be established.
60
61    This covers DNS resolution failures, refused connections, and other
62    network-level errors. The original ``httpx`` exception is chained
63    via ``__cause__``.
64    """

Raised when a connection to the IonQ API cannot be established.

This covers DNS resolution failures, refused connections, and other network-level errors. The original httpx exception is chained via __cause__.

class APIError(IonQError):
 75class APIError(IonQError):
 76    """Raised when the IonQ API returns an HTTP error response (4xx or 5xx).
 77
 78    Attributes:
 79        status_code: The HTTP status code.
 80        body: The parsed response body (``dict`` if JSON, ``str`` otherwise,
 81            or ``None`` if the body could not be read).
 82        message: A human-readable error message extracted from the response,
 83            or a default ``"HTTP <status>"`` string.
 84        request_id: The ``x-request-id`` header from the response, useful for
 85            contacting IonQ support about a specific request.
 86    """
 87
 88    def __init__(
 89        self,
 90        status_code: int,
 91        body: dict | str | None = None,
 92        message: str | None = None,
 93        *,
 94        request_id: str | None = None,
 95    ) -> None:
 96        self.status_code = status_code
 97        self.body = body
 98        self.request_id = request_id
 99        self.message = message or f"HTTP {status_code}"
100        super().__init__(self.message)

Raised when the IonQ API returns an HTTP error response (4xx or 5xx).

Attributes:
  • status_code: The HTTP status code.
  • body: The parsed response body (dict if JSON, str otherwise, or None if the body could not be read).
  • message: A human-readable error message extracted from the response, or a default "HTTP <status>" string.
  • request_id: The x-request-id header from the response, useful for contacting IonQ support about a specific request.
APIError( status_code: int, body: dict | str | None = None, message: str | None = None, *, request_id: str | None = None)
 88    def __init__(
 89        self,
 90        status_code: int,
 91        body: dict | str | None = None,
 92        message: str | None = None,
 93        *,
 94        request_id: str | None = None,
 95    ) -> None:
 96        self.status_code = status_code
 97        self.body = body
 98        self.request_id = request_id
 99        self.message = message or f"HTTP {status_code}"
100        super().__init__(self.message)
status_code
body
request_id
message
class APITimeoutError(APIConnectionError):
67class APITimeoutError(APIConnectionError):
68    """Raised when a request to the IonQ API times out.
69
70    Inherits from `APIConnectionError` so that catching connection errors
71    also catches timeouts.
72    """

Raised when a request to the IonQ API times out.

Inherits from APIConnectionError so that catching connection errors also catches timeouts.

class AuthenticationError(APIError):
103class AuthenticationError(APIError):
104    """Raised on ``401 Unauthorized``.
105
106    Typically means the API key is missing, invalid, or revoked.
107    """

Raised on 401 Unauthorized.

Typically means the API key is missing, invalid, or revoked.

class BadRequestError(APIError):
124class BadRequestError(APIError):
125    """Raised on ``400 Bad Request``.
126
127    The request body or query parameters failed server-side validation.
128    Inspect ``body`` for details.
129    """

Raised on 400 Bad Request.

The request body or query parameters failed server-side validation. Inspect body for details.

class IonQError(builtins.Exception):
50class IonQError(Exception):
51    """Base exception for all IonQ errors.
52
53    Catch this to handle any error raised by the library, including connection
54    failures, API errors, polling timeouts, and job failures.
55    """

Base exception for all IonQ errors.

Catch this to handle any error raised by the library, including connection failures, API errors, polling timeouts, and job failures.

class NotFoundError(APIError):
117class NotFoundError(APIError):
118    """Raised on ``404 Not Found``.
119
120    The requested resource (job, session, backend, etc.) does not exist.
121    """

Raised on 404 Not Found.

The requested resource (job, session, backend, etc.) does not exist.

class PermissionDeniedError(APIError):
110class PermissionDeniedError(APIError):
111    """Raised on ``403 Forbidden``.
112
113    The API key is valid but lacks permission for the requested operation.
114    """

Raised on 403 Forbidden.

The API key is valid but lacks permission for the requested operation.

class RateLimitError(APIError):
132class RateLimitError(APIError):
133    """Raised on ``429 Too Many Requests``.
134
135    The client has exceeded the API rate limit. The ``retry_after`` attribute
136    indicates how many seconds to wait before retrying, if the server provided
137    a ``Retry-After`` header.
138
139    Attributes:
140        retry_after: Seconds to wait before retrying, or ``None`` if the
141            server did not include a ``Retry-After`` header.
142    """
143
144    def __init__(
145        self,
146        status_code: int = 429,
147        body: dict | str | None = None,
148        message: str | None = None,
149        retry_after: float | None = None,
150        *,
151        request_id: str | None = None,
152    ) -> None:
153        super().__init__(status_code, body, message, request_id=request_id)
154        self.retry_after = retry_after

Raised on 429 Too Many Requests.

The client has exceeded the API rate limit. The retry_after attribute indicates how many seconds to wait before retrying, if the server provided a Retry-After header.

Attributes:
  • retry_after: Seconds to wait before retrying, or None if the server did not include a Retry-After header.
RateLimitError( status_code: int = 429, body: dict | str | None = None, message: str | None = None, retry_after: float | None = None, *, request_id: str | None = None)
144    def __init__(
145        self,
146        status_code: int = 429,
147        body: dict | str | None = None,
148        message: str | None = None,
149        retry_after: float | None = None,
150        *,
151        request_id: str | None = None,
152    ) -> None:
153        super().__init__(status_code, body, message, request_id=request_id)
154        self.retry_after = retry_after
retry_after
class ServerError(APIError):
157class ServerError(APIError):
158    """Raised on ``5xx`` server errors.
159
160    These are typically transient and are automatically retried by the default
161    transport (see `IonQClient`).
162    """

Raised on 5xx server errors.

These are typically transient and are automatically retried by the default transport (see IonQClient).