Files
meet/docs/openapi.yaml
T
briquet d65ac8c845 🐛(backend) return RFC 6749 errors from application token endpoint
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.
2026-10-21 21:37:36 +02:00

700 lines
24 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.0.3
info:
title: Meet External API
version: 1.0.0
description: |
External API for room management with application-delegated authentication.
#### Authentication Flow
1. Exchange application credentials for a JWT token via `/external-api/v1.0/application/token/`.
2. Use the JWT token in the `Authorization: Bearer <token>` header for all subsequent requests.
3. Tokens are scoped and allow applications to act on behalf of specific users.
#### Scopes
* `rooms:list` – List rooms accessible to the delegated user.
* `rooms:retrieve` – Retrieve details of a specific room.
* `rooms:create` – Create new rooms.
* `rooms:update` – Update the access level and configuration of existing rooms.
* `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: Authentication
description: Application authentication and token generation
- name: Rooms
description: Room management operations
paths:
/application/token/:
post:
tags:
- Authentication
summary: Generate JWT token
description: |
Exchange application credentials for a scoped JWT token that allows the application
to act on behalf of a specific user.
The application must be authorized for the user's email domain.
The returned token expires after a configured duration and must be refreshed by calling this endpoint again.
Request parameters may be sent either as "application/x-www-form-urlencoded"
(as specified by RFC 6749 for OAuth 2.0 token endpoints) or as "application/json".
operationId: generateToken
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/TokenRequest'
examples:
tokenRequest:
summary: Request token for user delegation
value:
client_id: "550e8400-e29b-41d4-a716-446655440000"
client_secret: "1234567890abcdefghijklmnopqrstuvwxyz"
grant_type: "client_credentials"
scope: "user@example.com"
application/json:
schema:
$ref: '#/components/schemas/TokenRequest'
examples:
tokenRequest:
summary: Request token for user delegation
value:
client_id: "550e8400-e29b-41d4-a716-446655440000"
client_secret: "1234567890abcdefghijklmnopqrstuvwxyz"
grant_type: "client_credentials"
scope: "user@example.com"
responses:
'200':
description: Token generated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/TokenResponse'
examples:
tokenResponse:
summary: Successful token generation
value:
access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
token_type: "Bearer"
expires_in: 3600
scope: "rooms:list rooms:retrieve rooms:create"
'400':
description: |
Invalid request. When several parameters are invalid, `error` reports
the most relevant one (client credentials first, then grant type) and
`error_description` lists every invalid parameter.
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
missingClientCredentials:
summary: Missing client credentials
value:
error: "invalid_client"
error_description: "client_id : This field is required.; client_secret : This field is required."
unsupportedGrantType:
summary: Unsupported grant type
value:
error: "unsupported_grant_type"
error_description: "grant_type : 'authorization_code' is not a valid choice."
missingParameter:
summary: Missing required parameter
value:
error: "invalid_request"
error_description: "grant_type : This field is required."
invalidScope:
summary: Scope is not a valid email address
value:
error: "invalid_scope"
error_description: "Scope should be a valid email address."
'401':
description: Client authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
invalidCredentials:
summary: Invalid credentials
value:
error: "invalid_client"
error_description: "Invalid credentials"
inactiveApplication:
summary: Application is inactive
value:
error: "invalid_client"
error_description: "Application is inactive"
'403':
description: Access denied - cannot delegate user
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
delegationDenied:
summary: Domain not authorized
value:
error: "invalid_scope"
error_description: "This application is not authorized for this email domain."
'404':
description: |
User not found. Only returned when the application is not allowed
to create users.
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
userNotFound:
summary: User not found
value:
error: "invalid_scope"
error_description: "User not found."
'409':
description: |
The user could not be created or retrieved because of a concurrent
request. Retrying the request should succeed.
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
provisionalUserConflict:
summary: Concurrent user creation
value:
error: "server_error"
error_description: "Failed to create or retrieve provisional user."
'415':
description: |
Unsupported media type. The request body must be sent as
"application/x-www-form-urlencoded" or "application/json".
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
examples:
unsupportedMediaType:
summary: Unsupported request content type
value:
error: "invalid_request"
error_description: "Unsupported media type 'text/plain' in request."
/rooms:
get:
tags:
- Rooms
summary: List rooms
description: |
Returns a list of rooms accessible to the authenticated user.
Only rooms where the delegated 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:**
- Delegated 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 the delegated user's rooms where they are
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:
TokenRequest:
type: object
required:
- client_id
- client_secret
- grant_type
- scope
properties:
client_id:
type: string
description: Application client identifier
example: "550e8400-e29b-41d4-a716-446655440000"
client_secret:
type: string
format: password
writeOnly: true
description: Application secret key
example: "1234567890abcdefghijklmnopqrstuvwxyz"
grant_type:
type: string
enum:
- client_credentials
description: OAuth2 grant type (must be 'client_credentials')
example: "client_credentials"
scope:
type: string
format: email
description: |
Email address of the user to delegate.
The application will act on behalf of this user.
Note: This parameter is named 'scope' to align with OAuth2 conventions,
but accepts an email address to identify the user. This design allows
for future extensibility.
example: "user@example.com"
TokenResponse:
type: object
properties:
access_token:
type: string
description: JWT access token
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWV0LWFwaSIsImF1ZCI6Im1lZXQtY2xpZW50cyIsImlhdCI6MTcwOTQ5MTIwMCwiZXhwIjoxNzA5NDk0ODAwLCJjbGllbnRfaWQiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJzY29wZSI6InJvb21zOmxpc3Qgcm9vbXM6cmV0cmlldmUgcm9vbXM6Y3JlYXRlIiwidXNlcl9pZCI6IjdiOGQ5YzQwLTNhMmItNGVkZi04NzFjLTJmM2Q0ZTVmNmE3YiIsImRlbGVnYXRlZCI6dHJ1ZX0.signature"
token_type:
type: string
description: Token type (always 'Bearer')
example: "Bearer"
expires_in:
type: integer
description: Token lifetime in seconds
example: 3600
scope:
type: string
description: Space-separated list of granted permission scopes
example: "rooms:list rooms:retrieve rooms:create"
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: Authentication required or token invalid/expired
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
invalidToken:
summary: Invalid or expired token
value:
error: "Invalid token."
tokenExpired:
summary: Token has expired
value:
error: "Token expired."
ForbiddenError:
description: Insufficient permissions 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."]