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

# network.consent@1 · OpenVibe.Services

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

# NetworkConsent `network.consent@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: network
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/network/consent.v1.json`](/docs/contracts/network.consent.json)

network.consent@1 (L24, data and AI by consent, 2026-10-31): one person's choice for one purpose. Nothing a person makes is off-limits to AI or data use by design; each use is off until that person turns it on, here. The record is the proof of what was allowed, by whom, under which wording and when: a choice is turned off by setting revoked\_at, never by deleting the row. Stored by OpenVibe.Network (consents, with every change in consent\_events), read by a person at GET /api/v1/me/consent and by services through POST /internal/consent/check (capability network.consent.read). A service that cannot reach Network treats every purpose as off.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `id` | string | yes | The choice's own id (cns\_<ULID>). A choice changed later (other kinds, another licence) is a new record; the old one is revoked. | - `pattern ^cns_[0-9A-HJKMNP-TV-Z]{26}$` |
| `subject` | string | yes | Whose choice it is. Only that person (or an account merge or deletion) changes it. | - `pattern ^usr_[0-9A-HJKMNP-TV-Z]{26}$` |
| `purpose` | enum | yes | What turning it on allows. ai.train: OpenVibe trains or fine-tunes models on the person's content. data.public: the person's content goes into OpenVibe's open dataset under the licence in options. face.recognize: the person's face is recognised (the features in options; OpenVibe.Vision keeps the encrypted templates). ai.looks is retired (0.256.0): the owner removed the rule that AI text never describes people (2026-10-31), so describing how someone looks needs no opt-in; it stays in the list so older readers still validate, and nothing writes it. app.data: one named app (client) may use the person's content for the purposes in options. | - `one of "ai.train", "data.public", "face.recognize", "ai.looks", "app.data"` |
| `on` | boolean | yes | Whether the purpose is allowed now: true while the record is active (revoked\_at null and expires\_at not passed). A service never decides from this field alone; it asks the check. |  |
| `kinds` | array of enum | yes | Which of the person's content it covers. Empty means every kind the purpose applies to. face.recognize and ai.looks take no kinds (they are about the person, not a piece of content). | - `maxItems 8`<br>- `unique items`<br>- `items: one of "streams", "vods", "clips", "voice", "chat", "posts", "pictures", "code"` |
| `client` | string \| null | yes | The app the choice is for: null means OpenVibe itself; otherwise the OAuth client id of the app the person granted it to. app.data always names one. Since 0.310.0 an app may ask for any purpose at sign-in (consent:<purpose>, network.consent-claim@1): the grant is one record per purpose with this client and source oauth, and it allows that app only, never OpenVibe itself or another app. | - `pattern ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$` |
| `options` | object | yes | What the purpose needs beyond on and kinds. An empty object for ai.train and ai.looks. | - `no other fields` |
| `options.licence` | enum |  | data.public only: the licence the person's content is published under. | - `one of "cc-by-4.0", "cc0-1.0"` |
| `options.features` | array of enum |  | face.recognize only: which recognition the person allows (OpenVibe.Vision's features). Empty means none yet. | - `maxItems 4`<br>- `unique items`<br>- `items: one of "verify", "find", "cast", "library"` |
| `options.purposes` | array of enum |  | app.data only, and not written since 0.310.0: an app's consent screen grants each purpose as its own record (client set), so a reader asks for the purpose itself. Kept so older records still validate. | - `maxItems 4`<br>- `unique items`<br>- `items: one of "ai.train", "data.public", "face.recognize", "ai.looks"` |
| `text_version` | string | yes | The exact wording the person saw when choosing (YYYY-MM-DD.n). A new wording never turns an existing choice on; it may only ask again. | - `pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}\.[0-9]{1,3}$` |
| `source` | enum | yes | Where the choice was made: the account page, an app's consent screen at sign-in (consent:<purpose>, with client set), the person's own API call, or OpenVibe.Vision's older face consents moved into Network. | - `one of "account", "oauth", "api", "import:vision"` |
| `granted_at` | string | yes | When the person turned it on. | - `format date-time` |
| `revoked_at` | string \| null | yes | When it was turned off (by the person, by revoking the app, by a merge or by deleting the account), or null while it holds. Use stops on the next check; a model already trained keeps what it learned, and the next dataset release leaves the person's content out. | - `format date-time` |
| `expires_at` | string \| null | yes | When the choice ends by itself (a time-limited grant, or the end of a face purpose), or null for no end. | - `format date-time` |

## Examples

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

 Valid: ai-train-streams

~~~~
{
  "id": "cns_01K6R3A1B2C3D4E5F6G7H8J9KA",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "ai.train",
  "on": true,
  "kinds": [
    "streams",
    "vods"
  ],
  "client": null,
  "options": {},
  "text_version": "2026-10-31.1",
  "source": "account",
  "granted_at": "2026-10-31T05:00:00.000Z",
  "revoked_at": null,
  "expires_at": null
}
~~~~

Valid: app-data-revoked

~~~~
{
  "id": "cns_01K6R3A1B2C3D4E5F6G7H8J9KC",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "app.data",
  "on": false,
  "kinds": [
    "pictures"
  ],
  "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
  "options": {
    "purposes": [
      "ai.train"
    ]
  },
  "text_version": "2026-10-31.1",
  "source": "oauth",
  "granted_at": "2026-10-31T05:00:00.000Z",
  "revoked_at": "2026-11-02T08:00:00.000Z",
  "expires_at": null
}
~~~~

Valid: face-recognize

~~~~
{
  "id": "cns_01K6R3A1B2C3D4E5F6G7H8J9KB",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "face.recognize",
  "on": true,
  "kinds": [],
  "client": null,
  "options": {
    "features": [
      "verify",
      "library"
    ]
  },
  "text_version": "2026-10-31.1",
  "source": "import:vision",
  "granted_at": "2026-10-31T05:00:00.000Z",
  "revoked_at": null,
  "expires_at": "2027-10-31T05:00:00.000Z"
}
~~~~

Valid: oauth-app-train

~~~~
{
  "id": "cns_01K9A3A1B2C3D4E5F6G7H8J9KC",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "ai.train",
  "on": true,
  "kinds": [],
  "client": "app_01K6R3A1B2C3D4E5F6G7H8J9KD",
  "options": {},
  "text_version": "2026-11-07.1",
  "source": "oauth",
  "granted_at": "2026-11-07T05:00:00.000Z",
  "revoked_at": null,
  "expires_at": null
}
~~~~

Rejected: bad-id

~~~~
{
  "id": "cns_123",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "ai.train",
  "on": true,
  "kinds": [
    "streams",
    "vods"
  ],
  "client": null,
  "options": {},
  "text_version": "2026-10-31.1",
  "source": "account",
  "granted_at": "2026-10-31T05:00:00.000Z",
  "revoked_at": null,
  "expires_at": null
}
~~~~

Rejected: unknown-purpose

~~~~
{
  "id": "cns_01K6R3A1B2C3D4E5F6G7H8J9KA",
  "subject": "usr_01K6R2Z8C4V7M9Q3T5W1X2Y3Z4",
  "purpose": "data.sell",
  "on": true,
  "kinds": [
    "streams",
    "vods"
  ],
  "client": null,
  "options": {},
  "text_version": "2026-10-31.1",
  "source": "account",
  "granted_at": "2026-10-31T05:00:00.000Z",
  "revoked_at": null,
  "expires_at": null
}
~~~~

## Validate

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