> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usenexio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Create a sandbox key, list your engines, submit one run and read its result.

This guide takes a developer from an empty terminal to a completed run in about ten minutes. It uses a sandbox environment, so nothing you do here touches live traffic.

**Prerequisites**

* A seat in your organization's Nexio portal with the admin or developer role (both can manage API keys and environments).
* At least one engine in your organization. Nexio sets up the first engine with you. The example below uses `vendor-review`, a fictional engine that a fictional company, Harbor Group, uses to check a supplier against its vendor requirements. Your engine's slug and input fields will differ.
* `curl`, Python 3 with `requests`, or Node.js 20 or later.

<Steps>
  <Step title="Create a sandbox environment">
    In the portal, open **Settings**, then **Environments**, and create an environment with the slug `dev`. A slug is 1 to 16 characters: lowercase letters, digits and underscores. See [Environments](/environments) for the rules.

    Every organization already has one `live` environment. Build against a sandbox first.
  </Step>

  <Step title="Create an API key">
    Open **Settings**, then **API keys**, choose **Create API Key**, enter a name, and pick the `dev` environment. The portal shows the key once. It looks like `nx_dev_` followed by 64 hexadecimal characters.

    Store it in an environment variable. Never put it in browser code: the API is server to server only.

    <Note>
      A sandbox key covers everything in this guide: engines, runs, conversations and webhooks. Records (`/api/v1/records`) and graph (`/api/v1/graph`) refuse it with `403 scoped_key_required`. For those, use your organization's `live` key or a scoped key issued by Nexio. See [Authentication and access](/authentication#routes-that-need-a-live-key-or-a-scoped-key).
    </Note>

    ```bash theme={null}
    export NEXIO_API_KEY="paste-the-key-here"
    ```
  </Step>

  <Step title="List your engines">
    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.usenexio.com/api/v1/engines \
        -H "Authorization: Bearer $NEXIO_API_KEY"
      ```

      ```python Python theme={null}
      import os
      import requests

      resp = requests.get(
          "https://api.usenexio.com/api/v1/engines",
          headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
          timeout=30,
      )
      resp.raise_for_status()
      print(resp.json())
      ```

      ```typescript TypeScript theme={null}
      const resp = await fetch("https://api.usenexio.com/api/v1/engines", {
        headers: { Authorization: `Bearer ${process.env.NEXIO_API_KEY}` },
      });
      if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
      console.log(await resp.json());
      ```
    </CodeGroup>

    Response `200`:

    ```json theme={null}
    {
      "engines": [
        {
          "id": "3f6c2a9e-8b41-4d7a-9c15-2e0b7d4a6f81",
          "slug": "vendor-review",
          "label": "Vendor review",
          "description": "Checks a supplier profile against vendor requirements.",
          "engine_type": "entity_analysis",
          "status": "active",
          "group_key": "",
          "created_at": "2026-09-01T14:02:11Z",
          "updated_at": "2026-09-18T09:40:03Z"
        }
      ]
    }
    ```

    Pick the `slug` of the engine you want to call. To see the fields it accepts, open the engine in the portal and choose **Contract**, or read `request_schema` from [`GET /api/v1/engines/{slug}/config`](/engines/configuration).
  </Step>

  <Step title="Submit a run">
    Send the engine's input under `input`. The `Idempotency-Key` header makes the call safe to retry: sending the same key with the same body returns the same run instead of creating a second one.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.usenexio.com/api/v1/engines/vendor-review/runs \
        -H "Authorization: Bearer $NEXIO_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: harbor-vendor-0042" \
        -d '{
          "input": {
            "request_id": "harbor-vendor-0042",
            "vendor": {
              "name": "Example Logistics",
              "country": "US",
              "annual_spend_usd": 480000,
              "certifications": ["ISO 9001"]
            }
          }
        }'
      ```

      ```python Python theme={null}
      import os
      import requests

      body = {
          "input": {
              "request_id": "harbor-vendor-0042",
              "vendor": {
                  "name": "Example Logistics",
                  "country": "US",
                  "annual_spend_usd": 480000,
                  "certifications": ["ISO 9001"],
              },
          }
      }
      resp = requests.post(
          "https://api.usenexio.com/api/v1/engines/vendor-review/runs",
          headers={
              "Authorization": "Bearer " + os.environ["NEXIO_API_KEY"],
              "Idempotency-Key": "harbor-vendor-0042",
          },
          json=body,
          timeout=30,
      )
      resp.raise_for_status()
      run_id = resp.json()["run_id"]
      print(resp.json())
      ```

      ```typescript TypeScript theme={null}
      const body = {
        input: {
          request_id: "harbor-vendor-0042",
          vendor: {
            name: "Example Logistics",
            country: "US",
            annual_spend_usd: 480000,
            certifications: ["ISO 9001"],
          },
        },
      };
      const resp = await fetch(
        "https://api.usenexio.com/api/v1/engines/vendor-review/runs",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "harbor-vendor-0042",
          },
          body: JSON.stringify(body),
        },
      );
      if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
      const { run_id } = await resp.json();
      console.log(run_id);
      ```
    </CodeGroup>

    Response `202`:

    ```json theme={null}
    {
      "run_id": "8d2f4b61-0c9e-4a3b-b7d5-1e6a9c0f2b47",
      "status": "queued"
    }
    ```

    <Tip>
      An engine with no released version answers `500 engine_version_none_released`. From a sandbox key you can add `"engine_version": "draft"` to the body to run the engine's current, unreleased configuration. See [Versions and releases](/engines/versions).
    </Tip>
  </Step>

  <Step title="Poll until the run finishes">
    Poll `GET /api/v1/runs/{run_id}` with a growing delay. A run is finished when `status` is one of the four terminal values: `completed`, `degraded`, `failed` or `cancelled`. See [Runs](/engines/runs) for the full state model.

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.usenexio.com/api/v1/runs/8d2f4b61-0c9e-4a3b-b7d5-1e6a9c0f2b47 \
        -H "Authorization: Bearer $NEXIO_API_KEY"
      ```

      ```python Python theme={null}
      import os
      import time
      import requests

      TERMINAL = {"completed", "degraded", "failed", "cancelled"}
      headers = {"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]}
      run_id = "8d2f4b61-0c9e-4a3b-b7d5-1e6a9c0f2b47"

      delay = 1.0
      while True:
          resp = requests.get(
              f"https://api.usenexio.com/api/v1/runs/{run_id}",
              headers=headers,
              timeout=30,
          )
          if resp.status_code == 429:
              time.sleep(int(resp.headers.get("Retry-After", "1")))
              continue
          resp.raise_for_status()
          run = resp.json()
          if run["status"] in TERMINAL:
              break
          time.sleep(delay)
          delay = min(delay * 2, 15.0)

      print(run["status"], run.get("output"))
      ```

      ```typescript TypeScript theme={null}
      const TERMINAL = new Set(["completed", "degraded", "failed", "cancelled"]);
      const runId = "8d2f4b61-0c9e-4a3b-b7d5-1e6a9c0f2b47";
      const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

      let delay = 1000;
      let run: any;
      while (true) {
        const resp = await fetch(`https://api.usenexio.com/api/v1/runs/${runId}`, {
          headers: { Authorization: `Bearer ${process.env.NEXIO_API_KEY}` },
        });
        if (resp.status === 429) {
          await sleep(Number(resp.headers.get("Retry-After") ?? "1") * 1000);
          continue;
        }
        if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
        run = await resp.json();
        if (TERMINAL.has(run.status)) break;
        await sleep(delay);
        delay = Math.min(delay * 2, 15000);
      }
      console.log(run.status, run.output);
      ```
    </CodeGroup>

    While the run is in progress, `status` is `queued` or `processing` and there is no `output`.
  </Step>

  <Step title="Read the result">
    A completed run carries the engine's `output`. Response `200`:

    ```json theme={null}
    {
      "run_id": "8d2f4b61-0c9e-4a3b-b7d5-1e6a9c0f2b47",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "5c1e9a7b3d2f4e60",
      "status": "completed",
      "environment": "test",
      "attempt": 1,
      "created_at": "2026-09-23T15:04:05Z",
      "started_at": "2026-09-23T15:04:06Z",
      "completed_at": "2026-09-23T15:04:19Z",
      "duration_ms": 12840,
      "total_duration_ms": 14102,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "output_phase": "final",
      "output": {
        "response_type": "VENDOR_REVIEW",
        "request_id": "harbor-vendor-0042",
        "diagnostics": [],
        "profile_summary": {
          "vendor_name": "Example Logistics",
          "country": "US"
        },
        "gaps": [
          {
            "id": "gap_001",
            "severity": "MEDIUM",
            "category": "SECURITY",
            "title": "No security attestation on file",
            "description": "The vendor lists ISO 9001 but no information security attestation.",
            "recommendation": "Request a current SOC 2 Type II report before onboarding.",
            "data_sources": ["input.vendor.certifications"]
          }
        ],
        "summary": {
          "total_gaps": 1,
          "high_severity": 0,
          "medium_severity": 1,
          "low_severity": 0
        }
      }
    }
    ```

    The fields inside `output` depend on the engine type and its configuration. The envelope around it (`run_id`, `status`, versions, timings) has the same shape for every engine, and optional fields are left out when they have no value. `environment` reads `test` for every sandbox key and `live` for the live environment. See [Engine types](/engines/overview).
  </Step>
</Steps>

## Common errors

| HTTP | Code                           | Cause                                                                                                                                                                 | Fix                                                                                 |
| ---- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| 401  | `unauthorized`                 | The `Authorization` header is missing, is not `Bearer`, or the key is unknown or revoked.                                                                             | Send `Authorization: Bearer <key>` with a key from **Settings**, then **API keys**. |
| 404  | `engine_not_found`             | No engine with that slug in your org.                                                                                                                                 | Use a `slug` from `GET /api/v1/engines`.                                            |
| 400  | `invalid_request`              | The body is not valid JSON, or has a field the API does not accept.                                                                                                   | Send only `input` and the documented optional fields.                               |
| 400  | `invalid_input`                | `input` failed the engine's input validation. `details` lists each field and message.                                                                                 | Fix the listed fields. Check the engine's contract.                                 |
| 409  | `idempotency_key_reused`       | The `Idempotency-Key` was already used with a different request. `details.run_id` names the first run. See [Idempotency](/reference/requests-and-errors#idempotency). | Use a new key for a new request.                                                    |
| 429  | `rate_limited`                 | Too many requests this minute.                                                                                                                                        | Wait for `Retry-After` seconds, then retry.                                         |
| 500  | `engine_version_none_released` | The engine has no released version.                                                                                                                                   | Publish a version, or pin `"draft"` from a sandbox key.                             |

The full list is in the [error reference](/reference/errors).

## What to build next

<CardGroup cols={2}>
  <Card title="Receive webhooks" icon="bell" href="/api-reference/webhooks/overview">
    Stop polling and get a signed event when a run finishes.
  </Card>

  <Card title="Runs" icon="arrows-rotate" href="/engines/runs">
    Statuses, degraded results, cancellation and idempotency in detail.
  </Card>

  <Card title="Authentication and access" icon="key" href="/authentication">
    Move from a sandbox key to live, and learn what each key can call.
  </Card>

  <Card title="Limits" icon="gauge" href="/reference/limits">
    Rate limits, request size bounds and the monthly run cap.
  </Card>
</CardGroup>
