SDK Documentation
Everything runs through one Python package. Write circuits in Qiskit, Cirq, PennyLane, pyQuil, or Braket, and we convert them for you; we handle where and how they execute.
1. Install
pip install qsim-sdk
2. Get a token
Create an account at app.zksf.org. Your API token is on the dashboard.
3. Run your first circuit
import qsim_sdk
from qiskit import QuantumCircuit
qc = QuantumCircuit(60) # yes, sixty qubits
qc.h(0)
for i in range(59):
qc.cx(i, i + 1)
client = qsim_sdk.Client(token="YOUR_TOKEN")
job = client.run(qc, shots=1000)
print(job["result"]["counts"]) # the answer
print(job["result"]["error_info"]) # how much to trust itThe router inspected your circuit, saw it was Clifford, and ran it with a stabilizer engine: exactly, in milliseconds, for a fraction of a cent. A 60-qubit statevector would have needed 16 million terabytes of RAM.
4. Estimate before you spend
est = client.estimate(qc)
# {'engine': 'mps.quimb.cpu', 'predicted_seconds': 5.1,
# 'predicted_cost_usd': 0.00014, 'reason': '60 qubits, low
# entangling density -> MPS attempt with convergence check'}Estimates are free, instant, and deliberately conservative. No job runs without you knowing the ceiling.
5. Understand the error certificate (ZCC-v0.1)
Every approximate result carries a ZCC-v0.1 statement of how far it can be from the truth. By default it is a fast convergence check; pass certified=True for a measured, single-run bound.
job["result"]["error_info"]
# {'method': 'MPS (quimb)', 'max_bond': 64,
# 'convergence_deviation': 0.0, 'converged': True}client.run(qc, engine="mps.quimb.cpu", certified=True)
# error_info = {'protocol': 'ZCC-v0.1',
# 'method': 'MPS, measured discarded-weight bound',
# 'truncation_weight': 3.2e-08, # exact discarded SVD weight
# 'error_bound': 2.5e-04, # max per-outcome prob error
# 'certified': True}convergence_deviation: how much the top outcome probabilities moved when we re-ran at double the bond dimension. 0.0 means the approximation captured the state.certified=True: builds the state with renormalization off and reads the accumulated discarded SVD weight from the norm deficit. The per-outcome probability error is then bounded bysqrt(2 · truncation_weight), measured in a single run rather than extrapolated. Each truncation is optimal by Eckart-Young and the weight is read from the state, not estimated. The bound holds under incoherent accumulation of the individual truncation errors, a condition deep circuits do not always satisfy, so it is stated as an empirically supported bound rather than a worst-case guarantee: it was never exceeded across 334 runs checked against exact simulation, 290 of them constructed to falsify it, which reach 20 qubits, the largest size at which an exact reference can be computed.- Pauli propagation reports the same protocol:
error_boundis the total discarded coefficient mass, which bounds the expectation-value error. - Exact engines (statevector, stabilizer) report
truncation_error: 0.0; only shot noise applies.
Any finished job can be exported as a downloadable PDF certificate with a public verification link, so a result you report can be independently checked by anyone. See the methodology write-up for the full protocol, or read the paper for the derivations and the falsification testing behind it.
6. Understand the hardware certificate (ZHF-v0.1)
QPU results carry a different statement: not how close an approximation is to the truth, but how closely a real device’s measured counts track the exact ideal distribution. That is ZHF-v0.1.
client.run(qc, engine="qpu.ionq", shots=100)
# certificate = {'protocol': 'ZHF-v0.1',
# 'device': 'IonQ Forte-1',
# 'fidelity_mode': 'direct',
# 'fidelity': 0.9774}fidelity: the Hellinger fidelity between the measured hardware counts and the exact ideal distribution computed for the same circuit, in [0, 1]. 1.0 would mean the measured counts matched the ideal distribution exactly.fidelity_mode: "direct": the circuit was small enough (≤ 24 qubits) to also simulate exactly, so the fidelity is measured directly against that ideal reference, not asserted from a vendor specification.- Above that qubit count, direct verification is not yet available; the certificate says so honestly rather than reporting a number it cannot back up.
Like ZCC-v0.1, any finished QPU job can be exported as a downloadable PDF certificate with a public verification link. See the methodology write-up for both protocols.
7. Choose an engine explicitly (optional)
client.run(qc, engine="mps.quimb.cpu", max_bond=128) # force MPS client.run(qc, engine="exact.cpu") # force exact
| Engine | Best for | Limits |
|---|---|---|
| exact.cpu | Ground truth, any gate set | ≤ 30 qubits |
| exact.gpu | Exact results, GPU-accelerated | ≤ 32 qubits |
| clifford | Stabilizer/error-correction circuits | Clifford gates only, 5,000+ qubits |
| mps.quimb.cpu | QAOA, ansätze, structured circuits | entanglement-dependent, ~128 qubits; certified error bounds |
| mps.aer.cpu | Independent tensor-network cross-check | entanglement-dependent |
| pauli.cpu | Expectation values of large circuits | needs an observable; 100 to 200 qubits, no bitstring counts |
| noisy.cpu | Preview hardware noise before you pay | device-noise model; optional error mitigation (ZNE) |
| qpu.rigetti | Real superconducting hardware | Rigetti Cepheus 108 qubits; scheduled queue, physical noise |
| qpu.ionq | Real trapped-ion hardware | IonQ Forte-1 36 qubits, 100-5000 shots; scheduled queue |
8. Error mitigation (opt-in)
Any run that carries an observable can request zero-noise error mitigation. The circuit is evaluated at several amplified noise levels and the observable is extrapolated back to the zero-noise limit, which removes much of the modelled gate noise from the reported value. It applies to the noisy.cpu simulator and to the qpu.rigetti and qpu.ionq hardware engines. On hardware it submits three folded circuits, so it bills three times the single-run cost.
client.run(qc, engine="qpu.rigetti", shots=200,
observable=[[1.0, "ZZ"]], mitigate=True)
# error_info: { protocol: "ZCC-Estimate-v0.1",
# technique: "zero-noise extrapolation",
# raw_expectation: 0.81, mitigated_expectation: 0.82,
# error_bound: 5.0e-02, estimated: true, certified: false }The returned figure is a mitigated value with a ZCC-Estimate-v0.1 uncertainty: the larger of the shot noise propagated through the extrapolation and the disagreement between the linear, quadratic, and Richardson extrapolations of the same measured data. It is computed without reference to the true answer, so the same statement carries over to hardware. Because an extrapolated value cannot be given a hard mathematical bound the way the exact, stabilizer, and tensor-network paths can, certified reads false by design: the result is reported as a well-founded statistical estimate, not as a guarantee, and the exported certificate states this explicitly.
9. When we say no
qsim_sdk.JobRejected: intractable classically at this structure: 60 qubits, depth 99, 826 entangling gates (13.8/qubit). Options: reduce entangling density below ~8/qubit for MPS, restrict to Clifford gates, or shrink to <= 30 qubits for exact simulation.
Deep, wide, highly entangled circuits are intractable for every classical method, and beyond today’s noisy hardware too. We reject them with the reason and your options, instead of billing you for results that cannot be trusted.
10. Worked example: real test runs on every tier
Everything above, shown on real jobs: five runs across CPU, GPU, and both quantum processors, with the environment, the results, and what they cost. You can reproduce them from the console or the SDK.
Run A (CPU): the benchmark suite, on a laptop
Environment: an ordinary consumer laptop (Intel i7-12700H, 32 GB RAM), CPU only: no GPU, no cluster, no quantum hardware. Circuits were submitted with the engine field left blank, so the router chose the method each time.
| Circuit | Qubits | Depth | Engine picked | Wall time | Accuracy |
|---|---|---|---|---|---|
| GHZ (Clifford) | 5,000 | 5,000 | clifford | 0.56 s | exact |
| QAOA MaxCut, p=3 | 100 | 304 | mps.quimb.cpu | 5.9 s | converged (dev 0.0) |
| Layered ansatz | 80 | 9 | mps.quimb.cpu | 4.3 s | converged (dev 0.0) |
| Exact statevector | 26 | 9 | exact.cpu | 2.7 s | exact (ground truth) |
Reading the accuracy column: exact means the method makes no approximation at all, so only shot noise applies. Converged (dev 0.0) means the job was automatically re-run at double the bond dimension and the top outcome probabilities did not move; the compression captured the state. Every approximate job you run gets this same check, in its error_info.
Run B (GPU): 8-qubit GHZ, production cloud
Environment: our production GPU tier, submitted through the console exactly as any signed-in user would. The circuit is an 8-qubit GHZ state: one Hadamard, a chain of CNOTs, measure everything.
OPENQASM 2.0; include "qelib1.inc"; qreg q[8]; creg c[8]; h q[0]; cx q[0],q[1]; cx q[1],q[2]; cx q[2],q[3]; cx q[3],q[4]; cx q[4],q[5]; cx q[5],q[6]; cx q[6],q[7]; measure q -> c;
Steps: paste the circuit, set shots to 1,000, pick exact.gpu from the engine menu, run. The result:
counts: {"00000000": 502, "11111111": 498}
error_info: {"method": "exact statevector (GPU)",
"truncation_error": 0, "shot_noise_only": true,
"shots": 1000}
resources: {"device": "GPU", "wall_seconds": 0.51}A GHZ state should collapse to all-zeros or all-ones with equal probability, so the ideal split over 1,000 shots is 500/500; the measured 502/498 is textbook shot noise. Execution took half a second on the GPU. The first GPU job after a quiet period can add a short warm-up delay while a worker spins up; it is not billed.
Run C (GPU): 32 qubits, the memory wall
The GPU tier’s exact statevector reaches 32 qubits, the point where a 32 GB laptop runs out of memory. A 32-qubit GHZ circuit (100 shots) holds a full 64 GiB statevector on the GPU, well past what a consumer machine can allocate.
counts: {"00000000000000000000000000000000": 52,
"11111111111111111111111111111111": 48}
error_info: {"method": "exact statevector (GPU)",
"truncation_error": 0, "shot_noise_only": true,
"shots": 100}
resources: {"device": "GPU", "qubits": 32,
"statevector": "64 GiB", "wall_seconds": 8.2}52/48 across the two GHZ states is the expected 50/50 split within shot noise, computed exactly (no approximation, truncation error 0) in 8.2 seconds. This is the crossover the platform is built for: the same SDK line that ran on a laptop scales onto GPU memory a laptop does not have.
Run D (QPU): Rigetti superconducting hardware
The same experiment on a real quantum processor: a 3-qubit GHZ circuit, 50 shots, engine qpu.rigetti, submitted through the console. Hardware runs are scheduled on the physical device, so this one returned after roughly 53 minutes in the queue; you can close the page and the result attaches to the job. The result:
counts: {"000": 23, "111": 22,
"011": 3, "010": 1, "001": 1}
error_info: {"method": "quantum hardware (Rigetti)",
"device": "Cepheus-1-108Q",
"shot_noise_and_device_noise": true,
"shots": 50,
"note": "raw hardware counts; no error mitigation applied"}45 of 50 shots landed in the two GHZ states, and 5 shots show bit-flip errors from real device noise: the physical signature of superconducting qubits, reported exactly as measured. Comparing this against the simulator runs above is itself a useful experiment; the same circuit can also be submitted with zero-noise error mitigation (section 8) to return a mitigated observable with a stated uncertainty, at three times the single-run cost.
Run E (QPU): IonQ Forte-1 trapped-ion hardware
The same family of experiment on a different kind of quantum computer: a 2-qubit GHZ (Bell) circuit, 100 shots, engine qpu.ionq, on IonQ’s Forte-1 trapped-ion processor. Trapped-ion queues are scheduled the same way; this run returned after about 5 hours. The result:
counts: {"00": 54, "11": 44, "01": 2}
error_info: {"method": "quantum hardware (IonQ)",
"device": "Forte-1",
"shot_noise_and_device_noise": true,
"shots": 100,
"note": "raw hardware counts; no error mitigation applied"}98 of 100 shots landed in the two GHZ states (00 and 11), with just 2 shots of device noise, so about 98% of shots fell in the ideal outcomes. Trapped-ion qubits carry different noise characteristics from superconducting ones; running the identical circuit on both, next to the exact simulator, is exactly the kind of comparison the platform is meant to make easy.
Run F (CPU): how to reproduce a 192-qubit result for a tenth of a cent
Statevector simulation stops around 34 qubits because memory doubles with every qubit. Pauli propagation does not track the full state; it evolves the observable backward through the circuit, so its cost scales with circuit structure rather than qubit count. That reaches well past 100 qubits on an ordinary CPU. Here is a 192-qubit GHZ circuit with an all-Z observable, engine pauli.cpu, which you can run yourself from the SDK in three lines:
import qsim_sdk
from qiskit import QuantumCircuit
n = 192
qc = QuantumCircuit(n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1) # a 192-qubit GHZ state
client = qsim_sdk.Client(token="YOUR_TOKEN")
job = client.run(
qc,
engine="pauli.cpu",
observable=[[1.0, "Z" * n]], # measure <Z...Z> across all 192 qubits
)
print(job["result"]["expectation"]) # 1.0
print(job["result"]["error_info"]) # certified, error_bound 0expectation: 1.0
error_info: {"protocol": "ZCC-v0.1",
"method": "Pauli propagation (Heisenberg picture)",
"truncated_coefficient_mass": 0, "error_bound": 0,
"certified": true, "converged": true}The exact answer is known analytically: for a GHZ state the all-Z expectation value is exactly +1, and the engine returns 1.0 with a ZCC-v0.1 error bound of 0 (no Pauli terms were truncated). The run completed in under a minute and cost $0.001, a tenth of a cent, at the standard pauli.cpu rate. The result carries a public, independently verifiable certificate: api.zksf.org/certify/5b8b2c4309d44d41.
Summary: one circuit family, every tier
| Tier | Circuit | Qubits | Result | Time |
|---|---|---|---|---|
| CPU | GHZ (Clifford) | 5,000 | exact | 0.56 s |
| CPU | QAOA MaxCut, p=3 | 100 | converged (dev 0.0) | 5.9 s |
| GPU | GHZ, 1,000 shots | 8 | 502/498 (ideal 500/500) | 0.5 s |
| GPU | GHZ, 100 shots | 32 | 52/48 (ideal 50/50); 64 GiB statevector | 8.2 s |
| QPU | GHZ, 50 shots (Rigetti) | 3 | 45/50 in GHZ states, 5 noise | ~53 min incl. queue |
| QPU | GHZ, 100 shots (IonQ) | 2 | 98/100 in GHZ states, 2 noise | ~5 h incl. queue |
| CPU | GHZ, Pauli propagation, all-Z | 192 | <Z…Z> = 1.0, certified (bound 0) | < 1 min |
11. API surface (v0)
Client(base_url, token): construct once, reuse.client.estimate(circuit, shots): free feasibility + cost forecast.client.run(circuit, shots, engine=None, **params): submit and wait; raisesJobRejected/JobFailed.client.submit(...)/client.job(job_id): non-blocking variant.
The SDK is a thin wrapper over a plain HTTP API, so any language can call it directly. The full specification is published in machine-readable form, browsable interactively at api.zksf.org/docs or as raw OpenAPI 3.1 at openapi.json. Certificates are readable as JSON at /certify/{cert_id}/json, alongside the HTML verification page and the PDF, so a third party can check one programmatically rather than by eye.
Questions, bugs, or a circuit you think we should handle but don’t? info@zksf.org. Benchmark challenges welcome.
12. Mobile app (Android)
The ZKSF Android app runs the same platform from a phone, signed in with the same account as the web console, so your jobs, history, and balance stay in sync across both. It is a native app, not a wrapped web page.
What you can do from the app:
- Run circuits. Paste or upload an OpenQASM 2.0 file (
.qasmor.txt), choose an engine (CPU, GPU, or a real Rigetti or IonQ QPU), estimate the cost for free, then run and read the certified result. - Error mitigation. Opt into zero-noise error mitigation on a hardware run, returning a mitigated value with a ZCC-Estimate-v0.1 uncertainty (it submits three hardware runs).
- Job history and certificates. Review past jobs and export the same public, verifiable certificates as on the web.
- Read offline. The Research articles with their interactive explainer videos, the Dictionary, and these Support Docs are all in the app; the text is offline and the videos stream on tap.
The app is for running one circuit at a time by hand. For programmatic use, parameter sweeps, or wiring results into a larger script, install the qsim-sdk Python package on a desktop or laptop; it cannot run on a phone.
13. Circuit templates (a beginner’s starting point)
Plenty of people want to run a real quantum circuit before they have learned to write OpenQASM. The console ships with six ready-made circuits for exactly that, chosen to sit where the textbook meets real use. Open the app, pick one from the Start from a template menu above the editor, and it loads the circuit, selects the right engine, and fills in any observable it needs.
How to use them, start to finish:
- Choose a template. The OpenQASM loads into the editor and a live circuit diagram appears beneath it.
- Read the diagram to see the structure: which gates act on which qubits, and in what order.
- Press Estimate (free) to see the predicted engine and cost, which for these is a fraction of a cent.
- Press Run circuit, then read the result and download its certificate.
- Edit the QASM freely to make it your own. The diagram redraws as you type, so you always see what you are about to run.
| Template | What it does | Engine | Walkthrough |
|---|---|---|---|
| GHZ / Bell state | Entangles three qubits so they always agree, the hello-world of entanglement. | clifford (exact) | Read |
| Grover search | Finds a marked item in an unsorted space; one iteration finds it with certainty. | clifford (exact) | Read |
| QAOA MaxCut | Splits a four-node ring to cut the most links; real combinatorial optimization. | exact.cpu | Read |
| VQE, H2 molecule | Finds hydrogen’s ground-state energy by tuning one angle; lands on -1.137 Ha. | pauli.cpu | Read |
| Bernstein-Vazirani | Reads a hidden bitstring in a single query where a classical computer needs one per bit. | clifford (exact) | Read |
| Quantum teleportation | Moves a qubit’s state across the circuit using entanglement and corrections alone. | exact.cpu | Read |
Every template runs on the same engines, billing, and certificates as any other job, so nothing you learn here is a toy sandbox: change the circuit, scale it up, and it keeps working. If you are brand new, GHZ is the gentlest place to begin.
14. Citing ZKSF
If a result produced here appears in published work, cite the archived client. The DOI below is the concept DOI: it always resolves to the most recent release, so it does not go stale.
10.5281/zenodo.21836619
The full record, including per-version DOIs, is at doi.org/10.5281/zenodo.21836619. To pin an exact release for reproducibility, use that release’s own version DOI from the record page rather than the concept DOI. The repository also ships a CITATION.cff, so GitHub’s “Cite this repository” button produces BibTeX and APA directly.
Individual jobs are separately citable: every certificate has a permanent public URL under api.zksf.org/certify/ that resolves without an account, so a reviewer can check the exact accuracy statement behind a specific number rather than taking the figure on trust.
