> ## 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.

# Add or delete tuples

> The Write API transactionally updates the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user.

In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored.

The API is not idempotent by default: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it throws an error.

To allow writes when an identical tuple already exists in the database, set `"on_duplicate": "ignore"` on the `writes` object.
To allow deletes when a tuple was already removed from the database, set `"on_missing": "ignore"` on the `deletes` object.
If a Write request contains both idempotent (ignore) and non-idempotent (error) operations, the most restrictive action (error) takes precedence. If a condition fails for a sub-request with an error flag, the entire transaction will be rolled back. This gives developers explicit control over the atomicity of the requests.

The API does not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit.
An `authorization_model_id` may be specified in the body. If it is, model ID is used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID is used.

## Example

### Adding relationships

To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following:
```json
{
  "writes": {
    "tuple_keys": [
      {
        "user": "user:anne",
        "relation": "writer",
        "object": "document:2021-budget"
      }
    ],
    "on_duplicate": "ignore"
  },
  "authorization_model_id": "01G50QVV17PECNVAHX1GG4Y5NC"
}
```
### Removing relationships

To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following:
```json
{
  "deletes": {
    "tuple_keys": [
      {
        "user": "user:bob",
        "relation": "reader",
        "object": "document:2021-budget"
      }
    ],
    "on_missing": "ignore"
  }
}
```




## OpenAPI

````yaml https://raw.githubusercontent.com/openfga/api/refs/heads/main/docs/openapiv3/apidocs.openapi.json post /stores/{store_id}/write
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}/write:
    post:
      tags:
        - Relationship Tuples
      summary: Add or delete tuples
      description: >
        The Write API transactionally updates the tuples for a certain store.
        Tuples and type definitions allow OpenFGA to determine whether a
        relationship exists between an object and an user.


        In the body, `writes` adds new tuples and `deletes` removes existing
        tuples. When deleting a tuple, any `condition` specified with it is
        ignored.


        The API is not idempotent by default: if, later on, you try to add the
        same tuple key (even if the `condition` is different), or if you try to
        delete a non-existing tuple, it throws an error.


        To allow writes when an identical tuple already exists in the database,
        set `"on_duplicate": "ignore"` on the `writes` object.

        To allow deletes when a tuple was already removed from the database, set
        `"on_missing": "ignore"` on the `deletes` object.

        If a Write request contains both idempotent (ignore) and non-idempotent
        (error) operations, the most restrictive action (error) takes
        precedence. If a condition fails for a sub-request with an error flag,
        the entire transaction will be rolled back. This gives developers
        explicit control over the atomicity of the requests.


        The API does not allow you to write tuples such as
        `document:2021-budget#viewer@document:2021-budget#viewer`, because they
        are implicit.

        An `authorization_model_id` may be specified in the body. If it is,
        model ID is used to assert that each written tuple (not deleted) is
        valid for the model specified. If it is not specified, the latest
        authorization model ID is used.


        ## Example


        ### Adding relationships


        To add `user:anne` as a `writer` for `document:2021-budget`, call write
        API with the following:

        ```json

        {
          "writes": {
            "tuple_keys": [
              {
                "user": "user:anne",
                "relation": "writer",
                "object": "document:2021-budget"
              }
            ],
            "on_duplicate": "ignore"
          },
          "authorization_model_id": "01G50QVV17PECNVAHX1GG4Y5NC"
        }

        ```

        ### Removing relationships


        To remove `user:bob` as a `reader` for `document:2021-budget`, call
        write API with the following:

        ```json

        {
          "deletes": {
            "tuple_keys": [
              {
                "user": "user:bob",
                "relation": "reader",
                "object": "document:2021-budget"
              }
            ],
            "on_missing": "ignore"
          }
        }

        ```
      operationId: Write
      parameters:
        - in: path
          name: store_id
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WriteBody'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WriteResponse'
          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,
            ClientWriteRequestOnDuplicateWrites,
            ClientWriteRequestOnMissingDeletes } = 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, // Optional; requests can override this.
            });


            async function main() {
                const body = {
                  "writes": [
                    {
                      "user": "user:anne",
                      "relation": "reader",
                      "object": "document:budget"
                    }
                  ]
                };
                const options = {
                
                };
                const response = await fgaClient.write(body, options);
            }


            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"), // Optional; requests can override this.
                })
                if err != nil {
                    panic(err)
                }
                
                body := ClientWriteRequest{
                  Writes: []ClientTupleKey{
                    ClientTupleKey{
                      User: "user:anne",
                      Relation: "reader",
                      Object: "document:budget",
                    },
                  },
                }
                options := ClientWriteOptions{
                
                }
                data, err := fgaClient.Write(context.Background()).Body(body).Options(options).Execute()
                if err != nil {
                    panic(err)
                }
                _ = data
            }
        - 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"), // Optional; requests can override this.
            });

            var body = new ClientWriteRequest {
              Writes = new List<ClientTupleKey> {
                new ClientTupleKey {
                  User = "user:anne",
                  Relation = "reader",
                  Object = "document:budget",
                },
              },
            };
            var options = new ClientWriteOptions {

            };
            var response = await fgaClient.Write(body, options);
        - lang: python
          label: Python
          source: >-
            import asyncio

            import os

            from openfga_sdk.client import OpenFgaClient, ClientConfiguration

            from openfga_sdk.client.models import ClientWriteRequest,
            ClientTuple

            from openfga_sdk.models import RelationshipCondition

            from openfga_sdk.client.models import ConflictOptions,
            ClientWriteRequestOnDuplicateWrites,
            ClientWriteRequestOnMissingDeletes


            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"), # Optional; requests can override this.
                )
                async with OpenFgaClient(configuration) as fga_client:
                    body = ClientWriteRequest(
                      writes=[
                        ClientTuple(
                          user="user:anne",
                          relation="reader",
                          object="document:budget",
                        )
                      ],
                    )
                    options = {
                    
                    }
                    response = await fga_client.write(body, options)

            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")); // Optional; requests can override this.
                    var fgaClient = new OpenFgaClient(config);
                    
                    var body = new ClientWriteRequest()
                      .writes(java.util.Arrays.asList(new ClientTupleKey()
                        .user("user:anne")
                        .relation("reader")
                        ._object("document:budget")));
                    var options = new ClientWriteOptions();
                    var response = fgaClient.write(body, options).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.

            # 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/write" \
              -H "content-type: application/json" \
              -d '{
              "authorization_model_id": "'"$FGA_MODEL_ID"'",
              "writes": {
                "tuple_keys": [
                  {
                    "user": "user:anne",
                    "relation": "reader",
                    "object": "document:budget"
                  }
                ]
              }
            }'
