openvidu/openvidu-dashboard/openspec/specs/api-conventions/spec.md

30 KiB

api-conventions Specification

Purpose

Defines the contract shared by every /api/v1 endpoint of the dashboard: naming, time range, pagination, sorting, search, response envelopes, errors, validation, request correlation, documentation, versioning and health probes, so that each resource endpoint only specifies what is particular to it.

Requirements

Requirement: Base path and resource naming

Edition: CE. Every dashboard API route SHALL live under /api/v1, use lower-case plural nouns for resources (rooms, participants, agents, tracks, egresses, ingresses, analytics, overview, status, auth, info), nest sub-resources at most one level under the parent identifier, and answer every unknown /api path with a JSON 404 instead of the SPA fallback.

Scenario: Sub-resource path

  • GIVEN a room with SID RM_abc
  • WHEN the client requests its participants
  • THEN the path is GET /api/v1/rooms/RM_abc/participants

Scenario: Unknown API path

  • WHEN the client requests GET /api/v1/does-not-exist or GET /api/anything
  • THEN the response is 404 with body { error: { code: 'NOT_FOUND', message } } and Content-Type: application/json, never the SPA index.html, and the message names the full path, such as No API route for GET /api/v1/does-not-exist

Requirement: JSON field conventions

Edition: CE. Every JSON body SHALL use camelCase field names, SHALL omit a field whose value is unknown or not applicable unless the resource spec declares it nullable, and SHALL spell enum values replicated from the LiveKit protocol as the protocol does (UPPER_SNAKE) and dashboard-defined enums in lower-case.

Scenario: Mixed enum origins

  • WHEN a participant of kind agent in an active room is returned
  • THEN the body contains "kind": "AGENT" (protocol enum) and "status": "active" (dashboard enum)

Scenario: Unknown value omitted

  • GIVEN a participant whose SDK reported no client information
  • WHEN it is returned by any endpoint
  • THEN the body has no client property rather than "client": null

Requirement: Time and quantity encoding

Edition: CE. Timestamps SHALL be ISO 8601 strings in UTC with millisecond precision and a Z suffix, durations SHALL be integers in milliseconds in fields whose name ends in Ms, and byte counts SHALL be plain integers without units.

Scenario: Entity timestamp and duration

  • GIVEN a room created at 2026-10-13 10:00:00.250 UTC that lasted 90 seconds
  • WHEN it is returned by any endpoint
  • THEN the body contains "createdAt": "2026-10-13T10:00:00.250Z" and "durationMs": 90000

Scenario: Byte counts

  • GIVEN a room that moved 1.5 MB upstream
  • WHEN it is returned by any endpoint
  • THEN the body contains "bytesUp": 1500000

Requirement: Time range parameters

Edition: CE. Endpoints that take a time range SHALL accept from and to as ISO 8601 timestamps (with offset or Z) or as relative expressions now, now-<n><unit> (unit in m, h, d) and now/<unit> (start of the current d or M, computed in tz), SHALL default to from=now-24h and to=now, SHALL resolve relative expressions at request time, and SHALL reject from not strictly before to with 400.

Scenario: Relative range resolved at request time

  • GIVEN the current time is 2026-10-13T12:00:00Z
  • WHEN the client sends ?from=now-24h&to=now
  • THEN the query covers 2026-10-12T12:00:00Z to 2026-10-13T12:00:00Z

Scenario: Start of month in the caller's zone

  • GIVEN tz=Europe/Madrid on 2026-10-13
  • WHEN the client sends ?from=now/M
  • THEN from resolves to 2026-09-30T22:00:00Z (local midnight of 1 October)

Scenario: Inverted range

  • WHEN the client sends ?from=2026-10-13T00:00:00Z&to=2026-10-12T00:00:00Z
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["from"]

Requirement: Timezone parameter

