mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-20 07:12:18 +00:00
d005f202c6
Accept `application/x-www-form-urlencoded` requests on the user token endpoint, in addition to the existing JSON support. This aligns the endpoint with the OAuth 2.0 specification for token endpoint requests (RFC 6749, sections 3.2 and 4.4.2), so standard OAuth 2.0 client libraries can call it without any customization, while keeping backward compatibility with existing JSON clients.
557 lines
18 KiB
YAML
557 lines
18 KiB
YAML
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` – **Coming soon** Update existing rooms, e.g., add attendees to a room.
|
||
* `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"
|
||
'401':
|
||
description: Authentication failed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/OAuthError'
|
||
examples:
|
||
invalidCredentials:
|
||
summary: Invalid credentials
|
||
value:
|
||
error: "Invalid credentials"
|
||
inactiveApplication:
|
||
summary: Application is inactive
|
||
value:
|
||
error: "Application is inactive"
|
||
'400':
|
||
description: Invalid request
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/OAuthError'
|
||
examples:
|
||
userNotFound:
|
||
summary: User not found
|
||
value:
|
||
error: "User not found."
|
||
'403':
|
||
description: Access denied - cannot delegate user
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/OAuthError'
|
||
examples:
|
||
delegationDenied:
|
||
summary: Domain not authorized
|
||
value:
|
||
error: "This application is not authorized for this email domain."
|
||
'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/Error'
|
||
examples:
|
||
unsupportedMediaType:
|
||
summary: Unsupported request content type
|
||
value:
|
||
detail: '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'
|
||
|
||
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'
|
||
|
||
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.
|
||
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: OAuth2-compliant error response
|
||
properties:
|
||
error:
|
||
type: string
|
||
description: Human-readable error description
|
||
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."]
|