Table of Contents

Authentication (OAuth 2.1)

The ResQueServe Dashboard is the only authorization server. Every API call needs a bearer token that you obtain from it:

Authorization: Bearer <access_token>

A token from the Dashboard (https://<dashboard-host>/oauth/token) is valid for the API of the Dashboard and of the Realm (https://<realm-host>/API/v1/...). See the overview for the hosts of the SIM Dispatcher.

Quick reference

What Value
Specifications OAuth 2.1, RFC 7009 (revocation), RFC 7591 (dynamic client registration), RFC 8252 (native apps), RFC 8414 and RFC 9728 (metadata), RFC 9700 (security)
Token type JWT bearer token
Access token lifetime 15 minutes (expires_in: 900)
Grant types client_credentials, authorization_code (PKCE required), refresh_token
PKCE method S256 only
Client authentication API clients: client_secret_post (secret in the form body). Authorization code and refresh requests are protected by PKCE and the one-time refresh token
Request format application/x-www-form-urlencoded for /oauth/token and /oauth/revoke, JSON for /oauth/register
Scope mcp:user

Always use https. Token responses are sent with Cache-Control: no-store.

Choose a method

Client credentials Authorization code with PKCE
Best for Servers, scripts, automation Applications that act for a signed-in user
User sign-in No, the token has the rights of the account that owns the API client Yes, in the browser
Credentials API client from the account settings Dynamic client registration
Effort One HTTP request per token Redirects, PKCE, callback

Client credentials

Create an API client

Sign in to the Dashboard, open Account, and go to API Clients / Developer. Enter a description and click Create. Copy the client_id and the client_secret: the secret is shown only once. An account can have up to 10 API clients. Keep the secret out of source control.

Request a token

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=<client-id>
&client_secret=<client-secret>
{
  "access_token": "<JWT>",
  "token_type": "Bearer",
  "expires_in": 900
}

The token carries the roles of the account that owns the API client. There is no refresh token: request a new token with the client credentials when the old one expires. Renew a little early (for example 60 seconds before expires_in runs out), and request a new token once if an API call answers 401.

TOKEN=$(curl -sX POST https://<dashboard-host>/oauth/token \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=<client-id>" \
  --data-urlencode "client_secret=<client-secret>" \
  | jq -r '.access_token')

curl -H "Authorization: Bearer $TOKEN" https://<dashboard-host>/API/v1/...

The .NET SDK does all of this for you.

Authorization code with PKCE

Use this flow when your application acts on behalf of a signed-in ResQueServe user, for example a web or desktop application, a native app, or an MCP-compatible tool.

Client                      Dashboard                    User's browser
  |-- POST /oauth/register ---->|                             |
  |<-- client_id, secret -------|                             |
  |-- redirect to /oauth/authorize --------------------------->|
  |                             |<-- user signs in -----------|
  |<-- callback with ?code=... ---------------------------------|
  |-- POST /oauth/token (code + code_verifier) -->|            |
  |<-- access_token, refresh_token -|                          |

1. Register a client

POST /oauth/register
Content-Type: application/json

{
  "redirect_uris": ["https://your-app.example.com/callback"],
  "grant_types": ["authorization_code"],
  "application_type": "web",
  "client_name": "My Application"
}
Field Required Description
redirect_uris Yes One or more absolute URIs without fragment, see redirect URIs
grant_types No Defaults to ["authorization_code"]
application_type No web (default) or native. Only native clients may register loopback redirects
client_name No Display name

The 201 Created response contains client_id and client_secret. The secret is shown only once. Registered clients expire after a while and must then be registered again. If the server requires an initial access token, send it as Authorization: Bearer <token>. Registration is limited to 5 requests per minute per IP address.

Redirect URIs

Redirect URIs are compared as exact strings (RFC 9700). These forms are accepted:

Form Example Notes
Claimed https link https://app.example.com/callback Preferred for native apps (Universal Links, App Links)
Private-use scheme, reverse-domain form com.example.app:/oauth The scheme must contain a dot (RFC 8252 ยง7.1)
Loopback http://127.0.0.1/callback, http://localhost/callback, http://[::1]/callback native clients only. In the authorization request only the port may differ
http on other hosts Development environments only

A URI with a fragment or credentials is always rejected (invalid_redirect_uri), as are javascript:, data:, file: and ftp: URIs.

2. Create the PKCE values

Generate a random code_verifier (43 to 128 characters of A-Z a-z 0-9 - . _ ~) and derive the challenge:

code_challenge = BASE64URL(SHA-256(ASCII(code_verifier)))   // no padding
CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '=+/' | cut -c1-64)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')

