A Python client library for the IonQ Cloud Platform API, providing full access to IonQ's quantum computing services. Supports both synchronous and asynchronous usage, with typed models for all request and response objects.
Auto-generated from the IonQ OpenAPI specification using openapi-python-client.
pip install ionq-coreRequires Python 3.12+.
from ionq_core import IonQClient
from ionq_core.api.backends import get_backends
from ionq_core.api.default import create_job
from ionq_core.models.circuit_job_creation_payload import CircuitJobCreationPayload
# Uses IONQ_API_KEY env var by default
client = IonQClient()
# List available backends
backends = get_backends.sync(client=client)
for backend in backends:
print(f"{backend.backend}: {backend.status} ({backend.qubits} qubits)")
# Submit a quantum circuit
job = create_job.sync(
client=client,
body=CircuitJobCreationPayload.from_dict({
"type": "ionq.circuit.v1",
"backend": "simulator",
"shots": 1000,
"input": {
"gateset": "qis",
"circuit": [
{"gate": "h", "targets": [0]},
{"gate": "cnot", "targets": [0], "controls": [1]},
],
},
}),
)
print(f"Job submitted: {job.id} (status: {job.status})")IonQ uses API key authentication. Get your key from the IonQ Cloud Console.
from ionq_core import IonQClient
# Option 1: Set the IONQ_API_KEY environment variable (recommended)
client = IonQClient()
# Option 2: Pass the key directly
client = IonQClient(api_key="your-api-key")
# Option 3: Use AuthenticatedClient for full control
from ionq_core import AuthenticatedClient
client = AuthenticatedClient(
base_url="https://api.ionq.co/v0.4",
token="your-api-key",
prefix="apiKey",
auth_header_name="Authorization",
)Some endpoints (e.g., listing backends) do not require authentication. Use Client for those:
from ionq_core import Client
client = Client(base_url="https://api.ionq.co/v0.4")Every endpoint has both sync and async variants:
import asyncio
from ionq_core import IonQClient
from ionq_core.api.backends import get_backends
async def main():
client = IonQClient()
backends = await get_backends.asyncio(client=client)
for backend in backends:
print(f"{backend.backend}: {backend.status}")
asyncio.run(main())Both client types support context managers for proper connection cleanup:
async with IonQClient() as client:
backends = await get_backends.asyncio(client=client)The client raises typed exceptions for all HTTP error responses:
from ionq_core import IonQClient, RateLimitError, AuthenticationError, ServerError
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")
except ServerError as e:
print(f"Server error: {e.status_code}")| Status code | Exception |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 429 | RateLimitError |
| 5xx | ServerError |
All exceptions inherit from APIError, which inherits from IonQError. Connection failures raise APIConnectionError; timeouts raise APITimeoutError.
The client automatically retries transient errors (429, 500, 502, 503, 520-529) and connection/timeout failures with exponential backoff. Default: 2 retries.
client = IonQClient(max_retries=5) # more retries
client = IonQClient(max_retries=0) # disable retriesRetry-After headers on 429 responses are respected.
Endpoints that return paginated results have auto-pagination helpers:
from ionq_core import IonQClient, iter_jobs
client = IonQClient()
for job in iter_jobs(client, status="completed"):
print(job.id)Async:
from ionq_core import aiter_jobs
async for job in aiter_jobs(client):
print(job.id)Also available: iter_session_jobs / aiter_session_jobs.
from ionq_core import IonQClient, wait_for_job
client = IonQClient()
job = create_job.sync(client=client, body=payload)
completed_job = wait_for_job(client, job.id, timeout=300)
print(completed_job.status) # "completed"Polls with exponential backoff (1s initial, 30s max). Raises JobTimeoutError on timeout, JobFailedError if the job fails. Async: async_wait_for_job.
Pure-Python unitary matrices for IonQ's native trapped-ion gates, useful for simulation and verification:
from ionq_core import gpi_matrix, gpi2_matrix, ms_matrix, zz_matrix
# Phase parameters (phi) are in turns (fractions of 2*pi)
# Interaction parameters (angle) are in units of pi
gpi_matrix(0) # 2x2 Pauli X
gpi2_matrix(0.25) # 2x2 pi/2 rotation
ms_matrix(0, 0) # 4x4 maximally-entangling MS gate (angle defaults to 0.25)
zz_matrix(0.1) # 4x4 ZZ interactionMatrices are returned as nested tuples of complex numbers (no numpy dependency).
SessionManager provides a context manager for IonQ priority sessions:
from ionq_core import IonQClient, SessionManager
client = IonQClient()
with SessionManager(client, "qpu.aria-1", max_jobs=10, max_time=60) as session:
# submit jobs using session.session_id
print(session.session_id)
print(session.status())
# Reconnect to an existing session
session = SessionManager.from_id(client, "existing-session-id")| Function | Module | Auth |
|---|---|---|
| List backends | ionq_core.api.backends.get_backends |
No |
| Get a backend | ionq_core.api.backends.get_backend |
No |
| Function | Module | Auth |
|---|---|---|
| List characterizations | ionq_core.api.characterizations.get_characterizations_for_backend |
No |
| Get a characterization | ionq_core.api.characterizations.get_characterization |
Yes |
| Function | Module | Auth |
|---|---|---|
| Create a job | ionq_core.api.default.create_job |
Yes |
| List jobs | ionq_core.api.default.get_jobs |
Yes |
| Get a job | ionq_core.api.default.get_job |
Yes |
| Delete a job | ionq_core.api.default.delete_job |
Yes |
| Delete jobs (bulk) | ionq_core.api.default.delete_jobs |
Yes |
| Cancel a job | ionq_core.api.default.cancel_job |
Yes |
| Cancel jobs (bulk) | ionq_core.api.default.cancel_jobs |
Yes |
| Get job cost | ionq_core.api.default.get_job_cost |
Yes |
| Get compiled circuit | ionq_core.api.default.get_compiled_file |
Yes |
| Estimate job cost | ionq_core.api.default.estimate_job_cost |
Yes |
| Function | Module | Auth |
|---|---|---|
| Create a session | ionq_core.api.default.create_session |
Yes |
| List sessions | ionq_core.api.default.get_sessions |
Yes |
| Get a session | ionq_core.api.default.get_session |
Yes |
| End a session | ionq_core.api.default.end_session |
Yes |
| List session jobs | ionq_core.api.default.get_session_jobs |
Yes |
| Function | Module | Auth |
|---|---|---|
| Who am I | ionq_core.api.whoami.get_whoami |
Yes |
| Get usage | ionq_core.api.usage.get_usages |
Yes |
Each endpoint module provides four functions:
sync- synchronous call, returns the parsed responseasyncio- async call, returns the parsed responsesync_detailed- synchronous call, returnsResponse[T]with status code, headers, and parsed bodyasyncio_detailed- async call, returnsResponse[T]with status code, headers, and parsed body
All request and response bodies are typed as attrs classes with from_dict() and to_dict() methods:
from ionq_core.models.backend import Backend
# Deserialize from API response dict
backend = Backend.from_dict({"backend": "qpu.aria-1", "status": "available", ...})
# Access typed attributes
print(backend.backend) # "qpu.aria-1"
print(backend.qubits) # 25
# Serialize back to dict
data = backend.to_dict()Optional fields use the Unset sentinel (not None) to distinguish between "not provided" and "explicitly null":
from ionq_core.types import UNSET, Unset
if not isinstance(backend.characterization_id, Unset):
print(f"Characterization: {backend.characterization_id}")import httpx
from ionq_core import IonQClient
client = IonQClient(timeout=httpx.Timeout(30.0, connect=10.0))client = IonQClient().with_headers({"X-Custom-Header": "value"})For full control over the HTTP layer, inject your own httpx.Client:
import httpx
from ionq_core import IonQClient
custom_httpx = httpx.Client(
base_url="https://api.ionq.co/v0.4",
headers={"Authorization": "apiKey your-key"},
timeout=60.0,
)
client = IonQClient(api_key="your-key")
client.set_httpx_client(custom_httpx)Use the _detailed variants to get status codes and headers:
from ionq_core.api.whoami import get_whoami
response = get_whoami.sync_detailed(client=client)
print(response.status_code) # HTTPStatus.OK
print(response.headers) # dict of response headers
print(response.parsed) # Whoami object
print(response.content) # raw bytesThe client is generated from the vendored OpenAPI spec. To regenerate after API changes:
# Fetch the latest spec
curl -s https://api.ionq.co/v0.4/api-docs -o openapi.json
# Apply overlay if present (patches spec issues that the generator can't handle)
if [ -f openapi-overlay.yaml ]; then
uvx oas-patch==0.6.0 overlay openapi.json openapi-overlay.yaml -o /tmp/patched-spec.json
else
cp openapi.json /tmp/patched-spec.json
fi
# Regenerate (custom template preserves hand-written __init__.py exports)
uvx openapi-python-client==0.28.3 generate \
--path /tmp/patched-spec.json \
--meta none \
--config openapi-python-client-config.yaml \
--custom-template-path custom-templates \
--output-path ionq_core \
--overwriteIf the upstream spec contains patterns that the code generator cannot handle, fixes are applied via an OpenAPI Overlay file (openapi-overlay.yaml) using oas-patch. The overlay is declarative, version-controlled, and applied automatically during generation. The vendored openapi.json is always the unmodified upstream spec. When the upstream issue is resolved, delete the corresponding action from the overlay (or the entire file) and the pipeline continues to work without it.
uv sync # Install dependencies
uv run pytest # Run tests
uv run ruff check # Lint
uv run ruff format --check # Check formatting
uv run ty check ionq_core/ # Type checkFor a new build to be accepted at PyPI, the version number in pyproject.toml must be incremented. Publishing is handled automatically via trusted publishing on tagged releases:
git tag v0.1.0
git push origin v0.1.0Apache-2.0. See LICENSE for details.