Edition: CE. Endpoints that bucket or snap by calendar units SHALL accept tz as an IANA zone name or UTC, SHALL default to UTC, SHALL use it only for bucketing and relative-range snapping (stored and returned timestamps stay UTC), and SHALL reject an unknown zone with 400.

Scenario: Unknown timezone

  • WHEN the client sends ?tz=Mars/Olympus
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["tz"]

Scenario: Daily buckets follow the zone

  • GIVEN tz=America/New_York and granularity=1d
  • WHEN a time series is requested
  • THEN every bucket starts at local midnight, serialised in UTC (for example 2026-10-13T04:00:00.000Z)

Requirement: Page size

Edition: CE. List endpoints SHALL accept limit as an integer between 1 and 100, SHALL default to 25 and SHALL reject any other value with 400. The cap of 100 is the page-size limit the UI offers (10, 25, 50, 100).

Scenario: Default page size

  • WHEN a list is requested without limit
  • THEN at most 25 items are returned

Scenario: Page size above the cap

  • WHEN the client sends ?limit=101 or ?limit=0 or ?limit=abc
  • THEN the response is 400 with code VALIDATION_ERROR

Requirement: Cursor pagination

Edition: CE. List endpoints SHALL paginate with an opaque cursor, return nextCursor and prevCursor as a string when a page exists in that direction and null otherwise, order every page deterministically by the sort key (the rank, for a ranked sort) with _id as tie-breaker, and reject with 400 a malformed cursor or one issued for a different sort/order.

Scenario: Walking forward and back

  • GIVEN 60 rooms sorted by createdAt descending and limit=25
  • WHEN the client requests the first page, then the page at nextCursor, then the page at its prevCursor
  • THEN the pages contain rooms 1-25, 26-50 and 1-25 respectively, with no gaps or duplicates, and the first page has prevCursor: null while the third page (rooms 51-60) would have nextCursor: null

Scenario: Cursor reused with another sort

  • GIVEN a cursor issued for sort=createdAt&order=desc
  • WHEN the client sends it with sort=name
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["cursor"]

Scenario: Items inserted while paging

  • GIVEN the client holds a nextCursor
  • WHEN new rooms are created before the next request
  • THEN the next page still starts right after the boundary item of the previous page

Scenario: Walking a ranked sort back

  • GIVEN 60 rooms in range sorted by duration descending, limit=25, three of them active
  • WHEN the client requests the first page, the page at nextCursor and then the page at its prevCursor
  • THEN the pages contain rooms 1-25, 26-50 and 1-25 of the ranking at the first page's asOf, with no gaps or duplicates

Scenario: Cursor content

  • WHEN a list endpoint issues a cursor
  • THEN it is a base64url string encoding the sort value and _id of the boundary item, the sort and order it was issued for and the direction, plus the asOf time of its first page for a ranked sort

Requirement: Optional total count

Edition: CE. List responses SHALL include total (number of items matching the filters) only when the resolved time range is not longer than DASHBOARD_LIST_TOTAL_MAX_RANGE (default 7d); otherwise total SHALL be omitted so that the UI shows "showing x-y" without a total.

Scenario: Short range

  • WHEN a list is requested with from=now-24h
  • THEN the response includes total

Scenario: Long range

  • GIVEN the default threshold
  • WHEN a list is requested with from=now-30d
  • THEN the response has no total property

Requirement: Sorting parameters

Edition: CE. List endpoints SHALL accept sort only from the sort keys that the resource spec declares, either index-backed or ranked, and order as asc or desc, SHALL apply the resource's documented default when they are absent, and SHALL reject any other value with 400.

Scenario: Unsupported sort key

  • WHEN the client sends ?sort=metadata
  • THEN the response is 400 with code VALIDATION_ERROR and details.issues[0].path equal to ["sort"]

Scenario: Default order

  • WHEN a list is requested without sort and order
  • THEN the items follow the resource's default (newest first for time-keyed resources)

Scenario: Ranked key accepted

  • WHEN the client sends GET /api/v1/egresses?sort=duration&from=now-24h&to=now
  • THEN the response is 200 with the egresses started in the range ranked by duration, longest first

