Skip to main content
Delegations allow you to grant other users or services access to your space. They enable secure, scoped, and time-limited sharing of data and capabilities.

What Delegations Enable

  • Share data: Let other users read or write to paths in your space
  • Grant permissions: Allow services to perform actions on your behalf
  • Chain access: Recipients can create sub-delegations to others
  • Time-bound access: Delegations automatically expire

Creating a Delegation

Grant another user access to your space.
Critical: Always use pkhDid (PKH DID format) for the delegateDID parameter, NOT the did property. Using the wrong DID format will cause server validation to fail.
// Alice grants Bob read access to her documents
const delegation = await tc.storage.delegate({
  delegateDID: bobPkhDid, // Use PKH DID!
  actions: ['read'],
  paths: ['documents/*'],
  expiresIn: '7d',
});

// Send delegation to Bob
console.log(delegation.portable); // Serialized for transport
// Grant full access to a specific path
const delegation = await tc.createDelegation({
  delegateDID: recipient.pkhDid,
  actions: ['read', 'write', 'delete'],
  paths: ['shared/*'],
});

Using a Received Delegation

When you receive a delegation, use it to access another user’s space.
// Bob received a delegation from Alice
const portableDelegation = /* received from Alice */;

// Load the delegation
await tc.storage.useDelegation(portableDelegation);

// Now Bob can access Alice's documents
const docs = await tc.storage.list('documents/', {
  space: aliceSpaceId,
});

Sub-Delegations

Recipients can further delegate their access to others, with constraints.
// Bob creates a sub-delegation for Carol
// Bob can only delegate permissions he has
const subDelegation = await tc.storage.delegate({
  delegateDID: carolPkhDid,
  actions: ['read'], // Must be subset of Bob's actions
  paths: ['documents/public/*'], // Must be within Bob's paths
  expiresIn: '1d', // Must expire before Bob's delegation
});

Delegation Constraints

Delegations must follow these rules:
ConstraintRule
ActionsMust be a subset of parent’s actions
PathsMust be within parent’s path scope
ExpiryMust expire at or before parent’s expiry
Not BeforeMust be at or after parent’s not_before
// Parent delegation: read + write to 'data/*', expires in 7 days

// Valid sub-delegation
await tc.createSubDelegation({
  parentDelegation,
  delegateDID: recipient.pkhDid,
  actions: ['read'],        // Subset of [read, write]
  paths: ['data/public/*'], // Within data/*
  expiresIn: '3d',          // Before 7 days
});

// Invalid - would be rejected
await tc.createSubDelegation({
  parentDelegation,
  delegateDID: recipient.pkhDid,
  actions: ['read', 'delete'], // 'delete' not in parent
  paths: ['other/*'],          // Outside parent scope
  expiresIn: '30d',            // After parent expires
});

PortableDelegation Format

Delegations are serialized as PortableDelegation for transport between users.
// Create delegation
const delegation = await tc.createDelegation({
  delegateDID: recipient.pkhDid,
  actions: ['read'],
  paths: ['shared/*'],
});

// Serialize for transport (JSON string)
const portable = delegation.portable;

// Send via any channel: API, email, QR code, etc.
sendToRecipient(portable);

// Recipient loads it
const received = await tc.useDelegation(portable);
The portable format is a JSON string containing the signed delegation chain, which can be verified cryptographically by the server.

DID Formats

Understanding DID formats is critical for delegations to work correctly.
TinyCloud uses two DID formats:
FormatPropertyExampleUse Case
PKH DIDpkhDiddid:pkh:eip155:1:0x1234...Delegations, user identity
Key DIDdiddid:key:z6Mk...#z6Mk...Internal session keys
// Correct - use PKH DID for delegations
await tc.createDelegation({
  delegateDID: recipient.pkhDid,  // did:pkh:eip155:1:0x...
});

// Wrong - session key DID will fail
await tc.createDelegation({
  delegateDID: recipient.did,     // did:key:z6Mk...
});

Complete Example

Alice shares a document folder with Bob, who then shares a subset with Carol.
1

Alice creates delegation for Bob

// Alice's client
const aliceToBob = await alice.createDelegation({
  delegateDID: bob.pkhDid,
  actions: ['read', 'write'],
  paths: ['documents/*'],
  expiresIn: '30d',
});

// Send to Bob
sendToUser(bob.address, aliceToBob.portable);
2

Bob receives and uses the delegation

// Bob's client
const received = await bob.useDelegation(aliceToBobPortable);

// Bob can now read Alice's documents
const files = await bob.kv.list('documents/', {
  space: alice.spaceId,
});
3

Bob creates sub-delegation for Carol

// Bob sub-delegates read-only access to Carol
const bobToCarol = await bob.createSubDelegation({
  parentDelegation: received,
  delegateDID: carol.pkhDid,
  actions: ['read'], // Only read, not write
  paths: ['documents/public/*'], // Narrower scope
  expiresIn: '7d', // Shorter duration
});

sendToUser(carol.address, bobToCarol.portable);
4

Carol accesses the data

// Carol's client
await carol.useDelegation(bobToCarolPortable);

// Carol can read Alice's public documents
const doc = await carol.kv.get('documents/public/readme.txt', {
  space: alice.spaceId,
});

Revoking Delegations

Currently, delegations expire automatically based on their expiresIn value. For immediate revocation, use short-lived delegations and refresh them as needed.
// Short-lived delegation that must be refreshed
const delegation = await tc.createDelegation({
  delegateDID: recipient.pkhDid,
  actions: ['read'],
  paths: ['shared/*'],
  expiresIn: '1h', // Expires in 1 hour
});

// To "revoke", simply don't issue a new delegation