# Delegating a decision to a service

> Goal: let a service of one's own system decide on a request (a risk score, a business right) or enrich it with a header - without a plugin in the gateway.

Guide - Since version 1.2.0 - Flexway v1.6.3 - https://flexway.io/en/docs/operations-external-call/

> Operations guide: configure, deploy and observe the gateway. What the capability does, product side: [product guide](https://flexway.io/en/docs/product-overview/); calling it: [developer guide](https://flexway.io/en/docs/developer-overview/).

**Goal**: let a service of one's own system decide on a request (a risk score, a business right) or
enrich it with a header - without a plugin in the gateway.

**Command** - the service is declared as an upstream, the policy names it:

```yaml
upstreams:
  risk: { url: http://risk.internal:8080 }       # pool, bulkhead, circuit breaker: its own
policies:
  definitions:
    risk-check:
      type: external-call
      phase: request                             # request: before the upstream; response: after its answer
      config:
        upstream: risk                           # a declared upstream, never a URL
        path: /score
        timeout: 150ms                           # required, at most the operation's budget
        on_failure: fail-closed                  # required: fail-open | fail-closed
        send: { headers: [X-Tenant], body: true }
        deny: { when: "response.status == 403", status: 403, code: risk_refused }
        set_headers: { X-Risk-Score: "response.body.score" }
```

**Check**: a `POST /orders` the service refuses gets `403` `application/problem+json`
(`code: risk_refused`) without the upstream seeing it; accepted, the upstream receives `X-Risk-Score`;
`flexway_policy_external_calls_total{policy="risk-check"}` counts each call by `outcome` (`ok`,
`denied`, `failed_open`, `failed_closed`); the trace shows an `external-call risk-check` span under
the request's.

| Rule | What it means |
| --- | --- |
| Declared service | `upstream` names an upstream of `upstreams` (HTTP, without `token_exchange`), `path` a literal path: no URL built from the request. The call is a `POST` |
| What is sent | The headers `send.headers` names, taken from the request prepared for the upstream (declared ones, the tenant the gateway wrote); never `Authorization` nor a protected header. `send.body`: the request body, read within `server.limits.max_body` |
| What it decides | `deny` (`when` on the answer, a `4xx` status, a declared code) or `set_headers` (CEL expressions on `response.status` and `response.body`, **the service's** answer) - nothing else |
| Failure | No answer in time, service unreachable, circuit open, a `5xx`, an answer a condition cannot read: `fail-open` goes on without the policy, `fail-closed` refuses (`504`, `503`, `502`) - never a default |
| Budget | The call **consumes** the operation's budget (`x-flexway-timeout`): a slow service leaves the upstream less time, never more time in total |
| Circuit breaker | The service is an upstream: failing, its circuit opens (`Flexway-Degraded: circuit-open; upstream=risk`) and the gateway stops calling it |
| Refused at load | No `timeout` or no `on_failure`; a `timeout` past the budget; an undeclared upstream; a protected header in `set_headers`; `send.body` on a stream or in the `response` phase; gRPC, inference - the message names the operation and the policy |
| Simulation | `/simulate` never calls the service: the policy is not applied there |

Full example: [External call](https://flexway.io/en/docs/operations-examples-gateway/#external-call).
