> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://infonite.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://infonite.dev/_mcp/server.

# The execution lifecycle

An execution is asynchronous from the first millisecond: the call that starts it answers `202` and hands you an `execution_id`, and the work happens behind that. Everything on this page answers the question that follows — *where is it now, and what do I do about it?*

---

## Three fields, three different questions

This is the part worth reading slowly, because the three are easy to confuse and they do not mean the same thing.

| Field                       | Answers                              | Values                                                                                                                                        |
| :-------------------------- | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| **`status_code`**           | *What kind of situation is this?*    | A closed set of **10 families**. This is what you branch on.                                                                                  |
| **`status_reason`**         | *Why, exactly?*                      | One of **many** precise codes. Each belongs to exactly one family. This is what you log, show to support, and use for the fine-grained cases. |
| **`authentication_status`** | *Did we get into the source at all?* | A separate axis, reported independently of the two above.                                                                                     |

Plus one convenience field that sits above all of them:

**`is_closed` is the only signal that means finished.** `true` — the run is over and nothing about it will change. `false` — it is alive, whatever the other fields say. It is in every state response, and it is what [Execution Status](api:HEAD/executions/handler/v1/\{execution_id}) turns into a `200`.

**`the four together, in a state response`**

```json title="the four together, in a state response"
{
  "is_closed": true,
  "status_code": "PARTIAL",
  "status_reason": "PARTIAL",
  "authentication_status": "AUTH_OK"
}
```

**A reason always implies its code**, never the other way round. `INCORRECT_CREDENTIALS` is always `AUTH_ERROR`; `AUTH_ERROR` might be any of nine reasons. Branch on the code, and reach for the reason when the code is not specific enough to decide — which, for the failures, it usually is not.

---

## The ten families

```
   ACCEPTED ─▶ WAITING ─▶ RUNNING ─┬─▶ COMPLETED · PARTIAL · FAILED
                                   │
                                   ├─▶ ACTION_REQUIRED ─▶ (you answer) ─▶ RUNNING
                                   │                   └─▶ ABORTED  (nobody answered in time)
                                   │
                                   └─▶ AUTH_ERROR · UNHANDLED_AUTH_ERROR
                                       CONFIGURATION_ERROR · TEMPORARY_ERROR · ABORTED
```

| `status_code`          | Closed? | Meaning                                                                                  | What it asks of you                                                   |
| :--------------------- | :------ | :--------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `ONGOING`              | no      | Working.                                                                                 | Nothing. Wait.                                                        |
| `ACTION_REQUIRED`      | no      | Waiting for an answer from your customer.                                                | [Answer it](/direct-executions/challenges), within the deadline.      |
| `COMPLETED`            | **yes** | Every requested feature answered.                                                        | Read the results.                                                     |
| `PARTIAL`              | **yes** | Some features answered, others did not.                                                  | Read the results, and decide whether what arrived is enough.          |
| `FAILED`               | **yes** | Nothing could be retrieved.                                                              | Look at the reason before retrying.                                   |
| `ABORTED`              | **yes** | Cancelled — by you, by a timeout, or because a person was needed and none was available. | Depends on who cancelled; see below.                                  |
| `AUTH_ERROR`           | **yes** | The login was rejected, in a way the source explained.                                   | **Talk to your customer.** Do not retry the same values.              |
| `UNHANDLED_AUTH_ERROR` | **yes** | The login failed in a way we could not classify.                                         | Retry later; if it persists, contact support with the `execution_id`. |
| `CONFIGURATION_ERROR`  | **yes** | The request itself was not valid.                                                        | Fix your integration. Retrying identically will fail identically.     |
| `TEMPORARY_ERROR`      | **yes** | Something on the way was unavailable.                                                    | Retry later. Nothing is wrong with your request.                      |

**`PARTIAL` is a finished execution, not a failing one.** It is what you get when a bank answers for accounts and times out for investments. What arrived is real and final; what did not will not appear later. Read `features` to see which is which, and treat the gap as a business decision — *is this enough to act on?* — rather than as an error to retry blindly.

---

## Every reason, by family

