How to get tuple changes
Please note that at this point in time, it is not considered production-ready and does not come with any SLAs; availability and uptime are not guaranteed. Limitations of Auth0 FGA during the Developer Community Preview can be found here.
This section illustrates how to call the Read Changes API to get the list of relationship tuple changes that happened in your store, in the exact order that they happened. The API response includes tuples that have been added or removed in your store. It does not include other changes, like updates to your authorization model and adding new assertions.
Before you start
- Node.js
- Go
- .NET
- Python
- CLI
- curl
- You have obtained the environment, store id, client id and client secret.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_API_HOST
,FGA_STORE_ID
,FGA_API_TOKEN_ISSUER
,FGA_API_AUDIENCE
,FGA_CLIENT_ID
andFGA_CLIENT_SECRET
as environment variables.
- You have obtained the environment, store id, client id and client secret.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_API_HOST
,FGA_STORE_ID
,FGA_API_TOKEN_ISSUER
,FGA_API_AUDIENCE
,FGA_CLIENT_ID
andFGA_CLIENT_SECRET
as environment variables.
- You have obtained the environment, store id, client id and client secret.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_API_HOST
,FGA_STORE_ID
,FGA_API_TOKEN_ISSUER
,FGA_API_AUDIENCE
,FGA_CLIENT_ID
andFGA_CLIENT_SECRET
as environment variables.
- You have obtained the environment, store id, client id and client secret.
- You have installed the SDK.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_API_HOST
,FGA_STORE_ID
,FGA_API_TOKEN_ISSUER
,FGA_API_AUDIENCE
,FGA_CLIENT_ID
andFGA_CLIENT_SECRET
as environment variables.
- You have obtained the environment, store id, client id and client secret.
- You have installed the CLI.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_SERVER_URL
,FGA_STORE_ID
,FGA_API_TOKEN_ISSUER
,FGA_API_AUDIENCE
,FGA_CLIENT_ID
andFGA_CLIENT_SECRET
as environment variables.
- You have obtained the environment, store id, client id and client secret.
- You have configured the authorization model and updated the relationship tuples.
- You have loaded
FGA_STORE_ID
andFGA_SERVER_URL
as environment variables.
Step By Step
To get a chronologically ordered list of tuples that have been written or deleted in your store, you can do so by calling the Read Changes API.
01. Configure The Auth0 FGA API Client
First you will need to configure the API client.
- Node.js
- Go
- .NET
- curl
const { CredentialsMethod, OpenFgaClient } = require('@openfga/sdk'); // OR import { CredentialsMethod, OpenFgaClient } from '@openfga/sdk';
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
const fgaClient = new OpenFgaClient({
apiScheme: process.env.FGA_API_SCHEME,
apiHost: process.env.FGA_API_HOST,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID,
credentials: { // Credentials are not needed if connecting to the Playground API
method: CredentialsMethod.ClientCredentials,
config: {
apiTokenIssuer: process.env.FGA_API_TOKEN_ISSUER,
apiAudience: process.env.FGA_API_AUDIENCE,
clientId: process.env.FGA_CLIENT_ID,
clientSecret: process.env.FGA_CLIENT_SECRET,
},
},
});
import (
"os"
openfga "github.com/openfga/go-sdk"
. "github.com/openfga/go-sdk/client"
"github.com/openfga/go-sdk/credentials"
)
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiScheme: os.Getenv("FGA_API_SCHEME")
ApiHost: os.Getenv("FGA_API_HOST"),
StoreId: os.Getenv("FGA_STORE_ID"),
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_MODEL_ID")),
Credentials: &credentials.Credentials{ // Credentials are not needed if connecting to the Playground API
Method: credentials.CredentialsMethodClientCredentials,
Config: &credentials.Config{
ClientCredentialsClientId: os.Getenv("FGA_CLIENT_ID"),
ClientCredentialsClientSecret: os.Getenv("FGA_CLIENT_SECRET"),
ClientCredentialsApiAudience: os.Getenv("FGA_API_AUDIENCE"),
ClientCredentialsApiTokenIssuer: os.Getenv("FGA_API_TOKEN_ISSUER"),
},
},
})
if err != nil {
// .. Handle error
}
}
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
namespace Example;
// Ensure the environment variables are set
// FGA_API_SCHEME = 'https'
// FGA_API_HOST = 'api.us1.fga.dev' for Dev Preview and Early Access / 'api.playground.fga.dev' for the FGA Playground
// FGA_STORE_ID = 'YOUR_STORE_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page
// FGA_MODEL_ID = 'YOUR_MODEL_ID' - optional, can be overridden per request, helps reduce latency
// FGA_API_TOKEN_ISSUER = 'fga.us.auth0.com' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_API_AUDIENCE = 'https://api.us1.fga.dev/' for Dev Preview and Early Access / not needed for the FGA Playground
// FGA_CLIENT_ID = 'YOUR_CLIENT_ID' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
// FGA_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' - Get this from your store settings in the dashboard, refer to the "How to get your API Keys" page / not needed for the FGA Playground
class MyProgram {
static async Task Main() {
var configuration = new ClientConfiguration() {
ApiScheme = Environment.GetEnvironmentVariable("FGA_API_SCHEME"),
ApiHost = Environment.GetEnvironmentVariable("FGA_API_HOST"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"),
Credentials = new Credentials() { // Credentials are not needed if connecting to the Playground API
Method = CredentialsMethod.ClientCredentials,
Config = new CredentialsConfig() {
ApiTokenIssuer = Environment.GetEnvironmentVariable("FGA_API_TOKEN_ISSUER"),
ApiAudience = Environment.GetEnvironmentVariable("FGA_API_AUDIENCE"),
ClientId = Environment.GetEnvironmentVariable("FGA_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("FGA_CLIENT_SECRET"),
}
}
};
var fgaClient = new OpenFgaClient(configuration);
}
}
To obtain the access token:
# Not needed when calling the Playground API
curl -X POST \
https://fga.us.auth0.com/oauth/token \
-H 'content-type: application/json' \
-d '{"client_id":"'$FGA_CLIENT_ID'","client_secret":"'$FGA_CLIENT_SECRET'","audience":"https://api.us1.fga.dev/","grant_type":"client_credentials"}'
# The response will be returned in the form
# {
# "access_token": "eyJ...Ggg",
# "expires_in": 86400,
# "scope": "read:tuples write:tuples check:tuples ... write:authorization-models",
# "token_type": "Bearer"
# }
# Store this `access_token` value in environment variable `FGA_BEARER_TOKEN`
# For non-playground environment
FGA_SERVER_URL='https://api.us1.fga.dev'
# For playground environment
# FGA_SERVER_URL='https://api.playground.fga.dev'
02. Get Changes For All Object Types
To get a paginated list of changes that happened in your store:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
var type = "";
var continuationToken = "";
var pageSize = 25;
await fgaClient.readChanges({ type }, { pageSize, continuationToken });
options := ClientReadChangesOptions{
PageSize: openfga.PtrInt32(25),
}
body := ClientReadChangesRequest{
}
data, err := fgaClient.ReadChanges(context.Background()).Body(body).Options(options).Execute()
if err != nil {
// .. Handle error
}
var body = new ClientReadChangesRequest { };
var options = new ClientReadChangesOptions {
PageSize = 25,
};
var response = await fgaClient.ReadChanges(body, options);
body = ClientReadChangesRequest()
options = new ClientReadChangesOptions {
page_size: 25,
};
response = await fga_client.read_changes(body, options)
fga tuple changes --store-id=${FGA_STORE_ID}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/changes \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"page_size": 25}'
The result will contain an array of up to 25 tuples, with the operation (write
or delete
), and the timestamp in which that operation took place. The result will also contain a continuation token. Save the continuation token in persistent storage between calls so that it is not lost and you do not have to restart from scratch on system restart or on error.
You can then use this token to get the next set of changes:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
var type = "";
var continuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==";
var pageSize = 25;
await fgaClient.readChanges({ type }, { pageSize, continuationToken });
options := ClientReadChangesOptions{
PageSize: openfga.PtrInt32(25),
ContinuationToken: openfga.PtrString("eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="),
}
body := ClientReadChangesRequest{
}
data, err := fgaClient.ReadChanges(context.Background()).Body(body).Options(options).Execute()
if err != nil {
// .. Handle error
}
var body = new ClientReadChangesRequest { };
var options = new ClientReadChangesOptions {
PageSize = 25,
ContinuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==",
};
var response = await fgaClient.ReadChanges(body, options);
body = ClientReadChangesRequest()
options = new ClientReadChangesOptions {
page_size: 25,
continuation_token: "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==",
};
response = await fga_client.read_changes(body, options)
fga tuple changes --store-id=${FGA_STORE_ID}
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/changes \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", "page_size": 25}'
Once there are no more changes to retrieve, the API will return the same token as the one you sent. Save the token in persistent storage to use at a later time.
- The default page size is 50. The maximum page size allowed is 100.
- The API response will not return all the changes immediately. There will be a delay of one minute between the time that you add or delete a tuple and the time that you see it in the Read Changes API response.
- The API response does not expand the tuples. If you wrote a tuple that includes a userset, like
{"user": "group:abc#member", "relation": "owner": "doc:budget"}
, the Read Changes API will return that exact tuple.
03. Get Changes For A Specific Object Type
Imagine you have the following authorization model:
- DSL
- JSON
model
schema 1.1
type user
type group
relations
define member: [user]
type folder
relations
define owner: [group#member,user]
type doc
relations
define owner: [group#member,user]
{
"schema_version": "1.1",
"type_definitions": [
{
"type": "user"
},
{
"type": "group",
"relations": {
"member": {
"this": {}
}
},
"metadata": {
"relations": {
"member": {
"directly_related_user_types": [
{
"type": "user"
}
]
}
}
}
},
{
"type": "folder",
"relations": {
"owner": {
"this": {}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "group",
"relation": "member"
},
{
"type": "user"
}
]
}
}
}
},
{
"type": "doc",
"relations": {
"owner": {
"this": {}
}
},
"metadata": {
"relations": {
"owner": {
"directly_related_user_types": [
{
"type": "group",
"relation": "member"
},
{
"type": "user"
}
]
}
}
}
}
]
}
It is possible to get a list of changes that happened in your store that relate only to one specific object type, like folder
, by issuing a call like this:
- Node.js
- Go
- .NET
- Python
- CLI
- curl
var type = "folder";
var continuationToken = "";
var pageSize = 25;
await fgaClient.readChanges({ type }, { pageSize, continuationToken });
options := ClientReadChangesOptions{
PageSize: openfga.PtrInt32(25),
}
body := ClientReadChangesRequest{
Type: "folder",
}
data, err := fgaClient.ReadChanges(context.Background()).Body(body).Options(options).Execute()
if err != nil {
// .. Handle error
}
var body = new ClientReadChangesRequest { Type = "folder" };
var options = new ClientReadChangesOptions {
PageSize = 25,
};
var response = await fgaClient.ReadChanges(body, options);
body = ClientReadChangesRequest("folder")
options = new ClientReadChangesOptions {
page_size: 25,
};
response = await fga_client.read_changes(body, options)
fga tuple changes --store-id=${FGA_STORE_ID} --type folder
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/changes \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"type": folder", "page_size": 25}'
The response will include a continuation token. In subsequent calls, you have to include the token and the type
. (If you send this continuation token without the type
parameter set, you will get an error).
- Node.js
- Go
- .NET
- Python
- CLI
- curl
var type = "folder";
var continuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==";
var pageSize = 25;
await fgaClient.readChanges({ type }, { pageSize, continuationToken });
options := ClientReadChangesOptions{
PageSize: openfga.PtrInt32(25),
ContinuationToken: openfga.PtrString("eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="),
}
body := ClientReadChangesRequest{
Type: "folder",
}
data, err := fgaClient.ReadChanges(context.Background()).Body(body).Options(options).Execute()
if err != nil {
// .. Handle error
}
var body = new ClientReadChangesRequest { Type = "folder" };
var options = new ClientReadChangesOptions {
PageSize = 25,
ContinuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==",
};
var response = await fgaClient.ReadChanges(body, options);
body = ClientReadChangesRequest("folder")
options = new ClientReadChangesOptions {
page_size: 25,
continuation_token: "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==",
};
response = await fga_client.read_changes(body, options)
fga tuple changes --store-id=${FGA_STORE_ID} --type folder
curl -X POST $FGA_SERVER_URL/stores/$FGA_STORE_ID/changes \
-H "Authorization: Bearer $FGA_API_TOKEN" \ # Not needed if service does not require authorization
-H "content-type: application/json" \
-d '{"type": folder", "continuation_token": "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==", "page_size": 25}'