Requirement: Search and enum filters

Edition: CE. List endpoints SHALL accept q (trimmed, 2 to 100 characters) as a case-insensitive prefix match over the searchable fields the resource spec declares, except a q that matches the list's entity ID pattern, which follows the exact-ID search whatever its length; status and kind take the resource's values, as a single value or a comma-separated list as the resource spec declares; empty q SHALL be ignored and an unknown status or kind value SHALL be rejected with 400.

Scenario: Prefix search is case-insensitive

  • GIVEN rooms named Demo-Room and demo-2
  • WHEN the client sends ?q=DEMO
  • THEN both rooms are returned and a room named my-demo is not

Scenario: Multi-valued kind

  • WHEN the client sends ?kind=EGRESS,AGENT on the participants list
  • THEN participants of any of the two kinds are returned (rooms use features for the same purpose)

Scenario: Exact ID instead of a prefix

  • GIVEN a session PA_x1y2 and a session whose identity is pa_x1y2-bot
  • WHEN the client sends ?q=PA_x1y2 on the participants list
  • THEN only the session PA_x1y2 is returned

Requirement: List response envelope

Edition: CE. Every list endpoint SHALL respond 200 with { items, nextCursor, prevCursor, total? } where items is an array (possibly empty) of the resource's list item shape and no other top-level properties exist.

Scenario: Empty result

  • WHEN no item matches the filters
  • THEN the response is 200 with { items: [], nextCursor: null, prevCursor: null, total: 0 } (total present only under the threshold rule)

Requirement: Time-series response envelope

Edition: CE. Every time-series endpoint SHALL respond 200 with { granularity, from, to, series: [{ name, points: [[ts, value]] }] } where from/to are the resolved range in UTC, granularity is the applied bucket size, ts is the bucket start as an ISO 8601 UTC string and every series is dense over the range (a bucket without data holds 0 for counters and null for averages; a UTC day whose uniqueIdentities the reconciler has not computed yet holds null).

Scenario: Dense series

  • GIVEN granularity=1h over a 24 h range with data in only 3 hours
  • WHEN the series is requested
  • THEN each series has 24 points and the 21 empty buckets hold 0 (counter) or null (average)

Requirement: Error format and status mapping

Edition: CE. Every error response under /api SHALL be JSON { error: { code, message, details? } } with message in English and code from the fixed code-to-status table of the "Status table" scenario. Unexpected failures SHALL map to INTERNAL_ERROR without exposing stack traces or internal messages.

Scenario: Unexpected failure

  • GIVEN a repository throws while serving a list
  • WHEN the response is sent
  • THEN it is 500 with { error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } }, the original error is logged with the request id and no stack trace is in the body

Scenario: Ranked sort over a too wide range

  • WHEN the client sends GET /api/v1/egresses?sort=endedAt&from=now-30d&to=now with the default limit of 7 days
  • THEN the response is 400 with code SORT_RANGE_TOO_WIDE

Scenario: Status table

  • WHEN an error response is sent under /api
  • THEN its code and HTTP status are one of VALIDATION_ERROR 400, SORT_RANGE_TOO_WIDE 400, UNAUTHORIZED 401, INVALID_CREDENTIALS 401, FORBIDDEN 403, PRO_FEATURE 403, INVALID_LICENSE 403, NOT_FOUND 404, CONFLICT 409, RATE_LIMITED 429, INTERNAL_ERROR 500 and NOT_READY 503

Requirement: Input validation

Edition: CE. Every path parameter, query string and JSON body SHALL be validated against a declared schema before any handler runs; a failure SHALL produce 400 VALIDATION_ERROR with details.issues: [{ path: string[], message }] listing every issue; unknown JSON body properties SHALL be rejected while unknown query parameters SHALL be ignored; JSON bodies above 1 MB SHALL be rejected with 400.