**Each heading below is a `status_code`** — one of the ten families in the table above — and the rows under it are the `status_reason` values that belong to it. That is the relationship in one sentence: *the family is the situation, the reason is the granular cause.*

So a state reading `status_code: AUTH_ERROR` will always carry one of the reasons in the `AUTH_ERROR` section, and never a reason from another one. If your integration only handles the ten families it will work; it will just be less specific than it could be, and the failures are exactly where that specificity is worth having.

### `status_code: ONGOING` — it is working

| `status_reason` | What is happening                                                                                                                  |
| :-------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `ACCEPTED`      | Queued. Nothing has started yet.                                                                                                   |
| `WAITING`       | Picked up; the engine is warming up.                                                                                               |
| `RUNNING`       | Logged in and extracting.                                                                                                          |
| `ASYNC_WAIT`    | Waiting on the source to produce something on its own schedule. Slow by design, not stuck — the credit registry is the usual case. |

### `status_code: ACTION_REQUIRED` — it is waiting for you

| `status_reason`  | What is happening                                                                                                                                                                                                                      |
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MFA_REQUIRED`   | A strong-authentication challenge: a code, a push, a confirmation.                                                                                                                                                                     |
| `INPUT_REQUIRED` | Any other field the engine turned out to need — typically one of its `lazy_fields`, which you can [send up front](/direct-executions/quickstart#lazy-does-not-mean-you-cannot-send-it) when you know the rule, and then it never asks. |

Both publish a `form_schema` describing exactly what to collect. See [Challenges & MFA](/direct-executions/challenges).

### `status_code: AUTH_ERROR` — we could not get in

The most actionable family, because almost every value here means *a person has to do something*, and none of them is fixed by retrying.

| `status_reason`         | What the source said                                                                                         | What to do                                                                                                                   |
| :---------------------- | :----------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| `INCORRECT_CREDENTIALS` | The credentials were rejected.                                                                               | Ask your customer for them again. **Do not retry**: institutions count failed attempts and enough of them locks the account. |
| `INCORRECT_MFA`         | The challenge was answered wrongly.                                                                          | Start again and collect the code carefully.                                                                                  |
| `BLOCKED_USER`          | The institution has blocked the access.                                                                      | Only your customer can unblock it, with their institution.                                                                   |
| `FRIEZED_CREDENTIALS`   | The access is temporarily frozen.                                                                            | Wait, or have the customer resolve it with the institution.                                                                  |
| `CHANGE_PASSWORD`       | The institution requires a password change before anyone can log in.                                         | The customer changes it, then you run again with the new one.                                                                |
| `MANUAL_INTERVENTION`   | The source is asking the person to do something in its own channel first — accept a notice, complete a step. | The customer does it; then run again.                                                                                        |
| `INCOMPATIBLE_ACCESS`   | This access is not usable through the channel this engine speaks to.                                         | A different engine for the same institution may fit; check the catalogue.                                                    |
| `DUPLICATED_SESSION`    | The source refused because another session is already open for that user.                                    | Retry after a few minutes.                                                                                                   |

**A tokenized execution that ends in this family also locks the token.** The stored credentials are the ones that were rejected, so check [Token Status](api:GET/executions/t10n/\{token_id}) and mint a new token from a fresh parametrized execution rather than retrying the locked one.

**`UNHANDLED_AUTH_ERROR` is its own family**, not a member of this one, and it has a single reason of the same name: the login failed in a way the engine could not classify. Treat it as transient, and if the same access keeps producing it, send us the `execution_id`.

### `status_code: CONFIGURATION_ERROR` — the request was not valid

| `status_reason`               | What went wrong                                                                                                 |
| :---------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `BAD_CONFIGURATIONS`          | A feature configuration the engine does not accept — often a date range outside its limits.                     |
| `INCORRECT_PARAMETERS_FORMAT` | The `parameters` did not match what the engine's form declares.                                                 |
| `INCORRECT_RESUME_FORMAT`     | The body you sent to resume did not match the `form_schema` that was published.                                 |
| `ENCRYPTION_ERROR`            | An encrypted value could not be opened — wrong key, wrong scheme for its size, mangled Base64.                  |
| `T10N_FORBIDDEN`              | Your application is not allowed to tokenize, and you asked for a ticket.                                        |
| `T10N_REJECTED`               | The token is revoked or broken.                                                                                 |
| `T10N_NOT_AVAILABLE`          | This engine does not support tokenization.                                                                      |
| `T10N_EXCEPTION`              | The stored credentials could not be read. Mint a new token.                                                     |
| `BAD_PROXY_CONFIGURATION`     | The routing this application uses for this engine is not usable. Contact support — this one is ours, not yours. |

The state carries `configurations_errors` and `parameters_errors` naming the offending fields, which is where to look before changing anything.

### `status_code: ABORTED` — it was cancelled

| `status_reason`                  | Who cancelled, and why                                                                           |
| :------------------------------- | :----------------------------------------------------------------------------------------------- |
| `CLIENT_CANCELLED`               | You did, with [Abort](api:DELETE/executions/handler/v1/\{execution_id}/abort). Not a failure.    |
| `USER_CANCELLED`                 | Your customer did, from an interactive surface. Not a failure.                                   |
| `ACTION_TIMEOUT`                 | A challenge was published and nobody answered in time. Not a failure — a missed window.          |
| `TIMEOUT`                        | The execution exceeded its `execution_timeout`.                                                  |
| `CUSTOMER_INTERVENTION_REQUIRED` | The source asked for something and the execution was started declaring that nobody could answer. |
| `SYSTEM_CANCELLED`               | The platform stopped it.                                                                         |

### `status_code: TEMPORARY_ERROR` — try again later

| `status_reason`                                                                             | What it means                                                                |
| :------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------- |
| `OUT_OF_SERVICE`                                                                            | The source itself is unavailable — maintenance, an outage, a nightly window. |
| `ENGINE_UNAVAILABLE`                                                                        | The engine is not serving right now.                                         |
| `ALREADY_EXECUTING`                                                                         | Another execution is already running for that access.                        |
| `ENGINE_BANNED` · `AUTO_CAPTCHA_ERROR` · `PROXY_ERROR` · `NETWORK_ERROR` · `INTERNAL_ERROR` | The run could not be set up. These are ours, and they are transient.         |

**Nothing in this family means your request was wrong.** A sensible retry — minutes for the setup ones, longer for `OUT_OF_SERVICE` — is the right response, with a cap so a source that is down all day does not become an infinite loop.

### `status_code: COMPLETED` · `PARTIAL` · `FAILED`

One reason each, carrying the same name as its family — `COMPLETED`, `PARTIAL`, `FAILED`. When the run ends well there is nothing more to say at this level; the detail lives one level down, in the features.

---

## The fourth axis: `authentication_status`

Reported separately from everything above, and it answers a question the execution status cannot: **did we get into the source?**

| Value           | Meaning                                                                                                       |
| :-------------- | :------------------------------------------------------------------------------------------------------------ |
| `not-performed` | The engine has not tried yet.                                                                                 |
| `AUTH_OK`       | The credentials were accepted. The extraction is under way — the execution status stays `ONGOING`.            |
| anything else   | The login was rejected, with one of the `AUTH_ERROR` reasons above. The execution ends with that same reason. |

The distinction is the one that decides what you do next:

* **We could not get in** → a conversation with your customer. Their credentials, their bank, their action.
* **We got in and the extraction fell short** (`PARTIAL`, `FAILED`, `TEMPORARY_ERROR` after `AUTH_OK`) → nothing to ask anyone. Retry later, or take what arrived.

The [`auth_ended` webhook](/direct-executions/webhooks) delivers this the moment it is known, which is usually well before the execution ends — that is what lets you start the *"ask for the credentials again"* conversation early.

---

## One level down: the features

`features` carries one entry per feature you requested, each with its own `status_reason` from a parallel vocabulary. **This is where a `PARTIAL` explains itself.**

| Feature `status_reason`                                                           | Meaning                                                                                                                                                                              |
| :-------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FEATURE_ACCEPTED` · `FEATURE_WAITING` · `FEATURE_RUNNING` · `FEATURE_ASYNC_WAIT` | Not finished yet.                                                                                                                                                                    |
| `FEATURE_ACTION_REQUIRED`                                                         | This feature is the one asking for something.                                                                                                                                        |
| `FEATURE_COMPLETED`                                                               | Retrieved.                                                                                                                                                                           |
| `FEATURE_PARTIAL`                                                                 | Retrieved incompletely — some of it is there.                                                                                                                                        |
| `FEATURE_FAILED` · `FEATURE_INTERNAL_ERROR` · `FEATURE_TEMPORARY_ERROR`           | Not retrieved. The first is definitive for this run; the others are worth another run.                                                                                               |
| `FEATURE_ABORTED` · `FEATURE_TIMEOUT` · `FEATURE_ACTION_TIMEOUT`                  | Stopped when the execution was.                                                                                                                                                      |
| `FEATURE_DISABLED` · `FEATURE_NOT_IMPLEMENTED`                                    | This engine does not offer it. Asking for it is **ignored** — no data, no error, and nothing in the results to say so — which is why the catalogue is where you build the list from. |
| `FEATURE_CONFIGURATION_ERROR` · `FEATURE_INCORRECT_PARAMETERS_FORMAT`             | Its configuration was rejected.                                                                                                                                                      |
| `FEATURE_ACTION_REJECTED`                                                         | The answer to its challenge was refused.                                                                                                                                             |

