> ## Documentation Index
> Fetch the complete documentation index at: https://openfga.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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:
```json
{
  "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.
```json
{
  "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
```python
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
```json
[{
    "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:

```json
{
  "tuple_key": {
    "object": "document:1",
    "relation": "writer"
  },
  "contextual_tuples": {
    "tuple_keys": [
      {
        "user": "folder:1",
        "relation": "parent",
        "object": "document:1"
      }
    ]
  }
}
```
this returns:
```json
{
  "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`
```json
{
  "tuple_key": {
    "object": "folder:1",
    "relation": "owner"
  }
}
```
which gives
```json
{
  "tree": {
    "root": {
      "name": "folder:1#owner",
      "leaf": {
        "users": {
          "users": [
            "user:bob"
          ]
        }
      }
    }
  }
}
```




## OpenAPI

````yaml https://raw.githubusercontent.com/openfga/api/refs/heads/main/docs/openapiv3/apidocs.openapi.json post /stores/{store_id}/expand
openapi: 3.0.3
info:
  contact:
    email: community@openfga.dev
    name: OpenFGA
    url: https://openfga.dev
  description: >-
    A high performance and flexible authorization/permission engine built for
    developers and inspired by Google Zanzibar.
  license:
    name: Apache-2.0
    url: https://github.com/openfga/openfga/blob/main/LICENSE
  title: OpenFGA
  version: 1.x
servers: []
security: []
tags:
  - name: AuthZenService
  - name: OpenFGAService
paths:
  /stores/{store_id}/expand:
    post:
      tags:
        - Relationship Queries
      summary: Expand relationships in userset tree format
      description: >
        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:

        ```json

        {
          "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.

        ```json

        {
          "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

        ```python

        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

        ```json

        [{
            "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:


        ```json

        {
          "tuple_key": {
            "object": "document:1",
            "relation": "writer"
          },
          "contextual_tuples": {
            "tuple_keys": [
              {
                "user": "folder:1",
                "relation": "parent",
                "object": "document:1"
              }
            ]
          }
        }

        ```

        this returns:

        ```json

        {
          "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`

        ```json

        {
          "tuple_key": {
            "object": "folder:1",
            "relation": "owner"
          }
        }

        ```

        which gives

        ```json

        {
          "tree": {
            "root": {
              "name": "folder:1#owner",
              "leaf": {
                "users": {
                  "users": [
                    "user:bob"
                  ]
                }
              }
            }
          }
        }

        ```
      operationId: Expand
      parameters:
        - in: path
          name: store_id
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExpandBody'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpandResponse'
          description: A successful response.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorMessageResponse'
          description: Request failed due to invalid input.
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthenticatedResponse'
          description: Not authenticated.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ForbiddenResponse'
          description: Forbidden.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PathUnknownErrorMessageResponse'
          description: Request failed due to incorrect path.
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbortedMessageResponse'
          description: Request was aborted due a transaction conflict.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnprocessableContentMessageResponse'
          description: Request timed out due to excessive request throttling.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InternalErrorMessageResponse'
          description: Request failed due to internal server error.
      x-codeSamples:
        - lang: node
          label: Node.js
          source: >-
            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;
            });
        - lang: go
          label: Go
          source: |-
            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
            }
        - lang: dotnet
          label: .NET
          source: |-
            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);
        - lang: python
          label: Python
          source: |-
            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())
        - lang: java
          label: Java
          source: |-
            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();
                }
            }
        - lang: bash
          label: curl
          source: >-
            # 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"
              }
            }'
