# Network

> lifecycle.fetch and proxied lx:connection calls — calling approved APIs from section JS through Settings → Network access.

Canonical URL: https://77865786950d:3005/scripting/network

Section JS cannot call `fetch` directly: every other spelling of `fetch(` is
blocked by the policy (`network.fetch`), along with `XMLHttpRequest` and
`WebSocket`. To call an API, the merchant allows its origin in
**Settings → Network access** and the script uses `lifecycle.fetch`.

| Need | Use |
|---|---|
| A public API that allows browser calls (CORS) | A **direct** rule + `lifecycle.fetch("https://…")` |
| An API that needs a secret key | A **proxied** connection + `lifecycle.fetch("lx:connection/<slug>/…")` |
| Send form entries to an API, without JS | The form's [`forward_to`](/scripting/forms#forward-entries-server-side) |

## lifecycle.fetch

```ts
lifecycle.fetch(url: string, init?: RequestInit): Promise<Response>
```

Returns the normal `Response`.

```js
const res = await lifecycle.fetch("https://api.example.com/v1/stock?sku=123");
const data = await res.json();
```

Each call:

- takes a URL **string** (a `Request` or `URL` object is refused);
- must match an enabled direct rule: an `https` origin (or a `*.domain`
  wildcard, or `self` for the store's own domain), a path prefix, and the HTTP
  method;
- sends cookies only when the rule is `self` with credentials
  `same-origin`; otherwise cookies are omitted;
- refuses redirects, times out after 10 seconds, and is aborted when the
  section stops;
- is limited to 30 calls per minute per section in each tab.

URLs containing credentials (`https://user:pass@…`) or `..` path segments are
refused. `self` can never reach `/cart`, `/checkout`, `/checkouts`,
`/account`, `/admin`, `/apps/` or Lexsis's own `/a/lp/lx-*` routes; use the
[cart commands](/scripting/cart) and [`lifecycle.forms`](/scripting/forms)
for those.

A refused call throws `LxNetworkBlockedError` (with `url` and `reason`) and
logs `[section-network] <url> blocked: <reason>`:

| Reason | Fix |
|---|---|
| `no allowlist rule matches this URL; add it in Settings → Network access` | Add a rule for the origin and path |
| `<METHOD> is not allowed by this rule` | Add the method to the rule |
| `only https URLs are allowed` | Use `https` |
| `<path> is reserved on the store's own domain` | Use cart commands or forms |
| `more than 30 requests per minute` | Batch or cache calls (see [Storage](/scripting/storage)) |
| `no connection named "<slug>"` | Create the proxied connection |

Anything written in section code — including API keys — is visible to every
shopper. Use a proxied connection for anything secret.

## Publish-time checks

When the URL is a literal, publishing checks it against the store's rules and
fails with check `section_network` if no rule allows it, or if an
`lx:connection/<slug>` has no matching connection. Dynamic URLs produce a
warning and are checked when the call runs.

## Direct calls need CORS

A direct rule only works if the API allows browser calls from the store's
domain. The **Test a URL** box in Network access checks this and suggests a
proxied connection when it fails.

## Proxied connections

A proxied connection keeps the API key on the server. In Network access, add a
rule with mode **Proxied**, give it a slug, and say where the key goes, for
example header `Authorization` with template `Basic {{secret}}`. Then paste the
key once. It's stored encrypted and only the last four characters are ever
shown again.

Section JS calls it by slug:

```js
await lifecycle.fetch("lx:connection/whatsapp/track/users", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ phoneNumber, countryCode: "+91" }),
});
```

Golem adds the key and calls `origin + path_prefix + /track/users`:

- The path stays under the rule's prefix. With **allowed body keys** set, any
  other keys are stripped. Request bodies are capped at 32 KB.
- Only `content-type` and `accept` are forwarded, and only the upstream
  `content-type` comes back.
- Upstream redirects, private addresses and responses over 1 MB are refused.
- Calls are limited per shopper (20/min per connection) and per store
  (600/min).
- In editor and draft previews the call returns
  `{"dry_run": true, "connection": "<slug>", "method": "<METHOD>"}` without
  calling the API.

Agents can read the store's rules with `lexsis_capture` action
`network_access`.
