Skip to main contentIBM Quantum Documentation Preview

Primitives with REST API

The steps in this topic describe how to run and configure primitive workloads with REST API, and demonstrate how to invoke them in any program of your choice.

Note

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.

Find details on how to initialize your account, view available backends, and invalidate tokens in 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;
include "stdgates.inc";
qreg q[2];
creg c[2];
x q[0];
cx q[0], q[1];
c[0] = measure q[0]; 
c[1] = measure q[1];
'''

The following code snippets assume that the qasm_string has been transpiled to a new string resulting_qasm. Find detailed instructions on how to use the Qiskit Transpiler Service API in this topic.

3. Run the quantum circuit using the Estimator V2 API

Note

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.

import requests
 
url = 'https://api.quantum-computing.ibm.com/runtime/jobs'
headers = {
    'Content-Type': 'application/json',
    'x-access-token':auth_id,
    # The following is useful to help you understand where jobs are coming from.
    'x-qx-client-application': f'<package>/<version>/<your_application>',
    }
job_input = {
    'program_id': 'estimator',
    "backend": backend,
    "hub": "hub1",
    "group": "group1",
    "project": "project1",
    "start_session": "False", # set to False if you just need to run a single job.
    "params": {
        "pubs": [ #primitive unified blocs (PUBs) containing one circuit each. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
            [resulting_qasm, # QASM circuit
             {"IIZII": 1, "XIZZZ": 2.3}, # Observable
             None # parameter values
             ]],
        "version": 2 # this defines the version of the Qiskit Runtime Primitive to use, c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
}}
 
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}")

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

>>> Job ID: 58223448-5100-4dec-a47a-942fb30edcad
>>> Job Status: JobStatus.RUNNING

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.7428980350102542, 'stds': 0.029884014518789213, 'ensemble_standard_error': 0.03261147170624149}, 'metadata': {'shots': 10016, 'target_precision': 0.01, '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.

import requests
 
url = 'https://api.quantum-computing.ibm.com/runtime/jobs'
headers = {
  'Content-Type': 'application/json',
  'x-access-token':auth_id,
  'x-qx-client-application': 'qiskit-version-2/0.39.2/'+'your_application' # specifying the application you might be running from. For an actual Integration project, this option is invaluable to know where jobs are coming from. At this time the "qiskit-version-2/0.39.2/" string is a necessary prefix.
  }
job_input = {
  'program_id': 'estimator',
  "backend": backend,
  "hub": "hub1",
  "group": "group1",
  "project": "project1",
  "start_session": "False", # set to False if you just need to run a single job.
  "params": {
      "pubs": [ #primitive unified blocs (PUBs) containing one circuit each. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
          [resulting_qasm, # QASM circuit
           {"IIZII": 1, "XIZZZ": 2.3}, # Observable
           None # parameter values
           ]],
      "version": 2, # this defines the version of the Qiskit Runtime Primitive to use, c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
      "options": {
          "resilience": {
            "measure_mitigation": True,
            "zne_mitigation": True,
            "zne": {
                "extrapolator":["exponential", "linear"],
                "noise_factors":[1, 3, 5],
            },
            #"pec_mitigation": False,
            #"pec": None,
            #"layer_noise_learning":None,
        },
      },
  }
}
 
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.

Find details on how to initialize your account, view available backends, and invalidate tokens in 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;
include "stdgates.inc";
qreg q[2];
creg c[2];
x q[0];
cx q[0], q[1];
c[0] = measure q[0]; 
c[1] = measure q[1];
'''

The code snippets given below assume that the qasm_string has been transpiled to a new string resulting_qasm. Find detailed instructions on how to use the Qiskit Transpiler Service API in this topic.

3. Run the quantum circuit using Sampler V2 API

Note

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.

import requests
 
url = 'https://api.quantum-computing.ibm.com/runtime/jobs'
headers = {
    'Content-Type': 'application/json',
    'x-access-token':auth_id,
    'x-qx-client-application': 'qiskit-version-2/0.39.2/'+'your_application' # specifying the application you might be running from. For an actual Integration project, this option is invaluable to know where jobs are coming from. At this time the "qiskit-version-2/0.39.2/" string is a necessary prefix.
    }
job_input = {
    'program_id': 'sampler',
    "backend": backend,
    "hub": "hub1",
    "group": "group1",
    "project": "project1",
    "start_session": "False", # set to False if you just need to run a single job.
    "params": {
        "pubs": [[resulting_qasm],[resulting_qasm,None,500]], # primitive unified blocs (PUBs) containing one circuit each. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
        "version": 2 # this defines the version of the Qiskit Runtime Primitive to use, c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
}}
 
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}")

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

>>> Job ID: 58223448-5100-4dec-a47a-942fb30edced
>>> Job Status: JobStatus.RUNNING

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']['c']['samples']
 
print(counts[:20])

Output

['0x3', '0x0', '0x2', '0x1', '0x0', '0x3', '0x0', '0x3', '0x1', '0x2', '0x2', '0x0', '0x2', '0x0', '0x3', '0x3', '0x2', '0x0', '0x1', '0x0']

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.

import requests
 
url = 'https://api.quantum-computing.ibm.com/runtime/jobs'
headers = {
  'Content-Type': 'application/json',
  'x-access-token':auth_id,
  'x-qx-client-application': 'qiskit-version-2/0.39.2/'+'your_application' # specifying the application you might be running from. For an actual Integration project, this option is invaluable to know where jobs are coming from. At this time the "qiskit-version-2/0.39.2/" string is a necessary prefix.
  }
job_input = {
  'program_id': 'sampler',
  "backend": backend,
  "hub": "hub1",
  "group": "group1",
  "project": "project1",
  "start_session": "False", # set to False if you just need to run a single job.
  "params": {
      "pubs": [[resulting_qasm]], # primitive unified blocs (PUBs) containing one circuit each. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
      "version": 2, # this defines the version of the Qiskit Runtime Primitive to use, c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
      "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}")

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

import requests
import qiskit_ibm_runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.qasm3 import dumps
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit import transpile
 
service = QiskitRuntimeService(channel='ibm_quantum')
backend = service.backend("<your selected backend>")
 
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 dictionary

3. 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)

5. Run the quantum circuit using Sampler V2 API

Note

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.

import requests
 
url = 'https://api.quantum-computing.ibm.com/runtime/jobs'
headers = {
    'Content-Type': 'application/json',
    '"Authorization": f"Bearer xxxxxxx",
    'x-qx-client-application': 'qiskit-version-2/<version>/'+'<your_application>' # This is set by the client making the request. You can use it to include information such as the package version used.  For an integration project, this option is helpful in understanding where jobs are coming from.  The prefix 'qiskit-version-2/<version>/' is required.
}
 
job_input = {
    'program_id': 'sampler',
    "backend": backend,
    "hub": "hub1",
    "group": "group1",
    "project": "project1",
    "start_session": "False", # set to False if you just need to run a single job.
    "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. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
        "pubs": [[qasm_str,parameter_values,500]], # primitive unified blocs (PUBs) containing one circuit each. c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
        "version": 2 # this defines the version of the Qiskit Runtime Primitive to use, c.f. https://docs.quantum.ibm.com/migration-guides/v2-primitives
}}
 
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}")
print(response.text)

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': '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']['c']['samples']
 
print(counts[:20])

Output

['0x1', '0x2', '0x1', '0x2', '0x1', '0x2', '0x0', '0x2', '0x1', '0x1', '0x2', '0x2', '0x1', '0x1', '0x1', '0x1', '0x1', '0x1', '0x1', '0x1']

Next steps

Recommendations