# Starting flows over the API

> Running and debugging · https://spoletta.com/docs/api

Other systems can start a flow too: a helpdesk when a ticket comes in, a form when someone sends it, a script at the end of a deploy. They send a request with an API key and what the flow should work on, and read back what the run made of it.

## Making an API key

1.  **Open the API keys tab**
    
    Open **Settings** and pick **API keys**. Anyone signed in can make keys.
    
2.  **Name the key and make it**
    
    Name it after what will use it, such as *Helpdesk*, and press **New key**.
    
3.  **Copy it now**
    
    The key is shown only this once. Press **Copy** and keep it where the other system keeps its secrets. The list shows only its first characters, when it was made and when it was last used.
    

A key is its maker's. A run it starts is started by that person and uses their MCP connections, and the key can start only the flows they may run: someone who only decides a flow's reviews can't start it this way.

> **Watch out:** Press **Delete** beside a key and whatever uses it is turned away from then on. Removing its owner from the instance stops it too. See [Settings and access](https://spoletta.com/docs/admin.md).

## What a flow takes

A run started with the Run button or a schedule takes no input. A run started over the API is given its inputs. These are the objects on the flow's top board that some process takes and no process generates, the ones that would otherwise start empty. Every input is required, and each is given by its name, as what a person would enter of it:

| The object | What you send |
| --- | --- |
| Has attributes | A JSON object with exactly its attributes |
| Is a list | An array of those objects |
| Has no attributes | Text |

An input with states may be given in one, under `states`. A flow that starts from a person filling something in, or from a step fetching it, takes no inputs, and its request needs no body.

> **A flow meant to be called:** Have its first process take the request object, with nothing generating it, and connect to the End node what the caller should get back. See [What happens in a run](https://spoletta.com/docs/engine.md).

A flow can't be started over the API if two of its inputs share a name, or if an input holds a file.

## Where to find a flow's details

The flow's settings panel has an **API** section. It shows the flow's address, each input and what it takes, and a curl command that starts it. If the flow can't be started this way, it says why. The same description comes back as JSON Schema from `GET /v1/flows/<id>`, with the key.

## Starting a run

Each flow has its own address. Send a `POST` to `/v1/flows/<id>/runs` with the key as `Authorization: Bearer <key>`. For a flow that triages a ticket it is given:

```sh
curl -X POST https://spoletta.example.com/v1/flows/12/runs \
  -H 'Authorization: Bearer <key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "inputs": {
      "Ticket": { "subject": "Sign-in fails", "body": "Since this morning the page stays blank." }
    },
    "states": { "Ticket": "new" }
  }'
```

The answer is the run, as reading it returns, and its `Location` header is where to read it from then on.

| Status | What it means |
| --- | --- |
| 201 | The run started. |
| 200 | The request was sent before with the same Idempotency-Key; this is the run it started. |
| 409 | The run ended at once as a duplicate, or the Idempotency-Key started a run of another flow. |
| 400 | The body isn't what the flow takes; the answer says what is wrong. |
| 401 | No key, or a key that was deleted or whose owner was removed. |
| 403, 404 | The key's owner may not run the flow, or there is no such flow. |
| 422 | The flow can't be started over the API, and the answer says why. |

## Reading the result

`GET /v1/runs/<id>`, with the key, returns what the run came to so far:

```json
{
  "id": 87,
  "flow": 12,
  "status": "done",
  "url": "https://spoletta.example.com/#/flows/12/runs/87",
  "startedAt": "2026-10-20T09:14:03Z",
  "finishedAt": "2026-10-20T09:15:41Z",
  "outcomes": {
    "Triage": { "state": "bug", "value": { "summary": "Sign-in page blank since the morning", "team": "Web" } }
  },
  "errors": []
}
```

| Field | What it holds |
| --- | --- |
| `status` | `running` until it ends, then `done` or `failed`; `terminated` for a duplicate |
| `url` | The run's page, for a person to open |
| `outcomes` | What the run left of the objects connected to the End node, by name, once they exist: each one's value and, if it is in one, its state |
| `errors` | The steps that failed, each with its process and its error |

Poll it until the status is no longer running.

## Retries and duplicates

A system that sends again when it isn't sure the first request arrived should send an `Idempotency-Key` header, such as the ID of the event it is about. A request with a key already used returns the run the first one started instead of starting another.

A persistent input with a unique attribute guards against the same event sent twice in another way: if another run already handled its unique value, the new run ends at once as a duplicate and the answer is 409. So an event sent twice is handled once. See [Lists and items](https://spoletta.com/docs/lists.md).

## On the run page

A run started over the API shows a first step, *Given through the API*, with what it was given, and the run panel says *Started … through the API key Helpdesk*. It re-runs like any other run. See [Running a flow](https://spoletta.com/docs/running.md).