A results endpoint for a feature that never finished answers `202`; one for a feature you never requested answers `204`. Both are described in [Reading the results](/direct-executions/results).

---

## How to follow one

#### [Webhooks — the design](/direct-executions/webhooks)

Four events: started, action required, authentication ended, ended. You do nothing until there is something to do.

#### Polling — the fallback

`HEAD` on the execution answers `202`, `423` or `200` with no body. Cheap enough for a loop; ask every few seconds, not every few milliseconds.

An engine talks to a real institution over a real network: seconds to minutes is normal, and a source that produces its report on its own schedule can take considerably longer. Size your own timeouts on the slowest feature you requested, not on the average.

---

## The clocks

Three timings come back on the state, and they answer three different questions:

| Field                | Measures                                                      | Use it to                             |
| :------------------- | :------------------------------------------------------------ | :------------------------------------ |
| `execution_time`     | Active processing only.                                       | Judge the engine.                     |
| `running_time`       | Since work began, including time parked waiting for a person. | Judge the whole run.                  |
| `total_elapsed_time` | Since you asked, including the queue.                         | Judge what your customer experienced. |

And three limits end an execution on their own:

* **`execution_timeout`** — active processing, 60 to 1200 seconds, chosen per execution (default 1200). Waiting for you does not count against it.
* **`action_required_expires`** — the deadline to answer a challenge. Past it, `ABORTED` / `ACTION_TIMEOUT`.
* **The unattended window** — an execution started with `customer_interaction_available: false` has up to three hours to find a moment when the source cooperates, then gives up.

---

## Running the same engine twice

An application cannot have two live executions for the same **customer and engine** — nor two carrying the same `external_execution_id` for the same customer, nor two on the same token. The second is refused with `409`, and the body names the one already running so you can follow it instead of guessing. It is a guard against a double-submitted form running two logins against a bank at the same time, which some institutions treat as an attack.

**If the parallel run is deliberate**, give each one a **different** `external_execution_id`: your own identifier makes them distinguishable and both are accepted. It travels through every event and every state, which also makes it the natural place for your case number.

---

## Ending it yourself

#### Abort

Stops a live execution and closes it as `CLIENT_CANCELLED` — a cancellation, not a failure. Whatever was extracted stays readable, and the slot is freed immediately.

#### Delete

Purges a **closed** execution and everything under it — records and documents — permanently. This is the one to build into your flow.

**Delete as soon as you have stored what you need.** Everything an execution retrieved is somebody's financial life; the strongest protection available is not to be holding it. Executions nobody deletes are purged after the retention period, but that is a safety net, not a data-minimisation policy.