3. Send the user to the authorization endpoint

GET /oauth/authorize
  ?response_type=code
  &client_id=<client-id>
  &redirect_uri=<redirect-uri>
  &code_challenge=<code-challenge>
  &code_challenge_method=S256
  &scope=mcp:user
  &state=<random-value>
Parameter Required Description
response_type Yes code
client_id Yes From the registration
redirect_uri Yes Exactly one of the registered URIs
code_challenge Yes From step 2
code_challenge_method Yes S256
scope No Space-separated scopes, defaults to mcp:user
state No Random value against CSRF, returned unchanged in the callback. Verify it

After the sign-in the user is redirected to your redirect_uri with code and state. A code is valid for 5 minutes and can be used once.

4. Exchange the code

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=<client-id>
&code=<code>
&code_verifier=<code-verifier>
&redirect_uri=<same-redirect-uri>
{
  "access_token": "<JWT>",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "<opaque token>"
}

5. Refresh the token

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&client_id=<client-id>
&refresh_token=<newest-refresh-token>

The response has the same format and contains a new refresh token. Every refresh token can be used once: always store the newest one. Using an old one again is treated as theft and ends the login, and the user has to sign in again. A login lasts 30 days from the sign-in; refreshing does not extend it. A suspended or deleted user gets no new token.

Sign out (revoke)

When the user disconnects your application, revoke the refresh token (RFC 7009):

POST /oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<refresh-token>
&client_id=<client-id>
&access_token=<access-token>

The endpoint answers 200 OK, also for unknown tokens. Revoking a refresh token ends the whole login: all its refresh tokens and access tokens, including the ones issued before the last refresh, stop working. Other logins of the user are not affected. The optional access_token revokes that token as well. If token is an access token, only that token is revoked.

Discovery

Clients can configure themselves from the metadata documents:

URL Standard Content
GET /.well-known/oauth-authorization-server (Dashboard) RFC 8414 Issuer, endpoints, grant types, PKCE methods, scopes
GET /.well-known/oauth-protected-resource (Dashboard and Realm) RFC 9728 The resource and its authorization server (always the Dashboard)
{
  "issuer": "https://<dashboard-host>",
  "authorization_endpoint": "https://<dashboard-host>/oauth/authorize",
  "token_endpoint": "https://<dashboard-host>/oauth/token",
  "registration_endpoint": "https://<dashboard-host>/oauth/register",
  "revocation_endpoint": "https://<dashboard-host>/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "none"]
}

Access token

The access token is a signed JWT. Treat it as opaque: use it only as a bearer credential, and do not rely on its claims in your client. The server decides about expiry and revocation, so a token can stop working before exp (for example after a revocation). Answer a 401 by requesting a new token.

Admin rights are never part of an OAuth access token, whatever the roles of the user are.

Limits

Endpoint Limit
POST /oauth/token, POST /oauth/revoke 10 requests per minute per IP address and client_id, and 60 per minute per IP address over all clients
POST /oauth/register 5 requests per minute per IP address

A rejected request answers 429 with the error too_many_requests. Do not retry immediately; wait before the next attempt, and back off further if it fails again.

Errors

The OAuth endpoints answer with the RFC 6749 JSON format. This is the only part of the API that does not use RFC 7807 problem details.

{
  "error": "invalid_grant",
  "error_description": "Human-readable explanation."
}
Endpoint error HTTP Cause
/oauth/authorize unsupported_response_type 400 response_type is not code
/oauth/authorize invalid_request 400 A required parameter is missing, or code_challenge_method is not S256
/oauth/authorize invalid_scope 400 A requested scope is not supported
/oauth/authorize invalid_client 400 client_id is unknown or expired
/oauth/authorize access_denied redirect The user lacks the roles for the requested scope
/oauth/token unsupported_grant_type 400 Unknown grant_type
/oauth/token invalid_request 400 A required parameter is missing
/oauth/token invalid_grant 400 Code or refresh token unknown, expired, revoked, already used or of another client; PKCE check failed; user suspended or deleted
/oauth/token invalid_client 401 Client unknown or secret wrong
/oauth/token unauthorized_client 400 The user of the API client is suspended
/oauth/register invalid_client_metadata 400 redirect_uris missing, or application_type is neither web nor native
/oauth/register invalid_redirect_uri 400 A redirect URI is not allowed, see redirect URIs
/oauth/register access_denied 401 An initial access token is required and missing or wrong
/oauth/revoke invalid_request 400 token or client_id missing
any rate-limited too_many_requests 429 See limits

invalid_client and unauthorized_client mean that a retry will not help: the client needs new credentials. Other errors can be handled in your application as usual.