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.
OAuth flow
Section titled “OAuth flow”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:
-
Request authorization
Your custom integration sends the user to Storyblok to grant access, listing the permissions and spaces it needs.
-
Grant access
On the consent page, the user reviews the requested permissions, adjusts them if needed, and chooses which spaces to authorize.
-
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.
-
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.
-
Call the Management API
The custom integration uses the access token to make authorized requests to the Management API.
-
Refresh or revoke the token
The custom integration refreshes the access token when it expires, or revokes it to revoke access.
Regions
Section titled “Regions”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.
Scopes
Section titled “Scopes”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 |
Authorization
Section titled “Authorization”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:
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=S256User consent page
Section titled “User consent page”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.
Authorization response
Section titled “Authorization response”When the user approves the permissions, Storyblok redirects to your redirect_uri with query parameters:
{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:
{redirect_uri}?error=access_denied&state={state}The error value is access_denied. Verify the state before you proceed.
Access token management
Section titled “Access token management”Request, introspect, refresh, and revoke access tokens with the following operations:
Access token request
Section titled “Access token request”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 |
POST https://mapi.storyblok.com/oauth/tokenA 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.
Grant introspection
Section titled “Grant introspection”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.
GET https://mapi.storyblok.com/v1/oauth/grantAuthorization: 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.
Access token refresh
Section titled “Access token refresh”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 |
POST https://mapi.storyblok.com/oauth/tokenA 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.
Access token revocation
Section titled “Access token revocation”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 |
POST https://mapi.storyblok.com/oauth/revokeWhen 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:
Authorization: Bearer <ACCESS_TOKEN>Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.
- Need help?Contact support (opens in a new window)
- Something isn’t right?Check system status (opens in a new window)
Get in touch with the Storyblok community