---
question: When does a workflow get a POST route?
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 a workflow class in an in-engine module, and the project's API running (`abi dev up`).
next:
  - /capabilities/build/workflows/run-a-workflow
  - /capabilities/build/workflows/test-a-workflow
  - /capabilities/build/applications/api
---

# Expose as an API

You do not register the route yourself. When the API starts, it calls each loaded 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. The route takes the project's API key, like the agent routes.

## Web UI

Not applicable. Nexus has no screen that publishes a workflow. The route comes from the Python class: see the Python steps.

## 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()`.

### 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](/user-guide/build/applications/api#authentication).

## Config

Not applicable. `config.yaml` has no per-workflow route setting. Its `api:` block holds what every route shares, such as `cors_origins`.
