> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asteroid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Inputs and outputs

> The contract between a workflow and the code that calls it: declared inputs, outcome labels, and the result schema.

A workflow has a contract with the code that calls it.

Your code sends **inputs**. The workflow returns an **outcome label** and a **result**, along with execution metadata. Nothing else crosses the boundary.

The graph behind the contract can change freely. The contract itself is what your integration depends on.

```mermaid theme={null}
flowchart LR
    C[Your code] -->|inputs| A[Agent]
    A -->|outcome + result| C
```

***

## Declared inputs

A workflow declares the inputs it accepts. Each one has four parts.

| Part          | What it means                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------------ |
| Name          | The key your code sends, and the variable the instructions read.                                 |
| Type schema   | A JSON Schema fragment. It says whether the value is a string, a number, an array, or an object. |
| Required flag | Whether an execution can start without this value.                                               |
| Default       | The value used when an optional input is missing.                                                |

Your code sends the values in the `inputs` object on the execute call.

```json theme={null}
{
  "inputs": {
    "patientName": "Jane Doe",
    "dateOfBirth": "1984-02-11",
    "appointmentTypes": ["annual physical", "follow-up"]
  }
}
```

### Rules

* A name matches `[a-zA-Z_][a-zA-Z0-9_]*` and is unique within the workflow.
* Values are validated against the declared schema when the execution starts.
* A required input with no value fails the execution immediately. It does not fail halfway through.
* An omitted optional input falls back to its default. With no default, it falls back to nothing.

<Tip>
  The workflow's deploy page lists the exact set of inputs, at `https://platform.asteroid.ai/agents/<agent-id>/deploy`. Read the names there rather than guessing them from the instructions.
</Tip>

### Inputs reach the instructions as variables

An input named `patient_name` appears in the instructions as `{{.patient_name}}`.

```text theme={null}
Navigate to the patient search page:
1. Enter {{.patient_name}} into the search bar
2. Select the matching result
3. Fill {{.diagnosis}}
4. Click Submit
```

