OAuth 2.0 for the GlassFrog API

How a client gets a person's approval to call the GlassFrog API v5, including the MCP endpoint, with OAuth 2.0. For the endpoints themselves, see the API v5 reference.

Overview

GlassFrog is an OAuth 2.0 authorization server for its own API. It follows the OAuth 2.1 profile: the authorization-code grant with PKCE (S256 only), public clients registered dynamically, short-lived access tokens and rotating refresh tokens. There are no implicit, password or client-credentials grants.

  1. Discover the authorization server from the metadata documents.
  2. Register the client once and keep its client ID.
  3. Send the person to the authorization endpoint with a PKCE code challenge. They sign in, choose an organization and choose what the client may access.
  4. Exchange the returned code and the code verifier for an access token and a refresh token.
  5. Call the API with the access token as a Bearer credential, and refresh it when it expires.

Connecting Grok

Grok's custom connectors don't register a client, so GlassFrog keeps one for Grok. In Grok, add a custom connector with these settings.

Server URL https://fr.glassfrog.com/api/v5/mcp
Client ID grok
Client secret Leave blank. Grok authenticates with PKCE alone.
Token auth method none (PKCE only)

Grok then sends you to GlassFrog to sign in and choose what it may access, as described in What the person approves.

Discovery

The authorization server lives at the root of the host that serves the API, outside /api/v5. Discover it rather than hard-coding it. Both documents are public JSON, and every URL in them is built from the host you asked.

Endpoint Purpose
GET, POST https://fr.glassfrog.com/oauth/authorize Authorization endpoint, where the person approves the client
POST https://fr.glassfrog.com/oauth/token Token endpoint, for the authorization-code and refresh-token grants
POST https://fr.glassfrog.com/oauth/revoke Token revocation (RFC 7009)
POST https://fr.glassfrog.com/oauth/register Dynamic client registration (RFC 7591)
POST https://fr.glassfrog.com/api/v5/mcp The MCP endpoint, a protected resource like the rest of API v5

Client registration

Register with POST https://fr.glassfrog.com/oauth/register and a JSON body (RFC 7591). Registration is open and needs no credential, and it grants nothing: a registered client can do nothing until a person approves it.

POST /oauth/register HTTP/1.1
Host: fr.glassfrog.com
Content-Type: application/json

{"client_name": "Example Client",
 "redirect_uris": ["https://client.example.com/oauth/callback"]}

HTTP/1.1 201 Created
Content-Type: application/json

{"client_id": "CLIENT_ID", "client_id_issued_at": 1790000000,
 "client_name": "Example Client",
 "redirect_uris": ["https://client.example.com/oauth/callback"],
 "token_endpoint_auth_method": "none",
 "grant_types": ["authorization_code", "refresh_token"],
 "response_types": ["code"], "scope": "api"}

Every registered client is public. The response carries a client_id and no secret, and the client authenticates at the token endpoint with PKCE alone (token_endpoint_auth_method: none). A requested authentication method or scope is replaced by what is registered, and the response says so.

Redirect URIs

client_name is optional, at most 100 characters, and may not contain markup or control characters. The consent screen shows it as reported by the client, next to the redirect host; without one, the redirect host stands in.

grant_types may list only authorization_code and refresh_token, and response_types only code. Leave them out to get exactly those.

A refused registration answers 400 with an RFC 7591 error body: error is invalid_redirect_uri or invalid_client_metadata, with an error_description.

A client that nobody has approved 14 days after registering is deleted. Register again if that happens.

Authorization request

Send the person's browser to GET https://fr.glassfrog.com/oauth/authorize with these query parameters:

Parameter Value
response_type code, the only response type.
client_id The client ID from registration.
redirect_uri One of the registered redirect URIs, exactly.
code_challenge Required. The base64url-encoded SHA-256 hash of a fresh, random code_verifier (RFC 7636).
code_challenge_method S256. Other methods are refused.
state Recommended. An unguessable value you check when the browser comes back.
scope Optional. Only api, which is also the default.
https://fr.glassfrog.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fclient.example.com%2Foauth%2Fcallback&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&state=STATE

https://client.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=STATE

After the person approves, the browser returns to redirect_uri with a code and your state. The code works once and expires after 5 minutes. If the person cancels, the redirect carries error=access_denied instead.

Tokens

POST https://fr.glassfrog.com/oauth/token takes a form-encoded body. A public client sends its client_id in the body and no secret.

Exchanging the code

Send the code, the same redirect_uri and the code_verifier behind the code challenge.

POST /oauth/token HTTP/1.1
Host: fr.glassfrog.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2Fclient.example.com%2Foauth%2Fcallback&client_id=CLIENT_ID&code_verifier=CODE_VERIFIER

HTTP/1.1 200 OK
Content-Type: application/json

{"access_token": "gfo_ACCESS_TOKEN", "token_type": "Bearer", "expires_in": 1800,
 "refresh_token": "gfr_REFRESH_TOKEN", "scope": "api", "created_at": 1790000000}

Access tokens start with gfo_ and expire after 30 minutes; refresh tokens start with gfr_. Treat both as opaque secrets.

Refreshing

Refresh with grant_type=refresh_token. Refresh tokens rotate: every refresh returns a new refresh token, and the one you sent stops working at once. Presenting a used refresh token again is treated as theft and revokes every token issued under that approval, so the person has to approve the client again. Keep a single copy of the current refresh token and replace it as soon as a refresh succeeds.

POST /oauth/token HTTP/1.1
Host: fr.glassfrog.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=gfr_REFRESH_TOKEN&client_id=CLIENT_ID

Calling the API

Send the access token in the Authorization header on any API v5 endpoint, including the MCP endpoint at https://fr.glassfrog.com/api/v5/mcp:

POST /api/v5/mcp HTTP/1.1
Host: fr.glassfrog.com
Authorization: Bearer gfo_ACCESS_TOKEN

OAuth access tokens are accepted only as Authorization: Bearer. An OAuth token in X-Auth-Token is rejected, a gfr_ refresh token is never a valid bearer credential, and API v3 and v4 do not accept OAuth tokens.

A request without a valid credential answers 401 with an RFC 6750 challenge whose resource_metadata points at the protected-resource document. error="invalid_token" is added when a credential was sent and rejected, such as an expired access token: refresh, then retry.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="GlassFrog", error="invalid_token", resource_metadata="https://fr.glassfrog.com/.well-known/oauth-protected-resource/api/v5"

OAuth requests count toward the organization's API rate limit, like requests made with API keys.

Scopes

There is one scope, api. It is the default, so a client may leave scope out; any other value is refused. What a token may do is decided by the grant set the person chose, never by the scope.

Revocation

A client revokes a token with POST https://fr.glassfrog.com/oauth/revoke (RFC 7009): a form-encoded token, an optional token_type_hint, and its client_id. It answers 200 whether or not the token was still valid. A client can revoke only its own tokens; a request with an unknown client ID, or for a token that belongs to another client, gets 403.

POST /oauth/revoke HTTP/1.1
Host: fr.glassfrog.com
Content-Type: application/x-www-form-urlencoded

token=gfr_REFRESH_TOKEN&token_type_hint=refresh_token&client_id=CLIENT_ID

A person can revoke a client at any time under Connected apps on the API tab of their profile in the organization.

Every token issued under an approval also ends when:

After that the API answers 401 and a refresh fails with invalid_grant. Start a new authorization request; the person has to approve the client again.

Rate limits

The OAuth endpoints are rate-limited per client IP address and answer 429 with a Retry-After header over the limit: