---
question: Which HTTP routes reach a workflow, and when does a class get one?
kind: task
via_web: na
via_cli: na
via_python: available
via_api: available
via_config: na
ref_python: as_api(router)
ref_api: POST /workflows/{workflow_name}
prereq: You need the project's API running (`abi dev up`) and a workflow class in a module. Nexus' `/api/workflows` needs ABI 3.x and a NATS project.
next:
  - /v2/capabilities/build/workflows/run-a-workflow
  - /v2/capabilities/build/workflows/test-a-workflow
  - /v2/capabilities/build/applications/api
---

> **You are reading the v2.x docs (engine modules). New projects use v3.** [Read this page in v3](/capabilities/build/workflows/expose-as-an-api) · [Migrate from 2.x to 3.0](/capabilities/upgrade/migrate-to-3)

# Expose as an API

Two HTTP surfaces reach a workflow:

| Route | Reaches | Auth | Runs |
|---|---|---|---|
| `POST /workflows/<slug>` on the engine API (ABI 2.x and 3.x) | A workflow class whose `run` is live and typed | The engine's `ABI_API_KEY` | `run` in process; the answer is the result |
| `/api/workflows` on Nexus (ABI 3.x, NATS project) | Every durable workflow of every module: `@workflow` methods and published classes (`workflow.<slug>`, `pipeline.<slug>`) | `ABI_API_KEY`, or a platform super admin's session | A durable run, recorded and audited |

You do not register either route. When the API starts, it calls each loaded engine workflow's `as_api(router)`, inherited from `Expose`. The default mounts `POST /workflows/<class_name_in_snake_case>` as soon as `run(parameters)` is a real method whose parameter is a Pydantic model. `/api/workflows` lists what the modules declare, as soon as they run.

## Web UI

Not applicable. Nexus has no screen that publishes a workflow: the routes come from the code. On ABI 3.x in a NATS project, Nexus starts durable workflows from System › Jobs: see [Run a workflow](/v2/capabilities/build/workflows/run-a-workflow?via=web).

## CLI

Not applicable. No command publishes a route. `abi new workflow` writes a class whose `as_api` is already inherited; the route appears once `run` is implemented.

## Python

### Implement `run`

The route exists only when `run` is live and typed:

```python title="workflows/CustomersWorkflow.py"
class CustomersWorkflowParameters(WorkflowParameters):
    entity_id: Annotated[str, Field(..., description="The ID of the entity to summarize")]


class CustomersWorkflow(Workflow[CustomersWorkflowParameters]):
    """Summarize an entity from the knowledge graph."""

    def run(self, parameters: CustomersWorkflowParameters) -> dict:
        ...
```

A `run` that is only `pass` or `raise NotImplementedError`, or that has no Pydantic parameters model, is not published. The class docstring becomes the route's description.

### Know the path

The path is the class name in snake case: `CustomersWorkflow` becomes `POST /workflows/customers_workflow`. The body is the parameters model. A Pydantic result is sent as its `model_dump()`.

The same slug names its durable workflow, `workflow.customers_workflow`, which Nexus' `/api/workflows` starts on ABI 3.x in a NATS project. A class whose `run` is `async` is not published as a durable workflow.

### Override `as_api` for another surface

Only when the default POST is wrong. Routes you add land under `/workflows`, with the same API key check:

```python title="workflows/CustomersWorkflow.py"
from fastapi import APIRouter


class CustomersWorkflow(Workflow[CustomersWorkflowParameters]):
    ...

    def as_api(self, router: APIRouter, route_name: str = "", name: str = "",
               description: str = "", description_stream: str = "", tags=None) -> None:
        @router.get("/customers/{entity_id}")
        def summarize(entity_id: str) -> dict:
            return self.run(CustomersWorkflowParameters(entity_id=entity_id))
```

If your `as_api` adds no route, the API falls back to the default POST. The default is `naas_abi_core.utils.process_api.register_run_route`.

## REST API

### Find the route

Restart the API, then open `/docs` on the API port (`http://localhost:9879/docs` with Docker Compose; `abi dev ports` prints it with `abi dev up`). The route is in the Workflows group, which shows only when at least one workflow is published.

### Call it

```bash
curl -X POST "http://localhost:9879/workflows/customers_workflow" \
  -H "Authorization: Bearer $ABI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"entity_id": "42"}'
```

The key is `ABI_API_KEY` in the project's `.env`. A wrong key answers 401. The other auth rules are on the [REST API](/v2/capabilities/build/applications/api#authentication).

### Or start it through Nexus

ABI 3.x, in a NATS project. Any durable workflow, including a published class, starts with `POST /api/workflows/{module_id}/{name}/runs`, where `name` is the `@workflow` name or `workflow.<slug>`:

```bash
curl -s -X POST "http://localhost:9879/api/workflows/my_module/workflow.customers_workflow/runs?wait=30" \
  -H "Authorization: Bearer $ABI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"entity_id": "42"}'
```

| Route | Does |
|---|---|
| `GET /api/workflows` | Lists the workflows, with their input and output schemas, limits and triggers. |
| `POST /api/workflows/{module_id}/{name}/runs` | Starts a run: `?wait=0..120`, `Idempotency-Key`, 422 with the schema faults. |
| `GET /api/workflows/{module_id}/runs/{run_id}` | Reads a run, with its steps and result. |
| `POST /api/workflows/{module_id}/runs/{run_id}/cancel` | Cancels a running run. |

The key stands for the operator: starts and cancels are written to the audit log with `via: api_key`. A user who is not a platform super admin gets 403. These routes are not in the generated API reference. The steps are on [Run a workflow](/v2/capabilities/build/workflows/run-a-workflow?via=api) and [Monitor and cancel a run](/v2/capabilities/build/workflows/monitor-and-cancel-a-run?via=api).

## Config

Not applicable. `config.yaml` has no per-workflow route setting. Its `api:` block holds what every route shares, such as `cors_origins`. On ABI 3.x, `nats.workflows.bridge: false` keeps the classes off `/api/workflows`; their kernel routes stay.