Nested JSON reads with a dot path, as `{{.user.name}}`. See [Nodes](/concepts/nodes#variables-in-instructions).

***

## Outcome labels

An outcome label answers one question: how did this execution finish?

Every [output node](/concepts/nodes#output-node) lists the labels it can produce. The label the execution lands on comes back to your code, and your code branches on it.

The default pair is `success` and `failure`. Replace it with labels that name your real end states.

| Kind of workflow | Labels worth declaring                                                        |
| ---------------- | ----------------------------------------------------------------------------- |
| Checkout         | `purchase_completed`, `payment_failed`, `item_unavailable`, `session_expired` |
| Scheduling       | `slots_found`, `no_slots_available`, `login_required`                         |
| Review           | `approved`, `rejected`, `needs_review`                                        |

### Rules

* A label matches `^[a-z0-9_]{1,30}$`. Lowercase letters, digits, and underscores only, 1 to 30 characters.
* Spaces, hyphens, and capitals are rejected when you save the node.
* Each output node declares at least 1 label and at most 20.
* Labels are unique within a node.

### Choosing labels

* Name the end state, not the feeling. Write `payment_completed`, not `done`.
* Declare every realistic end state, including the ones you would rather not meet.
* Keep the same names across workflows that do similar work.
* Test that an execution can actually reach each label.

<Warning>
  Outcome labels are the contract. Treat a new label as a breaking change to your callers, and ship it as a new [version](/concepts/versions).
</Warning>

***

## Result schema

Alongside the label, an output node can return structured data. You define the shape as a JSON Schema, and the workflow fills it in.

The format follows [OpenAI Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs), a subset of JSON Schema.

```json theme={null}
{
  "type": "object",
  "properties": {
    "product_name": {
      "type": "string",
      "description": "The name of the product"
    },
    "price": {
      "type": "number",
      "description": "The product price"
    },
    "in_stock": {
      "type": "boolean",
      "description": "Whether the product is available"
    }
  },
  "additionalProperties": false,
  "required": ["product_name", "price", "in_stock"]
}
```

### Rules

* The root is an object with a `properties` map.
* Every object type includes `"additionalProperties": false`.
* List every property in the `required` array.
* Use `description` on each property. The workflow reads it to decide what to put there.
* `oneOf`, `allOf` and `$ref` are rejected. `anyOf` and `enum` are allowed.
* The schema is at most 15,000 characters when serialized as JSON.

Check a schema with `schemaValidate` before you put it on an output node. See
[Build from your coding agent](/build/from-your-coding-agent).

Add a schema when downstream systems expect defined fields, or when the execution extracts several values. Start with labels alone, and add a schema once you know what you need back.

<Note>
  A result schema is not a [transition](/concepts/transitions#passing-data-across-a-transition) schema.

  The output node's schema defines the execution's final result. A transition's schema defines the payload handed to the next node, which reads it as `{{.output}}`. A transition payload never reaches your code.
</Note>

***

## What comes back

`GET /executions/{executionId}` returns the execution. The contract lives under `executionResult`.

| Field         | Type   | Use it for                                        |
| ------------- | ------ | ------------------------------------------------- |
| `outcome`     | string | Branching. One of the workflow's declared labels. |
| `result`      | object | The data, matching the result schema.             |
| `reasoning`   | string | Logs and support tickets. Never branch on it.     |
| `createdAt`   | string | When the result was recorded.                     |
| `id`          | string | The result's own identifier.                      |
| `executionId` | string | The execution this result belongs to.             |

```json theme={null}
{
  "id": "0a19c3f6-2b41-4d0e-9f77-1c8a5d2e6b30",
  "status": "completed",
  "executionResult": {
    "outcome": "no_slots_available",
    "reasoning": "The portal returned an empty calendar for the requested week.",
    "result": { "slots": [], "nextAvailable": "2026-04-08" },
    "createdAt": "2026-03-27T11:04:12Z"
  },
  "duration": 148.2,
  "inputs": { "patientName": "Jane Doe" },
  "recordingUrl": "https://…",
  "platformUrl": "https://platform.asteroid.ai/agents/…/executions/…"
}
```

<Info>
  `executionResult` is absent when an execution ends before it reaches an output node. Check that it exists before you read it.
</Info>

***

## Branch on outcome, not on status

The status tells you the execution finished. The outcome tells you what happened.

`completed` means the workflow reached an output node. It does not mean the work succeeded. An execution that correctly reports "this patient has no coverage" is `completed`. So is an execution that booked the appointment. The outcome label is what separates them.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { client, executionGet } from 'asteroid-odyssey';

  client.setConfig({
    baseUrl: 'https://odyssey.asteroid.ai/agents/v2',
    headers: { 'X-Asteroid-Agents-Api-Key': process.env.ASTEROID_API_KEY! },
  });

  const { data: execution, error } = await executionGet({
    path: { executionId: process.env.EXECUTION_ID! },
  });
  if (error) throw new Error(JSON.stringify(error));

  const { outcome, result } = execution.executionResult ?? {};

  switch (outcome) {
    case 'slots_found':
      await saveSlots(result);
      break;
    case 'no_slots_available':
      await scheduleRetry(result.nextAvailable);
      break;
    default:
      await escalate(execution.platformUrl, outcome);
  }
  ```

  ```python Python theme={null}
  import os

  from asteroid_odyssey import ApiClient, Configuration
  from asteroid_odyssey.api.execution_api import ExecutionApi

  config = Configuration(api_key={"ApiKeyAuth": os.environ["ASTEROID_API_KEY"]})

  with ApiClient(config) as api_client:
      execution = ExecutionApi(api_client).execution_get(
          execution_id=os.environ["EXECUTION_ID"],
      )

  result = execution.execution_result

  if result is None:
      escalate(execution.platform_url, None)
  elif result.outcome == "slots_found":
      save_slots(result.result)
  elif result.outcome == "no_slots_available":
      schedule_retry(result.result["nextAvailable"])
  else:
      escalate(execution.platform_url, result.outcome)
  ```

  ```bash cURL theme={null}
  curl "https://odyssey.asteroid.ai/agents/v2/executions/$EXECUTION_ID" \
    -H "X-Asteroid-Agents-Api-Key: $ASTEROID_API_KEY" \
    | jq '.executionResult.outcome, .executionResult.result'
  ```
</CodeGroup>

<Info>
  JSON and the TypeScript SDK use camelCase, as in `executionResult`. The Python SDK exposes the same field as `execution.execution_result`.
</Info>

***

## Related

<CardGroup cols={2}>
  <Card title="Call a workflow from your code" icon="plug" href="/integrate/call-an-agent" horizontal>Send inputs, wait for the execution, read the result</Card>
  <Card title="Executions and statuses" icon="hourglass" href="/concepts/executions" horizontal>Every status an execution passes through, and which ones are terminal</Card>
  <Card title="Nodes" icon="box" href="/concepts/nodes" horizontal>Where labels and result schemas are configured</Card>
  <Card title="Versions and publishing" icon="git-commit-horizontal" href="/concepts/versions" horizontal>Ship a contract change without breaking callers</Card>
</CardGroup>
