# Protection and network

> Who may open an app (password, signed links), what its machines may reach, and public UDP endpoints.

By default an app is public. Protection puts it behind a password or behind signed links that you hand out; the network policy says what the app's machines may reach on the way out; a UDP endpoint gives one machine a public UDP port.

## Endpoints

- [`GET /v1/apps/:name/protection`](#get-v1appsnameprotection): How the app is protected: public, password, or signed links
- [`PUT /v1/apps/:name/protection`](#put-v1appsnameprotection): Sets the protection mode
- [`POST /v1/apps/:name/protection/rotate`](#post-v1appsnameprotectionrotate): Rotates the password or signing secret
- [`POST /v1/apps/:name/protection/links`](#post-v1appsnameprotectionlinks): Creates a signed link that opens the app for a while
- [`DELETE /v1/apps/:name/protection/links/:id`](#delete-v1appsnameprotectionlinksid): Revokes a signed link
- [`POST /v1/apps/:name/protection/tokens`](#post-v1appsnameprotectiontokens): Creates an access token for a protected app
- [`GET /v1/apps/:name/policy/network`](#get-v1appsnamepolicynetwork): What the app's machines may reach on the way out
- [`PUT /v1/apps/:name/policy/network`](#put-v1appsnamepolicynetwork): Sets that policy
- [`GET /v1/apps/:name/udp`](#get-v1appsnameudp): The app's public UDP endpoints
- [`POST /v1/apps/:name/udp`](#post-v1appsnameudp): Requests a public UDP port for the app. The port is assigned by the platform
- [`DELETE /v1/apps/:name/udp/:id`](#delete-v1appsnameudpid): Releases a UDP endpoint

## GET /v1/apps/:name/protection

How the app is protected: public, password, or signed links.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/apps/<name>/protection" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## PUT /v1/apps/:name/protection

Sets the protection mode.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Request body

JSON, the shape `SetProtectionRequest` of the contract.

### Example request

```bash
curl -X PUT "https://api.rytiv.app/v1/apps/<name>/protection" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/apps/:name/protection/rotate

Rotates the password or signing secret.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/apps/<name>/protection/rotate" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `not-protected` |  |

## POST /v1/apps/:name/protection/links

Creates a signed link that opens the app for a while.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Request body

JSON, the shape `CreateAccessLinkRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | string | required | 1 to 64 characters. |
| `expiresAt` | string (ISO 8601) | optional | When the copy is deleted (a moment). Not together with `ttlSeconds`. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/apps/<name>/protection/links" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"before-migration"}'
```

### Response

`201` with a JSON object carrying `url` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `not-protected` |  |

## DELETE /v1/apps/:name/protection/links/:id

Revokes a signed link.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |
| `id` | The object's id. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/apps/<name>/protection/links/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`204` with no body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/apps/:name/protection/tokens

Creates an access token for a protected app.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Request body

JSON, the shape `CreateAccessTokenRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ttlSeconds` | integer | optional | Lifetime from now, in seconds. 1 to 86400. |
| `sub` | string | optional | 1 to 256 characters. |
| `next` | string | optional | at most 512 characters. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/apps/<name>/protection/tokens" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":1}'
```

### Response

`201` with a JSON object carrying `token`, `expiresAt`, `url`, `header`.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | `not-signed` |  |

## GET /v1/apps/:name/policy/network

What the app's machines may reach on the way out.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/apps/<name>/policy/network" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## PUT /v1/apps/:name/policy/network

Sets that policy.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Request body

JSON, the shape `PutNetworkPolicyRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `bandwidthMbit` | integer or null | required |  |
| `newConnectionsPerMinute` | integer or null | required |  |
| `reason` | string | optional | at most 500 characters. |

### Example request

```bash
curl -X PUT "https://api.rytiv.app/v1/apps/<name>/policy/network" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"bandwidthMbit":1,"newConnectionsPerMinute":1}'
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## GET /v1/apps/:name/udp

The app's public UDP endpoints.

**Auth:** API key with full access or the scope `read`. Every class of agent token may call it.

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Example request

```bash
curl -X GET "https://api.rytiv.app/v1/apps/<name>/udp" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`200` with a JSON body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## POST /v1/apps/:name/udp

Requests a public UDP port for the app. The port is assigned by the platform.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |

### Request body

JSON, the shape `CreateUdpEndpointRequest` of the contract.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `targetPort` | integer | optional | 1 to 65535. |

### Example request

```bash
curl -X POST "https://api.rytiv.app/v1/apps/<name>/udp" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"targetPort":1}'
```

### Response

`201` with a JSON object carrying `host`, `address`, `note` and the fields of the object the handler builds.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad-request` | The request is malformed or a field is out of range. `error` names every field; `issues` lists them. |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |

## DELETE /v1/apps/:name/udp/:id

Releases a UDP endpoint.

**Auth:** API key with full access (a key with scopes cannot call this route). An agent token of class `sandbox` or `twin` may call it; class `production` is refused (`agent-token-production-readonly`).

### Path parameters

| Name | Description |
| --- | --- |
| `name` | The app's name. |
| `id` | The object's id. |

### Example request

```bash
curl -X DELETE "https://api.rytiv.app/v1/apps/<name>/udp/<id>" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

### Response

`204` with no body.

### Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthenticated` | No API key, or one that does not work. |
| 403 | `token-scope` | The key's scopes do not cover this route. |
| 404 | `not-found` | Not found, or not yours. An object of another organization answers like one that does not exist. |
