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

# Declared-contract engines

> Build an engine whose input fields and output envelope are declared in its configuration, with repeatable, model-free answers.

A declared-contract engine is an `entity_analysis` engine whose configuration declares the input fields it accepts and the output envelope it returns, and says how each output block is built. No model is called. The same input and the same enrichment lookup results give the same output, apart from `request_id`, which is the run ID; a lookup's result can change between runs. Use it when a downstream system must parse every response the same way. The request and response schemas the engine publishes come straight from these declarations (see [Engines overview](/engines/overview#declared-schemas)).

The example is `vendor-intake`, a fictional engine that Harbor Group, a fictional company, uses to record a new supplier: it takes the supplier's details, echoes the fields its records system stores, and returns one plain next step.

**Prerequisites**

* An `entity_analysis` engine. The slug in this guide is `vendor-intake`. See [Create an engine](/engines/overview#how-it-works).
* The organization API key in `NEXIO_API_KEY`. Saving configuration and publishing accept only the organization key.

## How it works

1. **Declared input.** `input_schema` lists every accepted input field by path, with its type, whether it is required, and an optional pattern. The mode is `exclusive`: the declared fields are the whole input contract.
2. **Declared output.** `output_contract.blocks` lists every top-level output key, each with an optional JSON Schema. `always_present` can add `response_type` and `request_id`.
3. **Composition.** `output_composition` says how each block is produced: projected from the input or a source, rendered from a template, a constant, or built by a composer Nexio registers.
4. **Validation at execution.** The run is accepted with `202` and the declared input is checked when the worker starts it. A violation ends the run `failed`. Each composed block that declares a schema is checked against it; a mismatch also fails the run.

A declared-contract run never ends `degraded`. A missing upstream result is expressed inside the envelope (for example a `status` field), not as a run status.

## The configuration

```json theme={null}
{
  "schema_version": 1,
  "pack_key": "harbor_vendor_intake",
  "pack_version": 1,
  "privacy_policy": { "mode": "default_allow" },
  "domain_key": "vendor",
  "response_type": "vendor_intake",
  "analysis_dimensions": [],
  "profile_extraction_rules": [],
  "deterministic_checks": [],
  "overlay_categories": [],
  "requirement_rules": [],
  "deterministic_gap_rules": [],
  "knowledge_overlay": [],
  "input_schema": {
    "mode": "exclusive",
    "fields": [
      { "path": "vendor", "type": "object" },
      { "path": "vendor.name", "type": "string", "required": true },
      { "path": "vendor.country", "type": "string", "required": true, "pattern": "^[A-Z]{2}$" },
      { "path": "vendor.annual_spend_usd", "type": "number" },
      { "path": "vendor.security_attestation", "type": "boolean", "required": true },
      {
        "path": "vendor.contact_email",
        "type": "string",
        "privacy": { "classification": "pii", "display": "mask", "external": "drop" }
      },
      { "path": "reference", "type": "string", "description": "Your own ID for the request." }
    ]
  },
  "output_contract": {
    "always_present": ["response_type", "request_id"],
    "blocks": [
      {
        "key": "vendor",
        "schema": {
          "type": "object",
          "required": ["name", "country", "annual_spend_usd"],
          "properties": {
            "name": { "type": "string" },
            "country": { "type": "string" },
            "annual_spend_usd": { "type": ["number", "null"] }
          }
        }
      },
      { "key": "next_step", "schema": { "type": "string" } },
      { "key": "standard", "schema": { "type": "string" } }
    ]
  },
  "output_composition": {
    "vendor": {
      "kind": "projection",
      "source": "input.vendor",
      "map": { "name": "name", "country": "country", "annual_spend_usd": "annual_spend_usd" }
    },
    "next_step": {
      "kind": "template",
      "max_length": 300,
      "cases": [
        {
          "when": { "path": "input.vendor.security_attestation", "eq": false },
          "text": "Request a current security attestation from {input.vendor.name} before onboarding."
        },
        { "else": true, "text": "{input.vendor.name} can move to contract review." }
      ]
    },
    "standard": { "kind": "constant", "value": "supplier-onboarding-2026" }
  }
}
```

This configuration calls no external processor, so it needs no `egress_manifest_version`. Enabling an enrichment source (below) makes that key required; copy its value from `preset` in `GET /api/v1/engines/{engine_slug}/config`.

### Input schema rules

| Rule           | Detail                                                                                                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mode`         | `exclusive`. `additive` has no runtime yet; a configuration that declares it is refused with a validation issue whose message starts with `contract_config_not_enabled`. |
| `path`         | Dotted lowercase snake case under `input`, for example `vendor.country`. At most 200 fields.                                                                             |
| `type`         | `string`, `integer`, `number`, `boolean`, `object` or `array`.                                                                                                           |
| Closed objects | An `object` field must declare child fields, unless it sets `open: true`.                                                                                                |
| `open: true`   | An opaque object passed through and stored as sent. Nothing inside it is checked, and it cannot declare children.                                                        |
| `required`     | A required field that is missing, `null`, a blank string or an empty object fails the run. An optional field may be `null`.                                              |
| `pattern`      | A regular expression the whole string value must match, at most 200 bytes. String fields only.                                                                           |
| `privacy`      | Optional `{"classification": "pii", "display": "mask" or "show", "external": "drop" or "allow"}`.                                                                        |

### Composition kinds

| Kind           | Fields                                 | Produces                                                                                                                                                                                                                                                                                                                                              |
| -------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projection`   | `source`, `map`, optional `status_map` | An object whose fields are read from `source`, a dotted path such as `input.vendor` or `enrichment.<kind>`. A missing source field becomes an explicit `null`. With `status_map`, a `status` field is set from an enrichment source's outcome status, and `*` is the required fallback.                                                               |
| `template`     | `cases`, optional `max_length`         | A string. Cases are tried in order; each has a `when` guard (`eq` a scalar, or `present` true or false) or is the final `else`. `{input.<path>}` and `{enrichment.<kind>.<field>}` tokens are filled in; a token with no value is left empty. `max_length` cuts the string to that many characters. With no match and no `else`, the value is `null`. |
| `constant`     | `value`                                | The given JSON value.                                                                                                                                                                                                                                                                                                                                 |
| `composer`     | `name`                                 | A whole block from a composer registered by Nexio.                                                                                                                                                                                                                                                                                                    |
| `llm`, `rules` |                                        | No runtime yet for declared-contract engines. Refused with a validation issue whose message starts with `contract_config_not_enabled`.                                                                                                                                                                                                                |

Every declared block needs a composition entry, and no two entries may own the same part of a block. Tokens and guards must name declared input fields or enabled enrichment sources; anything else is refused when you save. A declared-contract configuration must not set `model`.

### Enrichment sources (optional)

An enabled entry in `enrichment_sources` runs a lookup that Nexio registers before composition, and its result is readable at `enrichment.<kind>`. An `input_map` on the source binds the lookup's address fields (`prospect.primary_address.street`, `.city`, `.state` and `.zip`) to declared string fields; several fields for one target are joined with single spaces. A target can be bound by only one source, and the binding feeds every enabled source.

<Steps>
  <Step title="Validate, save and publish">
    Save the configuration above as `vendor-intake-config.json`, then validate it, save it, and publish version `1.0`.

    <CodeGroup>
      ```bash curl theme={null}
      BODY=$(jq -n --slurpfile c vendor-intake-config.json '{config: $c[0]}')

      curl -s -X POST https://api.usenexio.com/api/v1/engines/vendor-intake/config/validate \
        -H "Authorization: Bearer $NEXIO_API_KEY" -H "Content-Type: application/json" -d "$BODY"

      curl -s -X PUT https://api.usenexio.com/api/v1/engines/vendor-intake/config \
        -H "Authorization: Bearer $NEXIO_API_KEY" -H "Content-Type: application/json" -d "$BODY"

      curl -s -X POST https://api.usenexio.com/api/v1/engines/vendor-intake/versions \
        -H "Authorization: Bearer $NEXIO_API_KEY" -H "Content-Type: application/json" \
        -d '{"changelog": "Vendor intake contract v1."}'
      ```

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

      import requests

      base = "https://api.usenexio.com/api/v1/engines/vendor-intake"
      headers = {"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]}
      with open("vendor-intake-config.json") as f:
          config = json.load(f)

      check = requests.post(f"{base}/config/validate", headers=headers, json={"config": config}, timeout=30).json()
      if not check["valid"]:
          raise SystemExit(check["errors"])

      requests.put(f"{base}/config", headers=headers, json={"config": config}, timeout=30).raise_for_status()
      release = requests.post(
          f"{base}/versions", headers=headers, json={"changelog": "Vendor intake contract v1."}, timeout=30
      ).json()
      print(release["version"])
      ```

      ```typescript TypeScript theme={null}
      import { readFile } from "node:fs/promises";

      const base = "https://api.usenexio.com/api/v1/engines/vendor-intake";
      const headers = {
        Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
        "Content-Type": "application/json",
      };
      const config = JSON.parse(await readFile("vendor-intake-config.json", "utf8"));

      const check = await (
        await fetch(`${base}/config/validate`, { method: "POST", headers, body: JSON.stringify({ config }) })
      ).json();
      if (!check.valid) throw new Error(JSON.stringify(check.errors));

      await fetch(`${base}/config`, { method: "PUT", headers, body: JSON.stringify({ config }) });
      const release = await (
        await fetch(`${base}/versions`, {
          method: "POST",
          headers,
          body: JSON.stringify({ changelog: "Vendor intake contract v1." }),
        })
      ).json();
      console.log(release.version);
      ```
    </CodeGroup>

    The validate call returns `{"valid": true, "errors": []}` and the publish returns version `1.0`.
  </Step>

  <Step title="Submit a supplier">
    <CodeGroup>
      ```bash curl theme={null}
      curl -s -X POST https://api.usenexio.com/api/v1/engines/vendor-intake/runs \
        -H "Authorization: Bearer $NEXIO_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "engine_version": "1.0",
          "input": {
            "vendor": {
              "name": "Example Logistics",
              "country": "US",
              "annual_spend_usd": 480000,
              "security_attestation": false,
              "contact_email": "ops@example.com"
            },
            "reference": "harbor-vendor-0107"
          }
        }'
      ```

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

      import requests

      resp = requests.post(
          "https://api.usenexio.com/api/v1/engines/vendor-intake/runs",
          headers={"Authorization": "Bearer " + os.environ["NEXIO_API_KEY"]},
          json={
              "engine_version": "1.0",
              "input": {
                  "vendor": {
                      "name": "Example Logistics",
                      "country": "US",
                      "annual_spend_usd": 480000,
                      "security_attestation": False,
                      "contact_email": "ops@example.com",
                  },
                  "reference": "harbor-vendor-0107",
              },
          },
          timeout=30,
      )
      print(resp.status_code, resp.json())
      ```

      ```typescript TypeScript theme={null}
      const resp = await fetch("https://api.usenexio.com/api/v1/engines/vendor-intake/runs", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          engine_version: "1.0",
          input: {
            vendor: {
              name: "Example Logistics",
              country: "US",
              annual_spend_usd: 480000,
              security_attestation: false,
              contact_email: "ops@example.com",
            },
            reference: "harbor-vendor-0107",
          },
        }),
      });
      console.log(resp.status, await resp.json());
      ```
    </CodeGroup>

    Response `202`:

    ```json theme={null}
    {
      "run_id": "8e4b2c7a-1f5d-4a3e-9c6b-0d2f8a7e5b14",
      "status": "queued"
    }
    ```
  </Step>

  <Step title="Read the envelope">
    Poll `GET /api/v1/runs/8e4b2c7a-1f5d-4a3e-9c6b-0d2f8a7e5b14` as on [Runs](/engines/runs#poll-a-run). The `output` of the completed run:

    ```json theme={null}
    {
      "response_type": "vendor_intake",
      "request_id": "8e4b2c7a-1f5d-4a3e-9c6b-0d2f8a7e5b14",
      "vendor": {
        "annual_spend_usd": 480000,
        "country": "US",
        "name": "Example Logistics"
      },
      "next_step": "Request a current security attestation from Example Logistics before onboarding.",
      "standard": "supplier-onboarding-2026"
    }
    ```

    `request_id` is the run ID. The envelope has exactly the declared keys, every time. A supplier sent without `annual_spend_usd` gets `"annual_spend_usd": null`, because a projection writes an explicit `null` for a missing source field, and the block schema allows it. A supplier with `"security_attestation": true` gets the `else` case: `"Example Logistics can move to contract review."`
  </Step>
</Steps>

## Input failures

A request that clears submission but breaks the declared input becomes a `failed` run. `error` names the first violation found:

| Condition                                                | `error`                                                             |
| -------------------------------------------------------- | ------------------------------------------------------------------- |
| Required field missing, `null`, blank or empty           | `required input vendor.name is missing or empty`                    |
| Wrong type                                               | `input vendor.security_attestation must be a boolean`               |
| An undeclared parent of declared fields is not an object | `input <path> must be an object`                                    |
| Pattern mismatch                                         | `input vendor.country does not match the required format`           |
| Undeclared field                                         | `input vendor.tax_id is not part of this engine's request contract` |

An `error` that names an input path is a request defect: resubmitting the same input fails the same way. For any other `error` on a declared-contract run, see `error_details.retryable` on [Runs](/engines/runs#poll-a-run).

## What to build next

| Next step                    | Page                                         |
| ---------------------------- | -------------------------------------------- |
| Receive envelopes by webhook | [Webhooks](/api-reference/webhooks/overview) |
| Pin and upgrade the contract | [Versions and releases](/engines/versions)   |
| Understand run statuses      | [Runs](/engines/runs)                        |

## Common errors

| Error                                                                                                                         | Cause                                                     | Fix                                                    |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------ |
| Validation issue starting `contract_config_not_enabled`                                                                       | `additive` mode, or an `llm` or `rules` composition.      | Use `exclusive` and the four enabled kinds.            |
| Validation issue at `model`                                                                                                   | Declared-contract configurations take no model.           | Remove `model`.                                        |
| Validation issue at `egress_manifest_version` (code `egress_manifest_version_required` or `egress_manifest_version_mismatch`) | Missing or stale value with an enabled enrichment source. | Copy the value from `preset`.                          |
| Validation issue at `output_composition[...].cases[...].text`                                                                 | The token names an undeclared field or a disabled source. | Declare the field, or enable the source.               |
| Run `failed`: `composed output violates the declared contract for block ...`                                                  | A block does not match its schema.                        | Fix the block schema or its composition, then publish. |