components:
  schemas:
    WriteBody:
      properties:
        authorization_model_id:
          example: 01G5JAVJ41T49E9TT3SKVS7X1J
          type: string
        deletes:
          $ref: '#/components/schemas/WriteRequestDeletes'
        writes:
          $ref: '#/components/schemas/WriteRequestWrites'
      type: object
    WriteResponse:
      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
    WriteRequestDeletes:
      properties:
        on_missing:
          default: error
          description: >-
            On 'error', the API returns an error when deleting a tuple that does
            not exist. On 'ignore', deletes of non-existent tuples are treated
            as no-ops.
          enum:
            - error
            - ignore
          example: ignore
          type: string
        tuple_keys:
          items:
            allOf:
              - $ref: '#/components/schemas/TupleKeyWithoutCondition'
              - type: object
          minItems: 1
          type: array
      required:
        - tuple_keys
      type: object
    WriteRequestWrites:
      properties:
        on_duplicate:
          default: error
          description: >-
            On 'error' ( or unspecified ), the API returns an error if an
            identical tuple already exists. On 'ignore', identical writes are
            treated as no-ops (matching on user, relation, object, and
            RelationshipCondition).
          enum:
            - error
            - ignore
          example: ignore
          type: string
        tuple_keys:
          items:
            allOf:
              - $ref: '#/components/schemas/TupleKey'
              - type: object
          minItems: 1
          type: array
      required:
        - tuple_keys
      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
    TupleKeyWithoutCondition:
      properties:
        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
    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
    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

````