OAuth 2.0 Scoped Grants
Custom integrations are external applications created by developers that operate independently of the Storyblok editor and spaces. They do not render within Storyblok. Instead, they authorize using OAuth 2.0 Scoped Grants and then connect to Storyblok through the Management API.
Custom integrations are different from plugins. Field, tool, and space plugins run inside Storyblok, in the Visual Editor, or in a dedicated section of a space to enhance the editing experience. Custom integrations have no interface inside Storyblok, they run as external applications. For details on plugins, see the plugins documentation.
OAuth 2.0 Scoped Grants is an authorization flow that allows your custom integration app to securely obtain a Management API access token with fine-grained permissions for specific resources in one or more Storyblok spaces.
Unlike the standard OAuth 2.0 flow, which grants read or write access to a single space, OAuth 2.0 Scoped Grants lets developers authorize only the resources and actions your custom integration requires across multiple spaces with a single authorization. It also uses Authorization Code Grant with Proof Key for Code Exchange (PKCE), short-lived access tokens, rotating refresh tokens, and token revocation to improve security.
OAuth Scoped Grants flow
Section titled “OAuth Scoped Grants flow”The OAuth Scoped Grants flow follows the OAuth 2.0 Authorization Code Grant with PKCE. The following steps outline the process in order:
- Request authorization. Your custom integration sends the user to Storyblok to authorize access, listing the permissions and spaces it needs.
- The user grants access. On the consent page, the user reviews the requested permissions, adjusts them if needed, and chooses which spaces to authorize.
- Storyblok confirms the approval. Storyblok sends the user back to your custom integration with a temporary authorization code. If the user declines, it returns an error instead.
- Exchange the code for a token. Your custom integration exchanges the authorization code for an access token, and a refresh token if offline access was approved.
- Call the Management API. The custom integration uses the access token to make authorized requests to the Management API.
- Refresh or revoke access. The custom integration refreshes the token when it expires, or revokes it to end access.
Regions
Section titled “Regions”The entire OAuth Scoped Grants flow is not specific to a region. You can use a single OAuth host for authorization, token exchange, token refresh, token revocation, and grant introspection, regardless of where the user’s spaces are located.
During the authorization process, the user consent page shows the user’s eligible spaces across all supported regions.
However, the region becomes important when using the access token to call the Management API. Send API requests to the Management API host for the intended space region.
If a grant includes spaces from multiple regions, use the grant introspection endpoint to determine the region of each authorized space before sending Management API requests.
Scopes
Section titled “Scopes”Permissions take the form resource:action, enforced per resource. Actions follow a hierarchy. Each higher level includes all actions from the levels below.
publish ⊃ write ⊃ readFor example, a stories:publish token can write and read stories, while a stories:read token can only read stories.
A grant is further limited to the scopes and spaces the user selects at the consent page.
| Available actions | Resources |
|---|---|
| read, write, publish | stories, releases |
| read, write | assets, asset_folders, collaborators, comments, components, datasources, datasource_entries, spaces, tags, users, webhooks, workflows |
| read | statistics |
| special | offline_access |
Authorization
Section titled “Authorization”Before sending the authorization request, generate a PKCE pair:
code_verifier: 43–128 characters.code_challenge:BASE64URL(SHA256(code_verifier)).
Set code_challenge_method to S256 (the only supported method). Store the code_verifier securely. You’ll need it when exchanging the authorization code for an access token.
Construct the authorization request URL using the following attributes:
client_id: Client ID of the integrationredirect_uri: one of your configured redirection endpointsresponse_type:codescope: A space- or+-separated list ofresource:actionscopes. Includeoffline_accessto receive a refresh token.state: Randomly generated value to prevent CSRF attacks. It is required.code_challengeandcode_challenge_method: PKCE, usingS256
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 they review and approve the access your custom integration is requesting.
The consent page shows the name of your custom integration and groups the requested permissions by resource, such as Asset folders or Stories. Each resource lists one or more permission levels (read, write, publish) as checkboxes, pre-checked by default. The user can uncheck any of them.
Spaces are selected in a separate Select spaces field. No space is selected by default, so the user must choose which spaces the custom integration can access. The grant applies only to the spaces the user selects in the consent page.
At least one resource permission and one space must be selected.
Authorization response
Section titled “Authorization response”After the user approves your custom integration’s permissions, Storyblok redirects to the specified URL with additional query parameters. The redirected URL has the following structure:
{redirect_uri}?code={code}&state={state}The parameters provided in the URL are:
redirect_uri: The URL configured in your custom integration settings as the redirection endpoint.code: A unique code generated by Storyblok for your integration to request access and refresh tokens.state: The exact value sent in the initial authorization request. This value is used to verify the request and prevent CSRF attacks.
If the user denies your custom integration’s permissions, Storyblok redirects to your redirect_uri with an error parameter along with the state value:
{redirect_uri}?error=access_denied&state={state}error is access_denied when the user declines. Verify state as you would on the success path before acting on the response.
Access token management
Section titled “Access token management”Access token request
Section titled “Access token request”Once you have validated the state and extracted the authorization code, exchange it for an access token by sending a POST request with these attributes:
grant_type:authorization_codecode: the authorization code from the redirectcode_verifier: the PKCE verifier matching yourcode_challengeclient_id: the custom integration’s client idclient_secret: the custom integration’s client secretredirect_uri
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. |
A token value is shown once in the response and cannot be retrieved again.
Grant introspection
Section titled “Grant introspection”Call the grant introspection endpoint with the grant’s own access token to retrieve its scopes, expiry, the custom integration’s identity, and the granted spaces along with their regions.
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" } ] }}Use each space’s region to choose the correct Management API host when sending API requests for that space. 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 these attributes:
grant_type:refresh_tokenrefresh_token: your current refresh tokenclient_idandclient_secret
POST https://mapi.storyblok.com/oauth/tokenRefresh tokens rotate. Each successful refresh returns a new access token and immediately invalidates the previous refresh token. Always store and use the latest refresh token. The one-month window for the refresh token resets on each refresh. An integration that is not active for a full month must re-authorize again.
Access token revocation
Section titled “Access token revocation”To revoke a token in a grant, send a POST request with the following attributes:
token: the raw token you want to revoke (either an access token or a refresh token)client_idandclient_secret- optional
token_type_hint: eitheraccess_tokenorrefresh_token. Hint only.
POST https://mapi.storyblok.com/oauth/revokeIf either the access token or the refresh token is revoked, both tokens will no longer be valid. To obtain access again, the user must re-authorize.
Authenticated requests to the Management API
Section titled “Authenticated requests to the Management API”Include the access token in the Authorization header when sending API requests:
Authorization: Bearer <ACCESS_TOKEN>For additional details on Management API endpoints, refer to the Storyblok Management API documentation.
Related resources
Section titled “Related resources”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.
Get in touch with the Storyblok community