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

# ai.offer@1 · OpenVibe.Services

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

# AiOffer `ai.offer@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: ai
- Visibility: first-party
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/ai/offer.v1.json`](/docs/contracts/ai.offer.json)

ai.offer@1: one community AI offer, a person's machine (OpenVibe Link's ai plugin, "Offer to the network") serving its local models to requests that opt in to community capacity. OpenVibe.Bot reports these fields (POST /internal/ai/offers/report on Network, network.resource.report; a report with any other field is refused whole), and OpenVibe.AI reads them from Network (GET /internal/ai/offers, network.registry.read, { offers, generated\_at, count }). Network adds \`pool\` (ai.offer-pool@1) to the head's offer when the machine is the head of an approved pool (bot.machine-pool@1), and leaves a pool member's own offer out of the list while it is in a pool. For a pool the hours, audience, price, readiness, idle-only and verification apply to the whole pool: it is ready only while every member has approved, renewed its readiness in the last few seconds, and is still the owner's. Nothing here is public: AI shows Exchange a hashed offer id and no owner or machine id.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `offer_id` | string | yes | \`ai:<machine\_id>\`: one offer per machine. | - `pattern ^ai:dev_[0-9A-HJKMNP-TV-Z]{26}$` |
| `machine_id` | string | yes | The machine that answers (for a pool, its head). | - `pattern ^dev_[0-9A-HJKMNP-TV-Z]{26}$` |
| `owner` | string | yes | The machine's owner, who earns the offer's credits (a self-served run earns none). | - `pattern ^usr_[0-9A-HJKMNP-TV-Z]{26}$` |
| `models` | array of string | yes | The models offered, as the machine's model server names them. | - `minItems 1`<br>- `maxItems 40`<br>- `items: minLength 1`<br>- `items: maxLength 200` |
| `hours` | object | yes | The local-time window the offer is open; start equal to end is all day, and an overnight window wraps. | - `no other fields` |
| `hours.start` | string | yes |  | - `pattern ^([01][0-9]\|2[0-3]):[0-5][0-9]$` |
| `hours.end` | string | yes |  | - `pattern ^([01][0-9]\|2[0-3]):[0-5][0-9]$` |
| `hours.tz` | string | yes | An IANA time zone, such as Europe/Paris, or UTC. | - `minLength 1`<br>- `maxLength 64` |
| `concurrency` | integer | yes | How many requests may run at once; another is refused as busy, never queued. | - `minimum 1`<br>- `maximum 8` |
| `price` | object | yes | Credits per million input and output tokens; 0 and 0 is a volunteer, who earns kudos. One price for a pool. | - `no other fields` |
| `price.in_per_million` | number | yes |  | - `minimum 0`<br>- `maximum 1000000` |
| `price.out_per_million` | number | yes |  | - `minimum 0`<br>- `maximum 1000000` |
| `audience` | enum | yes | Who may use it: everyone, the owner's followers, or one group (\`group\`). | - `one of "everyone", "followers", "group"` |
| `group` | string \| null | yes | The group's id when the audience is \`group\`, otherwise null. | - `maxLength 80` |
| `ready` | boolean | yes | Whether the machine (for a pool, every member) can answer now: online, inside its hours, not paused by idle-only or a kill switch. |  |
| `health` | object | yes |  | - `no other fields` |
| `health.status` | enum | yes |  | - `one of "up", "degraded", "down"` |
| `health.checked_at` | string |  |  | - `format date-time` |
| `pool` | [ai.offer-pool](/docs/contracts/ai.offer-pool) |  | Present when the offer is served by a pool of the owner's machines (ai.offer-pool@1). |  |

## Examples

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

 Valid: group-audience

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "qwen3:8b"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "group",
  "group": "grp_friends",
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  }
}
~~~~

Valid: pool-waiting-for-approvals

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": false,
  "health": {
    "status": "down"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
  }
}
~~~~

Valid: pool

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
  }
}
~~~~

Valid: single-machine

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "qwen3:8b"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  }
}
~~~~

Rejected: extra-field

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
  },
  "private_key": "not-permitted"
}
~~~~

Rejected: missing-owner

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
  }
}
~~~~

Rejected: offer-id-not-a-machine

~~~~
{
  "offer_id": "ai:everything",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "qwen3:8b"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  }
}
~~~~

Rejected: one-machine-pool

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1"
  }
}
~~~~

Rejected: pool-with-address

~~~~
{
  "offer_id": "ai:dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "machine_id": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
  "owner": "usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
  "models": [
    "llama-3.3-70b-instruct-q4_k_m"
  ],
  "hours": {
    "start": "22:00",
    "end": "08:00",
    "tz": "Europe/Paris"
  },
  "concurrency": 1,
  "price": {
    "in_per_million": 0.2,
    "out_per_million": 0.6
  },
  "audience": "everyone",
  "group": null,
  "ready": true,
  "health": {
    "status": "up",
    "checked_at": "2026-11-07T10:00:00.000Z"
  },
  "pool": {
    "machines": [
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
      "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2"
    ],
    "head": "dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1",
    "address": "192.168.1.20/24"
  }
}
~~~~

## Validate

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