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

# External Endpoint

> Score by calling an HTTP service you host. The engine batches candidates and POSTs them to your endpoint, then maps the response back.

`modelType: "external_endpoint"` — bypass in-engine scoring entirely and POST the candidate set to an HTTP service you operate. KaireonAI calls your URL with a batch of candidates and the customer's attributes; your service returns a score per candidate; the engine stamps those scores onto the candidates and continues the pipeline.

## When to use

* **You already have a production model in another stack** (Python/PyTorch, TensorFlow Serving, in-house C++) — don't rewrite, integrate.
* **You need real-time features the engine doesn't have** — joining against a third-party API, a fraud-score lookup, or a feature store at request time.
* **You want centralized model governance** — your ML platform team owns the model lifecycle; KaireonAI just consumes scores.

**Skip it when** request-time latency matters more than model freshness — each external call adds 20–200ms of network round-trip. In-engine algorithms run in 5–50µs. Use external only when the model can't run in-engine.

## How the engine calls you

In the default **batch** scoring mode, the engine POSTs the whole candidate set in one call. Each candidate is flattened to its `offerId` plus its own attributes; the customer's features go under `customer`:

```http theme={null}
POST {endpointUrl}
Content-Type: application/json
Authorization: Bearer {authConfig.bearerToken}   # only when authType = "bearer"

{
  "customer": { "tier": "Gold", "credit_score": 760, ... },
  "candidates": [
    { "offerId": "off-travel",   "...offer attributes": "..." },
    { "offerId": "off-cashback", "...offer attributes": "..." }
  ]
}
```

The request method (`POST`/`GET`), `Content-Type`, and body can be overridden with `requestTemplate`. A `bodyTemplate` string with `{{variable}}` placeholders is resolved against the customer's feature map — each `{{key}}` is replaced with the JSON value of `customer[key]` (or `null`). Before any call, the endpoint URL is checked by the SSRF guard (`validateAndResolve`, which DNS-resolves the host and rejects private / loopback IPs); a rejected URL scores every candidate with `fallbackScore` and no request is made.

Your service must return within `timeoutMs` (default **200ms**, range 50–30000). For batch mode, return one entry per candidate under the `batchScoresPath` (default `scores`); each entry must use the literal keys `offerId` and `score`:

```json theme={null}
{
  "scores": [
    { "offerId": "off-travel",   "score": 0.42 },
    { "offerId": "off-cashback", "score": 0.71 }
  ]
}
```

The engine clamps each returned `score` to `[0, 1]` and stamps it onto the matching candidate; any candidate missing from the response gets `fallbackScore`.

## Fixture config

```json theme={null}
{
  "modelType": "external_endpoint",
  "config": {
    "endpointUrl": "https://your-ml-service.example.com/score",
    "authType": "bearer",
    "authConfig": {
      "bearerToken": "<secret-ref-or-literal>"
    },
    "scoringMode": "batch",
    "responseMapping": {
      "batchScoresPath": "scores",
      "fallbackScore": 0.5
    },
    "timeoutMs": 200,
    "cacheTtlSeconds": 60
  }
}
```

`authType` is one of `none | api_key | bearer | aws_sigv4`. Only two are wired: `api_key` (sends a custom header built from `authConfig.apiKeyHeader` / `authConfig.apiKeyValue`) and `bearer` (`Authorization: Bearer {authConfig.bearerToken}`). **`aws_sigv4` is accepted by the schema but not implemented** — a model configured with it throws at call time, which the engine catches and turns into `fallbackScore` for every candidate. Front the endpoint with a gateway that accepts an API key or bearer token instead.

`responseMapping` adapts to your service's shape. For `scoringMode: "batch"`, `batchScoresPath` is a **dot-path** (e.g. `scores`, `data.result`, `predictions[0]`) to an array whose entries carry the literal keys `offerId` and `score` — those key names are fixed, not configurable. For `scoringMode: "single"`, `scorePath` (default `score`) dot-paths to a single number that is applied to every candidate.

`fallbackScore` (default `0.5`) is the value the engine uses when your service errors out, times out, fails SSRF validation, or returns malformed data — it keeps candidates alive but neutral. `timeoutMs` defaults to `200` (range 50–30000) and `cacheTtlSeconds` defaults to `60`.

## Training

Out of scope for KaireonAI. You train and version the model in your stack; the engine only ever calls the URL.

## Score interpretation

Whatever your service returns, **clamped to `[0, 1]`** by the engine (`Math.max(0, Math.min(1, score))`). Beyond that clamp there's no transform; if PRIE ranking is enabled it composes the clamped score via its geometric mean, so return values already in `[0, 1]` to keep the composition meaningful.

## Pitfalls

* **Latency** — external calls dominate request budget. Build your service to respond in \< 100ms p99. Cache aggressively. Pre-compute features at quiet times.
* **Timeout fallback** — when your service is slow or down, the engine uses `fallbackScore` for every candidate (every offer ties). Have an alert on `fallbackScore`-rate; if it's ever > 1%, your model is offline in production.
* **Authentication** — the bearer token (`authConfig.bearerToken`) or API-key value (`authConfig.apiKeyValue`) should be loaded from your secrets manager, not pasted into the model config JSON. Use the secrets-resolver pattern for any production deployment.
* **Network reliability** — TCP timeouts at TLS handshake, DNS flakiness, network partitions. The engine makes a **single attempt with no built-in retry**: any failure or timeout (enforced by `AbortController` at `timeoutMs`) resolves to `fallbackScore` for every candidate. Add retry or hedging in front of your service if you need it.
* **Schema drift** — if you rename fields in your service response, the `responseMapping` path stops resolving. Pin the schema; version-check on every response.
* **Async via `scoreOfferSetExternal`** — the engine batches multiple candidates in one HTTP call. Your service must return scores for ALL passed candidates, even on partial failure (use `fallbackScore` per candidate, not for the whole call).

## Lifecycle & cadence

External-endpoint models **do not train within KaireonAI** — the platform never updates `modelState`, never writes `metricsHistory`, never bumps `trainingSamples`. All learning happens in your remote service. `autoLearn`, `learnMode`, `learnSchedule` are ignored.

The platform's bookkeeping for these models stays empty by design. To track freshness of your remote service, use the operational `status` field (flip to `paused` if the upstream model is being retrained out-of-band) and surface your service's own training metrics through a separate dashboard. See [Model lifecycle](/ai-ml/model-lifecycle) for the operational state controls that DO apply to external endpoints.

## Cross-reference

* [Algorithm Selection Guide](/decisioning/algorithm-selection-guide).
* [ONNX Imported](/ai-ml/onnx-byo) — if your existing model is ONNX, prefer in-process ONNX over HTTP for latency.
