ProjectUsageResult network.project-usage-result@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/network/project-usage-result.v1.json

network.project-usage-result@1 (roadmap WS-N task 4, ADR-014): what GET /api/v1/projects/:project/usage?days=&env= on OpenVibe.Network answers a project's owner or admin (or staff): the project's usage per day, service, capability and unit, added up from the <service>.usage.recorded rollups (common.usage-recorded@1) the owning services emit after each hour closes; the project's recorded quotas with what the current window has used; and its recent failures with their codes and trace ids. Counts only, never who did what: no subject id, address, input or content.

Fields

FieldTypeRequiredDescriptionConstraints
project_idstringyes
  • pattern ^prj_[0-9A-HJKMNP-TV-Z]{26}$
envenumyesThe environments counted (the env query parameter; all by default).
  • one of "all", "sandbox", "production"
rangeobjectyes
  • no other fields
range.daysintegeryes
  • minimum 1
  • maximum 90
range.fromstringyesFirst UTC day counted.
  • format date
range.tostringyesLast UTC day counted (today).
  • format date
generated_atstringyes
  • format date-time
last_recorded_atstring | nullyesWhen the newest rollup for this project arrived; null before the first.
  • format date-time
freshnessstringyesHow current the numbers are, in words (rollups arrive after each hour closes).
  • maxLength 300
totalsarray of objectyesThe whole range per service, capability, unit and environment.
  • items: no other fields
totals[].servicehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/serviceyes
totals[].capabilityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/capabilityyes
totals[].unithttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/unityes
totals[].envhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/envyes
totals[].quantityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
totals[].errorshttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
dailyarray of objectyesOne row per UTC day, service, capability, dimension, unit and environment that saw usage; days without any are absent. Newest day first.
  • items: no other fields
daily[].daystringyes
  • format date
daily[].servicehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/serviceyes
daily[].capabilityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/capabilityyes
daily[].dimensionstring | nullyes
  • maxLength 80
daily[].unithttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/unityes
daily[].envhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/envyes
daily[].quantityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
daily[].errorshttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
quotasarray of objectyesThe project's recorded quotas (GET /api/v1/projects/:project/quotas), each with what its current window has used, in every environment together. used is null where the rollups cannot tell (a minute or hour window, or a unit no service reports for that capability).
  • items: no other fields
quotas[].capabilityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/capabilityyes
quotas[].limitintegeryes
  • minimum 0
quotas[].windowenumyes
  • one of "minute", "hour", "day", "month", "total"
quotas[].unithttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/unityes
quotas[].enforced_bystring | nullyesThe audience of the service that enforces it (openvibe.<owner>).
quotas[].usedinteger | nullyes
  • minimum 0
quotas[].remaininginteger | nullyes
  • minimum 0
quotas[].window_startstring | nullyesStart of the current window counted (today, this month); null for total and when used is null.
  • format date-time
quotas[].notestring | nullyes
  • maxLength 300
errorsobjectyes
  • no other fields
errors.totalhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
errors.by_codearray of objectyes
  • items: no other fields
errors.by_code[].servicehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/serviceyes
errors.by_code[].capabilityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/capabilityyes
errors.by_code[].codehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/codeyes
errors.by_code[].counthttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/countyes
errors.recentarray of objectyesThe newest failures the rollups sampled, newest first.
  • maxItems 50
  • items: no other fields
errors.recent[].atstringyes
  • format date-time
errors.recent[].envhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/envyes
errors.recent[].servicehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/serviceyes
errors.recent[].capabilityhttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/capabilityyes
errors.recent[].codehttps://openvibe.network/contracts/network/project-usage-result.v1.json#/$defs/codeyes
errors.recent[].statusinteger | nullyes
  • minimum 100
  • maximum 599
errors.recent[].trace_idstring | nullyes
  • pattern ^[0-9a-f]{32}$
errors.recent[].refstring | nullyes
  • pattern ^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$

Examples

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

