UsageRecorded common.usage-recorded@1

Generated at from openvibe-contracts v0.76.0 and openvibe-sdk v0.20.1.

Version
1.0.0
Owner
network
Visibility
first-party
Status
active
Compatibility
backward
Decision
ADR-014
Schema
https://openvibe.network/contracts/common/usage-recorded.v1.json

common.usage-recorded@1 (roadmap WS-N task 4, ADR-014): one rollup of a developer project's use of one capability in one environment over one closed window (an hour or a day), as the service that owns the capability counted it. The payload of every <service>.usage.recorded event; OpenVibe.Network adds them up per project and day for the project's dashboard on OpenVibe.Codes. The producer counts in the transaction that does its own accounting (a job's end, a stored event, a delivery attempt) and writes the rollup to its outbox after the window has closed: never one event per request. Totals, not deltas: a later event with the same key (source, project_id, env, capability, dimension, unit, window_start) replaces the earlier one, and a lower revision never replaces a higher one. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor the producing service. Never carries a subject id, an address, a session, a request's input or content, or a file name.

Fields

FieldTypeRequiredDescriptionConstraints
project_idstringyesThe developer project whose app token did the work.
  • pattern ^prj_[0-9A-HJKMNP-TV-Z]{26}$
envenumyesThe environment of that app token.
  • one of "sandbox", "production"
capabilitystringyesThe capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe.
  • pattern ^[a-z][a-z0-9_]*(\.[a-z0-9_]+){2,}$
  • maxLength 80
dimensionstringOptional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job.
  • pattern ^[a-z0-9][a-z0-9_.:-]*$
  • minLength 1
  • maxLength 80
unitstringyesWhat quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit.
  • pattern ^[a-z][a-z0-9_]{0,31}$
windowenumyesThe length of the window.
  • one of "hour", "day"
window_startstringyesUTC, on the hour (midnight for a day).
  • format date-time
window_endstringyeswindow_start plus the window, exclusive.
  • format date-time
quantityintegeryesUnits used in the window, by work that succeeded and work that failed alike.
  • minimum 0
errorsintegeryesOperations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity.
  • minimum 0
error_codesobjectThe errors by problem code (at most 20 codes; the rest are only in errors).
samplesarray of objectThe last failures of the window, newest first, so a developer can find them in their own logs.
  • maxItems 10
  • items: no other fields
samples[].atstringyes
  • format date-time
samples[].codestringyes
  • pattern ^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$
  • maxLength 80
samples[].statusintegerThe HTTP status the failure was answered with, when there was one.
  • minimum 100
  • maximum 599
samples[].trace_idstringThe W3C trace id of the request (or event) that failed.
  • pattern ^[0-9a-f]{32}$
samples[].refstringWhat failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
  • pattern ^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$
revisioninteger1 when absent. A producer that re-emits a window with corrected totals raises it.
  • minimum 1

Examples

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

Valid: events-publish-refused
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "sandbox",
  "capability": "events.app.publish",
  "unit": "events",
  "window": "hour",
  "window_start": "2026-09-26T09:00:00.000Z",
  "window_end": "2026-09-26T10:00:00.000Z",
  "quantity": 1800,
  "errors": 61,
  "error_codes": {
    "events.quota_exceeded": 60,
    "events.type_not_allowed": 1
  },
  "samples": [
    {
      "at": "2026-09-26T09:59:58.120Z",
      "code": "events.quota_exceeded",
      "status": 429,
      "trace_id": "0af7651916cd43dd8448eb211c80319c"
    }
  ]
}
Valid: minimal-day
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "day",
  "window_start": "2026-09-25T00:00:00.000Z",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": 0,
  "errors": 0
}
Valid: revised-totals
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "day",
  "window_start": "2026-09-25T00:00:00.000Z",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": 7,
  "errors": 0,
  "revision": 2
}
Valid: tools-jobs-hour
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.timeout",
      "status": 504,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
    },
    {
      "at": "2026-09-26T14:20:13.001Z",
      "code": "tools.job.failed",
      "status": 422,
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
    }
  ]
}
Rejected: carries-an-address
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.timeout",
      "status": 504,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
    },
    {
      "at": "2026-09-26T14:20:13.001Z",
      "code": "tools.job.failed",
      "status": 422,
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
    }
  ],
  "ip": "203.0.113.7"
}
Rejected: carries-the-app
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.timeout",
      "status": 504,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
    },
    {
      "at": "2026-09-26T14:20:13.001Z",
      "code": "tools.job.failed",
      "status": 422,
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
    }
  ],
  "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPQ"
}
Rejected: dimension-is-a-person
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "usr_01jab2c3d4e5f6g7h8j9k0mnpq",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.timeout",
      "status": 504,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
    },
    {
      "at": "2026-09-26T14:20:13.001Z",
      "code": "tools.job.failed",
      "status": 422,
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
    }
  ]
}
Rejected: error-code-not-an-id
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "Timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.timeout",
      "status": 504,
      "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
    },
    {
      "at": "2026-09-26T14:20:13.001Z",
      "code": "tools.job.failed",
      "status": 422,
      "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
    }
  ]
}
Rejected: negative-quantity
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "day",
  "window_start": "2026-09-25T00:00:00.000Z",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": -1,
  "errors": 0
}
Rejected: no-window-start
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "day",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": 0,
  "errors": 0
}
Rejected: per-minute-window
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "minute",
  "window_start": "2026-09-25T00:00:00.000Z",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": 0,
  "errors": 0
}
Rejected: sample-with-message
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.failed",
      "detail": "Cannot read holiday.jpg"
    }
  ]
}
Rejected: subject-in-sample
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "capability": "tools.job.create",
  "dimension": "img.process",
  "unit": "jobs",
  "window": "hour",
  "window_start": "2026-09-26T14:00:00.000Z",
  "window_end": "2026-09-26T15:00:00.000Z",
  "quantity": 42,
  "errors": 3,
  "error_codes": {
    "tools.job.failed": 2,
    "tools.job.timeout": 1
  },
  "samples": [
    {
      "at": "2026-09-26T14:51:07.412Z",
      "code": "tools.job.failed",
      "ref": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
    }
  ]
}
Rejected: unknown-env
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "staging",
  "capability": "tools.tool.run",
  "unit": "jobs",
  "window": "day",
  "window_start": "2026-09-25T00:00:00.000Z",
  "window_end": "2026-09-26T00:00:00.000Z",
  "quantity": 0,
  "errors": 0
}

Validate

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