Skip to content

OAuth 2.0 Scoped Grants

OAuth 2.0 scoped grants is an authorization flow that lets a custom integration obtain a Management API access token. A single authorization limits the token to the resources and actions the integration requires in one or more Storyblok spaces.

Custom integrations are private external applications that developers build for their Storyblok organizations. Unlike plugins, these integrations run outside Storyblok and have no interface in the Visual Editor or the space.

The OAuth scoped grants flow follows the OAuth 2.0 Authorization Code Grant with Proof Key for Code Exchange (PKCE).

The following steps outline the process in order:

  1. Request authorization

    Your custom integration sends the user to Storyblok to grant access, listing the permissions and spaces it needs.

  2. Grant access

    On the consent page, the user reviews the requested permissions, adjusts them if needed, and chooses which spaces to authorize.

  3. Receive the authorization code

    Storyblok sends the user back to your custom integration with a temporary authorization code. If the user declines, Storyblok returns an error instead.

  4. Exchange the code for a token

    Your custom integration swaps the authorization code for an access token, and a refresh token if the user approved offline access.

  5. Call the Management API

    The custom integration uses the access token to make authorized requests to the Management API.

  6. Refresh or revoke the token

    The custom integration refreshes the access token when it expires, or revokes it to revoke access.

The OAuth endpoints support all regions. Token exchange, refresh, revocation, and grant introspection all use mapi.storyblok.com, regardless of the region of the user’s spaces. The consent page lists the user’s eligible spaces across all regions.

Send Management API requests to the space’s region base URL. If a grant includes spaces from multiple regions, first call the grant introspection endpoint and get the region of each authorized space.

Permissions use the form resource:action, enforced per resource. Actions follow a hierarchy: the publish action includes everything that write allows, and write includes everything that read allows.

For example, a stories:publish token can write and read stories, while a stories:read token can only read stories.

A grant applies only to the scopes and the spaces the user selects on the consent page.

Available actions Resources
read, write, publish stories, releases
read, write ai_branding, assets, asset_folders, collaborators, comments, components, datasources, datasource_entries, spaces, tags, users, webhooks, workflows
write ai_translation
read statistics
special offline_access

Before sending the authorization request, generate the PKCE values:

Value Description
code_verifier 43–128 characters. Store it securely for the authorization code exchange.
code_challenge BASE64URL(SHA256(code_verifier))
code_challenge_method S256, the only supported method

Construct the authorization request URL with the following attributes:

Attribute Value
client_id Client ID of the integration
redirect_uri The configured redirection endpoint
response_type code
scope A space- or +-separated list of resource:action scopes. Include offline_access to receive a refresh token.
state Required. A random value that prevents CSRF attacks.
code_challenge The PKCE code challenge
code_challenge_method S256

For example, the URL would look like:

Terminal window
https://app.storyblok.com/#/oauth/init?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI&scope=stories:read stories:write offline_access&state=SOME_UUID&code_challenge=SHA_CODE&code_challenge_method=S256

After you redirect the user to the authorization endpoint, Storyblok shows the consent page, where users review and grant access to your custom integration.

The consent page groups the requested permissions by resource, such as Asset folders or Stories. Each resource lists one or more permission levels—read, write, or publish—that the user can control.

It also lets users select spaces where the grant applies.

Users must select at least one resource permission and one space.

When the user approves the permissions, Storyblok redirects to your redirect_uri with query parameters:

Terminal window
{redirect_uri}?code={code}&state={state}
Parameter Description
redirect_uri The redirection endpoint configured in your custom integration settings
code A unique code that Storyblok generates for your integration to request access and refresh tokens
state The exact value from the authorization request. Use it to verify the request and prevent CSRF attacks.

When the user denies the permissions, Storyblok redirects to your redirect_uri with an error parameter and the state value:

Terminal window
{redirect_uri}?error=access_denied&state={state}

The error value is access_denied. Verify the state before you proceed.

Request, introspect, refresh, and revoke access tokens with the following operations:

After you verify state and extract the authorization code, send a POST request to the token endpoint. Storyblok exchanges the code for an access token. The request has the following attributes:

Attribute Value
grant_type authorization_code
code The authorization code from the redirect
code_verifier The PKCE verifier that matches your code_challenge
client_id The custom integration’s client ID
client_secret The custom integration’s client secret
redirect_uri The same redirection endpoint as in the authorization request
Terminal window
POST https://mapi.storyblok.com/oauth/token

A successful response returns the following:

{
"access_token": "<ACCESS_TOKEN>",
"refresh_token": "<REFRESH_TOKEN>",
"token_type": "bearer",
"expires_in": 900,
"scope": "stories:read offline_access"
}
Token Prefix Lifetime
Authorization code — 60 seconds, single-use (concurrent double-exchange revokes the grant)
Access token sb_oat_ Valid for 15 minutes
Refresh token sb_ort_ Valid for one month. Requires offline_access scope.

The response includes a refresh token only if the user approves the offline_access scope. Otherwise, it includes an access token.

To retrieve the grant’s scopes, expiry, the custom integration’s identity, and the spaces along and regions, call the grant introspection endpoint with the grant’s access token.

Terminal window
GET https://mapi.storyblok.com/v1/oauth/grant
Authorization: Bearer <ACCESS_TOKEN>

A successful response returns the following:

{
"grant": {
"scopes": ["stories:read", "stories:write"],
"expires_at": "2026-07-06T12:00:00Z",
"app": { "name": "<APP_NAME>", "client_id": "<CLIENT_ID>" },
"spaces": [
{ "id": 123, "region": "eu" },
{ "id": 100000123, "region": "us" }
]
}
}

An expired or revoked token returns 401 Unauthorized error.

To refresh an access token, send a POST request with the following attributes:

Attribute Value
grant_type refresh_token
refresh_token The current refresh token
client_id The custom integration’s client ID
client_secret The custom integration’s client secret
Terminal window
POST https://mapi.storyblok.com/oauth/token

A refresh token works once. When you use it, the API returns a new access token along with a new refresh token, and the one you sent is no longer valid—store the replacement. Each refresh also restarts a one-month countdown, so a custom integration that’s inactive for a month must reauthorize.

To revoke a token in a grant, send a POST request with the following attributes:

Attribute Required Description
token Yes The raw access token or refresh token to revoke.
client_id Yes The integration’s client ID
client_secret Yes The integration’s client secret
token_type_hint No The token type: access_token or refresh_token
Terminal window
POST https://mapi.storyblok.com/oauth/revoke

When you revoke either the access token or the refresh token, both become invalid. To obtain access again, the user must reauthorize the integration.

Authenticated requests to the Management API

Section titled “Authenticated requests to the Management API”

When sending API requests, include the access token in the Authorization header:

Terminal window
Authorization: Bearer <ACCESS_TOKEN>

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.