Expand relationships in userset tree format
The Expand API returns all users and usersets that have certain relationship with an object in a certain store.
This is different from the /stores/{store_id}/read API in that both users and computed usersets are returned.
Body parameters tuple_key.object and tuple_key.relation are all required.
A contextual_tuples object may also be included in the body of the request. This object contains one field tuple_keys, which is an array of tuple keys. Each of these tuples may have an associated condition.
The response returns a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes.
Example
To expand all users that have the reader relationship with object document:2021-budget, use the Expand API with the following request body:
{
"tuple_key": {
"object": "document:2021-budget",
"relation": "reader"
},
"authorization_model_id": "01G50QVV17PECNVAHX1GG4Y5NC"
}
OpenFGA’s response is a userset tree of the users and usersets that have read access to the document.
{
"tree":{
"root":{
"type":"document:2021-budget#reader",
"union":{
"nodes":[
{
"type":"document:2021-budget#reader",
"leaf":{
"users":{
"users":[
"user:bob"
]
}
}
},
{
"type":"document:2021-budget#reader",
"leaf":{
"computed":{
"userset":"document:2021-budget#writer"
}
}
}
]
}
}
}
}
The caller can then call expand API for the writer relationship for the document:2021-budget.
Expand Request with Contextual Tuples
Given the model
model
schema 1.1
type user
type folder
relations
define owner: [user]
type document
relations
define parent: [folder]
define viewer: [user] or writer
define writer: [user] or owner from parent
and the initial tuples
[{
"user": "user:bob",
"relation": "owner",
"object": "folder:1"
}]
To expand all writers of document:1 when document:1 is put in folder:1, the first call could be:
{
"tuple_key": {
"object": "document:1",
"relation": "writer"
},
"contextual_tuples": {
"tuple_keys": [
{
"user": "folder:1",
"relation": "parent",
"object": "document:1"
}
]
}
}
this returns:
{
"tree": {
"root": {
"name": "document:1#writer",
"union": {
"nodes": [
{
"name": "document:1#writer",
"leaf": {
"users": {
"users": []
}
}
},
{
"name": "document:1#writer",
"leaf": {
"tupleToUserset": {
"tupleset": "document:1#parent",
"computed": [
{
"userset": "folder:1#owner"
}
]
}
}
}
]
}
}
}
}
This tells us that the owner of folder:1 may also be a writer. So our next call could be to find the owners of folder:1
{
"tuple_key": {
"object": "folder:1",
"relation": "owner"
}
}
which gives
{
"tree": {
"root": {
"name": "folder:1#owner",
"leaf": {
"users": {
"users": [
"user:bob"
]
}
}
}
}
}
const { OpenFgaClient, ConsistencyPreference } = require('@openfga/sdk');
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Set to the authorization model ID for this request.
});
async function main() {
const body = {
"relation": "reader",
"object": "document:budget"
};
const response = await fgaClient.expand(body);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});package main
import (
"context"
"os"
. "github.com/openfga/go-sdk/client"
)
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"),
StoreId: os.Getenv("FGA_STORE_ID"),
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Set to the authorization model ID for this request.
})
if err != nil {
panic(err)
}
body := ClientExpandRequest{
Relation: "reader",
Object: "document:budget",
}
response, err := fgaClient.Expand(context.Background()).Body(body).Execute()
if err != nil {
panic(err)
}
_ = response
}using System.Collections.Generic;
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
var fgaClient = new OpenFgaClient(new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Set to the authorization model ID for this request.
});
var body = new ClientExpandRequest {
Relation = "reader",
Object = "document:budget",
};
var response = await fgaClient.Expand(body);import asyncio
import os
from openfga_sdk.client import OpenFgaClient, ClientConfiguration
from openfga_sdk.client.models import ClientExpandRequest
async def main():
configuration = ClientConfiguration(
api_url=os.environ.get("FGA_API_URL"),
store_id=os.environ.get("FGA_STORE_ID"),
authorization_model_id=os.environ.get("FGA_MODEL_ID"), # Set to the authorization model ID for this request.
)
async with OpenFgaClient(configuration) as fga_client:
body = ClientExpandRequest(
relation="reader",
object="document:budget",
)
response = await fga_client.expand(body=body)
asyncio.run(main())import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.api.client.model.*;
import dev.openfga.sdk.api.model.*;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL"))
.storeId(System.getenv("FGA_STORE_ID"))
.authorizationModelId(System.getenv("FGA_MODEL_ID")); // Set to the authorization model ID for this request.
var fgaClient = new OpenFgaClient(config);
var body = new ClientExpandRequest()
.relation("reader")
._object("document:budget");
var response = fgaClient.expand(body).get();
}
}# Set FGA_API_URL to the URL of your OpenFGA server.
# Set FGA_STORE_ID to your store ID.
# Set FGA_MODEL_ID to your authorization model ID.
# These examples use a server with authentication disabled.
# For authenticated servers, see /docs/getting-started/setup-sdk-client.
curl -X POST "$FGA_API_URL/stores/$FGA_STORE_ID/expand" \
-H "content-type: application/json" \
-d '{
"authorization_model_id": "'"$FGA_MODEL_ID"'",
"tuple_key": {
"relation": "reader",
"object": "document:budget"
}
}'{
"tree": {
"root": {
"name": "<string>",
"difference": {
"base": "<unknown>",
"subtract": "<unknown>"
},
"intersection": {
"nodes": [
{}
]
},
"leaf": {
"computed": {
"userset": "<string>"
},
"tupleToUserset": {
"computed": [
{
"userset": "<string>"
}
],
"tupleset": "<string>"
},
"users": {
"users": [
"<string>"
]
}
},
"union": {
"nodes": [
{}
]
}
}
}
}{
"code": "validation_error",
"message": "Generic validation error"
}{
"code": "unauthenticated",
"message": "unauthenticated"
}{
"code": "forbidden",
"message": "the principal is not authorized to perform the action"
}{
"code": "undefined_endpoint",
"message": "Endpoint not enabled"
}{
"code": "10",
"message": "transaction conflict"
}{
"code": "throttled_timeout_error",
"message": "timeout due to throttling on complex request"
}{
"code": "internal_error",
"message": "Internal Server Error"
}Path Parameters
Body
Show child attributes
Show child attributes
"01G5JAVJ41T49E9TT3SKVS7X1J"
Controls the consistency preference for this request. Default value is UNSPECIFIED, which has the same behavior as MINIMIZE_LATENCY.
UNSPECIFIED, MINIMIZE_LATENCY, HIGHER_CONSISTENCY "MINIMIZE_LATENCY"
Show child attributes
Show child attributes
Response
A successful response.
A UsersetTree contains the result of an Expansion.
Show child attributes
Show child attributes
Was this page helpful?
const { OpenFgaClient, ConsistencyPreference } = require('@openfga/sdk');
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL,
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Set to the authorization model ID for this request.
});
async function main() {
const body = {
"relation": "reader",
"object": "document:budget"
};
const response = await fgaClient.expand(body);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});package main
import (
"context"
"os"
. "github.com/openfga/go-sdk/client"
)
func main() {
fgaClient, err := NewSdkClient(&ClientConfiguration{
ApiUrl: os.Getenv("FGA_API_URL"),
StoreId: os.Getenv("FGA_STORE_ID"),
AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // Set to the authorization model ID for this request.
})
if err != nil {
panic(err)
}
body := ClientExpandRequest{
Relation: "reader",
Object: "document:budget",
}
response, err := fgaClient.Expand(context.Background()).Body(body).Execute()
if err != nil {
panic(err)
}
_ = response
}using System.Collections.Generic;
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using Environment = System.Environment;
var fgaClient = new OpenFgaClient(new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Set to the authorization model ID for this request.
});
var body = new ClientExpandRequest {
Relation = "reader",
Object = "document:budget",
};
var response = await fgaClient.Expand(body);import asyncio
import os
from openfga_sdk.client import OpenFgaClient, ClientConfiguration
from openfga_sdk.client.models import ClientExpandRequest
async def main():
configuration = ClientConfiguration(
api_url=os.environ.get("FGA_API_URL"),
store_id=os.environ.get("FGA_STORE_ID"),
authorization_model_id=os.environ.get("FGA_MODEL_ID"), # Set to the authorization model ID for this request.
)
async with OpenFgaClient(configuration) as fga_client:
body = ClientExpandRequest(
relation="reader",
object="document:budget",
)
response = await fga_client.expand(body=body)
asyncio.run(main())import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.api.client.model.*;
import dev.openfga.sdk.api.model.*;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
public class Example {
public static void main(String[] args) throws Exception {
var config = new ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL"))
.storeId(System.getenv("FGA_STORE_ID"))
.authorizationModelId(System.getenv("FGA_MODEL_ID")); // Set to the authorization model ID for this request.
var fgaClient = new OpenFgaClient(config);
var body = new ClientExpandRequest()
.relation("reader")
._object("document:budget");
var response = fgaClient.expand(body).get();
}
}# Set FGA_API_URL to the URL of your OpenFGA server.
# Set FGA_STORE_ID to your store ID.
# Set FGA_MODEL_ID to your authorization model ID.
# These examples use a server with authentication disabled.
# For authenticated servers, see /docs/getting-started/setup-sdk-client.
curl -X POST "$FGA_API_URL/stores/$FGA_STORE_ID/expand" \
-H "content-type: application/json" \
-d '{
"authorization_model_id": "'"$FGA_MODEL_ID"'",
"tuple_key": {
"relation": "reader",
"object": "document:budget"
}
}'{
"tree": {
"root": {
"name": "<string>",
"difference": {
"base": "<unknown>",
"subtract": "<unknown>"
},
"intersection": {
"nodes": [
{}
]
},
"leaf": {
"computed": {
"userset": "<string>"
},
"tupleToUserset": {
"computed": [
{
"userset": "<string>"
}
],
"tupleset": "<string>"
},
"users": {
"users": [
"<string>"
]
}
},
"union": {
"nodes": [
{}
]
}
}
}
}{
"code": "validation_error",
"message": "Generic validation error"
}{
"code": "unauthenticated",
"message": "unauthenticated"
}{
"code": "forbidden",
"message": "the principal is not authorized to perform the action"
}{
"code": "undefined_endpoint",
"message": "Endpoint not enabled"
}{
"code": "10",
"message": "transaction conflict"
}{
"code": "throttled_timeout_error",
"message": "timeout due to throttling on complex request"
}{
"code": "internal_error",
"message": "Internal Server Error"
}