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

# Your first execution

Everything below runs against a **sandbox** application, so there is no real person and no real institution involved — the engine returns realistic, fabricated records. It is the same sequence you will run in production, including the parts that go wrong.

You need one thing to start: an application secret, from the [console](https://www.infonite.tech/console).

```bash
export INFONITE_SECRET="<your sandbox application secret>"
export INFONITE_API="https://clients.infonite.tech/api"
```

---

## What you can run through this API

#### Banks & cards

Retail and business banking, card issuers and consumer finance: accounts and transactions, cards, loans and mortgages, deposits, investments, direct debits.

#### Utilities & billing

Electricity, water, gas, telecoms and supplier portals: the invoices an account receives or issues, with the original documents.

#### Public administrations

Tax agency, social security, traffic authority, education registries, the credit registry: official records, with the certificates behind them.

**This walkthrough uses one engine — the sandbox Demo Bank — because a single worked example is easier to follow than three.** Nothing in it is banking-specific: the same calls run a utility or a public administration, with a different `engine_reference`, different `parameters` and different `features`. The only thing that changes at the end is which results endpoint you read.

---

## 1. See what you can run

```bash
curl -s "$INFONITE_API/config/engines/list" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

Every entry is an engine your application may execute **right now** — enabled for you, live, and available in your environment. The field you want from each one is `reference`; in a sandbox application you will find the demo bank among them:

```json
{
  "reference": "DEMOBANKXXXXFIN100ES9999-mobile",
  "name": "Demo Bank",
  "main_category": "financial",
  "markets": ["ES"],
  "features": ["customer_information_read", "accounts_read", "cards_read", "..."],
  "action_required_frequency": "rarely",
  "is_online": true
}
```

Narrow the list with `markets`, `main_category` and `features` — `?features=labor_check&features=yearly_individual_tax` with the default `features_mode=and` returns only engines that can do both.

**Cache the catalogue. Do not call this before every execution.**

It is a directory, not a live signal: it changes when we add or retire an engine, not from one minute to the next. Fetch it on a schedule — **once or twice a day is plenty** — keep your own copy, and launch your executions against that. Refresh it periodically anyway, so a new engine or a changed login form reaches you without a deploy.

Rate limits on this API are not aggressive today, and we would rather keep it that way. **We reserve the right to tighten them**, and the integrations that get tightened are the ones polling a directory in a loop. A cached catalogue is also faster for you, and it keeps working through a network blip.

---

## 2. Ask what that engine needs

```bash
curl -s "$INFONITE_API/config/engines/DEMOBANKXXXXFIN100ES9999-mobile" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

This is the call that removes the guesswork: it returns **`parameters`** (what the engine needs to log in) and **`features`** (what it can retrieve, each with its own settings).

`parameters` is a **form description**, in the JSON Schema dialect OpenAPI uses. That sounds heavier than it is — the next section reads one end to end.

**Build your input from this response, not from memory.** Institutions change what they ask for: a bank that wanted a document number last year may want an email today. An integration that reads `parameters` follows that change on its own; one with a hardcoded form breaks the morning it happens.

Two more fields are worth reading before you design anything else:

* **`action_required_frequency`** — how often this engine stops mid-run to ask your customer something: `never`, `on_first_use`, `rarely`, `often` or `always`. **`always` means it can never run unattended**, and starting one without `customer_interaction_available` is rejected outright.
* **`engine_restrictions`** — the source's own limits in plain language: how far back history goes, hours when it will not answer.

---

## 3. Read the parameters form

Here is the whole `parameters` object of the Demo Bank engine. Three fields, and every rule you need is visible in it:

**`parameters, as the engine publishes it`**

```json title="parameters, as the engine publishes it"
{
  "type": "object",
  "format": "cg-params",
  "properties": {
    "username": {
      "title": "Email",
      "description": "Enter your email",
      "type": "string",
      "format": "email",
      "examples": ["john.doe@example.com"]
    },
    "password": {
      "title": "Password",
      "description": "Enter your password",
      "type": "string",
      "format": "password",
      "minLength": 1,
      "maxLength": 10
    },
    "use_case": {
      "title": "Target use case",
      "$ref": "#/$defs/TestCase",
      "default": "Normal Execution"
    }
  },
  "required": ["username", "password"],
  "$defs": {
    "TestCase": {
      "type": "string",
      "enum": ["Normal Execution", "Two Factor SMS", "Blocked User", "Change Password", "..."]
    }
  },
  "x-meta": {
    "instructions_title": "Engine Parameters",
    "instructions_text": "Input any email to launch a demo execution",
    "submit_button_text": "Submit",
    "locking_fields": ["username"],
    "hidden_fields": [],
    "lazy_fields": []
  }
}
```

**Which gives you exactly this:**

**`what you send as parameters`**

```json title="what you send as parameters"
{
  "username": "john.doe@example.com",
  "password": "1234"
}
```

That is the whole job: **the keys of `properties` are the keys you send**, and `required` says which ones you cannot leave out. Everything else on each field is there to help you ask for it well.

### The field-by-field recipe

| In the schema                               | What it is for                                                                                 |
| :------------------------------------------ | :--------------------------------------------------------------------------------------------- |
| **the property key** (`username`)           | The name to send. Not the label — never send the title.                                        |
| `title`                                     | The label to show a person. Written for the end user, in the language of that source's market. |
| `description`                               | The helper text under the field.                                                               |
| `type`                                      | `string`, `number`, `boolean`, `array`.                                                        |
| `format`                                    | `password` (mask it), `email`, `date` (`YYYY-MM-DD`).                                          |
| `minLength` · `maxLength` · `pattern`       | Validate before you send, and a failed execution becomes an inline error instead.              |
| `examples`                                  | A ready-made placeholder.                                                                      |
| `enum` (often behind a `$ref` into `$defs`) | A closed list — render a select, send one of the values verbatim.                              |
| `default`                                   | What the engine uses if you omit the field.                                                    |
| `required`                                  | The fields that must be present. Anything not listed is optional.                              |

### What `x-meta` adds

Four keys, and they are the difference between a form that works and a form that works *well*:

| Key                                                               | What to do with it                                                                                                                                                                                                       |
| :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `instructions_title` · `instructions_text` · `submit_button_text` | Copy written for this specific source. Show it: it is more precise than anything generic you would write, and it changes when the institution changes.                                                                   |
| `locking_fields`                                                  | The parts of the credentials that identify the access and do not change — the username, typically. They are what `locking_hash` is derived from, and what a [stored token](/direct-executions/tokenization) is bound to. |
| `hidden_fields`                                                   | **Do not render these.** They have defaults and are handled for you; send one only if you have a reason to override it.                                                                                                  |
| `lazy_fields`                                                     | Fields the engine asks for **later, and only if it turns out to need them**. You may leave them out and let the execution pause to ask — or send them up front, when you know which one applies, and it never will.      |

### A second example, from a public administration

Not every source asks for a username and a password. The Spanish *Cl\@ve* engines ask for a national id and how the person wants to be challenged:

**`parameters of a Cl@ve engine, abridged`**

```json title="parameters of a Cl@ve engine, abridged"
{
  "properties": {
    "username": {
      "title": "DNI/NIE",
      "description": "Introduce tu DNI o NIE",
      "type": "string",
      "minLength": 1,
      "maxLength": 10
    },
    "access_type": {
      "title": "Método de Acceso",
      "$ref": "#/$defs/AccessType",
      "default": "sms"
    },
    "soporte": { "title": "Número de Soporte", "type": "string" },
    "date":    { "title": "Fecha de Validez", "type": "string", "format": "date" }
  },
  "required": ["username"],
  "$defs": { "AccessType": { "type": "string", "enum": ["qr", "push", "sms"] } },
  "x-meta": {
    "locking_fields": ["username"],
    "hidden_fields": ["soporte", "date", "access_type", "email"],
    "lazy_fields": ["soporte", "date"]
  }
}
```

Read it with the recipe above and the request writes itself:

**`what you send`**

```json title="what you send"
{ "username": "12345678Z" }
```

One required field. `access_type` has a default. `soporte` and `date` are **hidden and lazy**: the engine will ask for whichever one it needs, when it needs it.

### Lazy does not mean you cannot send it

A lazy field is an **option, not an obligation**. The engine asks for it later only because it cannot know in advance which one applies — but you often can, because the rule is a property of the document your customer just typed:

| If `username` is a… | The engine will need                                                       | Why                                           |
| :------------------ | :------------------------------------------------------------------------- | :-------------------------------------------- |
| **NIE**             | `soporte` — the support number of the foreigner's card or residence permit | The DNI's validity date does not exist for it |
| **DNI**             | `date` — the validity date, or the issue date for a permanent one          | There is no support number on a DNI           |

So if your own form can tell one from the other, ask for the right field there and send it in `parameters` with the rest. **A lazy field that arrives valid is never asked for again** — the execution runs straight through, and your customer answers one screen instead of two, minutes apart.

Leave it out and nothing breaks: the execution pauses at the moment the engine needs it and asks for exactly that field — [Challenges & MFA](/direct-executions/challenges) is that path. Send it wrong and you get the same pause, with the engine's own message explaining what it expected.

**The labels arrive in the source's own language** — Spanish for a Spanish administration — because they are written for the person who will read them. Show them as they come, or map them to your own copy by property key.

---

## 4. Start the execution

```bash
curl -s -X POST "$INFONITE_API/executions/init/v1/parametrized" \
  -H "X-APP-SECRET: $INFONITE_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "engine_reference": "DEMOBANKXXXXFIN100ES9999-mobile",
    "customer_id": "my-customer-1",
    "external_execution_id": "case-A-1029",
    "parameters": {
      "username": "john.doe@example.com",
      "password": "1234"
    },
    "features": [
      "customer_information_read",
      {
        "code": "accounts_read",
        "configurations": {
          "read_transactions": true,
          "from_date": "90 days ago",
          "to_date": "today"
        }
      },
      "cards_read"
    ],
    "base_configurations": {
      "customer_interaction_available": true,
      "execution_timeout": 600
    }
  }'
```

The answer is immediate, and it is not the result:

```json
{
  "execution_id": "6aa3d8b418d1c5dc9a8e3d36",
  "session_id": "5aa3dca503e37e6809539a58",
  "customer_id": "my-customer-1",
  "engine_reference": "DEMOBANKXXXXFIN100ES9999-mobile",
  "status_code": "ONGOING",
  "status_reason": "ACCEPTED"
}
```

`202 Accepted` means queued. Keep `execution_id`: everything from here on is addressed by it.

**A feature is a name, or a name with settings.** Send the bare string when the defaults are fine, and an object when they are not — the settings each feature accepts are in that engine's `features` from step 2. Dates are forgiving: `"90 days ago"`, `"1 year ago"` and `"today"` are all valid, alongside a plain `YYYY-MM-DD`. Ask for more history than the engine allows and the execution is rejected — unless you set `"limits_behaviour": "adapt"`, which trims the range to what is possible and carries on.

**`customer_interaction_available` is the flag that decides the shape of your integration.** `true` says somebody can answer a challenge from the source within minutes, so the execution will pause and wait for you. `false` says nobody is there: it runs at low priority, has up to three hours to find a good moment, and ends rather than waits if the source asks for a second factor. **It also decides whether your customer is contacted at all** — with `false` the engine never asks for a code, so the institution never sends one. Choose it per execution, not once for your whole product.

---

## 5. Follow it

The cheap way, for a loop:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -I \
  "$INFONITE_API/executions/handler/v1/6aa3d8b418d1c5dc9a8e3d36" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

`202` still working · `423` waiting for you · `200` finished. And the full picture when you need it:

```bash
curl -s "$INFONITE_API/executions/handler/v1/6aa3d8b418d1c5dc9a8e3d36" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

```json
{
  "execution_id": "6aa3d8b418d1c5dc9a8e3d36",
  "status_code": "COMPLETED",
  "status_reason": "COMPLETED",
  "authentication_status": "AUTH_OK",
  "is_closed": true,
  "execution_time": 12.4,
  "features": [
    {"code": "customer_information_read", "status_reason": "FEATURE_COMPLETED"},
    {"code": "accounts_read", "status_reason": "FEATURE_COMPLETED"},
    {"code": "cards_read", "status_reason": "FEATURE_COMPLETED"}
  ]
}
```

**In production, do not build the loop at all.** [Webhooks](/direct-executions/webhooks) tell you when the execution starts, when it needs an answer and when it ends — polling is the fallback, not the design.

**If it answered `423`**, the source asked for something. The body of the state carries `form_schema` with the fields to collect, and you send the values back — that whole conversation is in [Challenges and MFA](/direct-executions/challenges).

---

## 6. Read the results

One call for the whole picture of that kind:

```bash
# a bank, a card issuer, a consumer-finance provider
curl -s "$INFONITE_API/executions/results/6aa3d8b418d1c5dc9a8e3d36/financial/v1/global-position" \
  -H "X-APP-SECRET: $INFONITE_SECRET"

# a public administration — same shape of call, different family
curl -s "$INFONITE_API/executions/results/6aa3d8b418d1c5dc9a8e3d36/public/v1/global-position" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

Or one family at a time — `/financial/v1/accounts`, `/financial/v1/cards`, `/public/v1/labor-check`, `/commercial/v1/supplier-invoices`. The official documents behind any of them are listed by `/attachments/v1/all` and downloaded one by one.

**`202` from a results endpoint is not an error.** It means the execution, or that particular feature, has not finished. `204` means the feature was never requested for this execution — check what you sent in `features`.

---

## 7. Delete it

```bash
curl -s -X DELETE \
  "$INFONITE_API/executions/handler/v1/6aa3d8b418d1c5dc9a8e3d36/delete" \
  -H "X-APP-SECRET: $INFONITE_SECRET"
```

`204`, and the execution, its records and its documents are gone for good.

**Make this part of the flow, not a cleanup job you write later.** Everything an execution retrieved is somebody's financial life, and the strongest protection available is not to be holding it once you have taken what you need. Executions you never delete are purged after the retention period, but that is a safety net, not a policy.

---

## Rehearsing the paths that fail

The sandbox engines exist to be broken on purpose. The Demo Bank publishes a `use_case` parameter whose value drives the run: `Two Factor SMS` and `Two Factor Confirmation` raise a real challenge, `Blocked User` and `Change Password` end with the authentication reason an institution would return, `Out of Service` makes the source unavailable.

```json
{ "username": "john.doe@example.com", "password": "1234", "use_case": "Two Factor SMS" }
```

Build against those before you build against a real bank: the challenge-and-resume loop, the expired deadline, and the *"ask your customer for their credentials again"* branch are the three things that are painful to discover in production.

---

## Where to go from here

#### [Stop storing credentials](/direct-executions/tokenization)

Turn the first execution into a token, and run the same access next month without keeping the password.

#### [Encrypt what you send](/direct-executions/authentication)

Credentials can travel encrypted with your application's public key, so an intercepted request body reveals nothing.

#### [Understand the states](/direct-executions/lifecycle)

What each status means, what closes an execution, and why `PARTIAL` is a finished execution.

#### [Every endpoint](/api-reference/direct-executions/direct-executions-api/engines/direct-executions-v-1-engines-list)

The full reference, with a request explorer beside each operation.