> ## 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.

# Build from your coding agent

> Drive Asteroid from Claude Code, Cursor, or Codex — over MCP or with the SDK.

Your coding agent can build Asteroid workflows, run them, and read the results. Two paths lead there: the
Asteroid MCP server, or the [TypeScript](/sdks/typescript) and [Python](/sdks/python) SDK.

<Tip>
  Give your coding agent [https://docs.asteroid.ai/skill.md](https://docs.asteroid.ai/skill.md) and it can do
  most of the integration on its own. To install the docs as a skill in tooling that supports it:

  ```bash theme={null}
  npx skills add https://docs.asteroid.ai
  ```
</Tip>

## Which path

| You want to                                          | Use |
| :--------------------------------------------------- | :-- |
| Build and debug with a person watching each step     | MCP |
| Ask for things in plain language from your editor    | MCP |
| Sign in through the browser instead of holding a key | MCP |
| Keep workflow definitions in your repository         | SDK |
| Publish a workflow from CI                           | SDK |
| Call Asteroid from your own application              | SDK |

## Connect the MCP server

The MCP server signs you in through the browser. It does not take an API key. See
[Install the MCP server](/mcp/install) for the steps, one Tab per client, and what to do if it does not connect.

Once it is connected, see [What the MCP server can do](/mcp/tools) for the full list of tools, or keep reading
for a worked example.

## Build a workflow over MCP

The loop is `agentCreate`, then `agentExecutePost`, then poll `executionGet`, then `executionActivitiesGet`
when something goes wrong. `agentCreate` publishes the first graph as version 1, so the first execution needs no
publish step. Publishing starts to matter once you make later versions with `workflowCreate`. See
[What the MCP server can do](/mcp/tools) for what each of these tools takes and returns.

### A minimal workflow

Four nodes make a runnable workflow: a start node, one agent node, and two output nodes for the two endings.
Generate a fresh UUID for every node and every transition. An agent node carries `"type": "iris"` in the
payload, and so does a transition the workflow chooses at run time.

```json theme={null}
{
  "organizationId": "<organization UUID from getContext>",
  "name": "Product Price Checker",
  "workflow": {
    "rules": "",
    "settings": {
      "viewport_width": 1440,
      "viewport_height": 900,
      "max_timeout_mins": 15
    },
    "graph": {
      "nodes": [
        {
          "id": "0b8e4b1e-0000-4000-8000-000000000001",
          "name": "Start",
          "type": "start",
          "properties": { "type": "start" }
        },
        {
          "id": "0b8e4b1e-0000-4000-8000-000000000002",
          "name": "Check Price",
          "type": "iris",
          "properties": {
            "type": "iris",
            "instructions": "Go to {{.product_url}}. Find the current price and whether the product is in stock.",
            "model": "asteroid-balanced",
            "capabilities": {
              "browser_use": true,
              "computer_use": false,
              "ask_user_question": false
            }
          }
        },
        {
          "id": "0b8e4b1e-0000-4000-8000-000000000003",
          "name": "Success",
          "type": "output",
          "properties": {
            "type": "output",
            "outcomes": ["success"],
            "schema": {
              "type": "object",
              "properties": {
                "price": { "type": "number", "description": "Current price" },
                "in_stock": { "type": "boolean", "description": "Availability" }
              },
              "additionalProperties": false,
              "required": ["price", "in_stock"]
            }
          }
        },
        {
          "id": "0b8e4b1e-0000-4000-8000-000000000004",
          "name": "Failure",
          "type": "output",
          "properties": { "type": "output", "outcomes": ["failure"] }
        }
      ],
      "transitions": [
        {
          "id": "0b8e4b1e-0000-4000-8000-00000000000a",
          "from": "0b8e4b1e-0000-4000-8000-000000000001",
          "to": "0b8e4b1e-0000-4000-8000-000000000002",
          "type": "outcome_success",
          "require_confirmation": false,
          "properties": { "type": "outcome_success" }
        },
        {
          "id": "0b8e4b1e-0000-4000-8000-00000000000b",
          "from": "0b8e4b1e-0000-4000-8000-000000000002",
          "to": "0b8e4b1e-0000-4000-8000-000000000003",
          "type": "iris",
          "require_confirmation": false,
          "description": "Price and stock status were found.",
          "properties": { "type": "iris" }
        },
        {
          "id": "0b8e4b1e-0000-4000-8000-00000000000c",
          "from": "0b8e4b1e-0000-4000-8000-000000000002",
          "to": "0b8e4b1e-0000-4000-8000-000000000004",
          "type": "iris",
          "require_confirmation": false,
          "description": "The page failed to load, or the product was not found.",
          "properties": { "type": "iris" }
        }
      ],
      "sticky_notes": []
    }
  }
}
```

### Rules the API enforces

* The start node has one outgoing transition. Its type is `outcome_success`, and it points at a node that is
  not an output node.
* An agent node routes with `iris` or `selector` transitions, never with `outcome_success`.
* Every agent node needs a path to an output node for the failure case.
* The shape of the final result goes in the output node's `schema`. A `schema` on a transition is a different
  thing: it defines the data handed to the next node. See [Inputs and outputs](/concepts/inputs-and-outputs).

These rules tie a transition to the node it leaves, so no JSON schema can express them. Validation is the only
place they surface.

### Validate before you create

Check the output schema with `schemaValidate`, and the graph with `workflowSpecValidate`. Pass
`workflowSpecValidate` the same `workflow` you are about to send. It runs the check `agentCreate` runs, and
returns every issue at once instead of one per round trip.

Each issue carries a `severity` and a `path` to the field at fault. An `error` blocks creation. A `warning` is
advice. For a workflow that already exists, `workflowValidate` is the same check.

### Run it and read the result

Start the run with `agentExecutePost`. Pass the `agentId` and any `inputs` the instructions reference. Then
poll `executionGet` every 5 to 10 seconds until the status is `completed`, `failed`, or `cancelled`.

A single-node execution usually finishes in 30 to 60 seconds. A larger graph takes minutes. When an execution ends badly,
read `executionActivitiesGet` for the step-by-step timeline.

<Warning>
  Poll until the status is terminal. Do not loop while the status equals `running` — that exits the moment the
  workflow pauses to ask you something. Handle `paused_by_agent` and `awaiting_confirmation` yourself. See
  [Executions and statuses](/concepts/executions).
</Warning>

## Point your coding agent at the docs

Two URLs teach a coding agent how Asteroid works.

| URL                                           | What it is for                                                                                                                   |
| :-------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| [skill.md](https://docs.asteroid.ai/skill.md) | The capability contract. What Asteroid does, what each operation needs, and what the constraints are. Read this to plan actions. |
| [llms.txt](https://docs.asteroid.ai/llms.txt) | An index of every docs page with its description. Read this to find the right page.                                              |

Hand your coding agent `skill.md` and it can do most of the work on its own. Add `llms.txt` when it needs to
find a page it has not seen.

## Build with the SDK instead

The SDK holds an API key and runs anywhere — your laptop, your server, your CI job. Get a key from
[platform.asteroid.ai/keys](https://platform.asteroid.ai/keys), and set up the client with
[SDK setup](/sdks/setup).

The loop is create, validate, publish, execute.

| Step                                      | Function                 |
| :---------------------------------------- | :----------------------- |
| Create the workflow and its first version | `agentCreate`            |
| Create a later version                    | `agentWorkflowsCreate`   |
| Check a version before you save it        | `agentWorkflowsValidate` |
| Make a version the one new executions use | `agentWorkflowsPublish`  |
| Run one specific version                  | `agentWorkflowsExecute`  |

`agentCreate` publishes its first version, so a fresh workflow runs immediately. Later versions start
unpublished. Publish one, or run it directly with `agentWorkflowsExecute` to test it first.

<CodeGroup>
  ```ts create-agent.ts theme={null}
  import {
    client,
    agentCreate,
    agentWorkflowsCreate,
    agentWorkflowsValidate,
    agentWorkflowsPublish,
    agentWorkflowsExecute,
  } from 'asteroid-odyssey';
  import graph from './graph.json' with { type: 'json' };

  client.setConfig({
    headers: { 'X-Api-Key': process.env.ASTEROID_API_KEY! },
  });

  const settings = { viewport_width: 1440, viewport_height: 900, max_timeout_mins: 15 };

  // 1. Create the agent. Its first version is published for you.
  const { data: agent, error: createError } = await agentCreate({
    body: {
      organizationId: process.env.ASTEROID_ORG_ID!,
      name: 'Product Price Checker',
      workflow: { rules: '', graph, settings },
    },
  });
  if (createError) throw new Error(JSON.stringify(createError));
  const agentId = agent!.id;

  // 2. Validate the next version before you save it.
  const { data: validation } = await agentWorkflowsValidate({
    path: { agentId },
    body: { rules: '', graph, settings },
  });
  const blocking = validation?.issues.filter((i) => i.severity === 'error') ?? [];
  if (blocking.length > 0) throw new Error(JSON.stringify(blocking));

  // 3. Save it.
  const { data: created } = await agentWorkflowsCreate({
    path: { agentId },
    body: { rules: '', graph, settings },
  });
  const workflowId = created!.workflowId;

  // 4. Run that exact version before anyone else gets it.
  const { data: run } = await agentWorkflowsExecute({
    path: { agentId, workflowId },
    body: { inputVariables: { product_url: 'https://example.com/widget' } },
  });
  console.log('execution', run!.executionId);

  // 5. Publish it, so new runs use it.
  const { data: published } = await agentWorkflowsPublish({ path: { agentId, workflowId } });
  console.log('published version', published!.version);
  ```

  ```python create_agent.py theme={null}
  import json
  import os

  from asteroid_odyssey import ApiClient, Configuration
  from asteroid_odyssey.api.agents_api import AgentsApi
  from asteroid_odyssey.api.workflows_api import WorkflowsApi

  with open("graph.json") as f:
      graph = json.load(f)

  settings = {"viewport_width": 1440, "viewport_height": 900, "max_timeout_mins": 15}
  spec = {"rules": "", "graph": graph, "settings": settings}

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

  with ApiClient(config) as api_client:
      agents_api = AgentsApi(api_client)
      workflows_api = WorkflowsApi(api_client)

      # 1. Create the agent. Its first version is published for you.
      agent = agents_api.agent_create({
          "organizationId": os.environ["ASTEROID_ORG_ID"],
          "name": "Product Price Checker",
          "workflow": spec,
      })
      agent_id = agent.id

      # 2. Validate the next version before you save it.
      validation = workflows_api.agent_workflows_validate(agent_id, spec)
      blocking = [i for i in validation.issues if i.severity == "error"]
      if blocking:
          raise SystemExit(blocking)

      # 3. Save it.
      created = workflows_api.agent_workflows_create(agent_id, spec)
      workflow_id = created.workflow_id

      # 4. Run that exact version before anyone else gets it.
      run = workflows_api.agent_workflows_execute(
          agent_id,
          workflow_id,
          {"inputVariables": {"product_url": "https://example.com/widget"}},
      )
      print("execution", run.execution_id)

      # 5. Publish it, so new runs use it.
      published = workflows_api.agent_workflows_publish(agent_id, workflow_id)
      print("published version", published.version)
  ```
</CodeGroup>

Two details save you a debugging session:

* `agentExecutePost` names its variables `inputs`. `agentWorkflowsExecute` names them `inputVariables`.
* JSON and the TypeScript SDK use camelCase. The Python SDK exposes the same fields as snake\_case attributes,
  so `executionResult` reads as `execution.execution_result`.

## Live environments

Sometimes you want a browser to drive directly, outside of a workflow run, over the Chrome DevTools Protocol.
See [Live environments](/concepts/live-environments).

## Next

<CardGroup cols={2}>
  <Card title="Write good instructions" icon="pen-line" href="/build/instructions">The craft inside each node</Card>
  <Card title="Test, iterate, publish" icon="check-check" href="/build/test-and-publish">Get a draft ready, then make it live</Card>
  <Card title="Call a workflow from your code" icon="code" href="/integrate/call-an-agent">Execute, poll, and read the result</Card>
  <Card title="TypeScript SDK" icon="file-code" href="/sdks/typescript">Client setup and the common functions</Card>
</CardGroup>
