OAuth Beta
Foxglove supports OAuth 2.0 authentication, which is recommended if you're building an application that integrates with Foxglove. The application calls the Foxglove API as the Foxglove user who approved it, limited to the scopes the user approved and to that user's own permissions.
Create an application
An organization admin registers an application under Settings → Integrations → OAuth applications.
The consent page shows the name, developer, and description to the user.
Foxglove shows the client secret once, when you create the application or rotate it. The client ID stays available on the application page.
An application you register is private to your organization. Only members of that organization can approve it.
Authorize a user
Foxglove uses the OAuth 2.0 authorization code flow with PKCE (Proof Key for Code Exchange). Send the user to the Foxglove authorization page. The user signs in, reviews the application, and approves or denies it.
https://app.foxglove.dev/oauth/authorize
| Parameter | Description |
|---|---|
client_id | Required. Client ID from the application page |
redirect_uri | Required. A registered redirect URI, URL-encoded |
response_type | Required. code |
code_challenge | Required. Base64url-encoded SHA-256 hash of code_verifier, without padding |
code_challenge_method | Required. S256 |
scope | Required. The capabilities the application needs, separated by spaces and URL-encoded |
state | Optional, recommended. Returned unchanged on the callback. Up to 2048 characters |
code_verifier is a random string of 43 to 128 characters from A-Z, a-z, 0-9, -, ., _, and ~. Keep it until the token request. Send only code_challenge to the authorization page.
Example
https://app.foxglove.dev/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2Fcallback&scope=projects.list%20datasets.list&code_challenge=CHALLENGE&code_challenge_method=S256&state=STATE
Handle the callback
After the user approves, Foxglove redirects to redirect_uri with code and, when you sent one, state. Refuse a callback whose state doesn't match the value you stored.
A user who has already approved every requested scope is sent back with a code and doesn't see the consent page again. A request for a scope the user hasn't approved shows the consent page again, with the new scopes marked. If the user denies the request, the callback has error=access_denied instead of code. See Errors for the other values.
The authorization code expires after 60 seconds. See Authorization codes for the rest of its rules.
Example
https://example.com/oauth/callback?code=CODE&state=STATE
Exchange the code
Exchange the authorization code for an access token and a refresh token. Pass parameters in the body as a URL-encoded form with Content-Type: application/x-www-form-urlencoded, and authenticate with the client secret.
POST https://api.foxglove.dev/oauth/token
| Parameter | Description |
|---|---|
grant_type | Required. authorization_code |
code | Required. The code from the callback |
redirect_uri | Required. The same redirect_uri you sent to the authorization page |
code_verifier | Required. The verifier that produced code_challenge |
client_id | Required. Client ID from the application page |
client_secret | Required. The application's client secret |
Example
curl https://api.foxglove.dev/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=CODE" \
-d "redirect_uri=https://example.com/oauth/callback" \
-d "code_verifier=CODE_VERIFIER" \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET"
Response
{
"access_token": "fox_oat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "fox_ort_...",
"scope": "projects.list datasets.list"
}
expires_in is the access token lifetime in seconds. Schedule the next refresh from it. See Token lifetimes. scope lists the scopes the access token has.
Refresh an access token
Exchange the refresh token for a new access token and a new refresh token. Pass parameters in the body as a URL-encoded form with Content-Type: application/x-www-form-urlencoded, and authenticate with the client secret.
POST https://api.foxglove.dev/oauth/token
| Parameter | Description |
|---|---|
grant_type | Required. refresh_token |
refresh_token | Required. The refresh token from the previous response |
scope | Optional. A subset of the refresh token's scopes, separated by spaces. The new tokens keep the same scopes when omitted |
client_id | Required. Client ID from the application page |
client_secret | Required. The application's client secret |
Response
{
"access_token": "fox_oat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "fox_ort_...",
"scope": "projects.list datasets.list"
}
Store the new refresh token. The refresh token you presented can't be used again, and presenting it again revokes the approval. See Refresh token rotation.
Call the API
Send the access token to the Foxglove API in an Authorization: Bearer header:
curl https://api.foxglove.dev/v1/projects \
-H "Authorization: Bearer ACCESS_TOKEN"
The token acts as the user who approved the application, within that user's permissions, including role and project access. The request needs the route's capability both in the token's scopes and in that user's role. A token without the scope gets 403 with a WWW-Authenticate header that names the missing scope:
WWW-Authenticate: Bearer error="insufficient_scope", scope="datasets.list"
To get a token that has it, send the user to the authorization page again with every scope the application needs, not only the missing one. The new tokens carry only the scopes in that request.
Scopes
Each scope is a Foxglove API capability. Request only the scopes your application needs. The user sees them in plain language on the consent page, and can review them later under Settings → Connected accounts.
A scope never gives the application more than the user can do. If the user's role doesn't allow a capability, a request that needs it fails even when the token has the scope.
Applications can request these scopes. For the current list, read scopes_supported in the authorization server metadata at https://api.foxglove.dev/.well-known/oauth-authorization-server.
| Area | Read | Write |
|---|---|---|
| Recordings | recordings.list, data.imports.pending.list | recordings.delete, data.upload |
| Sessions | sessions.list | sessions.create, sessions.update, sessions.delete |
| Data coverage | data.coverage.list | |
| Topics | data.topics.list | |
| Data streaming | data.stream | |
| Data search | data.search | |
| Devices | devices.list | devices.create, devices.delete, devices.update |
| Events | events.list | events.create, events.update, events.delete |
| Event types | eventTypes.list | eventTypes.create, eventTypes.delete, eventTypes.update |
| Custom properties | properties.list | properties.create, properties.update, properties.delete |
| Extensions | extensions.list | extensions.create, extensions.delete |
| Layouts | layouts.list | layouts.create, layouts.delete, layouts.update |
| Imports | data.imports.list | data.imports.delete |
| Projects | projects.list | |
| Sites | sites.list | sites.create, sites.delete, sites.update |
| Datasets | datasets.list | datasets.create, datasets.update, datasets.delete |
| Episodes | episodes.list | episodes.create, episodes.update, episodes.delete |
| Automations | automations.list | automations.create, automations.update, automations.delete, automations.run |
Applications can't request capabilities that create or manage credentials, such as device tokens and site tokens.
Revoke access
Revoke an access token or a refresh token from your application. Pass parameters in the body as a URL-encoded form with Content-Type: application/x-www-form-urlencoded, and authenticate with the client secret.
POST https://api.foxglove.dev/oauth/revoke
| Parameter | Description |
|---|---|
token | Required. The access token or refresh token to revoke |
token_type_hint | Optional. access_token or refresh_token |
client_id | Required. Client ID from the application page |
client_secret | Required. The application's client secret |
Response
A successful call returns 200 and an empty JSON object, including when the token is already revoked or unknown.
Revoking a refresh token also invalidates the access tokens for that approval. Revoking an access token invalidates that access token. A refresh token that is still valid can obtain a new access token.
Revoke from Foxglove
The user can revoke their own approval under Settings → Connected accounts, on the Authorized applications card. An organization admin can revoke an application's access for every member of the organization under Settings → Integrations → Authorized applications. Either action deletes the approval and its tokens.
Rotate or delete an application
Rotate the client secret from the application's settings page. The previous secret stops working immediately. Access tokens and refresh tokens already issued keep working. The next call to the token or revocation endpoint must use the new secret. The new secret is shown once.
Rotating the client secret doesn't revoke access tokens or refresh tokens already issued. To cut off access right away, also revoke the application's approvals. An organization admin can revoke them for every member of the organization at once.
Deleting an application removes it, along with every approval and token issued for it.
Client authentication
Every application is a confidential client. The token and revocation endpoints always need the client secret, sent with the client ID as the client_id and client_secret form fields. Keep these calls on a server you control, and don't ship the secret in a native or browser application.
Token lifetimes and limits
Authorization codes
The user has 15 minutes to approve. The authorization code can be exchanged once and expires after 60 seconds. A mismatch on the client, redirect_uri, or code_verifier uses the code. A missing or malformed code_verifier, or rejected client credentials, doesn't. Send the user to the authorization page again to get a new code.
An unknown client_id or a redirect_uri that isn't registered stays on the Foxglove page. Those requests aren't sent to your callback. The user must be signed in to the organization that registered the application. A session in another organization stays on the Foxglove page, where the user can sign in to that organization.
Token lifetimes
The access token expires in one hour (expires_in is 3600). Each refresh token expires 30 days after it is issued.
Refresh token rotation
Each refresh returns a new refresh token, and the one you presented can't be used again. Presenting a refresh token that was already exchanged revokes that user's approval of your application. Later refresh attempts for that approval fail until the user approves the application again.
An access token already issued stays valid until it expires, the approval is revoked, or a refresh token for that approval is revoked.
Errors
Token and revocation errors
The token and revocation endpoints return JSON:
{
"error": "invalid_grant",
"error_description": "The authorization grant is no longer valid"
}
| Status | error | When |
|---|---|---|
400 | invalid_request | The body is malformed or larger than 16 KiB, code_verifier is missing or malformed, or the revocation request has no token |
400 | invalid_client | client_id or client_secret is missing or wrong |
400 | invalid_grant | code or refresh_token is missing, unknown, expired, or already used, code_verifier doesn't match code_challenge, or the grant doesn't match this client or redirect_uri. Presenting a refresh token that was already exchanged for a new one also revokes the approval |
400 | unsupported_grant_type | grant_type isn't authorization_code or refresh_token |
400 | invalid_scope | On a refresh, scope lists a capability that applications can't request, or one the refresh token doesn't have |
Authorization errors
The authorization page adds these query parameters to your redirect_uri when it can redirect:
| Parameter | Description |
|---|---|
error | Always included. One of the values below |
error_description | Human-readable detail. Included with invalid_request, invalid_scope, and unsupported_response_type |
state | The state you sent to the authorization page. Included when you sent one |
error | When |
|---|---|
invalid_request | response_type is missing, code_challenge_method isn't S256, or code_challenge is missing or malformed |
unsupported_response_type | response_type isn't code |
invalid_scope | scope is missing or empty, or lists a capability that applications can't request |
access_denied | The user denied the request |