Scenario: Several issues reported together

  • WHEN the client sends ?limit=500&order=sideways
  • THEN the response is 400 and details.issues contains one entry for ["limit"] and one for ["order"]

Scenario: Unknown query parameter

  • WHEN the client sends ?foo=bar to a list endpoint
  • THEN the parameter is ignored and the request succeeds

Requirement: Request correlation

Edition: CE. Every response SHALL carry an X-Request-Id header, reusing the incoming header value when it matches ^[A-Za-z0-9._-]{1,128}$ and generating a UUID otherwise, and every log line written while serving the request SHALL include that id.

Scenario: Generated id

  • WHEN a request arrives without X-Request-Id
  • THEN the response carries a generated UUID in X-Request-Id

Scenario: Propagated id

  • WHEN a request arrives with X-Request-Id: caddy-7f3a
  • THEN the response carries X-Request-Id: caddy-7f3a and the request log lines contain caddy-7f3a

Requirement: OpenAPI document

Edition: CE. The API SHALL expose an OpenAPI 3.1 document generated from the same schemas that validate requests and responses, covering every /api/v1 route the edition implements, at GET /api/v1/docs/openapi.json with a browsable page at GET /api/v1/docs; both SHALL be served only when NODE_ENV is not production and answer 404 otherwise.

Scenario: Development mode

  • GIVEN the server runs with NODE_ENV=development
  • WHEN the client requests GET /api/v1/docs/openapi.json
  • THEN the response is 200 with openapi: "3.1.0" and a paths entry for every route the edition implements

Scenario: Production mode

  • GIVEN the server runs with NODE_ENV=production
  • WHEN the client requests GET /api/v1/docs
  • THEN the response is 404 with code NOT_FOUND

Scenario: Locked routes left out

  • GIVEN the CE server runs with NODE_ENV=development
  • WHEN the client requests GET /api/v1/docs/openapi.json
  • THEN paths has no entry for a locked PRO route, such as /rooms/{sid} or /analytics/top, and no schema of a PRO response

Requirement: Versioning policy

Edition: CE. The API version SHALL be the first path segment after /api; additive changes (new endpoints, optional parameters, response fields or enum values) SHALL stay within v1, clients SHALL tolerate unknown fields and enum values, and a breaking change SHALL be published under the next version while the previous one keeps working for at least one minor release.

Scenario: Additive field

  • WHEN a release adds a field to the room list item
  • THEN the path stays /api/v1/rooms and the UI of the previous release keeps working against it

Scenario: Unknown version

  • WHEN the client requests GET /api/v2/rooms before v2 exists
  • THEN the response is 404 with code NOT_FOUND

Requirement: Legacy health check

Edition: CE. GET /api/healthcheck SHALL answer 200 { status: 'ok' } without authentication and without touching MongoDB, as the image health check and the health checks of the OpenVidu 3.9 deployments expect.

Scenario: MongoDB down

  • GIVEN MongoDB is unreachable
  • WHEN the client requests GET /api/healthcheck
  • THEN the response is still 200

Requirement: Liveness probe

Edition: CE. GET /api/v1/health SHALL answer 200 { status: 'ok', version } without authentication and without touching MongoDB, where version is the dashboard version.

Scenario: Liveness

  • WHEN the client requests GET /api/v1/health
  • THEN the response is 200 with status: "ok" and the dashboard version

Requirement: Readiness probe

Edition: CE. GET /api/v1/ready SHALL answer 200 { status: 'ready', checks: { mongo: 'ok', schema } } without authentication when a MongoDB ping succeeds within 2 s and the writer schema status (as computed by the writer schema gate: ok, newer, uninitialized, older) is not older, and SHALL answer 503 with code NOT_READY and details.checks when the ping fails or the status is older.

Scenario: Ready

  • GIVEN MongoDB answers the ping and schema_meta holds a compatible version
  • WHEN the client requests GET /api/v1/ready
  • THEN the response is 200 with checks: { mongo: 'ok', schema: 'ok' }

