How to Manage API Resources and Scopes in Logto: A Complete Guide

Logto manages API resources and scopes through a database-backed system where resources are identified by unique indicators (audiences) and scopes define granular permissions, accessible via Management API endpoints or the Admin Console UI.

In the logto-io/logto repository, API resources and scopes form the authorization backbone for protecting external APIs. Each API resource represents a logical entity that client applications can request access to, while scopes define the specific operations permitted within that resource. This guide explains the core implementation based on the source code and provides practical examples for managing these entities.

Understanding API Resources and Scopes in Logto

What is an API Resource?

An API resource is a logical representation of an external API that client applications can invoke. According to packages/schemas/src/types/resource.ts, each resource contains:

  • Indicator: A unique URL-like identifier (e.g., https://myapp.com/api) that serves as the audience (aud) claim in JWT tokens
  • Name: A human-readable label for the Admin Console
  • Default flag: A boolean marking whether this is the default Management API (only one allowed per tenant)
  • Permissions: An array of scopes defining available operations

What are Scopes (Permissions)?

A scope (also called a permission) belongs to a single API resource and describes a granular operation. Defined in packages/schemas/src/types/scope.ts, scopes are stored within the resource's permissions array and exposed through the Management API. Common examples include read:user or write:orders.

Creating and Configuring API Resources

Define the Resource Indicator

When creating a resource, you must specify a unique indicator that matches the aud claim expected by your API. The indicator acts as the audience identifier during token validation.

Add Scopes to the Resource

Scopes are attached to resources and validated during token issuance. Each scope requires a name (no spaces) and optional description.

Create an API Resource via Management API

The packages/core/src/routes/api-resource.ts file implements the CRUD endpoints. Below is a TypeScript example for creating a resource:

import fetch from 'node-fetch';

const LOGTO_ADMIN_URL = 'https://logto.example.com/api';
const ACCESS_TOKEN = 'YOUR_MANAGEMENT_API_TOKEN';

async function createApiResource() {
  const response = await fetch(`${LOGTO_ADMIN_URL}/api-resources`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${ACCESS_TOKEN}`,
    },
    body: JSON.stringify({
      name: 'My Awesome API',
      indicator: 'https://my-awesome-api.com/api',
      default: false,
    }),
  });

  const data = await response.json();
  console.log('Created API resource →', data);
}

createApiResource();

Add Scopes to an Existing Resource

To add permissions to a resource, POST to the resource-specific permissions endpoint:

async function addScope(resourceId: string) {
  const response = await fetch(
    `${LOGTO_ADMIN_URL}/api-resources/${resourceId}/permissions`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${ACCESS_TOKEN}`,
      },
      body: JSON.stringify({
        name: 'read:orders',
        description: 'Read order information',
      }),
    }
  );

  const permission = await response.json();
  console.log('Added permission →', permission);
}

Managing Resources Programmatically

Retrieve and Update Resources

The Management API supports full CRUD operations as implemented in packages/core/src/routes/api-resource.ts:

  • GET /api-resources – List all resources with their scopes
  • PATCH /api-resources/:id – Update resource name or indicator
  • DELETE /api-resources/:id – Remove resource and associated scopes

Example deletion:

async function deleteApiResource(resourceId: string) {
  await fetch(`${LOGTO_ADMIN_URL}/api-resources/${resourceId}`, {
    method: 'DELETE',
    headers: {
      Authorization: `Bearer ${ACCESS_TOKEN}`,
    },
  });

  console.log(`API resource ${resourceId} deleted`);
}

Admin Console Implementation

The React-based Admin Console provides UI components for these operations in packages/console/src/pages/api-resources/*, offering a visual interface for creating resources and managing their scopes without writing code.

Token Issuance and Verification

When clients request tokens, Logto validates the resource against stored indicators. The packages/core/src/services/token-service.ts file handles:

  1. Resource validation: Checking the resource parameter against the resources table
  2. Scope filtering: Ensuring requested scopes exist in the resource's permissions array
  3. Audience assignment: Setting the token's aud claim to the resource indicator

Clients request tokens by specifying the resource indicator:

import { LogtoClient } from '@logto/client';

const logto = new LogtoClient({
  endpoint: 'https://logto.example.com',
  resource: 'https://my-awesome-api.com/api',
});

async function getToken() {
  const token = await logto.getAccessToken();
  console.log('Access token for resource →', token);
}

The Default Management API

Logto automatically creates a default Management API resource during initialization via packages/schemas/src/seeds/management-api.ts. This special resource:

  • Uses an indicator generated by getManagementApiResourceIndicator in packages/schemas/src/utils/management-api.ts
  • Is automatically granted to machine-to-machine (M2M) clients
  • Powers the Admin Console and tunnel CLI functionality

The database alteration in packages/schemas/alterations/1.9.2-1695198741-remove-m2m-app-admin-access-switch.ts enforces that only one resource can be marked as default, preventing configuration conflicts.

Summary

Frequently Asked Questions

What is the difference between an API resource indicator and a scope in Logto?

The indicator is a unique URL-like string (e.g., https://api.example.com) that identifies the API resource itself and becomes the aud claim in access tokens. A scope is a granular permission string (e.g., read:users) that defines specific operations allowed on that resource. While the indicator identifies what API is being accessed, scopes define how the client can interact with it.

How do I obtain a Management API token to manage API resources programmatically?

Create a machine-to-machine (M2M) application in the Logto Admin Console and grant it the appropriate Management API scopes (such as api:read and api:create). Then use the OIDC token endpoint with the Management API indicator—generated by getManagementApiResourceIndicator in packages/schemas/src/utils/management-api.ts—to request an access token. This token authorizes requests to the /api-resources endpoints.

Can I have multiple default API resources in Logto?

No. Logto enforces a unique constraint allowing only one default API resource per tenant. This restriction is implemented in the database alteration file packages/schemas/alterations/1.9.2-1695198741-remove-m2m-app-admin-access-switch.ts. The default resource is reserved for the Logto Management API, which is automatically seeded during installation via packages/schemas/src/seeds/management-api.ts.

How does Logto validate the resource parameter during token issuance?

When a client requests a token with a specific resource parameter, the token-service.ts in packages/core/src/services/ validates that the indicator exists in the resources table. It then filters the requested scopes against the permissions stored in that resource's permissions array, ensuring only authorized scopes are included in the final JWT's scope claim.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →