Arke
BuildEntities

Update Entities

How to update entities with deep merge semantics, relationship upserts, and CAS concurrency control.

Basic Update

PUT /entities/{id}
Content-Type: application/json
Authorization: Bearer <token>

{
  "expect_tip": "bafyreig...",
  "properties": {
    "description": "Call me Ishmael. Some years ago—never mind how long precisely..."
  }
}

The expect_tip field is required and must match the entity's current tip CID. If another update occurred since you last read the entity, you'll get a 409 Conflict.


Properties: Deep Merge

Properties use deep merge semantics. Nested objects merge recursively rather than being replaced.

Example: Nested Merge

// Existing properties:
{ "metadata": { "author": "Melville", "year": 1851 } }

// Update request:
{
  "expect_tip": "bafyreig...",
  "properties": {
    "metadata": { "genre": "Novel" }
  }
}

// Result:
{ "metadata": { "author": "Melville", "year": 1851, "genre": "Novel" } }

Merge Rules

Source TypeBehavior
Plain objectMerges recursively with target
ArrayReplaces target array (no array merging)
Primitive (string, number, boolean, null)Replaces target value
undefinedSkipped (keeps target value)

Arrays Replace, Not Merge

Arrays have ambiguous merge semantics (append? replace items? dedupe?), so they always replace:

// Existing: { "tags": ["whale", "sea"] }
// Update:   { "properties": { "tags": ["classic"] } }
// Result:   { "tags": ["classic"] }

Removing Properties

Use properties_remove to delete specific keys at any nesting depth.

Remove Top-Level Keys

{
  "expect_tip": "bafyreig...",
  "properties_remove": ["deprecated_field", "old_field"]
}

Remove Nested Keys

Use a nested object structure to target deep keys:

{
  "expect_tip": "bafyreig...",
  "properties_remove": {
    "settings": {
      "notifications": ["email", "sms"]
    }
  }
}

// Before: { "settings": { "notifications": { "email": true, "sms": true, "push": true } } }
// After:  { "settings": { "notifications": { "push": true } } }

Dot Notation Not Supported

Important: You cannot use dot notation like ["settings.notifications.email"]. Use the nested object structure instead.

Add and Remove in Same Request

You can add and remove properties in the same update. Removals are applied after merges, so removals win on conflict:

{
  "expect_tip": "bafyreig...",
  "properties": { "new_field": "value" },
  "properties_remove": ["old_field"]
}

Relationships: Upsert Semantics

Relationships use upsert semantics. Adding a relationship that already exists (same predicate + peer) merges its properties rather than creating a duplicate.

Adding Relationships

{
  "expect_tip": "bafyreig...",
  "relationships_add": [
    {
      "predicate": "references",
      "peer": "01KFNR0Z394A878Y5AQ63MQEM2",
      "peer_type": "file",
      "peer_label": "moby-dick.txt"
    }
  ]
}

Upsert: Updating Existing Relationships

If a relationship already exists, properties are deep-merged:

// Existing relationship:
{ "predicate": "editor", "peer": "01JUSER...", "properties": { "since": "2024-01" } }

// Update request:
{
  "relationships_add": [{
    "predicate": "editor",
    "peer": "01JUSER...",
    "properties": { "expires_at": "2025-12-31" }
  }]
}

// Result:
{ "predicate": "editor", "peer": "01JUSER...", "properties": { "since": "2024-01", "expires_at": "2025-12-31" } }

Updating peer_label and peer_type

When upserting, you can also update peer_label and peer_type:

{
  "relationships_add": [{
    "predicate": "author",
    "peer": "01JUSER...",
    "peer_label": "Herman Melville"  // Updates the stored label
  }]
}

Removing Relationship Properties

Use properties_remove within a relationship add to remove specific properties:

{
  "relationships_add": [{
    "predicate": "editor",
    "peer": "01JUSER...",
    "properties_remove": ["temporary_flag"]
  }]
}

Removing Relationships

Remove Specific Relationship

Specify both predicate and peer:

{
  "expect_tip": "bafyreig...",
  "relationships_remove": [
    { "predicate": "references", "peer": "01KFNR0Z394A878Y5AQ63MQEM2" }
  ]
}

Remove All Relationships with Predicate

Omit peer to remove all relationships with that predicate:

{
  "expect_tip": "bafyreig...",
  "relationships_remove": [
    { "predicate": "references" }
  ]
}

This removes every references relationship, regardless of peer.

Order of Operations

Relationship changes are applied in this order:

  1. Removals first — specified relationships are removed
  2. Additions second — new/updated relationships are added

This means you can remove and re-add a relationship with different properties in one request:

{
  "relationships_remove": [{ "predicate": "role", "peer": "01JUSER..." }],
  "relationships_add": [{ "predicate": "role", "peer": "01JUSER...", "properties": { "level": "admin" } }]
}

Request Fields Summary

FieldTypeDescription
expect_tipstringRequired. Current tip CID for CAS guard
propertiesobjectProperties to deep merge
properties_removestring[] or objectProperties to remove (nested structure supported)
relationships_addarrayRelationships to add/upsert
relationships_removearrayRelationships to remove
notestringVersion note for this update

Conflict Resolution

When you receive a 409 Conflict:

{
  "error": "CAS conflict",
  "message": "Expected tip bafyreig... but found bafyreih...",
  "status": 409
}

Recovery pattern:

  1. Fetch the entity again: GET /entities/{id}
  2. Get the new cid from the response
  3. Reapply your changes to the fresh data
  4. Retry the update with the new CID as expect_tip

For high-concurrency scenarios, use GET /entities/{id}/tip for a lightweight CID-only fetch.


Collection Membership Validation

When adding a collection relationship, the API validates that you have permission to add entities to that collection. You need {type}:create permission on the target collection.

{
  "expect_tip": "bafyreig...",
  "relationships_add": [
    { "predicate": "collection", "peer": "01KCOL...", "peer_type": "collection" }
  ]
}

If you don't have permission, you'll receive a 403 Forbidden error.

On this page