Scenario: Writer not started yet

  • GIVEN MongoDB answers the ping and schema_meta has no schema document
  • WHEN the client requests GET /api/v1/ready
  • THEN the response is 200 with checks: { mongo: 'ok', schema: 'uninitialized' }

Scenario: Newer writer

  • GIVEN schema_meta records a schema version higher than the dashboard's
  • WHEN the client requests GET /api/v1/ready
  • THEN the response is 200 with checks: { mongo: 'ok', schema: 'newer' }

Scenario: Writer downgraded below the minimum

  • GIVEN schema_meta records a schema version lower than the dashboard's minimum
  • WHEN the client requests GET /api/v1/ready
  • THEN the response is 503 with code NOT_READY and details.checks: { mongo: 'ok', schema: 'older' }

Scenario: MongoDB unreachable

  • GIVEN the ping does not answer within 2 s
  • WHEN the client requests GET /api/v1/ready
  • THEN the response is 503 with code NOT_READY and details.checks.mongo equal to "error"

Requirement: SPA serving

Edition: CE. Every GET or HEAD request outside /api SHALL be answered without authentication with the matching file of the built SPA or, when no file matches, with the SPA index.html and status 200, so that HEAD /, client routes and the unstripped /dashboard/api/healthcheck of the HA proxy health check answer 200.

Scenario: Ready check

  • WHEN the client sends HEAD /
  • THEN the response is 200

Scenario: Client route

  • WHEN the client requests GET /rooms/RM_abc
  • THEN the response is 200 with the SPA index.html

Scenario: Unstripped prefix

  • GIVEN a proxy health check that requests /dashboard/api/healthcheck without stripping /dashboard
  • WHEN the request reaches the dashboard
  • THEN the response is 200 with the SPA index.html

Requirement: Range on the start field

Edition: CE. A list endpoint with a time range SHALL apply from and to to the list's start field whatever the sort key and order: createdAt for rooms, joinedAt for participants and agents, startedAt for egresses. Ingresses apply the activity range that ingress-api declares, one of whose branches is the creation time.

Scenario: End-time sort keeps the creation range

  • GIVEN a room created 3 days ago that ended 2 hours ago
  • WHEN the client sends GET /api/v1/rooms?sort=endedAt&from=now-24h&to=now
  • THEN the room is not in items, because its createdAt is outside the range

Scenario: Duration sort of participants

  • GIVEN a session joined 30 hours ago that left 1 hour ago
  • WHEN the client sends GET /api/v1/participants?sort=duration&from=now-24h&to=now
  • THEN the session is not in items

Requirement: Ranked sort keys

Edition: CE. A sort key that a resource spec declares as ranked SHALL order the items that match the range and the filters by the resource's rank expression evaluated at the list's asOf, with _id as tie-breaker in the same direction. The items SHALL be ranked in memory after a scan of the list's range index, so the documents examined never exceed the documents matching the range.

Scenario: Rooms by duration with a running room

  • GIVEN three rooms created in range: one closed after 40 minutes, one closed after 10 minutes and one active, created 25 minutes before asOf
  • WHEN the client sends GET /api/v1/rooms?sort=duration&order=desc
  • THEN items holds the 40-minute room, the active room and the 10-minute room, in that order

Scenario: Ascending order

  • WHEN the same request is sent with order=asc
  • THEN items holds the 10-minute room, the active room and the 40-minute room, in that order

Scenario: Ranking backed by the range index

  • GIVEN a database generated at scenario M
  • WHEN a ranked list request over a 7-day range is explained
  • THEN the plan scans the list's range index, contains no COLLSCAN, and totalDocsExamined is at most the number of documents matching the range

Requirement: Ranked sort reference time

Edition: CE. A ranked list SHALL take asOf from the request time of its first page and carry it in every cursor it issues; a request with a cursor SHALL evaluate the rank at the cursor's asOf, so a running entity keeps one rank across pages and no item repeats or goes missing while its live value grows.

