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

# Write Authorization Data

In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database.

Since these attributes and relations are live instances, meaning they can be affected by specific user actions within the application, they can be created/deleted with a simple Permify API call at runtime.

More specifically, the application client should update preferred database about the changes happening in entities or resources that are related to the authorization structure.

If we consider a document system; when some user joins a group that has edit access on some documents, the application side needs to write tuples to keep preferred database up-to-date. Besides, each attribute or relationship should be created according to its authorization model, Permify Schema.

Another example: when one a company executive grant admin role to user (lets say with id = 3) on their organization, application side needs to tell that update to Permify in order to reform that as tuples and store in preferred database.

<Info>
  You can use the `/v1/tenants/{tenant_id}/data/write` endpoint for both creating **relation tuples** and for creating **attribute data**.
</Info>

**Path:**

```javascript theme={null}
 POST /v1/tenants/{tenant_id}/data/write
```

## Content

* [Example Relationship Creation](#example-relationship-creation)
* [Example Attributes Creation](#example-attribute-creation)
* [Creating Attributes and Relationship In Single Request](#creating-attributes-relationships-in-singe-request)
* [Suggested Workflow](#suggested-workflow)
* [Parameters & Properties](#parameters-and-properties)

### Example Relationship Creation

Let's create an example relation tuple. If user:3 has been granted an admin role in organization:1, relational tuple `organization:1#admin@user:3` should be created as follows:

<Tabs>
  <Tab title="Go">
    ```go theme={null}
    rr, err: = client.Data.Write(context.Background(), & v1.DataWriteRequest {
        TenantId: "t1",
        Metadata: &v1.DataWriteRequestMetadata {
            SchemaVersion: ""
        },
        Tuples: [] * v1.Tuple {
            {
                Entity: & v1.Entity {
                    Type: "organization",
                    Id: "1",
                },
                Relation: "admin",
                Subject: & v1.Subject {
                    Type: "user",
                    Id: "3",
                },
            }
        },
    })
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    client.data
      .write({
        tenantId: "t1",
        metadata: {
          schemaVersion: "",
        },
        tuples: [
          {
            entity: {
              type: "organization",
              id: "1",
            },
            relation: "admin",
            subject: {
              type: "user",
              id: "3",
            },
          },
        ],
      })
      .then((response) => {
        // handle response
      });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    with permify.ApiClient(configuration) as api_client:
        api_instance = permify.DataApi(api_client)

        body = permify.DataWriteRequest(
            tenant_id='t1',  
            metadata={"schemaVersion": ""},
            tuples=[{
                "entity": {
                    "type": "organization",
                    "id": "1",
                },
                "relation": "admin",
                "subject": {
                    "type": "user",
                    "id": "3",
                },
            }]
        )
    ```
  </Tab>

  <Tab title="cURL">
    ```curl theme={null}
    curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "metadata": {
            "schema_version": ""
        },
        "tuples": [
            {
            "entity": {
                "type": "organization",
                "id": "1"
            },
            "relation": "admin",
            "subject":{
                "type": "user",
                "id": "3",
                "relation": ""
            }
        }
        ]
    }'
    ```
  </Tab>
</Tabs>

### Example Attribute Creation

You can use `attributes` argument to create attribute/attributes with a single API call, similarly creating a `relational tuple`.

Let's say **document:1** is a **private (boolean)** document, that only specific users have view access - `document:1$is_private|boolean:true`.

<Info>
  As you noticed, the attribute tuple syntax differs from the relationship syntax, structured similarly as:
  `entity $ attribute | value`
</Info>

<Tabs>
  <Tab title="Go">
    ```go theme={null}
    // Convert the wrapped attribute value into Any proto message
    value, err := anypb.New(&v1.BooleanValue{
        Data: true,
    })
    if err != nil {
    	// Handle error
    }

    cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{
        TenantId: "t1",,
        Metadata: &v1.DataWriteRequestMetadata{
            SchemaVersion: "",
        },
        Attributes: []*v1.Attribute{
            {
                Entity: &v1.Entity{
                    Type: "document",
                    Id:   "1",
                },
                Attribute: "is_private",
                Value:     value,
            },
        },
    })
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    const booleanValue = BooleanValue.fromJSON({ data: true });

    const value = Any.fromJSON({
        typeUrl: 'type.googleapis.com/base.v1.BooleanValue',
        value: BooleanValue.encode(booleanValue).finish()
    });

    client.data.write({
        tenantId: "t1",
        metadata: {
            schemaVersion: ""
        },
        attributes: [{
            entity: {
                type: "document",
                id: "1"
            },
            attribute: "is_private",
            value: value,
        }]
    }).then((response) => {
        // handle response
    })
    ```
  </Tab>

  <Tab title="Python">
    ```
    boolean_value = BooleanValue.from_json({"data": True})

    value = Any.from_json({
        "typeUrl": 'type.googleapis.com/base.v1.BooleanValue',
        "value": BooleanValue.encode(boolean_value).finish()
    })

    with permify.ApiClient(configuration) as api_client:
        api_instance = permify.DataApi(api_client)
        tenant_id = 't1'

        body = permify.DataWriteRequest(
            tenant_id=tenant_id,
            metadata={"schemaVersion": ""},
            attributes=[{
                "entity": {
                    "type": "document",
                    "id": "1"
                },
                "attribute": "is_private",
                "value": value,
            }]
        )
    ```
  </Tab>

  <Tab title="cURL">
    ```curl theme={null}
    curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \
    --header 'Content-Type: application/json' \
    --data-raw '{
    {
        "metadata": {
            "schema_version": ""
        },
        "attributes": [
            {
                "entity": {
                    "type": "document",
                    "id": "1"
                },
                "attribute": "is_private",
                "value": {
                    "@type": "type.googleapis.com/base.v1.BooleanValue",
                    "data": true
                }
            }
        ]
    }
    }'
    ```
  </Tab>
</Tabs>

<Warning>
  **value** field is mandatory on attribute data creation!

  Here are the available attribute value types:

  * **type.googleapis.com/base.v1.StringValue**
  * **type.googleapis.com/base.v1.BooleanValue**
  * **type.googleapis.com/base.v1.IntegerValue**
  * **type.googleapis.com/base.v1.DoubleValue**
  * **type.googleapis.com/base.v1.StringArrayValue**
  * **type.googleapis.com/base.v1.BooleanArrayValue**
  * **type.googleapis.com/base.v1.IntegerArrayValue**
  * **type.googleapis.com/base.v1.DoubleArrayValue**
</Warning>

### Creating Attributes and Relationship In Single Request

Assume we want to both create relational tuple and attribute within in single request. Specifically we want to create following tuples,

* `document:1#editor@user:1`
* `document:1$is_private|boolean:true`

<Tabs>
  <Tab title="Go">
    ```go theme={null}
    // Convert the wrapped attribute value into Any proto message
    value, err := anypb.New(&v1.BooleanValue{
        Data: true,
    })
    if err != nil {
    	// Handle error
    }

    cr, err := client.Data.Write(context.Background(), &v1.DataWriteRequest{
        TenantId: "t1",,
        Metadata: &v1.DataWriteRequestMetadata{
            SchemaVersion: "",
        },
    	Tuples: []*v1.Attribute{
            {
                Entity: &v1.Entity{
                    Type: "document",
                    Id:   "1",
                },
                Relation: "editor",
                Subject:  &v1.Subject{
    		        Type: "user",
    		        Id:   "1",
    				Relation: "",
    			},
            },
        },
        Attributes: []*v1.Attribute{
            {
                Entity: &v1.Entity{
                    Type: "document",
                    Id:   "1",
                },
                Attribute: "is_private",
                Value:     value,
            },
        },
    })
    ```
  </Tab>

  <Tab title="Node">
    ```javascript theme={null}
    const booleanValue = BooleanValue.fromJSON({ data: true });

    const value = Any.fromJSON({
        typeUrl: 'type.googleapis.com/base.v1.BooleanValue',
        value: BooleanValue.encode(booleanValue).finish()
    });

    client.data.write({
        tenantId: "t1",
        metadata: {
            schemaVersion: ""
        },
        tuples: [{
            entity: {
                type: "document",
                id: "1"
            },
            relation: "editor",
            subject: {
                type: "user",
                id: "1"
            }
        }],
        attributes: [{
            entity: {
                type: "document",
                id: "1"
            },
            attribute: "is_private",
            value: value,
        }]
    }).then((response) => {
        // handle response
    })
    ```
  </Tab>

  <Tab title="Python">
    ````
    boolean_value = BooleanValue.from_json({"data": True})

    value = Any.from_json({
        "typeUrl": 'type.googleapis.com/base.v1.BooleanValue',
        "value": BooleanValue.encode(boolean_value).finish()
    })

    with permify.ApiClient(configuration) as api_client:
        api_instance = permify.DataApi(api_client)
        tenant_id = 't1'

        body = permify.DataWriteRequest(
            tenant_id=tenant_id,
            metadata={"schemaVersion": ""},
            tuples=[{
                "entity": {
                    "type": "document",
                    "id": "1"
                },
                "relation": "editor",
                "subject": {
                    "type": "user",
                    "id": "1"
                },
            }],
            attributes=[{
                "entity": {
                    "type": "document",
                    "id": "1"
                },
                "attribute": "is_private",
                "value": value,
            }]
        )

    </Tab>
    <Tab title="cURL">

    ```curl
    curl --location --request POST 'localhost:3476/v1/tenants/{tenant_id}/data/write' \
    --header 'Content-Type: application/json' \
    --data-raw '{
    {
        "metadata": {
            "schema_version": ""
        },
        "tuples": [
          {
            "entity": {
              "type": "document",
              "id": "1"
            },
            "relation": "editor",
            "subject": {
              "type": "user",
              "id": "1"
            }
        }
        ],
        "attributes": [
            {
                "entity": {
                    "type": "document",
                    "id": "1"
                },
                "attribute": "is_private",
                "value": {
                    "@type": "type.googleapis.com/base.v1.BooleanValue",
                    "data": true
                }
            }
        ]
    }
    }'
    ````
  </Tab>
</Tabs>

### Suggested Workflow

The most of the data that should written in Permify also needs to be write or engage with applications database as well. So where and how to write relationships into both applications database and Permify ?

#### Two Phase Commit Approach

In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as storing the owner of the document when an user creates a document.

To give more concurrent example of this action, let's take a look at below createDocument function

```go theme={null}
func CreateDocuments(db *gorm.DB) error {

  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
      // if transaction fails, then delete malformed relation tuple
      permify.DeleteData(...)
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(docs).Error; err != nil {
     tx.Rollback()
     // if transaction fails, then delete malformed relation tuple
     permify.DeleteData(...)
     return err
  }

  // if transaction successful, write relation tuple to Permify
  permify.WriteData(...)

  return tx.Commit().Error
}
```

The key point to take way from above approach is if the transaction fails for any reason, the relation will also be deleted from Permify to provide maximum consistency.

#### Data That Not Stored In Application Database

Although ownership generally stored in application databases, there are some data that not needed to be stored in your actual database. Such as defining organizational roles, group members, project editors etc.

For example, you can model a simple project management authorization in Permify as follows,

```perm theme={null}
entity user {}

entity team {

    relation owner @user
    relation member @user
}

entity project {

    relation team @team
    relation owner @user

    action view = team.member or team.owner or project.owner
    action edit = project.owner or team.owner
    action delete = project.owner or team.owner

}
```

This **team member** relation won't need to be stored in the application database. Storing it only in Permify - preferred database - is enough. In that situation, `WriteData` can be performed in any logical place in your stack.

### Parameters & Properties


## OpenAPI

````yaml post /v1/tenants/{tenant_id}/data/write
openapi: 3.0.0
info:
  title: Permify API
  description: >-
    Permify is an open source authorization service for creating fine-grained
    and scalable authorization systems.
  version: v1.6.10
  contact:
    name: API Support
    url: https://github.com/Permify/permify/issues
    email: hello@permify.co
  license:
    name: AGPL-3.0 license
    url: https://github.com/Permify/permify/blob/master/LICENSE
servers: []
security: []
tags:
  - name: Permission
  - name: Watch
  - name: Schema
  - name: Data
  - name: Bundle
  - name: Tenancy
paths:
  /v1/tenants/{tenant_id}/data/write:
    post:
      tags:
        - Data
      summary: write data
      operationId: data.write
      parameters:
        - name: tenant_id
          description: >-
            Identifier of the tenant, if you are not using multi-tenancy (have
            only one tenant) use pre-inserted tenant <code>t1</code> for this
            field. Required, and must match the pattern \“[a-zA-Z0-9-,]+\“, max
            64 bytes.
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Data.WriteBody'
        required: true
      responses:
        '200':
          description: A successful response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataWriteResponse'
        default:
          description: An unexpected error response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
      x-codeSamples:
        - label: go
          lang: go
          source: >-
            // Convert the wrapped attribute value into Any proto message

            value, err := anypb.New(&v1.BooleanValue{
                Data: true,
            })

            if err != nil {
                // Handle error
            }


            cr, err := client.Data.Write(context.Background(),
            &v1.DataWriteRequest{
                TenantId: "t1",
                Metadata: &v1.DataWriteRequestMetadata{
                    SchemaVersion: "",
                },
                Tuples: []*v1.Tuple{
                    {
                        Entity: &v1.Entity{
                            Type: "document",
                            Id:   "1",
                        },
                        Relation: "editor",
                        Subject:  &v1.Subject{
                            Type: "user",
                            Id:   "1",
                            Relation: "",
                        },
                    },
                },
                Attributes: []*v1.Attribute{
                    {
                        Entity: &v1.Entity{
                            Type: "document",
                            Id:   "1",
                        },
                        Attribute: "is_private",
                        Value:     value,
                    },
                },
            })
        - label: node
          lang: javascript
          source: |-
            const booleanValue = BooleanValue.fromJSON({ data: true });

            const value = Any.fromJSON({
                typeUrl: 'type.googleapis.com/base.v1.BooleanValue',
                value: BooleanValue.encode(booleanValue).finish()
            });

            client.data.write({
                tenantId: "t1",
                metadata: {
                    schemaVersion: ""
                },
                tuples: [{
                    entity: {
                        type: "document",
                        id: "1"
                    },
                    relation: "editor",
                    subject: {
                        type: "user",
                        id: "1"
                    }
                }],
                attributes: [{
                    entity: {
                        type: "document",
                        id: "1"
                    },
                    attribute: "is_private",
                    value: value,
                }]
            }).then((response) => {
                // handle response
            })
        - label: cURL
          lang: curl
          source: >-
            curl --location --request POST
            'localhost:3476/v1/tenants/{tenant_id}/data/write' \

            --header 'Content-Type: application/json' \

            --data-raw '{
                "metadata": {
                    "schema_version": ""
                },
                "tuples": [
                    {
                        "entity": {
                            "type": "document",
                            "id": "1"
                        },
                        "relation": "editor",
                        "subject": {
                            "type": "user",
                            "id": "1"
                        }
                    }
                ],
                "attributes": [
                    {
                        "entity": {
                            "type": "document",
                            "id": "1"
                        },
                        "attribute": "is_private",
                        "value": {
                            "@type": "type.googleapis.com/base.v1.BooleanValue",
                            "data": true
                        }
                    }
                ]
            }'
components:
  schemas:
    Data.WriteBody:
      type: object
      properties:
        metadata:
          $ref: '#/components/schemas/DataWriteRequestMetadata'
        tuples:
          type: array
          items:
            $ref: '#/components/schemas/Tuple'
          description: >-
            tuples contains the list of tuples (entity-relation-entity triples)
            that need to be written.
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/Attribute'
          description: >-
            attributes contains the list of attributes (entity-attribute-value
            triples) that need to be written.
      description: |-
        DataWriteRequest defines the structure of a request for writing data.
        It contains the necessary information such as tenant_id, metadata,
        tuples and attributes for the write operation.
    DataWriteResponse:
      type: object
      properties:
        snap_token:
          type: string
          description: >-
            The snap token to avoid stale cache, see more details on [Snap
            Tokens](../../operations/snap-tokens).
      description: >-
        DataWriteResponse defines the structure of the response after writing
        data.

        It contains the snap_token generated after the write operation.
    Status:
      type: object
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
        details:
          type: array
          items:
            $ref: '#/components/schemas/Any'
    DataWriteRequestMetadata:
      type: object
      properties:
        schema_version:
          type: string
          description: >-
            schema_version represents the version of the schema for the data
            being written.
      description: >-
        DataWriteRequestMetadata defines the structure of metadata for a write
        request.

        It includes the schema version of the data to be written.
    Tuple:
      type: object
      properties:
        entity:
          $ref: '#/components/schemas/Entity'
        relation:
          type: string
        subject:
          $ref: '#/components/schemas/Subject'
      description: Tuple is a structure that includes an entity, a relation, and a subject.
    Attribute:
      type: object
      properties:
        entity:
          $ref: '#/components/schemas/Entity'
        attribute:
          type: string
          title: Name of the attribute
        value:
          $ref: '#/components/schemas/Any'
      description: >-
        Attribute represents an attribute of an entity with a specific type and
        value.
    Any:
      type: object
      properties:
        '@type':
          type: string
          description: >-
            A URL/resource name that uniquely identifies the type of the
            serialized

            protocol buffer message. This string must contain at least

            one "/" character. The last segment of the URL's path must represent

            the fully qualified name of the type (as in

            `path/google.protobuf.Duration`). The name should be in a canonical
            form

            (e.g., leading "." is not accepted).


            In practice, teams usually precompile into the binary all types that
            they

            expect it to use in the context of Any. However, for URLs which use
            the

            scheme `http`, `https`, or no scheme, one can optionally set up a
            type

            server that maps type URLs to message definitions as follows:


            * If no scheme is provided, `https` is assumed.

            * An HTTP GET on the URL must yield a [google.protobuf.Type][]
              value in binary format, or produce an error.
            * Applications are allowed to cache lookup results based on the
              URL, or have them precompiled into a binary to avoid any
              lookup. Therefore, binary compatibility needs to be preserved
              on changes to types. (Use versioned type names to manage
              breaking changes.)

            Note: this functionality is not currently available in the official

            protobuf release, and it is not used for type URLs beginning with

            type.googleapis.com. As of May 2023, there are no widely used type
            server

            implementations and no plans to implement one.


            Schemes other than `http`, `https` (or the empty scheme) might be

            used with implementation specific semantics.
      additionalProperties: {}
      description: >-
        `Any` contains an arbitrary serialized protocol buffer message along
        with a

        URL that describes the type of the serialized message.


        Protobuf library provides support to pack/unpack Any values in the form

        of utility functions or additional generated methods of the Any type.


        Example 1: Pack and unpack a message in C++.

            Foo foo = ...;
            Any any;
            any.PackFrom(foo);
            ...
            if (any.UnpackTo(&foo)) {
              ...
            }

        Example 2: Pack and unpack a message in Java.

            Foo foo = ...;
            Any any = Any.pack(foo);
            ...
            if (any.is(Foo.class)) {
              foo = any.unpack(Foo.class);
            }
            // or ...
            if (any.isSameTypeAs(Foo.getDefaultInstance())) {
              foo = any.unpack(Foo.getDefaultInstance());
            }

         Example 3: Pack and unpack a message in Python.

            foo = Foo(...)
            any = Any()
            any.Pack(foo)
            ...
            if any.Is(Foo.DESCRIPTOR):
              any.Unpack(foo)
              ...

         Example 4: Pack and unpack a message in Go

             foo := &pb.Foo{...}
             any, err := anypb.New(foo)
             if err != nil {
               ...
             }
             ...
             foo := &pb.Foo{}
             if err := any.UnmarshalTo(foo); err != nil {
               ...
             }

        The pack methods provided by protobuf library will by default use

        'type.googleapis.com/full.type.name' as the type URL and the unpack

        methods only use the fully qualified type name after the last '/'

        in the type URL, for example "foo.bar.com/x/y.z" will yield type

        name "y.z".


        JSON

        ====

        The JSON representation of an `Any` value uses the regular

        representation of the deserialized, embedded message, with an

        additional field `@type` which contains the type URL. Example:

            package google.profile;
            message Person {
              string first_name = 1;
              string last_name = 2;
            }

            {
              "@type": "type.googleapis.com/google.profile.Person",
              "firstName": <string>,
              "lastName": <string>
            }

        If the embedded message type is well-known and has a custom JSON

        representation, that representation will be embedded adding a field

        `value` which holds the custom JSON in addition to the `@type`

        field. Example (for message [google.protobuf.Duration][]):

            {
              "@type": "type.googleapis.com/google.protobuf.Duration",
              "value": "1.212s"
            }
    Entity:
      type: object
      properties:
        type:
          type: string
        id:
          type: string
      description: Entity represents an entity with a type and an identifier.
    Subject:
      type: object
      properties:
        type:
          type: string
        id:
          type: string
        relation:
          type: string
      description: >-
        Subject represents an entity subject with a type, an identifier, and a
        relation.

````