Metadata-Version: 2.4
Name: quantufai
Version: 0.2.0
Summary: QuantufAI developer SDK: quote quantum runs before any spend, read job status/results/receipts, export circuits, run the free sandbox, and run the paid GPU simulator through an explicit quote->confirm flow — it can price and read, and cannot spend without a signed quote hash.
Author: QuantufAI, Inc.
License: Proprietary
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: verify
Requires-Dist: cryptography>=41; extra == "verify"

# quantufai — the QuantufAI Python SDK

Quantum compute a program can **price and read — never spend**.

> ✅ **Published 2026-07-15:** this package is live on PyPI —
> `pip install quantufai` (https://pypi.org/project/quantufai/). The
> provisional filing cleared, so the publish gate is lifted. For local
> development you can still install from source: `pip install -e sdks/python`.

## What this SDK can do

| You want | Call | Key scope |
|---|---|---|
| "What would this run cost?" | `client.quote(circuit, shots=...)` | `quotes:read` |
| "Is it done yet?" | `client.job_status(job_id)` / `client.wait(job_id)` | `jobs:read` |
| Counts + ledger + error bars, verbatim | `client.job_result(job_id)` | `jobs:read` |
| The signed, tamper-evident receipt | `client.governed_receipt(job_id)` | `jobs:read` |
| Check a receipt you hold | `client.verify_receipt(receipt)` | `jobs:read` (platform tier) / none (offline tier) |
| The exact circuit that ran, as Qiskit/Cirq/Braket/pytket/QASM | `client.export_circuit(job_id, format=..., which=...)` | `results:export` |
| Download the result artifact | `client.export_result(job_id)` | `results:export` |
| Run on the **free local simulator** ($0) | `client.sandbox_simulate(circuit)` | `runs:simulate` (sandbox key) |
| Price the **paid GPU simulator** (nothing charged) | `client.simulate_quote(qasm, shots=...)` | `runs:execute` |
| Run the **paid GPU simulator** — only with the signed quote hash | `client.simulate_run(qasm, quote_confirmation_hash, ...)` | `runs:execute` |
| What can this key do? | `client.me()` | `account:read` |

## What this SDK cannot do — by design, not omission

**There is no dispatch-to-hardware method. There is no approval method. There
is no billing method.** Spending money on quantum **hardware** requires a human
approving a signed quote in the QuantufAI dashboard:

- The platform enforces quote-before-spend server-side; scoped API keys
  cannot approve a hardware spend.
- The SDK's free execution method (`sandbox_simulate`) pins every request to
  the free local simulator (`preferredProviders: ["classical"]`), so **even a
  key carrying the paid `runs:execute` scope cannot reach billable hardware
  through this SDK**. Zero eligible providers is a typed failure on the
  platform — never a silent reroute.

The **one** programmatic-spend lane the SDK exposes — the paid GPU simulator —
holds the same line: it is a deliberate **two-call** flow. `simulate_quote`
prices the run and returns a signed, single-use `quote_confirmation_hash`
(nothing is charged); `simulate_run` executes **only** when you pass that hash
back. If the hash is missing/empty, `simulate_run` returns a typed
`quote_confirmation_required` refusal **before issuing any HTTP request** — the
SDK fail-closes client-side, so **it cannot spend a cent without a hash you
obtained and passed on purpose**. There is deliberately no one-shot
convenience wrapper (see "Why no one-shot `simulate()`" below).

An AI agent driving this client can tell you exactly what an experiment would
cost, read every receipt, and run the GPU simulator only through an explicit,
priced, capped confirm step — it cannot buy anything by accident, and it can
never reach quantum hardware.

## REFUSED is a status, not an exception

Every deliberate platform refusal — missing scope, sandbox clamp, someone
else's job, quota exhausted, unprovable circuit translation — comes back as a
typed `Refusal` with the platform's `code`, `message`, and `details`
verbatim:

```python
result = client.job_result("not-my-job")
if isinstance(result, quantufai.Refusal):
    print(result.status)   # "REFUSED"
    print(result.code)     # e.g. "job_not_found", "insufficient_scope"
    print(result.details)  # e.g. {"requiredScope": "jobs:read", ...}
```

Exceptions are reserved for transport failures (`TransportError`) and
unexpected 5xx answers (`PlatformError`, body preserved).

## Quickstart (sandbox — no card, $0)

```python
import quantufai

client = quantufai.Client(
    api_key="qfai_sk_...",        # or $QUANTUFAI_API_KEY; sandbox tier: mint
                                  # with {"tier": "sandbox"} in the dashboard
    base_url="https://YOUR_HOST", # or $QUANTUFAI_BASE_URL
)

bell = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[2]; creg c[2];
h q[0]; cx q[0], q[1];
measure q -> c;"""

# 1. Price it first — nothing is reserved, charged, or dispatched by quoting.
quotes = client.quote(bell, shots=1000, qubits=2)
if isinstance(quotes, quantufai.Refusal):
    raise SystemExit(f"refused: {quotes.code} — {quotes.message}")
for q in quotes.quotes:
    print(f"{q.provider}: ~${q.estimated_cost_usd} ({q.reasons})")

# 2. Run on the FREE local simulator (the only execution this SDK performs).
run = client.sandbox_simulate(bell, shots=1000, qubits=2)
if isinstance(run, quantufai.Refusal):
    raise SystemExit(f"refused: {run.code} — {run.message}")
print(run.state, run.counts)   # real statevector physics, real counts, $0

# 3. Poll (async runs), then fetch the signed receipt.
result = client.wait(run.job_id)
receipt = client.governed_receipt(run.job_id)

# 4. Verify the receipt — offline Ed25519 tier and/or the platform's check.
report = client.verify_receipt(receipt)
print(report.verified, report.offline.public_signature)

# 5. Take the exact circuit home. `which` is required — the platform refuses
#    to guess between "original" and "as-dispatched".
code = client.export_circuit(run.job_id, format="qiskit", which="as-dispatched")
print(code.source)
```

## Running on the GPU simulator (paid)

The paid GPU simulator is the SDK's one programmatic-spend lane, and it is safe
by construction: **quote → inspect the capped price → confirm with the signed
hash.** The cost is quoted and capped *before* you confirm, and `simulate_run`
cannot run without the hash the quote handed you.

```python
import quantufai

client = quantufai.Client(api_key="qfai_sk_...", base_url="https://YOUR_HOST")

bell = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[2]; creg c[2];
h q[0]; cx q[0], q[1];
measure q -> c;"""

# 1. QUOTE — prices the run and mints the signed, single-use approval hash.
#    Nothing is reserved, charged, or dispatched by quoting.
quote = client.simulate_quote(bell, shots=1024)
if isinstance(quote, quantufai.Refusal):
    raise SystemExit(f"refused: {quote.code} — {quote.message}")

# 2. INSPECT — the price is disclosed and capped BEFORE you confirm. Decide
#    with your own budget in hand; the estimate is approximate (settlement is
#    at the service-metered GPU-seconds), and this is never quantum hardware.
print(f"~${quote.estimated_usd} for ~{quote.estimated_gpu_seconds} GPU-s "
      f"@ ${quote.per_gpu_second_usd}/GPU-s "
      f"(confidence={quote.cost_confidence}, is_hardware={quote.is_hardware})")
print(f"hash expires at {quote.expires_at}")
if quote.budget_policy:  # honest rider: a run may still need an admin approval
    print("budget policy note:", quote.budget_policy)

# ...only now, on YOUR decision, confirm the spend by passing the hash back.
MY_CAP_USD = 0.05
if (quote.estimated_usd or 0) > MY_CAP_USD:
    raise SystemExit("over my cap — not confirming")

# 3. RUN — executes ONLY with the signed hash. The exact `qasm` and `shots`
#    must match what was quoted (both are bound into the hash).
result = client.simulate_run(bell, quote.quote_confirmation_hash, shots=1024)
if isinstance(result, quantufai.Refusal):
    # e.g. quote_confirmation_rejected (409, tampered/expired/replayed — nothing
    # charged), card_required (402), gpu_sim_not_provisioned (503) ...
    raise SystemExit(f"refused: {result.code} — {result.message}")

print(result.counts)                 # the GPU service's OWN measurement, verbatim
print("charged $", result.settled_usd, "for", result.gpu_seconds, "GPU-s")
print(result.receipt["engine"]["isHardware"])  # False — never quantum hardware
```

**The client-side fail-closed guard.** `quote_confirmation_hash` is a required
argument with no default. If it is missing/empty/`None`, `simulate_run` returns
a typed refusal *without touching the network*:

```python
r = client.simulate_run(bell, None)   # or "" — no hash on hand
assert isinstance(r, quantufai.Refusal)
assert r.code == "quote_confirmation_required"
assert r.http_status == 0             # no HTTP request was ever issued
```

### Why no one-shot `simulate()`

There is intentionally no `client.simulate(qasm, confirm=True)` convenience.
A single wrapper that quotes-then-runs would collapse the deliberate inspect
step — the whole point of the two calls is that you (or a human) see the priced,
capped quote *between* them. A one-boolean `confirm=True` is exactly the kind of
accidental-spend surface this SDK refuses to grow; requiring the caller to
obtain and thread the server-signed hash is the friction that keeps
"cannot spend without a hash" true in practice, not just on paper.

## Use it from PennyLane or Qiskit (free sandbox only)

Two thin, optional bridge packages let you run circuits on QuantufAI's **free
sandbox simulator** straight from those frameworks:

- **[`pennylane-quantufai`](../pennylane-quantufai)** — the PennyLane device
  `qml.device("quantufai.sandbox", ...)`.
- **[`qiskit-quantufai`](../qiskit-quantufai)** — the Qiskit provider
  `QuantufAIProvider().get_backend()` (`quantufai_sandbox_simulator`).

Both route **only** through `sandbox_simulate`, so — like this SDK — a framework
device/backend is structurally incapable of spending. Paid GPU simulation and
hardware still require the explicit quote → confirm flow here (a human approving
a signed quote); a framework device deliberately cannot reach it.

## Receipts: what the tiers mean

- **Publicly-verifiable tier** (`publicSignature`, Ed25519): anyone can
  verify offline with QuantufAI's published public key —
  `verify_receipt(receipt, public_key=..., offline_only=True)` or the
  standalone `tools/verify-receipt.mjs`. Needs the optional extra:
  `pip install 'quantufai[verify]'`.
- **Server-attested tier** (`signature`, HMAC): the platform's own
  attestation; only the platform can check it (that's what the platform tier
  of `verify_receipt` asks for). Older receipts carry only this tier — the
  SDK reports that honestly instead of pretending to a verdict.

## Results are verbatim

Counts are never re-binned, error bars are never computed client-side —
`JobResult.error_bars` returns exactly the uncertainty fields the platform
attached (with their location in the payload), and returns nothing when the
platform attached none.

## Changelog

- **0.2.0** — Added the paid GPU-simulator lane: `simulate_quote` (prices a
  run, returns the signed single-use `quote_confirmation_hash`, charges
  nothing) and `simulate_run` (executes only with that hash; fail-closes
  client-side with a typed `quote_confirmation_required` refusal — and zero
  HTTP — when the hash is missing). New `SimulateQuote`/`SimulateResult` types.
  Honesty posture unchanged: quote → inspect the capped price → confirm; no
  hidden spend; never quantum hardware. Matches the REST contract of API PR
  #635 (`POST /api/v1/simulate/quote` and `/run`); ships after it.
- **0.1.0** — Initial published release: price and read, never spend.

## History: this package replaces deleted fabricating stubs

The repo previously carried `sdks/python/quantufai.py` and
`sdks/js/quantufai.ts` — stubs that called endpoints that never existed and
**invented job statuses by pattern-matching chat text**. They were deleted
(PR #579) and a CI test keeps them deleted. This package is their honest
replacement: every call maps to a real, mounted, scope-gated endpoint, and
anything the platform refuses surfaces as a typed `REFUSED`.

## Published (2026-07-15) — how it shipped

The provisional filing cleared (roadmap #4 gate) and `quantufai` is live on
PyPI: https://pypi.org/project/quantufai/. It was built with `python -m build`
from `sdks/python/` and published from the org account (2FA + trusted
publishing). The SDK may now be linked from public docs.

The honesty posture still holds on any announcement (exact words matter):
"quantum compute an AI agent can spend safely — quote-before-spend enforced,
auditable receipts." **Never claim "first MCP"** (IBM's Qiskit MCP servers and
Conductor's CODA exist); "first *governed* one" is the true, stronger claim.
