---
title: Define inputs and outputs
description: Pass arguments into a managed workflow and return results from it.
source: https://quantum.cloud.ibm.com/docs/en/managed-workflows/managed-workflow-inputs-and-outputs
---

# Define inputs and outputs

A managed workflow works like a Python function that runs on remote compute. Compare a local function with the same code as a workflow:

```python
def my_workflow(circuits, shots=4096):
    ...
    return {"counts": counts}

result = my_workflow(circuits=circuits, shots=1024)
```

In a managed workflow, `run()` is the call, `get_arguments()` gives the workflow its parameters, and `save_result()` is its `return` statement. Because the workflow runs as a separate job on remote compute, it differs from a local function call in three ways:

- **The client encodes arguments and results.** Each value must be one that the client can encode, and some types change. See [Read arguments in your workflow](#read-arguments-in-your-workflow).
- **The call does not wait.** `run()` returns a job immediately, and you read the result later with `job.result()`. See [Save results](#save-results).
- **Nothing checks the call.** A Python function rejects an unknown keyword argument, but a workflow ignores it unless you attach a schema. See [Best practice: validate arguments with a schema](#best-practice-validate-arguments-with-a-schema).

![Your local notebook, run\_workflow\_remotely.ipynb, calls run(args). A workflow runs an optional schema check, then creates the managed workflow job, source\_files/my\_workflow.py. The job reads its arguments with get\_arguments(), runs your code, such as mapping, transpiling circuits, running primitives, and post-processing, and returns a result with save\_result(). A workflow stores the result, and your notebook reads it with job.result().](https://quantum.cloud.ibm.com/docs/images/managed-workflows/inputs-outputs.svg)

## Read arguments in your workflow

Pass inputs to your workflow from your local notebook:

```python
job = my_workflow.run(circuits=circuits, shots=1024)
```

To enable this, edit your workflow code to fetch arguments with `get_arguments()`, which returns a dictionary whose keys are the argument names:

```python
from qiskit_serverless import get_arguments

arguments = get_arguments()
circuits = arguments["circuits"]
shots = arguments.get("shots", 4096)
```

The dictionary contains only the arguments that the run passed, so use `arguments.get()` with a default value for optional arguments. You can use any argument name except the protected keywords `config` and `function_size`, because `run()` uses these keywords directly. `run()` also rejects `compute_profile`. To choose compute resources, pass `function_size`.

To read the size that the job runs at, call `get_function_size()` in your workflow code:

```python
from qiskit_serverless import get_function_size

size = get_function_size()  # For example, "M"
```

If the run does not pass `function_size`, `get_function_size()` returns the workflow's default size. See [Choose a size for each run](/docs/managed-workflows/managed-workflow-compute-profiles#choose-a-size-for-each-run).

### Supported argument types

Each value must be JSON-serializable, or an object that the client can encode, such as a Qiskit circuit or a NumPy array. Because the client encodes each argument before it sends the job and `get_arguments()` decodes it inside the workflow, the workflow receives the original Python object.

The client sends `int`, `float`, `str`, `bool`, `None`, and lists or dictionaries of these unchanged. It encodes other objects, such as a `QuantumCircuit`, a `numpy.ndarray`, or a `complex` number, as a dictionary with a `__type__` tag and a `__value__`. You need this form when you [write an argument schema](#best-practice-validate-arguments-with-a-schema).

Because JSON has no equivalent for some types, these types change during encoding. Tuples arrive as lists, dictionary keys arrive as strings, and NumPy integers such as `np.int64` arrive as `int`. If the client cannot encode a value, such as `bytes` or `np.float32`, `run()` raises a `TypeError` and does not create a job.

Some objects cannot be passed as arguments. For example, pass a backend's name as a string instead of a `Backend` object, and create the backend inside the workflow. See [Run quantum jobs from a workflow](/docs/managed-workflows/manage-quantum-classical-workflows).

### Size limits

The arguments of one run can be up to 32 MB after encoding. For example, one 100-qubit circuit with a depth of 100 encodes to about 39 KB, so a single run can take a batch of about 800 such circuits. For larger inputs, [upload a file](/docs/managed-workflows/manage-files-across-managed-workflows) and pass its name as an argument.

## Save results

Use `save_result()` at the end of your workflow to return a result to your notebook. Read the result in your notebook with `job.result()`:

```python
result = job.result()
```

To enable this, call `save_result()` in your workflow code:

```python
from qiskit_serverless import save_result

if not save_result({"counts": counts, "circuit": isa_circuit}):
    raise ValueError("The result could not be encoded.")
```

`save_result()` encodes the result in the same way that the client encodes arguments, and `job.result()` decodes it. A result can therefore contain the [supported argument types](#supported-argument-types), such as circuits, NumPy arrays, and the result of a primitive job, with the same type changes.

- **Check the return value.** If `save_result()` cannot encode the value, it logs a warning and returns `False` instead of raising an error. Without a check, the job can end with the `DONE` status and no result.
- **Only the last `save_result()` is stored.** Each call replaces the saved result. To return several values, save them in one dictionary, or write them to the logs.
- **An empty dictionary means no result.** If the workflow never calls `save_result()`, `job.result()` returns `{}`.

To return files, such as large arrays or plots, save them to [file storage](/docs/managed-workflows/manage-files-across-managed-workflows) and return their names in the result.

## Best practice: validate arguments with a schema

As a best practice, attach a [JSON Schema](https://json-schema.org/) to every workflow that takes arguments. The schema describes the arguments that the workflow accepts, and the workflow checks the arguments of every run against it before it creates the job. If the arguments do not match, `run()` fails immediately with an error that names the bad field, and the workflow does not create a job. Without a schema, bad arguments reach the workflow after the job starts, and the workflow ignores a misspelled argument name.

Set the schema with `arguments_schema` when you upload the workflow. Pass a dictionary, not a JSON string:

```python
from qiskit_ibm_catalog import ManagedWorkflow, ManagedWorkflowClient

arguments_schema = {
    "type": "object",
    "additionalProperties": False,
    "required": ["circuits"],
    "properties": {
        "circuits": {
            "type": "array",
            "minItems": 1,
            "items": {"$ref": "#/$defs/circuit"},
        },
        "shots": {"type": "integer", "minimum": 1, "maximum": 100000},
        "backend_name": {"type": "string", "minLength": 1},
    },
    "$defs": {
        "circuit": {
            "type": "object",
            "required": ["__type__", "__value__"],
            "properties": {
                "__type__": {"const": "QuantumCircuit"},
                "__value__": {"type": "string"},
            },
        }
    },
}

client = ManagedWorkflowClient()

my_workflow = client.upload(
    ManagedWorkflow(
        title="my-workflow",
        entrypoint="my_workflow.py",
        working_dir="./source_files/",
        arguments_schema=arguments_schema,
    )
)
```

Keep the following points in mind when you write a schema:

- **Describe the encoded form of Qiskit and NumPy objects.** The schema checks the arguments as the client sends them, so a circuit is an object such as `{"__type__": "QuantumCircuit", "__value__": "<base64-encoded QPY>"}`, not a string or an array. Check the `__type__` tag, and treat `__value__` as an opaque string. A schema that describes a circuit as a string rejects every valid run.
- **Set `"additionalProperties": false`.** Without this setting, the workflow ignores a misspelled argument name, such as `shotz`, without a warning, and runs with its default value. Declare every argument that the workflow accepts before you enable this setting.
- **Mark arguments as required.** Add each argument that the workflow reads without a default value to `required`.

If the arguments do not match the schema, the error message names the rule and the location of the bad value:

```text
'type' validation failed, schema requires 'integer': rejected value '4096'
path: ['shots']
```

A schema checks the type, shape, and range of each argument. A schema cannot check whether two valid arguments conflict, or whether a circuit fits a backend. Keep those checks in the workflow code.

### Update or remove a schema

- To replace the schema, set a new `arguments_schema` and upload the workflow again.
- To remove the schema, upload the workflow with `arguments_schema={}`.
- If you upload the workflow without `arguments_schema`, the workflow keeps the stored schema.

To see the schema that is in effect, read `my_workflow.arguments_schema`. A value of `None` means that the workflow has no schema.

### Check arguments before you run

Call `validate_arguments()` to check a set of arguments against the schema without starting a job:

```python
my_workflow.validate_arguments({"circuits": [circuit], "shots": 1024})
# {'valid': True}
```

If the arguments are not valid, `validate_arguments()` raises a `QiskitServerlessException` with the same message that `run()` returns for those arguments. If the workflow has no schema, any arguments are valid. A valid result does not guarantee that `run()` succeeds, because `run()` also checks other limits, such as the number of active jobs.

### Schema rules

A managed workflow rejects the upload if the schema breaks any of the following rules:

- The schema can be up to 64 KiB, with up to 64 levels of nesting and up to 200 subschemas.
- References must point inside the same schema, such as `#/$defs/circuit`, and every reference must resolve. References to URLs or files are not allowed.
- The schema must be an object or a boolean.

Also avoid the following problems, which the upload does not catch:

- **Nested quantifiers in `pattern`.** A pattern such as `^(a+)+$` can take a very long time to evaluate. A workflow stops validation after a CPU time limit and rejects the run. Write patterns without a quantifier around a group that already contains one, such as `^ibm_[a-z0-9_]{1,40}$`.
- **`false` as a schema.** The schema `false` rejects every run. To remove validation, use `{}`.

### Troubleshoot a schema

| Problem                                                | Cause                                                                                                                                    |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Every run is rejected on a circuit argument.           | The schema describes the circuit as a string or an array. Describe it as an object with a `__type__` tag.                                |
| Valid runs are rejected.                               | `additionalProperties` is `false`, and the schema does not declare an argument that the run passes. The error `path` names the argument. |
| Invalid runs are not rejected.                         | The workflow has no schema. Check `my_workflow.arguments_schema`.                                                                        |
| No run is checked, although you set a schema.          | The schema was passed as a JSON string. Pass a dictionary.                                                                               |
| The upload fails with a `400` error.                   | The schema breaks one of the [schema rules](#schema-rules).                                                                              |
| The error says that validation took too much CPU time. | A `pattern` has nested quantifiers. Rewrite the pattern.                                                                                 |