Scenario: Running room across pages

  • GIVEN 30 rooms in range, one of them active, and the first page of sort=duration&limit=25 requested at 12:00:00
  • WHEN the client requests the page at nextCursor at 12:05:00
  • THEN the second page ranks the active room by its duration at 12:00:00, and the two pages together hold the 30 rooms with no gaps or duplicates

Scenario: First page

  • WHEN a ranked list is requested without cursor at 12:00:00
  • THEN every running entity of the page is ranked by its live value at 12:00:00, and the cursors of the response carry 12:00:00 as asOf

Requirement: Ranked sort range limit

Edition: CE. A ranked sort key other than the ingresses' createdAt SHALL be accepted only when the resolved range is no longer than the resource's limit: DASHBOARD_LIST_TOTAL_MAX_RANGE for rooms, egresses and ingresses; for participants and agents the smaller of 24 h and that value, or any range when room is set. A wider request SHALL answer 400 with code SORT_RANGE_TOO_WIDE; an exact-ID search ignores the sort and is never refused.

Scenario: Rooms over 30 days

  • GIVEN the default DASHBOARD_LIST_TOTAL_MAX_RANGE of 7 days
  • WHEN the client sends GET /api/v1/rooms?sort=duration&from=now-30d&to=now
  • THEN the response is 400 with { error: { code: 'SORT_RANGE_TOO_WIDE', message } }

Scenario: Rooms over 7 days

  • GIVEN the default DASHBOARD_LIST_TOTAL_MAX_RANGE of 7 days
  • WHEN the client sends GET /api/v1/rooms?sort=duration&from=now-7d&to=now
  • THEN the response is 200 with the rooms ranked by duration

Scenario: Participants over 3 days

  • WHEN the client sends GET /api/v1/participants?sort=duration&from=now-3d&to=now
  • THEN the response is 400 with code SORT_RANGE_TOO_WIDE

Scenario: Exact ID with a ranked sort

  • WHEN the client sends GET /api/v1/rooms?q=RM_abc&sort=duration&from=now-30d&to=now
  • THEN the response is 200 with the room RM_abc as the only item

Scenario: Participants of one room

  • WHEN the client sends GET /api/v1/participants?sort=duration&room=RM_abc&from=now-90d&to=now
  • THEN the response is 200 with the sessions of RM_abc joined in the range, ranked by duration

Scenario: Index-backed sort over any range

  • WHEN the client sends GET /api/v1/rooms?sort=createdAt&from=now-90d&to=now
  • THEN the response is 200, since createdAt is not ranked

Edition: CE. When q matches the entity ID pattern of the list (^RM_[A-Za-z0-9]+$ for rooms, ^PA_[A-Za-z0-9]+$ for participants and agents, ^EG_[A-Za-z0-9]+$ for egresses, ^IN_[A-Za-z0-9]+$ for ingresses), the list SHALL answer only the entity with that ID, ignoring the range, the other filters and the sort, with both cursors null and total: 1, or an empty list with total: 0 when no entity has that ID.

Scenario: ID outside the range and the filters

  • GIVEN a closed room RM_ab12cd created 40 days ago
  • WHEN the client sends GET /api/v1/rooms?q=RM_ab12cd&from=now-24h&status=active
  • THEN the response is 200 with that single room in items, total: 1 and both cursors null

Scenario: Unknown ID

  • WHEN the client sends GET /api/v1/egresses?q=EG_missing
  • THEN the response is 200 with { items: [], nextCursor: null, prevCursor: null, total: 0 }

Scenario: Lower-case prefix

  • WHEN the client sends GET /api/v1/rooms?q=rm_ab12cd
  • THEN the value does not match the ID pattern and is searched as a room name prefix within the range and filters

Requirement: Stale flag

