mirror of
https://github.com/suitenumerique/meet.git
synced 2026-10-21 20:15:29 +00:00
d65ac8c845
The application token endpoint implements the client credentials
grant (RFC 6749 section 4.4), but its errors did not follow section
5.2: serializer errors were keyed by field, and the other errors used
either `detail` or a free-text `error`. Standard OAuth 2.0 client
libraries could not parse them.
Every error is now rendered as
`{"error": <code>, "error_description": <text>}` by a view-specific
exception handler:
- missing or blank client credentials and wrong credentials or
inactive applications return `invalid_client`
- an unsupported grant type returns `unsupported_grant_type`
- an invalid email, a denied domain or an unknown user return
`invalid_scope`;
- the provisional user creation conflict returns `server_error`,
since the RFC defines no code for server-side failures here
- any other error returns `invalid_request`.
Serializer errors are flattened into one description listing every
invalid field. Descriptions are restricted to the characters allowed
by the RFC, as DRF messages may be translated or contain quotes.
486 lines
16 KiB
YAML
486 lines
16 KiB
YAML
openapi: 3.0.3
|
||
info:
|
||
title: Meet External API
|
||
version: 1.0.0
|
||
description: |
|
||
External API for room management with resource server authentication.
|
||
[[description by Oauth 2.0]](https://www.oauth.com/oauth2-servers/the-resource-server/)
|
||
|
||
#### Authentication Flow
|
||
|
||
1. Authenticate with the authorization server using your credentials
|
||
2. During authentication, request the scopes you need: `lasuite_visio` (mandatory) plus action-specific scopes
|
||
3. Receive an access token and a refresh token that includes the requested scopes
|
||
4. Use the access token in the `Authorization: Bearer <token>` header for all API requests
|
||
5. When the access token expires, use the refresh token to obtain a new access token without re-authenticating
|
||
|
||
#### Scopes
|
||
|
||
* `lasuite_visio` - **Mandatory** Base scope required for any API access
|
||
* `lasuite_visio:rooms:list` – List rooms accessible to the delegated user.
|
||
* `lasuite_visio:rooms:retrieve` – Retrieve details of a specific room.
|
||
* `lasuite_visio:rooms:create` – Create new rooms.
|
||
* `lasuite_visio:rooms:update` – Update the access level and configuration of existing rooms.
|
||
* `lasuite_visio:rooms:delete` – **Coming soon** Delete rooms generated by the application.
|
||
|
||
#### Upcoming Features
|
||
|
||
* **Add attendees to a room:** You will be able to update a room to include a list of attendees, allowing them to bypass the lobby system automatically.
|
||
* **Delete application-generated rooms:** Rooms created via the application can be deleted when no longer needed.
|
||
|
||
contact:
|
||
name: API Support
|
||
email: antoine.lebaud@mail.numerique.gouv.fr
|
||
|
||
servers:
|
||
- url: https://visio-sandbox.beta.numerique.gouv.fr/external-api/v1.0
|
||
description: Sandbox server
|
||
|
||
tags:
|
||
- name: Rooms
|
||
description: Room management operations
|
||
|
||
paths:
|
||
/rooms:
|
||
get:
|
||
tags:
|
||
- Rooms
|
||
summary: List rooms
|
||
description: |
|
||
Returns a list of rooms accessible to the authenticated user.
|
||
Only rooms where the user has access will be returned.
|
||
operationId: listRooms
|
||
security:
|
||
- BearerAuth: [rooms:list]
|
||
parameters:
|
||
- name: page
|
||
in: query
|
||
description: Page number for pagination
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
default: 1
|
||
- name: page_size
|
||
in: query
|
||
description: Number of items per page
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
default: 20
|
||
responses:
|
||
'200':
|
||
description: List of accessible rooms
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
count:
|
||
type: integer
|
||
description: Total number of rooms
|
||
next:
|
||
type: string
|
||
nullable: true
|
||
description: URL to next page
|
||
previous:
|
||
type: string
|
||
nullable: true
|
||
description: URL to previous page
|
||
results:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/Room'
|
||
examples:
|
||
roomList:
|
||
summary: Paginated room list
|
||
value:
|
||
count: 2
|
||
next: "https://visio-sandbox.beta.numerique.gouv.fr/external-api/v1.0/rooms?page=2"
|
||
previous: null
|
||
results:
|
||
- id: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||
slug: "aae-erez-aaz"
|
||
access_level: "trusted"
|
||
url: "https://visio-sandbox.beta.numerique.gouv.fr/aae-erez-aaz"
|
||
telephony:
|
||
enabled: true
|
||
pin_code: "123456"
|
||
phone_number: "+1-555-0100"
|
||
default_country: "US"
|
||
configuration: { }
|
||
'401':
|
||
$ref: '#/components/responses/UnauthorizedError'
|
||
'403':
|
||
$ref: '#/components/responses/ForbiddenError'
|
||
|
||
/rooms/:
|
||
post:
|
||
tags:
|
||
- Rooms
|
||
summary: Create a room
|
||
description: |
|
||
Creates a new room with secure defaults for external API usage.
|
||
|
||
**Defaults:**
|
||
- user is set as owner
|
||
- Room slug auto-generated for uniqueness
|
||
- Telephony PIN auto-generated when enabled
|
||
- Creation tracked with application client_id for auditing
|
||
operationId: createRoom
|
||
security:
|
||
- BearerAuth: [rooms:create]
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RoomCreate'
|
||
examples:
|
||
emptyBody:
|
||
summary: No parameters (use all defaults)
|
||
value: { }
|
||
withAccessLevel:
|
||
summary: Specify access level
|
||
value:
|
||
access_level: "trusted"
|
||
withConfiguration:
|
||
summary: Provide room configuration
|
||
value:
|
||
configuration:
|
||
everyone_can_mute: true
|
||
responses:
|
||
'201':
|
||
description: Room created successfully
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Room'
|
||
'401':
|
||
$ref: '#/components/responses/UnauthorizedError'
|
||
'403':
|
||
$ref: '#/components/responses/ForbiddenError'
|
||
|
||
/rooms/{id}:
|
||
get:
|
||
tags:
|
||
- Rooms
|
||
summary: Retrieve a room
|
||
description: Get detailed information about a specific room by its ID
|
||
operationId: retrieveRoom
|
||
security:
|
||
- BearerAuth: [rooms:retrieve]
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
description: Room UUID
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
responses:
|
||
'200':
|
||
description: Room details
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Room'
|
||
examples:
|
||
room:
|
||
summary: Room details
|
||
value:
|
||
id: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||
slug: "aae-erez-aaz"
|
||
access_level: "trusted"
|
||
url: "https://visio-sandbox.beta.numerique.gouv.fr/aae-erez-aaz"
|
||
telephony:
|
||
enabled: true
|
||
pin_code: "123456"
|
||
phone_number: "+1-555-0100"
|
||
default_country: "US"
|
||
configuration: { }
|
||
'401':
|
||
$ref: '#/components/responses/UnauthorizedError'
|
||
'403':
|
||
$ref: '#/components/responses/ForbiddenError'
|
||
'404':
|
||
$ref: '#/components/responses/RoomNotFoundError'
|
||
|
||
patch:
|
||
tags:
|
||
- Rooms
|
||
summary: Update a room
|
||
description: |
|
||
Partially updates a room. Only rooms where the user is administrator or
|
||
owner can be updated; any other role gets a `403`.
|
||
|
||
**Updatable fields:** `access_level` and `configuration`. Every other field
|
||
(`id`, `name`, `slug`, `pin_code`) is read-only and silently ignored when sent.
|
||
|
||
`configuration` is replaced as a whole, it is not merged with the stored one.
|
||
Send the complete object you want the room to end up with.
|
||
|
||
Full replacement (`PUT`) is not supported. Use `PATCH` instead.
|
||
operationId: updateRoom
|
||
security:
|
||
- BearerAuth: [rooms:update]
|
||
parameters:
|
||
- name: id
|
||
in: path
|
||
required: true
|
||
description: Room UUID
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/RoomUpdate'
|
||
examples:
|
||
accessLevelOnly:
|
||
summary: Change the access level
|
||
value:
|
||
access_level: "restricted"
|
||
configurationOnly:
|
||
summary: Replace the room configuration
|
||
value:
|
||
configuration:
|
||
everyone_can_mute: true
|
||
responses:
|
||
'200':
|
||
description: Room updated successfully
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Room'
|
||
'400':
|
||
$ref: '#/components/responses/BadRequestError'
|
||
'401':
|
||
$ref: '#/components/responses/UnauthorizedError'
|
||
'403':
|
||
$ref: '#/components/responses/ForbiddenError'
|
||
'404':
|
||
$ref: '#/components/responses/RoomNotFoundError'
|
||
'405':
|
||
description: |
|
||
Method not allowed, `PUT` is not supported on this endpoint.
|
||
|
||
components:
|
||
securitySchemes:
|
||
BearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
description: |
|
||
JWT token obtained from the `/application/token` endpoint.
|
||
Include in requests as: `Authorization: Bearer <token>`
|
||
|
||
schemas:
|
||
RoomCreate:
|
||
type: object
|
||
description: |
|
||
Optional fields for room creation. All fields have secure defaults if omitted.
|
||
properties:
|
||
access_level:
|
||
$ref: '#/components/schemas/RoomAccessLevel'
|
||
configuration:
|
||
$ref: '#/components/schemas/RoomConfiguration'
|
||
|
||
RoomUpdate:
|
||
type: object
|
||
description: |
|
||
Fields that can be updated on an existing room. Both are optional, omitted
|
||
fields keep their current value.
|
||
properties:
|
||
access_level:
|
||
$ref: '#/components/schemas/RoomAccessLevel'
|
||
configuration:
|
||
$ref: '#/components/schemas/RoomConfiguration'
|
||
|
||
RoomConfiguration:
|
||
type: object
|
||
description: |
|
||
Optional room behaviour settings. Unknown fields are rejected.
|
||
All fields are optional and default to `null` (server-side defaults apply).
|
||
properties:
|
||
can_publish_sources:
|
||
type: array
|
||
nullable: true
|
||
description: |
|
||
Restricts which media tracks participants are allowed to publish.
|
||
If `null`, all sources are permitted.
|
||
items:
|
||
type: string
|
||
enum:
|
||
- camera
|
||
- microphone
|
||
- screen_share
|
||
- screen_share_audio
|
||
example: [ "camera", "microphone" ]
|
||
everyone_can_mute:
|
||
type: boolean
|
||
nullable: true
|
||
description: |
|
||
Whether any participant can mute others, or only the room owner/moderator.
|
||
If `null`, the server default applies.
|
||
example: true
|
||
additionalProperties: false
|
||
|
||
RoomAccessLevel:
|
||
type: string
|
||
enum:
|
||
- public
|
||
- trusted
|
||
- restricted
|
||
description: |
|
||
Controls who can join the room without going through the lobby.
|
||
|
||
- `public`: Anyone with the room link can join directly, no authentication required.
|
||
- `trusted`: Authenticated users join directly. Unauthenticated users wait in the lobby for approval.
|
||
- `restricted`: Only participants explicitly trusted by the owner bypass the lobby. Everyone else waits for approval regardless of authentication.
|
||
|
||
`public` is rejected with a `400` unless the deployment explicitly enables it
|
||
for this API. This applies both when creating a room and when updating one.
|
||
example: "trusted"
|
||
|
||
Room:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
readOnly: true
|
||
description: Unique room identifier
|
||
example: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||
slug:
|
||
type: string
|
||
readOnly: true
|
||
description: URL-friendly room identifier (auto-generated)
|
||
example: "aze-eere-zer"
|
||
access_level:
|
||
$ref: '#/components/schemas/RoomAccessLevel'
|
||
url:
|
||
type: string
|
||
format: uri
|
||
readOnly: true
|
||
description: Full URL to access the room
|
||
example: "https://visio-sandbox.beta.numerique.gouv.fr/aze-eere-zer"
|
||
telephony:
|
||
type: object
|
||
readOnly: true
|
||
description: Telephony dial-in information (if enabled)
|
||
properties:
|
||
enabled:
|
||
type: boolean
|
||
description: Whether telephony is available
|
||
example: true
|
||
pin_code:
|
||
type: string
|
||
description: PIN code for dial-in access
|
||
example: "123456"
|
||
phone_number:
|
||
type: string
|
||
description: Phone number to dial
|
||
example: "+1-555-0100"
|
||
default_country:
|
||
type: string
|
||
description: Default country code
|
||
example: "US"
|
||
configuration:
|
||
$ref: '#/components/schemas/RoomConfiguration'
|
||
|
||
OAuthError:
|
||
type: object
|
||
description: OAuth 2.0 error response (RFC 6749, section 5.2)
|
||
required:
|
||
- error
|
||
- error_description
|
||
properties:
|
||
error:
|
||
type: string
|
||
description: |
|
||
Error code. `server_error` is not defined by RFC 6749 for the token
|
||
endpoint and is only returned on an unexpected server-side failure.
|
||
enum:
|
||
- invalid_request
|
||
- invalid_client
|
||
- unsupported_grant_type
|
||
- invalid_scope
|
||
- server_error
|
||
example: "invalid_client"
|
||
error_description:
|
||
type: string
|
||
description: |
|
||
Human-readable description of the error. Contains printable ASCII
|
||
characters only, without double quotes or backslashes.
|
||
example: "Invalid credentials"
|
||
|
||
Error:
|
||
type: object
|
||
properties:
|
||
detail:
|
||
type: string
|
||
description: Error message
|
||
example: "Invalid token."
|
||
|
||
ValidationError:
|
||
type: object
|
||
properties:
|
||
field_name:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: List of validation errors for this field
|
||
example: ["This field is required."]
|
||
|
||
responses:
|
||
UnauthorizedError:
|
||
description: The access token is expired, revoked, malformed, or invalid for other reasons. The client can obtain a new access token and try again.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Error'
|
||
examples:
|
||
invalidToken:
|
||
summary: Invalid token
|
||
value:
|
||
error: "Invalid token."
|
||
|
||
ForbiddenError:
|
||
description: Insufficient scope for this operation
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Error'
|
||
examples:
|
||
insufficientScope:
|
||
summary: Missing required scope
|
||
value:
|
||
detail: "Insufficient permissions. Required scope: 'rooms:xxxx'"
|
||
|
||
RoomNotFoundError:
|
||
description: Room not found
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Error'
|
||
examples:
|
||
roomNotFound:
|
||
summary: Room does not exist
|
||
value:
|
||
detail: "Not found."
|
||
|
||
BadRequestError:
|
||
description: Invalid request data
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ValidationError'
|
||
examples:
|
||
validationError:
|
||
summary: Field validation failed
|
||
value:
|
||
scope: ["Invalid email address."]
|