<!-- Generated from the docs page by the site build (site/integrations/md-twins.mjs); never edit by hand. -->

# Quickstart

OpenQASM 3 in, canonical counts out. The complete happy path; every field is in the [API reference](https://pauli.xyz/reference), the raw spec at [`/openapi.json`](https://pauli.xyz/openapi.json).

## 0 · Get a key

[Create a workspace](https://pauli.xyz/signup), get an admin key; every client below reads it from the environment:

```
export PAULI_API_KEY=pauli_sk_…   # shown once at creation; rotate any time
```

## 1 · Qiskit

Every registry device is a Qiskit `BackendV2`: pick one, run, read counts in Qiskit's own key order. Runs stay pinned to the backend you chose, never a silent fallback:

```
pip install qiskit-pauli   # Python ≥ 3.10
```

```
from qiskit import QuantumCircuit
from qiskit_pauli import PauliProvider

qc = QuantumCircuit(3, 3)                 # GHZ
qc.h(0); qc.cx(0, 1); qc.cx(0, 2)
qc.measure(range(3), range(3))

provider = PauliProvider()                # reads PAULI_API_KEY
backend = provider.least_busy(simulator=True)  # drop simulator=True for hardware
counts = backend.run(qc, shots=1000).result().get_counts()
print(counts)                             # {'000': 489, '111': 511}
```

The rest of this page runs on `provider.client`, the full typed client underneath every backend: money as exact `Decimal` (float raises), an idempotency key on every submit, exceptions keyed on the stable codes below.

## 2 · Estimate the fleet (free)

One call prices *your* circuit on every eligible device, with predicted queue, fidelity, and reasoned exclusions:

```
from qiskit import qasm3

client = provider.client                  # the full pauli-sdk client
fleet = client.jobs.estimate(qasm3.dumps(qc), shots=1000)
for e in fleet.estimates:                 # cheapest first
    print(e.device, e.amount, e.predicted_queue_seconds, e.fidelity_score)
for x in fleet.excluded:                  # reasoned exclusions, complete
    print(x.device, x.reasons[0].code)
```

```
ionq.simulator 0.000000 1.0 1.0
ionq.qpu.forte-enterprise-1 2.700000 6871.0 0.953
```

## 3 · Submit with a policy

Pick `cheapest`, `fastest`, `highest_fidelity`, or `balanced`; pin a device, or constrain by provider, price, queue, or region. The job returns at once with the routing decision and exact cost; every submit carries an idempotency key (auto-generated unless you pass your own), so a retry can never double-submit:

```
job = client.jobs.submit(qasm3.dumps(qc), shots=1000,
                         policy="highest_fidelity", only=["ionq"],
                         allow_simulator_fallback=True, max_spend_usd="10.00",
                         idempotency_key="exp-042-run-7",
                         webhook_url="https://example.com/hooks/pauli")
print(job.routing_decision.selected_device, job.cost_estimate.amount)
```

Pre-execution failures fall back to the next-best device unless `allow_fallback=False`; every hop is recorded, and you pay only for the device that ran.

## 4 · Poll, or take the webhook

```
job.wait()   # jittered backoff to a terminal state; timeout=… to bound it
# created → validating → estimating → queued → transpiling → submitted → running → completed
```

With a `webhook_url` (https only), every state transition POSTs once, signed with the submitting key’s webhook secret (shown at key creation); dedupe on `X-Pauli-Event-Id`:

```
import hashlib, hmac, time

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    pairs = [p.split("=", 1) for p in header.split(",")]  # header: X-Pauli-Signature
    t = next(v for k, v in pairs if k == "t")
    if abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, v) for k, v in pairs if k == "v1")
```

The SDK ships this recipe as `pauli_sdk.webhooks.unwrap(body, headers, secret=…)`.

## 5 · Results: one bit order

```
{
  "counts": {"000": 489, "111": 511},
  "bit_ordering": "cbit0_left",
  "register_map": {"c": ["q[0]", "q[1]", "q[2]"]},
  "metadata": {"provider_job_id": "…", "calibration_snapshot_id": "…"},
  "insights": {"shape": "cat_pair", "confidence": "high", "headline": "GHZ-like cat state: …"},
  "billing": {"billed_usd": "2.470000"}
}
```

**The counts convention**: the leftmost character of every key is classical bit 0 of the first-declared register; registers flatten in declaration order. Every provider normalizes to it; analysis code never branches.

**Insights**: an advisory classification of the histogram's shape (cat pair, uniform, periodic comb, Porter-Thomas speckle, …) with a plain-language headline and shot-aware confidence, derived from the counts alone. A reading aid, never a scoring surface.

## 6 · Your own credentials (BYOK)

Register credentials (/console/credentials or `PUT /v1/credentials/{provider}`) and jobs on that provider run under your account: the provider bills you directly; your wallet pays only the platform fee ([pricing](https://pauli.xyz/pricing)). Estimates, status, and results show `byok`; fallback never crosses billing modes.

## 7 · Agents (MCP)

Model Context Protocol at `https://pauli.xyz/mcp` (Streamable HTTP), same bearer key. The tools mirror this page's flow.

```
claude mcp add --transport http pauli https://pauli.xyz/mcp \
  --header "Authorization: Bearer $PAULI_API_KEY"
```

## 8 · Paying from an agent

A paid submit past your balance returns `402` `billing.insufficient_credits`; its `detail` is a payment challenge naming the shortfall, the funding endpoint, and how to confirm crediting. Fund it (top-ups return a hosted `checkout_url` for a human to complete), then resubmit with the same `Idempotency-Key`. Auto top-up (`PATCH /v1/billing/settings`) refills from a saved card before jobs ever see the 402. MCP tool results carry the same challenge verbatim.

The other 402s, `billing.monthly_cap_exceeded` and `billing.key_cap_exceeded`, are caps you set; they carry no challenge. Raise the cap if the spend is intended.

---

## Errors

Every error is `{"error": {"code", "message", "detail?", "request_id"}}` with a stable code; the registry is [`/public/errors`](https://pauli.xyz/public/errors). Quote `request_id` to support. The ones you’ll meet first:

| Code | Meaning |
| --- | --- |
| program.invalid | OpenQASM 3 didn’t parse; detail carries line and column. |
| routing.no_feasible_device | Nothing can run this program under your constraints; detail lists per-device reasons. |
| billing.insufficient_credits | 402: fund the shortfall and resubmit (section 8). |