Edition: CE. Where a resource spec declares the stale flag, an active room or participant SHALL carry stale: true when its lastStatsAt is older than the stale window of status-api (max(90 s, 60 s + I + 15 s), I the status's writer.batchIntervalMs, 10 s when absent) at the server's clock when the response is built, and SHALL omit stale otherwise, also when it has no lastStatsAt.

Scenario: Default interval

  • GIVEN a writer batch interval of 10 s and an active room whose lastStatsAt is 100 s old at the server's clock
  • WHEN the room is returned
  • THEN it carries stale: true

Scenario: Slow writer

  • GIVEN a writer batch interval of 60 s, whose stale window is 135 s, and an active room whose lastStatsAt is 100 s old
  • WHEN the room is returned
  • THEN it has no stale property

Scenario: Signal-only participant

  • GIVEN an active participant that never published nor subscribed and has no lastStatsAt
  • WHEN it is returned
  • THEN it has no stale property

Scenario: Closed entity

  • GIVEN a closed room whose lastStatsAt is 2 hours old
  • WHEN it is returned
  • THEN it has no stale property

Requirement: OpenAPI document metadata

Edition: CE. The OpenAPI document SHALL carry the following:

  • an info.description of the API conventions;
  • info.license: Apache 2.0 unless the edition provides another;
  • one servers entry with the dashboard's public base path;
  • a tags list with a description per tag, holding only the tags of the listed operations.

Scenario: Behind a base path

  • GIVEN the server runs with DASHBOARD_BASE_PATH=/dashboard/ and NODE_ENV=development
  • WHEN the client reads the document
  • THEN servers[0].url is /dashboard, and the paths start with /api/v1

Scenario: Conventions described

  • WHEN the client reads info.description
  • THEN it explains the following:
    • the bearer access token and the refresh cookie;
    • the from, to and tz time expressions;
    • cursor pagination with limit and total;
    • the ApiError envelope;
    • ETag and If-None-Match;
    • the X-Request-Id header;
    • the versioning policy.

Scenario: Tags

  • WHEN the client reads tags
  • THEN every operation's tags are listed there with a description, and no listed tag lacks an operation

Requirement: OpenAPI operations

Edition: CE. Every operation of the OpenAPI document SHALL have the following:

  • a unique operationId, a summary and tags;
  • a description for every parameter, with comma-separated list parameters as arrays of their allowed values (style: form, explode: false);
  • an If-None-Match header parameter when it can answer 304;
  • on the auth routes, the refresh cookie as a cookie security scheme, and the Set-Cookie header of the responses that set or clear it.

Scenario: Operation identifiers

  • WHEN the client reads every operation of the document
  • THEN each has an operationId that no other operation shares, such as listRooms for GET /api/v1/rooms

Scenario: List parameter

  • WHEN the client reads the kind parameter of GET /api/v1/participants
  • THEN it has a description, style: form, explode: false, and an array schema whose items enumerate the participant kinds

Scenario: Conditional request

  • WHEN the client reads GET /api/v1/overview
  • THEN it declares the If-None-Match header parameter, the ETag header of its 200, and its 304
  • WHEN the client reads POST /api/v1/auth/refresh
  • THEN its security names the refresh cookie scheme (in: cookie, name: ov_dashboard_refresh), and its 200 and 401 declare Set-Cookie

Requirement: OpenAPI error responses

Edition: CE. Every error response an operation can answer SHALL be declared with the ApiError schema. This means 400 only for operations that take parameters or a body, 401 for operations that need or accept a token, and the route's own errors such as 404, 429 and 503. A response that only one edition answers SHALL appear only in that edition's document.

Scenario: Operation without input

  • WHEN the client reads GET /api/v1/health
  • THEN it declares no 400

Scenario: Error body

  • WHEN the client reads the 429 of POST /api/v1/auth/login
  • THEN its content is application/json with the ApiError schema

Scenario: Edition-specific error

  • WHEN the client reads GET /api/v1/analytics/series in the CE document
  • THEN it declares the 403 of a PRO metric, dimension or node scope; and no CE operation declares a 403 PRO_FEATURE for being a PRO route