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-existorGET /api/anything - THEN the response is
404with body{ error: { code: 'NOT_FOUND', message } }andContent-Type: application/json, never the SPAindex.html, and the message names the full path, such asNo 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
clientproperty 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/Madridon 2026-10-13 - WHEN the client sends
?from=now/M - THEN
fromresolves 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
400with codeVALIDATION_ERRORanddetails.issues[0].pathequal 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
400with codeVALIDATION_ERRORanddetails.issues[0].pathequal to["tz"]
Scenario: Daily buckets follow the zone
- GIVEN
tz=America/New_Yorkandgranularity=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=101or?limit=0or?limit=abc - THEN the response is
400with codeVALIDATION_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
createdAtdescending andlimit=25 - WHEN the client requests the first page, then the page at
nextCursor, then the page at itsprevCursor - THEN the pages contain rooms 1-25, 26-50 and 1-25 respectively, with no gaps or duplicates, and the
first page has
prevCursor: nullwhile the third page (rooms 51-60) would havenextCursor: 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
400with codeVALIDATION_ERRORanddetails.issues[0].pathequal 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
durationdescending,limit=25, three of them active - WHEN the client requests the first page, the page at
nextCursorand then the page at itsprevCursor - 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
_idof the boundary item, thesortandorderit was issued for and the direction, plus theasOftime 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
totalproperty
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
400with codeVALIDATION_ERRORanddetails.issues[0].pathequal to["sort"]
Scenario: Default order
- WHEN a list is requested without
sortandorder - 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
200with 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-Roomanddemo-2 - WHEN the client sends
?q=DEMO - THEN both rooms are returned and a room named
my-demois not
Scenario: Multi-valued kind
- WHEN the client sends
?kind=EGRESS,AGENTon the participants list - THEN participants of any of the two kinds are returned (rooms use
featuresfor the same purpose)
Scenario: Exact ID instead of a prefix
- GIVEN a session
PA_x1y2and a session whose identity ispa_x1y2-bot - WHEN the client sends
?q=PA_x1y2on the participants list - THEN only the session
PA_x1y2is 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
200with{ 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=1hover 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) ornull(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
500with{ 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=nowwith the default limit of 7 days - THEN the response is
400with codeSORT_RANGE_TOO_WIDE
Scenario: Status table
- WHEN an error response is sent under
/api - THEN its
codeand HTTP status are one ofVALIDATION_ERROR400,SORT_RANGE_TOO_WIDE400,UNAUTHORIZED401,INVALID_CREDENTIALS401,FORBIDDEN403,PRO_FEATURE403,INVALID_LICENSE403,NOT_FOUND404,CONFLICT409,RATE_LIMITED429,INTERNAL_ERROR500 andNOT_READY503
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
400anddetails.issuescontains one entry for["limit"]and one for["order"]
Scenario: Unknown query parameter
- WHEN the client sends
?foo=barto 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-7f3aand the request log lines containcaddy-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
200withopenapi: "3.1.0"and apathsentry 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
404with codeNOT_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
pathshas 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/roomsand the UI of the previous release keeps working against it
Scenario: Unknown version
- WHEN the client requests
GET /api/v2/roomsbeforev2exists - THEN the response is
404with codeNOT_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
200withstatus: "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_metaholds a compatible version - WHEN the client requests
GET /api/v1/ready - THEN the response is
200withchecks: { mongo: 'ok', schema: 'ok' }
Scenario: Writer not started yet
- GIVEN MongoDB answers the ping and
schema_metahas no schema document - WHEN the client requests
GET /api/v1/ready - THEN the response is
200withchecks: { mongo: 'ok', schema: 'uninitialized' }
Scenario: Newer writer
- GIVEN
schema_metarecords a schema version higher than the dashboard's - WHEN the client requests
GET /api/v1/ready - THEN the response is
200withchecks: { mongo: 'ok', schema: 'newer' }
Scenario: Writer downgraded below the minimum
- GIVEN
schema_metarecords a schema version lower than the dashboard's minimum - WHEN the client requests
GET /api/v1/ready - THEN the response is
503with codeNOT_READYanddetails.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
503with codeNOT_READYanddetails.checks.mongoequal 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
200with the SPAindex.html
Scenario: Unstripped prefix
- GIVEN a proxy health check that requests
/dashboard/api/healthcheckwithout stripping/dashboard - WHEN the request reaches the dashboard
- THEN the response is
200with the SPAindex.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 itscreatedAtis 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
itemsholds 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
itemsholds 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, andtotalDocsExaminedis 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=25requested at 12:00:00 - WHEN the client requests the page at
nextCursorat 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
cursorat 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_RANGEof 7 days - WHEN the client sends
GET /api/v1/rooms?sort=duration&from=now-30d&to=now - THEN the response is
400with{ error: { code: 'SORT_RANGE_TOO_WIDE', message } }
Scenario: Rooms over 7 days
- GIVEN the default
DASHBOARD_LIST_TOTAL_MAX_RANGEof 7 days - WHEN the client sends
GET /api/v1/rooms?sort=duration&from=now-7d&to=now - THEN the response is
200with 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
400with codeSORT_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
200with the roomRM_abcas 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
200with the sessions ofRM_abcjoined 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, sincecreatedAtis not ranked
Requirement: Exact-ID search
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_ab12cdcreated 40 days ago - WHEN the client sends
GET /api/v1/rooms?q=RM_ab12cd&from=now-24h&status=active - THEN the response is
200with that single room initems,total: 1and both cursorsnull
Scenario: Unknown ID
- WHEN the client sends
GET /api/v1/egresses?q=EG_missing - THEN the response is
200with{ 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
lastStatsAtis 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
lastStatsAtis 100 s old - WHEN the room is returned
- THEN it has no
staleproperty
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
staleproperty
Scenario: Closed entity
- GIVEN a closed room whose
lastStatsAtis 2 hours old - WHEN it is returned
- THEN it has no
staleproperty
Requirement: OpenAPI document metadata
Edition: CE. The OpenAPI document SHALL carry the following:
- an
info.descriptionof the API conventions; info.license: Apache 2.0 unless the edition provides another;- one
serversentry with the dashboard's public base path; - a
tagslist 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/andNODE_ENV=development - WHEN the client reads the document
- THEN
servers[0].urlis/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,toandtztime expressions; - cursor pagination with
limitandtotal; - the
ApiErrorenvelope; ETagandIf-None-Match;- the
X-Request-Idheader; - 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-Matchheader parameter when it can answer304; - on the auth routes, the refresh cookie as a cookie security scheme, and the
Set-Cookieheader of the responses that set or clear it.
Scenario: Operation identifiers
- WHEN the client reads every operation of the document
- THEN each has an
operationIdthat no other operation shares, such aslistRoomsforGET /api/v1/rooms
Scenario: List parameter
- WHEN the client reads the
kindparameter ofGET /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-Matchheader parameter, theETagheader of its200, and its304
Scenario: Refresh cookie
- WHEN the client reads
POST /api/v1/auth/refresh - THEN its security names the refresh cookie scheme (
in: cookie,name: ov_dashboard_refresh), and its200and401declareSet-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
429ofPOST /api/v1/auth/login - THEN its content is
application/jsonwith theApiErrorschema
Scenario: Edition-specific error
- WHEN the client reads
GET /api/v1/analytics/seriesin the CE document - THEN it declares the
403of a PRO metric, dimension or node scope; and no CE operation declares a403PRO_FEATUREfor being a PRO route