- JavaScript 98.5%
- CSS 0.9%
- PLpgSQL 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .github/workflows | ||
| deploy | ||
| migrations | ||
| public | ||
| server | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| .gitleaks.toml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| SECURITY.md | ||
| STATUS.json | ||
OpenVibe.Services
The developer platform of the OpenVibe network: one console for projects and apps over OpenVibe.Network, scoped credentials, capability grants, usage and project export; app and mod releases; API, contract, SDK and event reference generated from what production runs; OAuth and webhook tools; and one resource index over every service.
Status: alpha. Serves https://openvibe.services (openvibe-ovh, unit openvibe-services on 127.0.0.1:4930 behind nginx). The developer portal moved here from OpenVibe.Codes on 2026-10-08 (owner decision: Codes became the open coding-agent harness); its production data then was nil (0 manifests, 0 releases, 0 playground runs), so nothing was copied, and openvibe.codes answers its old portal paths with permanent redirects to the same paths here. Tests run against in-process stand-ins for Network, Events and Media. See STATUS.json for exactly what works and what does not.
Domain: openvibe.services · Port: 4930 · Service id: services
Plan: plan T13 (the developer platform and the resource control plane); binding decisions ADR-014 (the portal over Network's projects API) and ADR-048 (the control plane) in OpenVibe.Contracts.
License: AGPL-3.0 (same as every OpenVibe service).
Purpose
A place where a developer outside the network goes from an OpenVibe account to a working, scoped integration using only public docs, the SDK and their own credentials. Services is a console: the identity authority (OpenVibe.Network) owns projects, apps, credentials, grants and quotas (ADR-014), and Services shows and forwards what the signed-in person asks for, with that person's own Network token.
Owns
- Release metadata keyed to Network app ids: validated app and mod manifests, releases (
draft → published → deprecated → revoked), an append-only release log. - The merged resource index (ADR-048,
services.resource.read): every authority's resources, read from each one and merged, never stored as the truth; Services' own manifests (services.manifest,mfs_) and releases (services.release,rel_) are read in process. Trust tiers and playground runs are metadata and audit, never resources. - Trust tiers per ADR-013 (
unreviewed,reviewed,first-party) — metadata only: a tier changes defaults and discovery, never a grant check. Databases written with the earlier four names are migrated at boot (untrusted→unreviewed, verified/trusted→reviewed, platform-maintained→first-party), idempotently. - Playground run logs (who ran what, outcome, stage, problem code; never a credential).
- The events
services.app.published,services.app.deprecated,services.app.revoked, andservices.moderation.actionfor staff revocations and trust tier changes (the network's moderation audit log), through the openvibe-sdk transactional outbox. - The generated reference (rendered from the pinned packages at boot; nothing hand-written that can drift) and the console's pages.
Does not own
- Projects, members, apps, credentials, grants, allowances, quotas, audit: OpenVibe.Network (
/api/v1/projects). Services never copies them. - Secrets: Network returns a client secret once; Services renders it in that one response (
Cache-Control: no-store) and keeps no copy. Tests scan every table, every log line and every later response for each secret. - Token issuance and quota enforcement: Network issues tokens; the service that owns a capability enforces its quota. Services labels quotas "recorded limit — enforced by
<service>". - The registry (Network) and the contracts (OpenVibe.Contracts).
What is here
| Surface | Path | Notes |
|---|---|---|
| Portal | /projects, /projects/mine, /projects/:project, /projects/:project/resources, /projects/:project/apps/:app |
Create projects; add members; your resources (/projects/mine: what the signed-in person owns outside any project, such as robots, chat rooms, streams, watches and Actor tasks. Every authority is asked with ?owner=<their subject> (openvibe-sdk 0.41), and the merged page is kept to their own rows in no project, so an authority without the filter yet never shows another person's. Only the session's own subject is ever asked for); resources (every member and staff: /projects/:project/resources?service=&kind=, the merged index for that one project, server-rendered: name and OVRN, kind, state, owner, created and updated, 50 a page on the index's own cursor; Network confirms membership before any authority is asked, and an authority that could not be read is named with the problem it gave); secrets (/projects/:project/secrets, ADR-056: Network's metadata for every member, name, version, fingerprint and which services may sign with each or receive it for which origins; admins and owners set a value, which passes through to Network once and is never shown, stored or logged here, delete it and grant or revoke services); create sandbox/production apps (secret shown once); rotate and revoke credentials; request, approve, deny and revoke grants through a scope editor that offers only grantable capabilities (public, or partner when in the allowance) with each one's description, owner and visibility; quotas; usage (owner, admins and staff: `/projects/:project/usage?days=7 |
| OAuth helper | /oauth, /oauth/test-callback |
Explains authorization code + PKCE (S256) for apps, builds a test authorize URL, and a callback that shows code/state/error and never exchanges the code. |
| Webhook tools | /tools/webhooks |
Verify a delivery as a receiver that requires signature v2 does: X-OpenVibe-Signature-V2: t=<ts>,v2=<HMAC of "<ts>.<raw body>"> must match and t must be within ±300 s of now (the page shows the age and the window, and tells a stale-but-correct signature from a wrong one); X-OpenVibe-Timestamp must equal t. v2 only: the v1 header (X-OpenVibe-Signature) was retired on 2026-09-28 and has no field. In the browser with Web Crypto, or on the server without JavaScript with openvibe-sdk's verifyDeliveryV2; constant-time; secret dropped. Generate a sample delivery signed as Events signs it (X-OpenVibe-Timestamp and v2, signDeliveryHeaders) for any event type the contracts catalog lists. |
| Docs | /docs/* |
Contracts (field tables, fixtures as examples, raw schema), capability catalog (grantable ones highlighted), event types, SDK reference from its .d.ts files, ADRs. Every page states the versions it was generated from. The service registry is read live from Network, with health as Network reports it. |
| Playgrounds | /projects/:project/apps/:app/playground |
Media upload (sandbox, media.object.upload) and Events publish (events.app.publish: types app.<project_key>.*, source app-<app ULID>), run as the app with its own token (from its secret typed for that one request, or a pasted token). Refused — with the missing grant named, and nothing requested or called — unless Network lists the grant as approved. Sandbox apps only. |
| Manifests | /manifests/validate, /projects/:project/apps/:app/releases/new |
Validate services.app-manifest@1 and mods.mod-manifest@1 with openvibe-contracts; create, publish, deprecate and revoke releases. |
| Resource index | GET /api/v1/resources, GET /api/v1/resources/:ovrn, GET /api/v1/authorities |
The merged index over every authority (ADR-048, services.resource.read): see The merged index below. |
| Resource control | POST /api/v1/resources/control |
One common.resource-control-request@1, routed to the authority that owns the resource (ADR-048, services.resource.control): see Control operations below. |
| Policy | /policy/* |
Proposal process and the decision record; compatibility and deprecation policy rendered from ADR-002 and ADR-016 as published; licensing read from package metadata; transparency (what Services stores and does not). The community documents — code of conduct, contributing, the contributor ladder and the moderation policy — are OpenVibe.Codes' (https://openvibe.codes/policy/<slug>); their old addresses here redirect there. |
| API | /api/v1/* |
Public release reads (services.release.read: no token needed; a presented token must hold it), manifest validation, docs versions; release management by an app with its own token (services.release.manage); Services' own resource index (services.resource.read, first-party). Guarded with openvibe-contracts requireCapability. RFC 9457 problems. |
| Machine | /api/health, /api/ready, /release.json, /metrics |
Readiness is truthful (db and docs required; Network, JWKS, OAuth client and events relay reported as optional checks). /metrics answers loopback callers only. |
| Telemetry | — | Every HTTP request is aggregated by route template, method and status class, never by raw URL; /shared files, static assets, health, readiness and /metrics are not counted. Once a minute one http.request sample per key, plus the active-requests, p95 latency and event-loop-lag gauges, is written to this service's own telemetry_samples table (openvibe-sdk 0.46 telemetry, the platform.telemetry-sample@1 shape; migration 0006_telemetry.sql). Rows older than 14 days are deleted daily. server/app.js mounts openvibe-sdk's telemetryMiddleware (a no-op until init); server/index.js calls telemetry.init() at production start, runs the 14-day retention and flushes on stop. |
| Crawl artifacts | /robots.txt, /sitemap.xml, /llms.txt, /llms-full.txt |
Built with openvibe-shared/seo for search and AI crawlers. robots.txt keeps every disallow (the portal, staff, sign-in and the API are not crawlable) and names the sitemap; the sitemap lists the fixed public pages with STATUS.json's content date and each public app and published release with its real publish time (never "today"); llms.txt maps the site; llms-full.txt lists every fixed public page the sitemap does, one title and one line of text each. |
Everything is server-rendered and usable without JavaScript. The only scripts of Services' own are optional: in-browser webhook verification and a copy button. The coding-harness catalog that used to sit under /docs/harnesses is OpenVibe.Codes' (https://openvibe.codes/harnesses).
Who may read the index
- A Network service token for audience
openvibe.services,svc:principal, holdingservices.resource.read(first-party): the whole index, over every authority and every project;?project=only narrows it. An app, mod or agent token is refused even if it somehow holds the grant — a person reads with their own user token, never through a developer app's credential. - A person's Network user token: only projects that person owns or belongs to, and only ONE at a time —
?project=prj_…(or the OVRN's own project) is required, and Network answers the membership question (GET /api/v1/projects/:project, the developer projects API) with the caller's own token. A project the person is not a member of is404 resources.project_not_found, the same non-disclosure Network itself uses — never a403that would confirm the project exists. A person is never accepted unscoped.
GET /api/v1/authorities is first-party operational data (loopback origins) and answers only a service token.
The merged index
The fan-out, each authority's opaque-cursor walk and the merge are openvibe-sdk/resources' createResourceIndex
(SDK v0.34.0, docs/service.md §"The resource index and the control plane") — Services does not reimplement any of
it. Services adds:
- the authority set — one adapter per authority (
server/authorities/adapter.js), each calling that authority's loopback origin with its own Network client-credentials token (server/authorities/tokens.js: one cached token per audience, scoped to that authority'sresource.readcapability, refreshed 60 s before expiry); - a deterministic total order over the merged resources (service, OVRN, id) and an opaque keyset cursor into it, so a page walk repeats nothing and skips nothing while the underlying data is stable;
- local filters for
project,kindandserviceon top of the authorities' own answers: a scoped read (a person's, above all) never shows a row outside its scope even if an authority ignores a query parameter; - the partial answer.
common.resource-list-result@1'spartialarray (contracts 1.1.0) names the authorities a page could not read, each with the problem code it answered: a page whose fan-out was incomplete omits those rows and carriespartial: [{ service, code }], and a complete page carries nopartialkey at all. The code is the authority's own problem code when it answered one, and Services' ownresources.authority_timeout/resources.authority_unavailablewhen nothing answered (a timeout, or a network or token-mint failure) — nothing new is registered in contracts.
An authority is never required to answer for the index to answer: a timeout (default 5 s per authority), a refusal, a malformed page or a Network that cannot mint its token makes that authority partial and leaves the rest of the page intact.
Control operations
POST /api/v1/resources/control (plan T13 step 10) takes one common.resource-control-request@1 and answers the
owning authority's common.resource-control-result@1 (200, or 202 when pending). Services applies nothing:
- Who may call. A first-party
svc:token holdingservices.resource.control(any project), or a person's own user token for a project they own or administer (Network answers the role; not a member is404, a member below admin403). A person acts for themself only:on_behalf_ofis absent or names them, and Services sends it on. - Routing. The body is checked with
contracts.resources.checkControlRequest(400 control.bad_request; the contract tiesproject_idto the resource's own project). The authority is the OVRN's service, orresource_kind's forcreate(400 resources.unknown_serviceotherwise), and the request is forwarded throughopenvibe-sdk/resourcescreateResourceClientwith Services' token for that authority's own<id>.resource.control. An authority whose manifest lists no active<id>.resource.control, or whose control path answers 404/405/501, isfailedwithresources.authority_unsupported. Today that is every authority (none ships a control endpoint yet), and Services' own resources too. An answer that is not a valid result, or names another project's resource, isfailedwithresources.authority_bad_response. An authority's ownrefused(with itsconfirmation_required) passes through. - Idempotency.
services_control_logholds one row peridempotency_key(UNIQUE). A repeat gets the stored answer and the authority is not asked again. A repeat while the first is still running is409 control.in_progress. The key reused by another caller or for another request is409 control.idempotency_conflict. - Sensitive actions (WS-Z2). A request made for an agent (
on_behalf_of: { type: 'agent' }) that deletes, spends money (a money field inparams) or publishes isrefusedwithconfirmation_required: { confirmation_id, reason }. Network's confirmations (/internal/confirmations,network.confirmation.manage) mint the id and ask the agent's owner. The retry carriesconfirmation_id. Services spends the approval once, for exactly that request (its digest), before the authority is asked. A standing rule's approval proceeds at once. A dry run, and a person acting with their own token, are not gated here. - Record. One
services_auditrow per accepted request (a replay too), written with the log row's answer (migration0004_control.sql). Neither table storesparams(only their digest) or a token.
The authority registry
server/authorities/index.js builds the registry at boot from the pinned openvibe-contracts service manifests:
every service whose manifest lists <id>.resource.read and whose capability manifest says status: active is an
authority. With contracts v0.110.0 that is network, events, host, codes and media (services.resource.read is
Services' own id, and Services is never its own authority); the moment a service's manifest turns that on, it is in
the registry on the next boot, and nothing here is hand-maintained. Each authority's loopback origin comes from its
manifest (internalOrigin); SERVICES_<ID>_URL overrides one for the operator. The audience Services mints for is
openvibe.<id>.
There is no polling loop: the index reads the authorities on each request (step 6). The rebuildable read model and
*.resource.* ingestion are step 7.
Depends on
- OpenVibe.Network — SSO (OAuth client
services, PKCE S256), JWKS,/api/v1/projects(called server-side with the person's Network access token), the registry (/api/v1/registry/services,/.well-known/openvibe), client-credentials tokens (Services' own for the events relay; the app's own in playgrounds), and the WS-Z2 confirmations (/internal/confirmations) for sensitive control operations. - OpenVibe.Events — the outbox relay publishes
services.app.*with Services' service token (events.event.publish); the Events playground calls it with the app's token (events.app.publish); the project archive pulls the project's app events with a Network export token (events.app.read). - OpenVibe.Media — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (
media.object.list,media.object.read). - PostgreSQL 18 and Valkey 9 (OpenVibe.Host
roles/data/, ADR-035): every read and write is async throughopenvibe-sdk/db; Valkey holds the per-actor limit counters (optional). - openvibe-contracts v0.154.0, openvibe-sdk v0.65.0, openvibe-shared v4.15.2 (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
No path in Services sends or accepts a shared loopback key (tested by grep and at runtime).
Grants and registration the lead must add
Implemented here (the service manifest's capabilities): services.release.read (public release reads;
no token needed, a presented token must hold it) and services.release.manage (release management by an
app with its own token), both guarded with openvibe-contracts requireCapability; and
services.resource.read (the authority resource index, ADR-048: GET /api/v1/resources and
GET /api/v1/resources/:ovrn), first-party — the capability is served here, so a caller presents a
service token holding it.
Called elsewhere: as the service principal services, events.event.publish at Events (the outbox relay);
as the signed-in person, with their own Network token, Network's /api/v1/projects; as the app, with
its own token, media.object.upload (Media) and events.app.publish (Events) in the playgrounds; and,
for the project archive, Network's read-only export tokens holding events.app.read,
media.object.list and media.object.read.
In Network (server/identity/principals.js / server/db/database.js, as for coupons and host):
- OAuth client
services, nameOpenVibe.Services, redirect URIhttps://openvibe.services/auth/callback. - Service grant
['services', 'events.event.publish', 'openvibe.events', []](the outbox relay). - Control operations (plan T13 step 10):
['services', 'network.confirmation.manage', 'openvibe.network', []](the sensitive-action gate; until it is granted, a gated request fails withconfirmations.unavailableand nothing is applied), and['services', '<id>.resource.control', 'openvibe.<id>', []]for each authority once it ships a control endpoint and registers that capability. - Account export and deletion (ADR-033):
['services', 'events.subscription.manage', 'openvibe.events', []](the two account subscriptions, created at boot), then, last and once the release is live,network.account.export.contributeandnetwork.account.deletion.confirmfor audienceopenvibe.network.
Account export and deletion (ADR-033)
network.account.export_requested and network.account.deleted arrive at POST /internal/events. The route is
loopback-only (nginx answers 404 for /internal/, and the handler refuses a forwarded request) and signed with
SERVICES_EVENTS_SECRET. They are answered by server/domain/account-data.js over openvibe-sdk/account-data, with
one receipt per export and deletion in account_data_events (migration 0003).
- Export: the playground and recipe runs the person started, and the manifests and releases they created.
- Deletion:
- Their runs are deleted.
- Manifests and releases belong to their project and stay:
created_bybecomesdeletedand the publisher, deprecator and reviser become NULL. - The resource index's owner becomes NULL.
- The append-only release log, the control log and the staff trust tiers stay attributed, for accountability.
The Services service manifest, services.release.manage|read, services.resource.read and services.app-manifest@1 are released in openvibe-contracts v0.150.0 (they replaced the codes.* forms, retired that release); the CI contracts check is blocking.
For the playgrounds to succeed end to end (not Services' code; configuration elsewhere): Network DEV_SANDBOX_AUDIENCES including openvibe.media and openvibe.events, a staff-set allowance containing media.object.upload and events.app.publish for the project, a Media tenant keyed by the project id, and OpenVibe.Events serving events.app.publish for app tokens. On 2026-09-23 these were in place on production: a sandbox app uploaded to Media (tenant prj_…-sandbox) and published and read app events through public endpoints. That run used curl, not the playgrounds, and it is not yet a committed, repeatable check.
Configuration
See .env.example. Required in production: OV_OAUTH_CLIENT_SECRET, SERVICES_FORM_SECRET, BASE_URL, DATABASE_URL (and DATABASE_DIRECT_URL for the boot migrations, run as the owner). Optional: EVENTS_URL (relay), INDEXNOW_KEY (see below), SERVICES_STAFF_SUBJECTS, SERVICES_PLAYGROUND_*, SERVICES_REGISTRY_TTL_MS, SERVICES_LIMITS_MINUTE / SERVICES_LIMITS_HOUR.
IndexNow (openvibe-shared/indexnow)
With INDEXNOW_KEY set (8–128 hex or alphanumeric characters, what IndexNow's own tools
generate), the key file is served at /<key>.txt as text/plain and every public release
transition tells the engines: publish, deprecate and revoke of a published or deprecated release
ping api.indexnow.org with the release page (while it is published), the app page, /updates
and /sitemap.xml; the module batches and debounces these. Nothing pings for a draft, and a
release that was never public stays silent. Unset: the feature is off — no key file, nothing sent
(what tests and drills do). The key is not a secret in the credential sense: engines fetch it by
design. test/indexnow.test.js.
Per-actor limits
/api/v1, the console (/projects), release actions, the tools' forms and the staff trust form also
limit who calls them, once the caller is known and before any work (before a form or upload is read,
before Network is asked): server/http/actor-limits.js, openvibe-sdk/limits, roadmap WS-R task 4. An
app token counts as its app (app:app_…), a signed-in person as user:usr_…, anyone else by address.
Past a limit: 429 problem+json rate_limited with Retry-After, one [Limits] log line and
services_rate_limited_total{limit,window}. The per-address limits (pages 300 a minute, console writes 60,
tools 30, API writes 60, sign-in) and the playground's runs an hour stay. Services hosts no git remotes, so
there are no clone or push routes to leave out.
| Routes | Per caller, a minute / an hour |
|---|---|
| API reads; console pages (each asks Network with the person's token) | SERVICES_LIMITS_MINUTE / SERVICES_LIMITS_HOUR (120 / 3000) |
| Project create | 5 / 30 |
| Members, roles, archive, delete | 20 / 200 |
| Project export (JSON and the full archive) | 3 / 20 |
| App create | 10 / 60 |
| Redirect URIs, grants, app revoke | 30 / 300 |
| Credential rotate and revoke | 10 / 60 |
| Playground runs (above the 60 an hour per person) | 10 / 120 |
| Release create and "validate only" (console and API) | 20 / 200 |
| Release publish, deprecate, revoke (console and API) | 20 / 200 |
| Manifest validate (form and API); webhook verify and sample | 30 / 600 each |
| Staff trust tier | 30 / 300 |
Never limited per actor: /api/health, /api/ready, /release.json, /metrics, sign-in, and the
public pages and docs. test/actor-limits.test.js; the other tests boot with the per-actor limits off,
as they raise the per-address limit.
Deploy (for the lead)
Production deploys with sudo ovhost deploy services on the host (strategy git-checkout: fetch,
fast-forward /opt/openvibe.services, install on a lockfile change, restart, wait for /api/ready).
The unit is openvibe-services.service on 127.0.0.1:4930, the env file /etc/openvibe/services.env. The database is
ov_services on the host's data role (sudo /opt/openvibe.host/roles/data/add-service.sh services writes its settings); the
release migrates it at boot. nginx serves openvibe.services from
deploy/nginx/openvibe.services.conf.
Rollback: ovhost puts the previous sha back by itself when /api/ready does not answer 2xx after the
restart; afterwards sudo ovhost rollback services --to <sha>. A release from before the console (the bare index,
before 2026-10-08) never reads migration 0002's tables, so a rollback across it serves the index again and the console
pages answer 404 until the next deploy.
First install (done once; kept for a rebuild):
git cloneto/opt/openvibe.services;npm ci --omit=devwith Node 22./etc/openvibe/services.envfrom.env.example(secrets from the Network client registration).deploy/systemd/openvibe-services.service→/etc/systemd/system/;systemctl enable --now openvibe-services.deploy/nginx/openvibe.services.conf→/etc/nginx/sites-available/, certificate foropenvibe.services+www, reload nginx.- Check
curl -s http://127.0.0.1:4930/api/ready. - Only when the launch rule below holds: remove
openvibe.servicesfromOpenVibe.Sites/sites.jsonand switch routing (done on 2026-10-08, when the console moved here).
Development
npm install
fnm exec --using=22.22.1 npm test # every test/*.test.js on temp PGlite databases with mock Network, Events, Media
fnm exec --using=22.22.1 npm run dev # http://localhost:4930
npx openvibe-contracts-check --service services --src server
Tests: the crawl artifacts (robots.txt, sitemap.xml, llms.txt and llms-full.txt — the public pages only, llms-full.txt listing every page the sitemap does, and lastmod from STATUS.json, never the clock); ADR-013 trust-tier migration; secrets never persisted or re-displayed; Network errors surfaced honestly (404/403/409/422/503, unreachable, expired session refresh); scope editor offers only grantable capabilities and refuses forged requests before Network sees them; generated docs match the pinned versions (every contract, capability, event type, SDK module); webhook verification, v2 only (tampering, lengths, malformed headers, the ±300 s window both ways, timestamp header mismatch, a v1-only delivery refused, samples without v1, constant-time, the in-browser verifier agreeing); playgrounds refuse without the grant and call nothing; the usage page (owner, admins and staff only, Network not asked for anyone else, filters forwarded as offered, a Network failure shown as it answered); the resources pages (your resources: only the signed-in person's own rows in no project, even from an authority that ignores ?owner=, never another subject named in a query; a project's: any member, a stranger refused before any authority is asked, nothing of another project, malformed filters refused, unreadable authorities named, names escaped, a cursor walk that never repeats or skips a resource); project export (complete, no secrets, audit for admin+ only, never partial) and delete (owner only, Network first, drafts and runs removed, public releases revoked with an event); manifest validation; release lifecycle and events; the authority resource index (auth matrix — no token 401, a token without services.resource.read 403, with it 200 — every page and read valid against common.resource-list-result@1/common.resource-summary@1, ?project= never returning another project's rows, ?kind=, cursor paging, GET /:ovrn found and 404, a bad query 400); no internal key anywhere; PKCE sign-in; readiness, release.json, loopback metrics; the released services manifest matching the code; IndexNow (off without a key, the key file served with a key, a publish pinging the page and the sitemap, a draft never pinging); and the home page's size budgets (test/perf-budget.test.js, openvibe-shared/perf-budget).
Acceptance (must be true before "done")
- A new external developer goes from account to a working Media, event and capability integration with only public docs, the SDK and scoped credentials — no loopback key. Partly shown: one production run on 2026-09-23 covered account, project, sandbox app, auto-approved grants, Media upload and app event publish/read with public endpoints (curl, not the SDK or the playgrounds). It is not committed as a repeatable check, and it did not cover webhook delivery to an external endpoint, publishing a release or revoking credentials.
- Credentials can be inspected, scoped, rotated and revoked. Yes, through Network's API (tested against a stand-in; not yet used on production).
- The playground cannot exceed its project's grants. Yes (tested).
- A published app or mod release carries compatibility and trust metadata. Yes (tested).
Launch rule
The domain kept its placeholder page on OpenVibers/OpenVibe.Sites until all of the following existed (plan §12.12). The portal left Sites as openvibe.codes on 2026-09-23 and moved to openvibe.services on 2026-10-08. The items: an owning runtime with health/readiness and observability (done); canonical identity/auth integration (done; the OAuth client services carries the redirect URI https://openvibe.services/auth/callback); server-rendered public routes useful without JavaScript (done); real persistence and end-to-end workflows (persistence done; the external-developer path ran once on production with curl, not yet through the console); capability and event registration against OpenVibe.Contracts (released); a migration/seed strategy (none needed: no data is imported; raw old developer keys are never imported), a security review and sitemap/robots behaviour (sitemap and robots done; the security review is the threat notes below, self-authored); acceptance tests proving the advertised functionality (against stand-ins). Roadmap binding: do not present Services as a working developer platform where these paths are mocked.
Security (threat notes)
Reporting a vulnerability: SECURITY.md.
- Session tokens are httpOnly (unlike the navbar-readable
ov_tokenelsewhere) because this site displays secrets; forms carry an HMAC form token and require a same-siteOrigin. - Secrets never reach logs: errors are logged without request bodies; playground failure details are scrubbed of the credential before they are stored or shown.
- Services never fetches a URL a developer types (the webhook tester produces a
curlfor their own endpoint instead). - Grant checks happen twice: Services refuses to forward non-grantable capabilities, and Network decides. Playgrounds verify the app token's signature, subject, project, environment, audience and capability before calling anything.
Part of the OpenVibe network. Built in the open by OpenVibers.
- openvibe-contracts: v0.216.0
- openvibe-sdk: v0.65.0
- openvibe-shared: v4.15.2