Valid: dashboard
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "all",
  "range": {
    "days": 30,
    "from": "2026-08-28",
    "to": "2026-09-26"
  },
  "generated_at": "2026-09-26T15:05:00.000Z",
  "last_recorded_at": "2026-09-26T15:02:11.000Z",
  "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
  "totals": [
    {
      "service": "tools",
      "capability": "tools.job.create",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "service": "events",
      "capability": "events.app.publish",
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "daily": [
    {
      "day": "2026-09-26",
      "service": "tools",
      "capability": "tools.job.create",
      "dimension": "img.process",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "day": "2026-09-26",
      "service": "events",
      "capability": "events.app.publish",
      "dimension": null,
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "quotas": [
    {
      "capability": "tools.job.create",
      "limit": 1000,
      "window": "day",
      "unit": "jobs",
      "enforced_by": "openvibe.tools",
      "used": 42,
      "remaining": 958,
      "window_start": "2026-09-26T00:00:00.000Z",
      "note": null
    },
    {
      "capability": "events.app.publish",
      "limit": 120,
      "window": "minute",
      "unit": "events",
      "enforced_by": "openvibe.events",
      "used": null,
      "remaining": null,
      "window_start": null,
      "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
    }
  ],
  "errors": {
    "total": 64,
    "by_code": [
      {
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "count": 60
      },
      {
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.failed",
        "count": 2
      }
    ],
    "recent": [
      {
        "at": "2026-09-26T14:51:07.412Z",
        "env": "production",
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.timeout",
        "status": 504,
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
      },
      {
        "at": "2026-09-26T09:59:58.120Z",
        "env": "sandbox",
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "status": 429,
        "trace_id": "0af7651916cd43dd8448eb211c80319c",
        "ref": null
      }
    ]
  }
}
Valid: nothing-yet
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "production",
  "range": {
    "days": 7,
    "from": "2026-09-20",
    "to": "2026-09-26"
  },
  "generated_at": "2026-09-26T15:05:00.000Z",
  "last_recorded_at": null,
  "freshness": "No usage has been recorded for this project yet.",
  "totals": [],
  "daily": [],
  "quotas": [],
  "errors": {
    "total": 0,
    "by_code": [],
    "recent": []
  }
}
Rejected: a-year
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "all",
  "range": {
    "days": 365,
    "from": "2026-08-28",
    "to": "2026-09-26"
  },
  "generated_at": "2026-09-26T15:05:00.000Z",
  "last_recorded_at": "2026-09-26T15:02:11.000Z",
  "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
  "totals": [
    {
      "service": "tools",
      "capability": "tools.job.create",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "service": "events",
      "capability": "events.app.publish",
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "daily": [
    {
      "day": "2026-09-26",
      "service": "tools",
      "capability": "tools.job.create",
      "dimension": "img.process",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "day": "2026-09-26",
      "service": "events",
      "capability": "events.app.publish",
      "dimension": null,
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "quotas": [
    {
      "capability": "tools.job.create",
      "limit": 1000,
      "window": "day",
      "unit": "jobs",
      "enforced_by": "openvibe.tools",
      "used": 42,
      "remaining": 958,
      "window_start": "2026-09-26T00:00:00.000Z",
      "note": null
    },
    {
      "capability": "events.app.publish",
      "limit": 120,
      "window": "minute",
      "unit": "events",
      "enforced_by": "openvibe.events",
      "used": null,
      "remaining": null,
      "window_start": null,
      "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
    }
  ],
  "errors": {
    "total": 64,
    "by_code": [
      {
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "count": 60
      },
      {
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.failed",
        "count": 2
      }
    ],
    "recent": [
      {
        "at": "2026-09-26T14:51:07.412Z",
        "env": "production",
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.timeout",
        "status": 504,
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
      },
      {
        "at": "2026-09-26T09:59:58.120Z",
        "env": "sandbox",
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "status": 429,
        "trace_id": "0af7651916cd43dd8448eb211c80319c",
        "ref": null
      }
    ]
  }
}
Rejected: negative-use
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "all",
  "range": {
    "days": 30,
    "from": "2026-08-28",
    "to": "2026-09-26"
  },
  "generated_at": "2026-09-26T15:05:00.000Z",
  "last_recorded_at": "2026-09-26T15:02:11.000Z",
  "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
  "totals": [
    {
      "service": "tools",
      "capability": "tools.job.create",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "service": "events",
      "capability": "events.app.publish",
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "daily": [
    {
      "day": "2026-09-26",
      "service": "tools",
      "capability": "tools.job.create",
      "dimension": "img.process",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "day": "2026-09-26",
      "service": "events",
      "capability": "events.app.publish",
      "dimension": null,
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "quotas": [
    {
      "capability": "tools.job.create",
      "limit": 1000,
      "window": "day",
      "unit": "jobs",
      "enforced_by": "openvibe.tools",
      "used": -5,
      "remaining": 958,
      "window_start": "2026-09-26T00:00:00.000Z",
      "note": null
    },
    {
      "capability": "events.app.publish",
      "limit": 120,
      "window": "minute",
      "unit": "events",
      "enforced_by": "openvibe.events",
      "used": null,
      "remaining": null,
      "window_start": null,
      "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
    }
  ],
  "errors": {
    "total": 64,
    "by_code": [
      {
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "count": 60
      },
      {
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.failed",
        "count": 2
      }
    ],
    "recent": [
      {
        "at": "2026-09-26T14:51:07.412Z",
        "env": "production",
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.timeout",
        "status": 504,
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
      },
      {
        "at": "2026-09-26T09:59:58.120Z",
        "env": "sandbox",
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "status": 429,
        "trace_id": "0af7651916cd43dd8448eb211c80319c",
        "ref": null
      }
    ]
  }
}
Rejected: recent-error-names-a-person
{
  "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "env": "all",
  "range": {
    "days": 30,
    "from": "2026-08-28",
    "to": "2026-09-26"
  },
  "generated_at": "2026-09-26T15:05:00.000Z",
  "last_recorded_at": "2026-09-26T15:02:11.000Z",
  "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
  "totals": [
    {
      "service": "tools",
      "capability": "tools.job.create",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "service": "events",
      "capability": "events.app.publish",
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "daily": [
    {
      "day": "2026-09-26",
      "service": "tools",
      "capability": "tools.job.create",
      "dimension": "img.process",
      "unit": "jobs",
      "env": "production",
      "quantity": 42,
      "errors": 3
    },
    {
      "day": "2026-09-26",
      "service": "events",
      "capability": "events.app.publish",
      "dimension": null,
      "unit": "events",
      "env": "sandbox",
      "quantity": 1800,
      "errors": 61
    }
  ],
  "quotas": [
    {
      "capability": "tools.job.create",
      "limit": 1000,
      "window": "day",
      "unit": "jobs",
      "enforced_by": "openvibe.tools",
      "used": 42,
      "remaining": 958,
      "window_start": "2026-09-26T00:00:00.000Z",
      "note": null
    },
    {
      "capability": "events.app.publish",
      "limit": 120,
      "window": "minute",
      "unit": "events",
      "enforced_by": "openvibe.events",
      "used": null,
      "remaining": null,
      "window_start": null,
      "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
    }
  ],
  "errors": {
    "total": 64,
    "by_code": [
      {
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "count": 60
      },
      {
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.failed",
        "count": 2
      }
    ],
    "recent": [
      {
        "at": "2026-09-26T14:51:07.412Z",
        "env": "production",
        "service": "tools",
        "capability": "tools.job.create",
        "code": "tools.job.timeout",
        "status": 504,
        "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
        "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
        "subject": {
          "type": "user",
          "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
        }
      },
      {
        "at": "2026-09-26T09:59:58.120Z",
        "env": "sandbox",
        "service": "events",
        "capability": "events.app.publish",
        "code": "events.quota_exceeded",
        "status": 429,
        "trace_id": "0af7651916cd43dd8448eb211c80319c",
        "ref": null
      }
    ]
  }
}

Validate

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