Primitives with REST API
The steps in this topic describe how to run and configure primitive workloads with the Qiskit Runtime REST API, and demonstrate how to invoke them in any program of your choice.
This documentation utilizes the Python requests module to demonstrate the Qiskit Runtime REST API. However, this workflow can be executed using any language or framework that supports working with REST APIs. Refer to the API reference documentation for details.
Estimator primitive with REST API
1. Initialize the account
Because Qiskit Runtime Estimator is a managed service, you first need to initialize your account. You can then select the device you want to use to calculate the expectation value.
You will also need a bearer token. This is a short-lived token (different from an API key) which is used to authenticate requests to the REST API. To generate one, call the IAM Identity Services API as shown in the following sample request:
curl -X POST 'https://iam.cloud.ibm.com/identity/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=MY_APIKEY'Expected Response
{
"access_token": "eyJhbGciOiJIUz......sgrKIi8hdFs",
"refresh_token": "SPrXw5tBE3......KBQ+luWQVY=",
"token_type": "Bearer",
"expires_in": 3600,
"expiration": 1473188353
}# Use 'service' to invoke operations.
import requests
import json
url = 'https://iam.cloud.ibm.com/identity/token'
api_key = 'MY_APIKEY'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
data = f'grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={api_key}'
response = requests.post(url, headers=headers, data=data)
# Bearer token to authorize requests to the REST API
bearer_token = response.json()['access_token']For other details on how to initialize your account, view available backends, and validate tokens, read this topic.
2. Create a QASM circuit
You need at least one circuit as the input to the Estimator primitive.
Define a QASM quantum circuit. For example:
qasm_string = """
OPENQASM 3.0;
include "stdgates.inc";
bit[2] meas;
rz(pi/2) $1;
sx $1;
rz(pi) $1;
x $2;
cz $2, $1;
sx $1;
rz(pi/2) $1;
"""3. Run the quantum circuit using the Estimator V2 API
You'll need to replace auth_id, crn, and backend with the appropriate values.
url = 'https://quantum.cloud.ibm.com/api/v1/jobs'
auth_id = "Bearer <YOUR_BEARER_TOKEN>"
crn = "<SERVICE-CRN>"
backend = "<BACKEND_NAME>"headers = {
"Content-Type": "application/json",
"Authorization": auth_id,
"Service-CRN": crn,
}
job_input = {
"program_id": "estimator",
"backend": backend,
"params": {
"version": "2",
"pubs": [ # primitive unified blocs (PUBs) containing one circuit each.
[
qasm_string, # QASM circuit
{"IZI": 1, "IXI": 2.3}, # Observable
None, # parameter values
]
],
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")Output:
Job created: {"id":"d6147q3traac73bef6f0","backend":"ibm_pittsburgh"}
4. Check job status and get results
Next, pass the job_id to the API:
response_status_singlejob = requests.get(url + "/" + job_id, headers=headers)
response_status_singlejob.json().get("state")Output:
{'status': 'Queued'}
Once the job has completed, get job results:
response_result = requests.get(
url + "/" + job_id + "/results", headers=headers
)
res_dict = response_result.json()
estimator_result = res_dict["results"]
print(estimator_result)Output:
[{'data': {'evs': -0.9374102500618965, 'stds': 0.030982815700848845, 'ensemble_standard_error': 0.03686001673650654}, 'metadata': {'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32}}]
5. Work with Runtime options
Error mitigation techniques allow users to mitigate circuit errors by modeling the device noise at the time of execution. This typically results in quantum pre-processing overhead related to model training, and classical post-processing overhead to mitigate errors in the raw results by using the generated model.
The error mitigation techniques built in to primitives are advanced resilience options. To specify these options, use the resilience_level option when submitting your job.
The following examples demonstrate the default options for dynamical decoupling, twirling, and TREX + ZNE. Find more options and further details in the Error mitigation and suppression techniques topic.
job_input = {
"program_id": "estimator",
"backend": backend,
"params": {
"pubs": [ # primitive unified blocs (PUBs) containing one circuit each.
[
qasm_string, # QASM circuit
{"IZI": 1, "IXI": 2.3}, # Observable
None, # parameter values
]
],
"version": "2",
"options": {
"resilience": {
"measure_mitigation": True,
"zne_mitigation": True,
"zne": {
"extrapolator": ["exponential", "linear"],
"noise_factors": [1, 3, 5],
},
},
},
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")job_input = {
"program_id": "estimator",
"backend": backend,
"params": {
"pubs": [ # primitive unified blocs (PUBs) containing one circuit each.
[
qasm_string, # QASM circuit
{"IZI": 1, "IXI": 2.3}, # Observable
None, # parameter values
]
],
"version": "2",
"options": {
"dynamical_decoupling": {
"enable": True,
"sequence_type": "XpXm",
"extra_slack_distribution": "middle",
"scheduling_method": "alap",
},
},
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")job_input = {
"program_id": "estimator",
"backend": backend,
"params": {
"pubs": [ # primitive unified blocs (PUBs) containing one circuit each.
[
qasm_string, # QASM circuit
{"IZI": 1, "IXI": 2.3}, # Observable
None, # parameter values
]
],
"version": "2",
"options": {
"twirling": {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": "auto",
"shots_per_randomization": "auto",
"strategy": "active-accum",
},
},
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")Sampler primitive with REST API
1. Initialize the account
Because Qiskit Runtime Sampler is a managed service, you first need to initialize your account. You can then select the device you want to use to run your calculations on.
You will also need a bearer token, which can be generated using the instructions above. You can also find more information about generating bearer tokens in the IAM Identity Services API documentation.
For other details on how to initialize your account, view available backends, and validate tokens, read this topic.
2. Create a QASM circuit
You need at least one circuit as the input to the Sampler primitive.
Define a QASM quantum circuit:
qasm_string = """
OPENQASM 3.0;
include "stdgates.inc";
bit[2] meas;
rz(pi/2) $1;
sx $1;
rz(pi) $1;
x $2;
cz $2, $1;
sx $1;
rz(pi/2) $1;
meas[0] = measure $1;
meas[1] = measure $2;
"""3. Run the quantum circuit using Sampler V2 API
The jobs below use Qiskit Runtime V2 primitives. Both SamplerV2 and EstimatorV2 take one or more primitive unified blocs (PUBs) as the input. Each PUB is a tuple that contains one circuit and the data broadcasted to that circuit, which can be multiple observables and parameters. Each PUB returns a result.
You'll need to replace auth_id, crn, and backend with the appropriate values.
url = 'https://quantum.cloud.ibm.com/api/v1/jobs'
auth_id = "Bearer <YOUR_BEARER_TOKEN>"
crn = "<SERVICE-CRN>"
backend = "<BACKEND_NAME>"headers = {
"Content-Type": "application/json",
"Authorization": auth_id,
"Service-CRN": crn,
}
job_input = {
"program_id": "sampler",
"backend": backend,
"params": {
"pubs": [
[qasm_string],
[qasm_string, None, 500],
], # primitive unified blocs (PUBs) containing one circuit each.
"version": "2",
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")Output:
Job created: {"id":"d6147vio8gvs73f0h130","backend":"ibm_pittsburgh"}
4. Check job status and get results
Next, pass the job_id to the API:
response_status_singlejob = requests.get(url + "/" + job_id, headers=headers)
response_status_singlejob.json().get("state")Output:
{'status': 'Queued'}
Once the job has completed, get job results:
response_result = requests.get(
url + "/" + job_id + "/results", headers=headers
)
res_dict = response_result.json()
# Get results for the first PUB
counts = res_dict["results"][0]["data"]["meas"]["samples"]
print(counts[:20])Output:
['0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x2', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3', '0x3']
5. Work with Runtime options
Error mitigation techniques allow users to mitigate circuit errors by modeling the device noise at the time of execution. This typically results in quantum pre-processing overhead related to model training, and classical post-processing overhead to mitigate errors in the raw results by using the generated model.
The error mitigation techniques built in to primitives are advanced resilience options. To specify these options, use the resilience_level option when submitting your job.
Sampler V2 does not support specifying resilience levels. However, you can turn on or off individual error mitigation / suppression methods.
The following examples demonstrate the default options for dynamical decoupling and twirling. Find more options and further details in the Error mitigation and suppression techniques topic.
headers = {
"Content-Type": "application/json",
"Authorization": auth_id,
"Service-CRN": crn,
}
job_input = {
"program_id": "sampler",
"backend": backend,
"params": {
"pubs": [
[qasm_string]
], # primitive unified blocs (PUBs) containing one circuit each.
"version": "2",
"options": {
"dynamical_decoupling": {
"enable": True,
"sequence_type": "XpXm",
"extra_slack_distribution": "middle",
"scheduling_method": "alap",
},
},
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")headers = {
"Content-Type": "application/json",
"Authorization": auth_id,
"Service-CRN": crn,
}
job_input = {
"program_id": "sampler",
"backend": backend,
"params": {
"pubs": [
[qasm_string]
], # primitive unified blocs (PUBs) containing one circuit each.
"version": "2",
"options": {
"twirling": {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": "auto",
"shots_per_randomization": "auto",
"strategy": "active-accum",
},
},
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print("Job created:", response.text)
else:
print(f"Error: {response.status_code}")Sampler primitive with REST API and parameterized circuits
1. Initialize the account
Because Qiskit Runtime is a managed service, you first need to initialize your account. You can then select the device you want to use to run your calculations on.
Find details on how to initialize your account, view available backends, and invalidate tokens in this topic.
2. Define parameters
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.qasm3 import dumps
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
service = QiskitRuntimeService()
backend = service.least_busy()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
theta = Parameter("theta")
phi = Parameter("phi")
parameter_values = {
"theta": 1.57,
"phi": 3.14,
} # In case we want to pass a dictionary3. Create a quantum circuit and add parameterized gates
qc = QuantumCircuit(2)
# Add parameterized gates
qc.rx(theta, 0)
qc.ry(phi, 1)
qc.cx(0, 1)
qc.measure_all()
# Draw the original circuit
qc.draw("mpl")
# Get an ISA circuit
isa_circuit = pm.run(qc)4. Generate QASM 3 code
qasm_str = dumps(isa_circuit)
print("Generated QASM 3 code:")
print(qasm_str)Output:
Generated QASM 3 code:
OPENQASM 3.0;
include "stdgates.inc";
input float[64] phi;
input float[64] theta;
bit[2] meas;
rz(pi/2) $0;
sx $0;
rz(pi + theta) $0;
sx $0;
rz(5*pi/2) $0;
sx $1;
rz(pi + phi) $1;
rz(-pi/2) $1;
sx $1;
cz $0, $1;
rz(pi/2) $1;
sx $1;
rz(pi/2) $1;
barrier $0, $1;
meas[0] = measure $0;
meas[1] = measure $1;
5. Run the quantum circuit using Sampler V2 API
The following jobs use Qiskit Runtime V2 primitives. Both SamplerV2 and EstimatorV2 take one or more primitive unified blocs (PUBs) as the input. Each PUB is a tuple that contains one circuit and the data broadcasted to that circuit, which can be multiple observables and parameters. Each PUB returns a result.
headers = {
"Content-Type": "application/json",
"Authorization": auth_id,
"Service-CRN": crn,
}
job_input = {
"program_id": "sampler",
"backend": backend.name,
"params": {
# Choose one option: direct parameter transfer or through a dictionary
# "pubs": [[qasm_str,[1,2],500]], # primitive unified blocs (PUBs) containing one circuit each.
"pubs": [
[qasm_str, parameter_values, 500]
], # primitive unified blocs (PUBs) containing one circuit each.
"version": "2",
},
}
response = requests.post(url, headers=headers, json=job_input)
if response.status_code == 200:
job_id = response.json().get("id")
print(f"Job created: {response.text}")
else:
print(f"Error: {response.status_code}")Output:
Job created: {"id":"d614873traac73bef6tg","backend":"ibm_pittsburgh"}
6. Check job status and get results
Next, pass the job_id to the API:
response_status_singlejob = requests.get(f"{url}/{job_id}", headers=headers)
response_status_singlejob.json().get("state")Output:
{'status': 'Queued'}
Once the job has completed, get job results:
response_result = requests.get(f"{url}/{job_id}/results", headers=headers)
res_dict = response_result.json()
# Get results for the first PUB
counts = res_dict["results"][0]["data"]["meas"]["samples"]
print(counts[:20])Output:
['0x1', '0x2', '0x1', '0x1', '0x1', '0x1', '0x1', '0x1', '0x2', '0x1', '0x1', '0x1', '0x1', '0x2', '0x2', '0x2', '0x2', '0x2', '0x2', '0x1']
Next steps
- There are several ways to run workloads, depending on your needs: job mode, session mode, and batch mode. Learn how to work with session mode and batch mode in the execution modes topic. Note that Open Plan users cannot submit session jobs.
- Learn how to initialize your account with REST API.
- Read Migrate to V2 primitives.
- Practice with primitives by working through the Cost function lesson in IBM Quantum Learning.
- Learn how to transpile locally in the Transpile section.
- Migrate to the Qiskit Runtime V2 primitives.