---
title: "website.policy@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/website.policy"
updated: null
---

# website.policy@1 · OpenVibe.Services

HTML: [website.policy@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/website.policy)

# WebsitePolicy `website.policy@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: connect
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/website/policy.v1.json`](/docs/contracts/website.policy.json)

website.policy@1: how a Share decides who may reach it and under what conditions (public, OpenVibe sign-in, named people or projects, password, one-time link, expiry), plus IP/country rules, rate limits, a bot check, a WAF preset and required device posture. mode is the only field required except that expiry also requires a non-null expires\_at. id, owner and version are present when Connect returns a stored policy. Password and one-time policies stay inactive until their credential or link is configured through the CON-5 API. A password is stored as a one-way hash beside this record and is never part of this document; password\_set only says one exists.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string |  | Present on a stored policy; omitted when the policy is written inline with a new share. | - `pattern ^pol_[0-9A-HJKMNP-TV-Z]{26}$` |
| `owner` | any |  | The person who owns the policy; set from the token when a policy is created. |  |
| `project_id` | string \| null |  | The project scope, when the policy belongs to one. | - `pattern ^prj_[0-9A-HJKMNP-TV-Z]{26}$` |
| `name` | string |  |  | - `minLength 1`<br>- `maxLength 80` |
| `mode` | enum | yes | public: anyone with the link after the abuse screen; signin: any OpenVibe account (optionally a project); named: allowed\_subjects/allowed\_projects only; password: a shared password; one\_time: single-use links; expiry: public access only until expires\_at. An expires\_at may also cap any other mode. | - `one of "public", "signin", "named", "password", "one_time", "expiry"` |
| `allowed_subjects` | [identity.subject-ref](/docs/contracts/identity.subject-ref) |  | Explicit people or agents allowed in named mode; a project's members are allowed through allowed\_projects instead. | - `maxItems 500`<br>- `unique items` |
| `allowed_projects` | array of string |  | Projects whose members may reach a named or signin share. | - `maxItems 100`<br>- `unique items`<br>- `items: pattern ^prj_[0-9A-HJKMNP-TV-Z]{26}$` |
| `ip_allow` | array of string |  | CIDR ranges allowed before any other rule; empty means no IP allowlist. | - `maxItems 100`<br>- `items: minLength 2`<br>- `items: maxLength 64` |
| `ip_deny` | array of string |  | CIDR ranges always refused; evaluated before the allowlist. | - `maxItems 100`<br>- `items: minLength 2`<br>- `items: maxLength 64` |
| `country_allow` | array of string |  | ISO 3166-1 alpha-2 country codes allowed; empty means all. | - `maxItems 250`<br>- `items: pattern ^[A-Z]{2}$` |
| `country_deny` | array of string |  |  | - `maxItems 250`<br>- `items: pattern ^[A-Z]{2}$` |
| `rate_limits` | object |  |  | - `no other fields` |
| `rate_limits.per_share_per_minute` | integer |  |  | - `minimum 1`<br>- `maximum 1000000` |
| `rate_limits.per_ip_per_minute` | integer |  |  | - `minimum 1`<br>- `maximum 100000` |
| `rate_limits.max_connections` | integer |  |  | - `minimum 1`<br>- `maximum 100000` |
| `rate_limits.max_request_bytes` | integer |  |  | - `minimum 1`<br>- `maximum 1073741824` |
| `bot_check` | enum |  |  | - `one of "none", "turnstile"`<br>- `default "none"` |
| `waf_preset` | enum |  | Defense in depth at the edge; Cloudflare's managed rules run in front of it for HTTP/WS. | - `one of "none", "standard", "strict", "api"`<br>- `default "standard"` |
| `posture_required` | array of enum |  | Self-reported device posture a share may require; a signal, not a guarantee. | - `maxItems 10`<br>- `unique items`<br>- `items: one of "node_current", "os_patched", "firewall_on", "disk_encryption", "no_risky_ports"` |
| `password_set` | boolean |  | Read-only: true when a password hash exists. The hash itself never leaves Connect. |  |
| `password_hint` | string \| null |  | An optional hint shown on the password page; never the password. | - `maxLength 120` |
| `one_time_max_uses` | integer |  | For one\_time mode; 1 unless the owner allows more. | - `minimum 1`<br>- `maximum 1000` |
| `session_ttl_seconds` | integer |  | How long a host-only \_\_Host-ov\_session on the share host stays valid after sign-in. | - `minimum 60`<br>- `maximum 604800` |
| `expires_at` | string \| null |  | The absolute end of the policy; required and non-null for expiry mode, optional for other modes. | - `format date-time` |
| `version` | integer |  | The signed policy version the edge caches and checks on every request; present on a stored policy. | - `minimum 1` |
| `created_at` | string |  |  | - `format date-time` |
| `updated_at` | string |  |  | - `format date-time` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: expiry

~~~~
{
  "id": "pol_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
  "owner": {
    "type": "user",
    "id": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A6"
  },
  "project_id": null,
  "name": "Launch demo",
  "mode": "expiry",
  "allowed_subjects": [],
  "allowed_projects": [],
  "ip_allow": [],
  "ip_deny": [],
  "country_allow": [
    "DE",
    "FR"
  ],
  "country_deny": [],
  "rate_limits": {
    "per_share_per_minute": 600,
    "per_ip_per_minute": 60,
    "max_connections": 100,
    "max_request_bytes": 10485760
  },
  "bot_check": "turnstile",
  "waf_preset": "standard",
  "posture_required": [],
  "password_set": false,
  "session_ttl_seconds": 3600,
  "expires_at": "2026-11-01T00:00:00.000Z",
  "version": 3,
  "created_at": "2026-10-29T08:00:00.000Z",
  "updated_at": "2026-10-29T09:00:00.000Z"
}
~~~~

Valid: password

~~~~
{
  "id": "pol_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
  "owner": {
    "type": "user",
    "id": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A6"
  },
  "project_id": null,
  "name": "Password for the demo",
  "mode": "password",
  "allowed_subjects": [],
  "allowed_projects": [],
  "ip_allow": [],
  "ip_deny": [],
  "country_allow": [
    "DE",
    "FR"
  ],
  "country_deny": [],
  "rate_limits": {
    "per_share_per_minute": 600,
    "per_ip_per_minute": 60,
    "max_connections": 100,
    "max_request_bytes": 10485760
  },
  "bot_check": "turnstile",
  "waf_preset": "standard",
  "posture_required": [],
  "password_set": true,
  "password_hint": "the one from the call",
  "one_time_max_uses": 1,
  "session_ttl_seconds": 3600,
  "expires_at": null,
  "version": 3,
  "created_at": "2026-10-29T08:00:00.000Z",
  "updated_at": "2026-10-29T09:00:00.000Z"
}
~~~~

Rejected: bad-mode

~~~~
{
  "mode": "invite_only"
}
~~~~

Rejected: missing-expiry

~~~~
{
  "id": "pol_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
  "owner": {
    "type": "user",
    "id": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A6"
  },
  "project_id": null,
  "name": "Launch demo",
  "mode": "expiry",
  "allowed_subjects": [],
  "allowed_projects": [],
  "ip_allow": [],
  "ip_deny": [],
  "country_allow": [
    "DE",
    "FR"
  ],
  "country_deny": [],
  "rate_limits": {
    "per_share_per_minute": 600,
    "per_ip_per_minute": 60,
    "max_connections": 100,
    "max_request_bytes": 10485760
  },
  "bot_check": "turnstile",
  "waf_preset": "standard",
  "posture_required": [],
  "password_set": false,
  "session_ttl_seconds": 3600,
  "version": 3,
  "created_at": "2026-10-29T08:00:00.000Z",
  "updated_at": "2026-10-29T09:00:00.000Z"
}
~~~~

Rejected: null-expiry

~~~~
{
  "id": "pol_01J8Z3Q4R5S6T7V8W9X0Y1Z2A4",
  "owner": {
    "type": "user",
    "id": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A6"
  },
  "project_id": null,
  "name": "Launch demo",
  "mode": "expiry",
  "allowed_subjects": [],
  "allowed_projects": [],
  "ip_allow": [],
  "ip_deny": [],
  "country_allow": [
    "DE",
    "FR"
  ],
  "country_deny": [],
  "rate_limits": {
    "per_share_per_minute": 600,
    "per_ip_per_minute": 60,
    "max_connections": 100,
    "max_request_bytes": 10485760
  },
  "bot_check": "turnstile",
  "waf_preset": "standard",
  "posture_required": [],
  "password_set": false,
  "session_ttl_seconds": 3600,
  "expires_at": null,
  "version": 3,
  "created_at": "2026-10-29T08:00:00.000Z",
  "updated_at": "2026-10-29T09:00:00.000Z"
}
~~~~

Rejected: secret-field

~~~~
{
  "mode": "password",
  "password_hash": "$argon2id$v=19$m=65536,t=3,p=4$example$example"
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('website.policy@1', value);   // { valid, errors: [{ path, message }] }
~~~~
