POST
/
v1
/
tenants
/
{tenant_id}
/
data
/
write

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.

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

Path:

 POST /v1/tenants/{tenant_id}/data/write

Content

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:

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",
            },
        }
    },
})

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.

As you noticed, the attribute tuple syntax differs from the relationship syntax, structured similarly as: entity $ attribute | value

// 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,
        },
    },
})

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

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
// 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,
        },
    },
})

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

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,

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

Path Parameters

tenant_id
string
required

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.

Body

application/json

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.

metadata
object

DataWriteRequestMetadata defines the structure of metadata for a write request. It includes the schema version of the data to be written.

tuples
object[]

tuples contains the list of tuples (entity-relation-entity triples) that need to be written.

attributes
object[]

attributes contains the list of attributes (entity-attribute-value triples) that need to be written.

Response

200 - application/json

DataWriteResponse defines the structure of the response after writing data. It contains the snap_token generated after the write operation.

snap_token
string

The snap token to avoid stale cache, see more details on Snap Tokens.