components:
  schemas:
    ExpandBody:
      properties:
        authorization_model_id:
          example: 01G5JAVJ41T49E9TT3SKVS7X1J
          type: string
        consistency:
          allOf:
            - $ref: '#/components/schemas/ConsistencyPreference'
            - description: >-
                Controls the consistency preference for this request. Default
                value is `UNSPECIFIED`, which has the same behavior as
                `MINIMIZE_LATENCY`.
        contextual_tuples:
          $ref: '#/components/schemas/ContextualTupleKeys'
        tuple_key:
          $ref: '#/components/schemas/ExpandRequestTupleKey'
      required:
        - tuple_key
      type: object
    ExpandResponse:
      properties:
        tree:
          $ref: '#/components/schemas/UsersetTree'
      type: object
    ValidationErrorMessageResponse:
      example:
        code: validation_error
        message: Generic validation error
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
      type: object
    UnauthenticatedResponse:
      example:
        code: unauthenticated
        message: unauthenticated
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
      type: object
    ForbiddenResponse:
      example:
        code: forbidden
        message: the principal is not authorized to perform the action
      properties:
        code:
          $ref: '#/components/schemas/AuthErrorCode'
        message:
          type: string
      type: object
    PathUnknownErrorMessageResponse:
      example:
        code: undefined_endpoint
        message: Endpoint not enabled
      properties:
        code:
          $ref: '#/components/schemas/NotFoundErrorCode'
        message:
          type: string
      type: object
    AbortedMessageResponse:
      example:
        code: '10'
        message: transaction conflict
      properties:
        code:
          type: string
        message:
          type: string
      type: object
    UnprocessableContentMessageResponse:
      example:
        code: throttled_timeout_error
        message: timeout due to throttling on complex request
      properties:
        code:
          $ref: '#/components/schemas/UnprocessableContentErrorCode'
        message:
          type: string
      type: object
    InternalErrorMessageResponse:
      example:
        code: internal_error
        message: Internal Server Error
      properties:
        code:
          $ref: '#/components/schemas/InternalErrorCode'
        message:
          type: string
      type: object
    ConsistencyPreference:
      default: UNSPECIFIED
      description: |-
        Controls the consistency preferences when calling the query APIs.

         - UNSPECIFIED: Default if not set. Behavior will be the same as MINIMIZE_LATENCY.
         - MINIMIZE_LATENCY: Minimize latency at the potential expense of lower consistency.
         - HIGHER_CONSISTENCY: Prefer higher consistency, at the potential expense of increased latency.
      enum:
        - UNSPECIFIED
        - MINIMIZE_LATENCY
        - HIGHER_CONSISTENCY
      example: MINIMIZE_LATENCY
      type: string
    ContextualTupleKeys:
      properties:
        tuple_keys:
          items:
            allOf:
              - $ref: '#/components/schemas/TupleKey'
              - type: object
          maxItems: 100
          type: array
      required:
        - tuple_keys
      type: object
    ExpandRequestTupleKey:
      properties:
        object:
          example: document:2021-budget
          maxLength: 256
          type: string
        relation:
          example: reader
          maxLength: 50
          type: string
      required:
        - relation
        - object
      type: object
    UsersetTree:
      description: A UsersetTree contains the result of an Expansion.
      properties:
        root:
          $ref: '#/components/schemas/Node'
      type: object
    ErrorCode:
      default: no_error
      enum:
        - no_error
        - validation_error
        - authorization_model_not_found
        - authorization_model_resolution_too_complex
        - invalid_write_input
        - cannot_allow_duplicate_tuples_in_one_request
        - cannot_allow_duplicate_types_in_one_request
        - cannot_allow_multiple_references_to_one_relation
        - invalid_continuation_token
        - invalid_tuple_set
        - invalid_check_input
        - invalid_expand_input
        - unsupported_user_set
        - invalid_object_format
        - write_failed_due_to_invalid_input
        - authorization_model_assertions_not_found
        - latest_authorization_model_not_found
        - type_not_found
        - relation_not_found
        - empty_relation_definition
        - invalid_user
        - invalid_tuple
        - unknown_relation
        - store_id_invalid_length
        - assertions_too_many_items
        - id_too_long
        - authorization_model_id_too_long
        - tuple_key_value_not_specified
        - tuple_keys_too_many_or_too_few_items
        - page_size_invalid
        - param_missing_value
        - difference_base_missing_value
        - subtract_base_missing_value
        - object_too_long
        - relation_too_long
        - type_definitions_too_few_items
        - type_invalid_length
        - type_invalid_pattern
        - relations_too_few_items
        - relations_too_long
        - relations_invalid_pattern
        - object_invalid_pattern
        - query_string_type_continuation_token_mismatch
        - exceeded_entity_limit
        - invalid_contextual_tuple
        - duplicate_contextual_tuple
        - invalid_authorization_model
        - unsupported_schema_version
        - cancelled
        - invalid_start_time
      type: string
    AuthErrorCode:
      default: no_auth_error
      enum:
        - no_auth_error
        - auth_failed_invalid_subject
        - auth_failed_invalid_audience
        - auth_failed_invalid_issuer
        - invalid_claims
        - auth_failed_invalid_bearer_token
        - bearer_token_missing
        - unauthenticated
        - forbidden
      type: string
    NotFoundErrorCode:
      default: no_not_found_error
      enum:
        - no_not_found_error
        - undefined_endpoint
        - store_id_not_found
        - unimplemented
      type: string
    UnprocessableContentErrorCode:
      default: no_throttled_error_code
      enum:
        - no_throttled_error_code
        - throttled_timeout_error
      type: string
    InternalErrorCode:
      default: no_internal_error
      enum:
        - no_internal_error
        - internal_error
        - deadline_exceeded
        - already_exists
        - resource_exhausted
        - failed_precondition
        - aborted
        - out_of_range
        - unavailable
        - data_loss
      type: string
    TupleKey:
      properties:
        condition:
          $ref: '#/components/schemas/RelationshipCondition'
        object:
          example: document:2021-budget
          maxLength: 256
          type: string
        relation:
          example: reader
          maxLength: 50
          type: string
        user:
          example: user:anne
          maxLength: 512
          type: string
      required:
        - user
        - relation
        - object
      type: object
    Node:
      properties:
        difference:
          $ref: '#/components/schemas/UsersetTree.Difference'
        intersection:
          $ref: '#/components/schemas/Nodes'
        leaf:
          $ref: '#/components/schemas/Leaf'
        name:
          type: string
        union:
          $ref: '#/components/schemas/Nodes'
      required:
        - name
      type: object
    RelationshipCondition:
      properties:
        context:
          description: >-
            Additional context/data to persist along with the condition.

            The keys must match the parameters defined by the condition, and the
            value types must

            match the parameter type definitions.
          type: object
        name:
          description: >-
            A reference (by name) of the relationship condition defined in the
            authorization model.
          example: condition1
          maxLength: 256
          type: string
      required:
        - name
      type: object
    UsersetTree.Difference:
      properties:
        base:
          $ref: '#/components/schemas/Node'
        subtract:
          $ref: '#/components/schemas/Node'
      required:
        - base
        - subtract
      type: object
    Nodes:
      properties:
        nodes:
          items:
            allOf:
              - $ref: '#/components/schemas/Node'
              - type: object
          type: array
      required:
        - nodes
      type: object
    Leaf:
      description: |-
        A leaf node contains either
        - a set of users (which may be individual users, or usersets
          referencing other relations)
        - a computed node, which is the result of a computed userset
          value in the authorization model
        - a tupleToUserset nodes, containing the result of expanding
          a tupleToUserset value in a authorization model.
      properties:
        computed:
          $ref: '#/components/schemas/Computed'
        tupleToUserset:
          $ref: '#/components/schemas/UsersetTree.TupleToUserset'
        users:
          $ref: '#/components/schemas/Users'
      type: object
    Computed:
      properties:
        userset:
          type: string
      required:
        - userset
      type: object
    UsersetTree.TupleToUserset:
      properties:
        computed:
          items:
            allOf:
              - $ref: '#/components/schemas/Computed'
              - type: object
          type: array
        tupleset:
          type: string
      required:
        - tupleset
        - computed
      type: object
    Users:
      properties:
        users:
          items:
            type: string
          type: array
      required:
        - users
      type: object

````