English
OAuth
Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.
GET /api/v1/admin/oauth-apps/{provider}
Describe an OAuth application
Returns the callback URL and the scopes even when no application is registered: what the administrator needs to create it in the provider console. Never the secret, only a four-character hint.
Access — Member session or API key with scope admin:read (administrator role required).
Parameters
| Name | In | Type | Required |
|---|---|---|---|
provider | path | string | yes |
Responses
200 — The application state.
| Field | Type | Required |
|---|---|---|
provider | "google" | "microsoft" | yes |
configured | boolean | yes |
clientId | string | null | yes |
hasSecret | boolean | yes |
clientSecretHint | string | null | yes |
status | "active" | "disabled" | null | yes |
redirectUri | string (uri) | yes |
scopes | string[] | yes |
scopesByCapability | object | yes |
clientIdPattern | string | yes |
clientIdHint | string | yes |
updatedAt | string (date-time) | null | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"provider": { "type": "string", "enum": [ "google", "microsoft" ] },
"configured": { "type": "boolean" },
"clientId": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"hasSecret": { "type": "boolean" },
"clientSecretHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"status": {
"anyOf": [
{ "type": "string", "enum": [ "active", "disabled" ] },
{ "type": "null" }
]
},
"redirectUri": { "type": "string", "format": "uri" },
"scopes": { "type": "array", "items": { "type": "string" } },
"scopesByCapability": {
"type": "object",
"propertyNames": {
"type": "string",
"enum": [ "mail", "drive", "calendar", "sheets", "files", "sharepoint" ]
},
"additionalProperties": { "type": "array", "items": { "type": "string" } }
},
"clientIdPattern": { "type": "string", "minLength": 1 },
"clientIdHint": { "type": "string", "minLength": 1 },
"updatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{ "type": "null" }
]
}
},
"required": [
"provider",
"configured",
"clientId",
"hasSecret",
"clientSecretHint",
"status",
"redirectUri",
"scopes",
"scopesByCapability",
"clientIdPattern",
"clientIdHint",
"updatedAt"
],
"additionalProperties": false
}401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — oauth.unsupported_provider. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
PUT /api/v1/admin/oauth-apps/{provider}
Register an OAuth application
The client secret enters here and never leaves. Refused when the instance has no encryption key. The client id is checked against the provider’s expected shape. Recorded in the audit log (client id only).
Access — Member session or API key with scope admin:write (administrator role required).
Parameters
| Name | In | Type | Required |
|---|---|---|---|
provider | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
clientId | string | yes |
clientSecret | string | yes |
status | "active" | "disabled" | no |
JSON Schema
json
{
"type": "object",
"properties": {
"clientId": { "type": "string", "minLength": 1, "maxLength": 512 },
"clientSecret": { "type": "string", "minLength": 1, "maxLength": 512 },
"status": { "type": "string", "enum": [ "active", "disabled" ] }
},
"required": [ "clientId", "clientSecret" ]
}Responses
200 — The application state.
| Field | Type | Required |
|---|---|---|
provider | "google" | "microsoft" | yes |
configured | boolean | yes |
clientId | string | null | yes |
hasSecret | boolean | yes |
clientSecretHint | string | null | yes |
status | "active" | "disabled" | null | yes |
redirectUri | string (uri) | yes |
scopes | string[] | yes |
scopesByCapability | object | yes |
clientIdPattern | string | yes |
clientIdHint | string | yes |
updatedAt | string (date-time) | null | yes |
Same schema as GET /api/v1/admin/oauth-apps/{provider}.
400 — The request does not match its schema.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — oauth.unsupported_provider, oauth.encryption_disabled, request.bad_request, oauth.invalid_client_id. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
POST /api/v1/oauth/{provider}/start
Start an OAuth connection
Returns the authorisation URL to send the browser to. The body only names the capability (mail by default); scopes and redirect come from the instance. Requires a registered application for the provider.
Access — Member session only: API keys are refused (api_key.session_required).
Parameters
| Name | In | Type | Required |
|---|---|---|---|
provider | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
capability | "mail" | "drive" | "calendar" | "sheets" | "files" | "sharepoint" | no |
JSON Schema
json
{
"type": "object",
"properties": {
"capability": {
"type": "string",
"enum": [ "mail", "drive", "calendar", "sheets", "files", "sharepoint" ]
}
}
}Responses
200 — The authorisation URL and its expiry.
| Field | Type | Required |
|---|---|---|
authorizationUrl | string (uri) | yes |
expiresAt | string (date-time) | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"authorizationUrl": { "type": "string", "format": "uri" },
"expiresAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
}
},
"required": [ "authorizationUrl", "expiresAt" ],
"additionalProperties": false
}400 — The request does not match its schema.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — oauth.unsupported_provider, request.bad_request, oauth.app_not_configured, oauth.capability_not_supported, oauth.encryption_disabled. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.