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

# External calls

> How off-chain Gora apps call external HTTP endpoints, and how a committee agrees on the response.

# External calls

Real apps often need data from outside the validator process: a price feed, a weather API, a model provider, or a webhook. Gora apps reach external endpoints through a sandboxed `fetch`-like host function. The committee then has to agree on what came back. This page explains how that works and how to design your app so consensus succeeds.

## The host function

Inside your app you call:

```ts theme={null}
const res = await gora.fetch("https://api.example.com/v1/price?symbol=BTC", {
  method: "GET",
  headers: { "X-Source": "gora-app" },
  timeout_ms: 5000,
});

const body = await res.json();
```

`gora.fetch` is provided by the Gora runtime, not by your language's built-in `fetch`. It enforces:

| Limit                        | Default                   | Why                                              |
| ---------------------------- | ------------------------- | ------------------------------------------------ |
| Total timeout                | 5 s                       | A slow upstream cannot stall the round.          |
| Response size                | 256 KB                    | Avoid memory exhaustion.                         |
| Concurrent calls per request | 4                         | Predictable resource use.                        |
| Allowed schemes              | `https://`                | No raw IP, no `http://` for production policy.   |
| Allowlist                    | Declared in `policy.json` | The app states up front which hosts it talks to. |

Apps declare their allowlist:

```json theme={null}
{
  "network": {
    "allow_hosts": [
      "api.example.com",
      "api.openai.com"
    ]
  }
}
```

A call to a host not in the allowlist fails fast. The runtime never silently routes around the policy.

## How the committee agrees

Validators run your app independently. If they each made their own live HTTP call, they would get slightly different responses (caches, timing, server clocks, rate limits) and never agree. Two strategies keep responses consistent.

### Strategy A — Proposer fetches, committee verifies

This is the default for deterministic apps.

```text theme={null}
Proposer runs the app, including gora.fetch calls.
The proposer's result includes a "fetch transcript":
  { url, method, body_hash, headers_kept, status }
The committee re-runs the app, but its gora.fetch is replayed
from the proposer's transcript instead of hitting the network.
Each committee member confirms the result matches.
```

The committee never re-calls the upstream. It only checks that, given the proposer's transcript, the app produces the same output. This makes the round fast and bandwidth-light. The trust assumption is the same as for any deterministic output: a dishonest proposer is caught by the committee's re-execution, because the re-execution uses the same transcript and so produces the same answer only if the proposer's app code is honest about what it did with the data.

### Strategy B — Quorum fetch (consensus-grade data)

For requests where the upstream value is itself the thing being attested (a price oracle, an event finality check), the app sets `consensus: "quorum"` on the fetch:

```ts theme={null}
const res = await gora.fetch(url, { consensus: "quorum", quorum_field: "price" });
```

Every committee member fetches independently. The runtime collects the values, picks the median (or strict-majority match), and exposes that value to the app. The transcript records every validator's raw response for audit. The round only commits if at least two-thirds of the committee got a usable response.

Use quorum mode sparingly: it multiplies upstream load by the committee size and is slower.

## Time and the wall clock

Every fetch in proposer-fetch mode captures the proposer's clock as part of the transcript. Verifiers see the proposer's `fetch_started_at` instead of their own `Date.now()` so re-execution stays deterministic. If your app needs wall-clock time *between* fetches, read it from `req.now_ms`, which is the round's pinned timestamp — not from `Date.now()`.

## What to put in the response, what to keep out

A fetch transcript is part of the attestation. Keep it minimal.

| Keep                                                          | Avoid                                                  |
| ------------------------------------------------------------- | ------------------------------------------------------ |
| The URL, method, status, and the body fields you actually use | Full HTML pages, tracking cookies, vendor `X-` headers |
| A hash of the body                                            | The raw body verbatim if it's larger than 8 KB         |
| Headers you read in the app (e.g. `Etag`, `Retry-After`)      | Bearer tokens, API keys, `Authorization` headers       |

The runtime automatically strips known-sensitive headers (`Authorization`, `Cookie`, `X-Api-Key`) from the transcript. Anything else you want stripped, set `redact_request_headers: [...]` on the fetch call.

## Errors and round-fail

Network failures during proposer fetch behave like any other proposer execution error: the proposer broadcasts a round-fail with the reason, and the network retries with a new sequence. In quorum mode, a fetch that does not reach two-thirds usable responses also round-fails. Both cases are recorded in the round log so you can see why a request didn't land.

## Combining VRF with external data

A common pattern: pick a winner from an externally-fetched list, using Gora VRF as the source of randomness.

```ts theme={null}
const list = await (await gora.fetch(roster_url)).json();
const idx = Number(BigInt("0x" + req.vrf.output_hex.slice(0, 16)) % BigInt(list.length));
const winner = list[idx];
```

Determinism is preserved because both inputs (the fetched body via the proposer's transcript, the VRF via the round context) are agreed before voting starts.

## What's intentionally not supported

| Not supported                                       | Why                                                           |
| --------------------------------------------------- | ------------------------------------------------------------- |
| Long-lived sockets / SSE                            | Round timing forbids it.                                      |
| Raw TCP / UDP                                       | Out of scope for the host function.                           |
| Calls that depend on the validator's IP geolocation | Different validators would see different content.             |
| Background fetches after the app returns            | The app result is the round's output.                         |
| Per-validator API keys                              | Keys live in app config and are uniform across the committee. |

For workloads that genuinely need long-running off-chain work (multi-minute agents, batch jobs), see the long-running execution surface from MVP 08.5; that path runs outside a single round.

## Related

* [Randomness and VRF](./randomness-and-vrf) — combine external data with verifiable randomness.
* [Validators and consensus](./validators-and-consensus) — what "committee re-runs" actually means.
* [Set policy and payments](../app/set-policy-and-payments) — declare your network allowlist.
