cube-js/cube · error · CubejsHandlerError

API scope is missing: ${scope}

Error message

API scope is missing: ${scope}

What it means

Cube enforces API scopes per request: assertApiScope computes the scopes for the request's securityContext (via contextToApiScopes or the default) and throws this 403 Forbidden if the scope required by the endpoint (e.g. 'sql', 'jobs', 'graphql') is not among them. Authentication succeeded; the token's identity simply lacks permission for this API surface.

Source

Thrown at packages/cubejs-api-gateway/src/gateway.ts:2817

          return defaultApiScope;
        } else {
          return this.contextToApiScopesDefFn();
        }
      };
  }

  protected async assertApiScope(
    scope: ApiScopes,
    securityContext?: any,
  ): Promise<void> {
    const scopes =
      await this.contextToApiScopesFn(
        securityContext || {},
        getEnv('defaultApiScope') || await this.contextToApiScopesDefFn(),
      );
    const permited = scopes.indexOf(scope) >= 0;
    if (!permited) {
      throw new CubejsHandlerError(
        403,
        'Forbidden',
        `API scope is missing: ${scope}`
      );
    }
  }

  protected extractAuthorizationHeaderWithSchema(req: Request) {
    const authHeader = req.headers?.['x-cube-authorization'] || req.headers?.authorization;

    if (typeof authHeader === 'string') {
      const parts = authHeader.split(' ', 2);
      if (parts.length === 1) {
        return parts[0];
      }

      return parts[1];
    }

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Update contextToApiScopes to grant the missing scope for the relevant securityContexts (add 'sql'/'jobs'/etc. for roles that need it).
  2. Check the defaultApiScope environment variable — if it excludes the scope, either widen it or ensure tokens carry claims mapping to broader scopes.
  3. Verify the request's token actually contains the claims your contextToApiScopes inspects (decode the JWT).
  4. If the client shouldn't use that endpoint, stop calling it (e.g. use the REST data API instead of the SQL API).

Example fix

// before
contextToApiScopes: (ctx) => ['data'],
// after
contextToApiScopes: (ctx) => ctx.role === 'analyst' ? ['data', 'sql'] : ['data'],
Defensive patterns

Strategy: validation

Validate before calling

async function assertScopeAllowed(securityContext, requiredScope) {
  const scopes = await contextToApiScopes(securityContext, ['data']);
  if (!scopes.includes(requiredScope)) {
    throw new Error(`UI should hide this endpoint: missing scope ${requiredScope}`);
  }
}

Type guard

function hasScope(scopes, scope) {
  return Array.isArray(scopes) && scopes.includes(scope);
}

Try / catch

try {
  const result = await cubeApi.sql(); // or jobs/graphql endpoint
} catch (e) {
  if (e.status === 403 && /API scope is missing/.test(e.message)) {
    // token identity lacks this scope; route user to a permitted surface or upgrade their role
  }
  throw e;
}

Prevention

When it happens

Trigger: A request to a scope-gated endpoint (SQL API, jobs API, GraphQL) whose securityContext resolves to scopes that don't include the required scope — e.g. contextToApiScopes returns ['data'] but the client hits /v1/sql or the SQL interface.

Common situations: After tightening contextToApiScopes or setting the defaultApiScope env var too narrowly, previously working clients start failing; users with viewer roles attempting to use Cube SQL; tokens without role claims falling into the most restrictive branch.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/a3b6712eb545d1bb. Report an issue: GitHub.