Skip to main content

Getting Started with the FGA Permissions Index

This guide walks through how to start using the FGA Permissions Index.


Prerequisites

Before requesting an index, make sure you have:

(Optional) Test Store

If you want to test the Permissions Index without connecting to your existing FGA data, you can create a new store and use a demo authorization model:

  1. Download the demo .fga.yaml file
  2. Navigate to the FGA Dashboard.
  3. Create a new store called test-permissions-index.
  4. Navigate to the Model Explorer page.
  5. Drag the downloaded permissions-index-demo.fga.yaml file into the Model Explorer to upload the model and tuples.
  6. Select Import model and Import tuples. This will seed your store with the model and tuples.

Uploading the demo model file in the FGA Dashboard Model Explorer


Step 1: Create An Index

Developer Preview: Creating a Permissions Index is a manual process for now. Self-service index creation, via an API and the FGA Dashboard, is planned for General Availability (GA).

To create an index, contact your Auth0 FGA Account Executive (AE) or Technical Account Manager (TAM) and provide:

  • Store ID (store_id)
  • Authorization Model ID (authorization_model_id)
  • The relation to be indexed (e.g., document#can_view)
Need an Index ID before proceeding

Once the index is created, you will receive an Index ID (a ULID, e.g., 01KJZVQNY35CWGXYEB164AWKJE).

Step 2: Configure Client Credentials

After receiving your index_id, you need to configure API client credentials with the appropriate permissions to access the Permissions Index API endpoints.

Create client credentials for your application:

  • In the FGA Dashboard, navigate to the Store Settings page and create or manage a client.
  • Enable Read Permissions Index access to grant your client the ability to 1) list all indexes, 2) read the details of a specific index, and 3) read streamed expansion events.
tip

Save the Client ID and Client Secret, which you will use to obtain a Bearer token for authenticating your API requests in the next step.

Step 3: Authenticate

All Permissions Index endpoints require a Bearer authentication token. Obtain one using the OAuth2 client credentials flow, as described in Call the API guide.

All access tokens are short-lived tokens. Cache the token for reuse and obtain a new one before expiry to avoid interruptions on the expansions stream.

Step 4: Call the Permissions Index API Endpoints

The Permissions Index API has three main endpoints:

List Indexes

Returns a list of all the indexes in the store, including all metadata for each specific index.

const response = await fgaClient.executeApiRequest({
operationName: 'ListIndexes', // For telemetry/logging
method: 'GET',
path: '/stores/{store_id}/indexes',
pathParams: {
store_id: process.env.FGA_STORE_ID,
},
});

console.log("Indexes", response.indexes)

Get Index

Returns metadata for a specific index, including total tuples ingested, total expansions, expansion factor, and freshness.

const response = await fgaClient.executeApiRequest({
operationName: 'GetIndex', // For telemetry/logging
method: 'GET',
path: '/stores/{store_id}/indexes/{index_id}',
pathParams: {
store_id: process.env.FGA_STORE_ID,
index_id: process.env.FGA_INDEX_ID,
},
});

console.log("index name", response.name)

Read Expansions

Streams all expansion events from the beginning of the index, or from a saved from continuation token.

const resp = await fgaClient.executeStreamedApiRequest({
operationName: "ReadExpansions",
method: "GET",
path: '/stores/{store_id}/indexes/{index_id}/expansions',
pathParams: {
store_id: process.env.FGA_STORE_ID,
index_id: process.env.FGA_INDEX_ID,
},
queryParams: {
authorization_model_id: process.env.FGA_MODEL_ID,
},
})

for await (const item of parseNDJSONStream(resp)) {
console.log(item)
}

Query parameters:

ParameterRequiredDescription
authorization_model_idYesAn Authorization Model ID previously assigned to the index
fromNoAn opaque continuation token to resume from a previous stream position

Response format: NDJSON (newline-delimited JSON). Each line is one event. The stream is long-lived — keep the connection open to receive ongoing expansion updates.

Expansion Events

When a tuple is written or deleted in FGA, the index computes the downstream impact and delivers expansion events. Each event carries a from continuation token that you should persist so you can resume after a disconnect.

{
"result": {
"event": {
"from": "FmhrVnpxNHBsUWgyNHc3NXh0R2NIX2e...",
"subject_type": "user",
"subject_id": "anne",
"object_type": "document",
"object_id": "budget",
"relation": "can_view",
"operation": "EXPANSION_OPERATION_INSERT",
"tuple_written_at": "2026-04-14T00:00:00Z"
}
}
}

operation is one of:

  • EXPANSION_OPERATION_INSERT — a permission was granted; add this row to your permissions table
  • EXPANSION_OPERATION_DELETE — a permission was revoked; remove the matching row

Freshness Events

The stream periodically emits freshness events. Use the as_fresh_as timestamp to understand how current the index is at any point in time.

{
"result": {
"freshness": {
"as_fresh_as": "2026-04-14T18:20:54.368739Z"
}
}
}

Freshness is calculated as NOW() - result.freshness.as_fresh_as. When an index is first created, it begins processing your store's tuples and becomes available for your use immediately. However, the index's freshness will initially be very stale while the index ingests your data. You can monitor this process by tracking the freshness events emitted from your index.

Other Event Types

EventDescription
result.heartbeatKeepalive signal — no action required
result.closedStream was closed by the server; includes a reason
result.errorStream error

The server closes each stream connection after 5 minutes. When that happens, you will receive a close event like this before reconnecting with your last saved from continuation token:

{
"result": {
"closed": {
"reason": "STREAM_CLOSED_REASON_CONNECTION_LIFETIME_EXCEEDED"
}
}
}

Step 5: Persist Expansions in Your Database

Use expansion events to maintain a colocated permissions table next to your application data. A minimal table schema looks like:

CREATE TABLE permissions (
subject_type TEXT NOT NULL,
subject_id TEXT NOT NULL,
subject_relation TEXT NOT NULL DEFAULT '',
object_type TEXT NOT NULL,
object_id TEXT NOT NULL,
relation TEXT NOT NULL,
PRIMARY KEY (subject_type, subject_id, subject_relation, object_type, object_id, relation)
);

On EXPANSION_OPERATION_INSERT, upsert the row. On EXPANSION_OPERATION_DELETE, delete the matching row.

Once populated, you can filter search results with a simple JOIN — no runtime FGA API call required:

SELECT d.*
FROM documents d
JOIN permissions p
ON p.object_type = 'document'
AND p.object_id = d.id
AND p.relation = 'can_view'
WHERE p.subject_type = 'user'
AND p.subject_id = 'anne'
AND d.title LIKE '%budget%';

Step 6: Resume After Disconnect

The stream guarantees at-least-once, ordered delivery.

First connection — omit from to start from the beginning of the index:

GET {fga_api_url}/stores/{store_id}/indexes/{index_id}/expansions
?authorization_model_id={model_id}

As you process expansion events, save the from continuation token from each event to durable storage.

Reconnecting — pass the last saved from continuation token to resume without missing events:

GET {fga_api_url}/stores/{store_id}/indexes/{index_id}/expansions
?authorization_model_id={model_id}
&from=FkN2dlQ0SnNUVDhlQUFpT0tPUzVnbEF8bhO23KSQTFcEi610UAKLopnLVl0uhRKN...

Have Feedback?

You can use any of our support channels for any questions or suggestions you may have.