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

# Manage network policies

> Read and replace custom allowed hosts with explicitly permissioned API keys.

Network policies store exact public hosts that Reason cloud may reach in addition to its platform allowlist. These are outbound destination rules, not the API's inbound IP access list.

## Grant access

In **Settings > API**, a workspace owner or admin can generate a key, expand its permissions, and explicitly select **Read network policies** (`network:read`) and, when needed, **Manage network policies** (`network:write`). Both are excluded from default permissions and the permission presets. Existing keys do not gain either scope automatically.

* Reading a saved policy requires `network:read` and access to that workspace.
* Replacing a policy requires `network:write` and a current workspace owner/admin role.
* `network:write` is elevated: only owners/admins can mint or manage keys carrying it. A key cannot grant itself additional authority. `org:write` or `run` alone does not authorize a network-policy write.
* To rotate a key, generate a replacement with the required permissions, update its consumers, then delete the old key in Settings > API to revoke it. Revoking a key does not undo policies it already saved.

Send the key as `Authorization: Bearer $REASON_API_KEY`. Resolve the workspace's organization ID with `GET /v3/self`.

## Endpoints

| Policy | Read | Replace |
| - | - | - |
| Workspace | `GET /v3/organizations/{orgId}/network-policy` | `PUT /v3/organizations/{orgId}/network-policy` |

Both endpoints return only the saved custom list:

```json theme={null}
{"network_allowed_hosts":["api.example.com","db.example.com:5432"]}
```

A PUT body has exactly the same shape: `network_allowed_hosts` is required and other fields are rejected. It **replaces the entire list**, not an incremental merge. Use `{"network_allowed_hosts":[]}` to clear that policy's custom entries. The response is not a complete effective allowlist or proof that a running machine has adopted it.

The existing `PATCH /v3/organizations/{orgId}/projects/{projectId}` also requires `network:write` when its body contains `network_allowed_hosts`, in addition to its existing `run` scope and role/project permission checks.

## Read, add, remove, or clear hosts

These examples use `curl` and `jq`, with a key injected into `REASON_API_KEY`. Do not paste keys into source files or logs. Use the workspace endpoint to configure custom hosts for cloud execution.

```bash wrap theme={null}
POLICY_URL="https://api.reasonmachines.com/v3/organizations/$ORG_ID/network-policy"

# Read the current saved list.
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $REASON_API_KEY" "$POLICY_URL"

# Add a host while preserving the other entries.
current=$(curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $REASON_API_KEY" "$POLICY_URL") || exit 1
printf '%s' "$current" | jq --arg host 'api.example.com' \
  '{network_allowed_hosts: ((.network_allowed_hosts + [$host]) | unique)}' |
  curl --fail-with-body --silent --show-error -X PUT \
    -H "Authorization: Bearer $REASON_API_KEY" \
    -H 'Content-Type: application/json' --data-binary @- "$POLICY_URL"

# Re-read, then remove a host while preserving the other entries.
current=$(curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $REASON_API_KEY" "$POLICY_URL") || exit 1
printf '%s' "$current" | jq --arg host 'api.example.com' \
  '{network_allowed_hosts: [.network_allowed_hosts[] | select(. != $host)]}' |
  curl --fail-with-body --silent --show-error -X PUT \
    -H "Authorization: Bearer $REASON_API_KEY" \
    -H 'Content-Type: application/json' --data-binary @- "$POLICY_URL"

# Clear only this policy's custom list.
curl --fail-with-body --silent --show-error -X PUT \
  -H "Authorization: Bearer $REASON_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"network_allowed_hosts":[]}' "$POLICY_URL"
```

Read-modify-PUT is not atomic. Concurrent writers are **last write wins**; coordinate writers so a stale read does not overwrite another update. Reads require `network:read` even when the same key already has `network:write`.

## Accepted hosts

Each policy supports at most 50 entries. Values are trimmed and lowercased. Use an exact public DNS hostname, optionally followed by a port from 1 to 65535. A bare host allows port 443 only; `db.example.com:5432` allows exactly port 5432.

Wildcards, URLs, paths, IP literals, private/local names, trailing-dot names, and path-tenant registries under `gcr.io` or `pkg.dev` are rejected. Duplicate effective rules are rejected: `api.example.com` and `api.example.com:443` are the same rule. Normalization also makes casing and surrounding whitespace irrelevant for duplicate detection. Existing DNS and private-network protections still apply; adding a hostname does not authorize private addresses.

## What changes at runtime

Workspace custom hosts are added to Reason's platform allowlist; clearing them does not disable the platform's base destinations. Removing a host from one policy cannot override permission from another applicable source. This API does not provide deny rules or a way to disable confinement.

**Legacy project network hosts are saved configuration only in the current runtime.** Projects group sessions and no longer contribute their network hosts to session execution. There is no current workspace/project union and no dedicated project network-policy endpoint: use the workspace endpoint to change custom hosts used by cloud sessions. Clearing a legacy project list does not remove a workspace grant.

The custom hosts are read when a new confined Hands execution starts. An existing execution lease keeps its frozen policy, including during recovery; saving a policy is not an immediate update or revocation on a running machine. Start a fresh execution after changing the policy and verify the destination there. These cloud confinement settings do not reconfigure a connected device's own network.

## Errors

A missing scope returns `403` with `missing_scope` and the required scope. Scope possession does not bypass current role, workspace, or project permissions. Invalid hosts or an invalid body return `400`; a missing or expired key returns `401`. Inspect the response before retrying rather than broadening the allowed hosts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.