Skip to main content
Delegations allow you to grant other users or services scoped, time-limited access to data in your space. They are the core mechanism for sharing in TinyCloud.

What Delegations Enable

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

Creating a Delegation

Grant another user access to paths in your space by specifying their DID, the actions they can perform, and the paths they can access.
Critical: For delegations, use the recipient’s primary DID (tc.did after signIn). After signIn, tc.did returns the primary DID — the identity that authenticated. Before signIn, it returns the session key DID. See DID Formats for details.
// Alice delegates read access to Bob
const delegation = await tc.kv.delegate({
  delegateDID: bob.did,  // Bob's primary DID (after signIn)
  actions: ['read'],
  paths: ['documents/*'],
  expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});

// Send the portable delegation to Bob
console.log(delegation.portable);

Using a Received Delegation

When you receive a delegation (as a portable JSON string), load it into your SDK to access the delegator’s space.
// Bob received a portable delegation from Alice
const portableDelegation = /* received from Alice */;

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

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

const reportResult = await tc.kv.get('documents/report.json', {
  space: aliceSpaceId,
});
The recipient does not get a copy of the data. They access it directly in the delegator’s space, subject to the scoped permissions in the delegation.

DelegatedAccess

When using a delegation in the Node SDK, useDelegation returns a DelegatedAccess object. This object provides a scoped KV interface that automatically targets the delegator’s space.
const access = await bob.useDelegation(portableDelegation);

// access.kv is scoped to Alice's space with the delegated permissions
await access.kv.get('shared/document');   // Read from Alice's space
await access.kv.put('shared/response', { message: 'Thanks!' }); // Write to Alice's space
await access.kv.list('shared/');          // List keys in Alice's space

Sub-Delegations

Recipients can further delegate their access to others. Sub-delegations must be strictly narrower than the parent delegation.
// Bob creates a sub-delegation for Carol
const subDelegation = await tc.kv.delegate({
  delegateDID: carol.did,        // Carol's primary DID (after signIn)
  actions: ['read'],             // Must be subset of Bob's actions
  paths: ['documents/public/*'], // Must be within Bob's paths
  expiry: new Date(Date.now() + 24 * 60 * 60 * 1000), // Must expire before Bob's delegation
});

Delegation Constraints

Every sub-delegation must be a strict subset of its parent. The server enforces these rules:
ConstraintRuleExample
ActionsMust be a subset of parent’s actionsParent has get + put, child can have get only
PathsMust be within parent’s path scopeParent has data/*, child can have data/public/*
ExpiryMust expire at or before parent’s expiryParent expires in 7d, child must expire in 7d or less
Not BeforeMust be at or after parent’s not_beforeParent starts Jan 1, child cannot start before Jan 1
// Parent: read + write to 'data/*', expires in 7 days

await bob.createSubDelegation({
  parentDelegation,
  delegateDID: carol.did,         // Carol's primary DID (after signIn)
  actions: ['tinycloud.kv/get'],  // Subset of [get, put]
  path: 'data/public/',           // Within data/*
  expiry: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), // Before 7 days
});
// Accepted by server
// Parent: read + write to 'data/*', expires in 7 days

await bob.createSubDelegation({
  parentDelegation,
  delegateDID: carol.did,
  actions: ['tinycloud.kv/get', 'tinycloud.kv/del'], // 'del' not in parent
  path: 'other/',                                      // Outside parent scope
  expiry: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // After parent expires
});
// Rejected by server

DID Format

This is the most common source of delegation failures. Use the recipient’s primary DID (tc.did after signIn), not the session key DID.
TinyCloud uses two DID types:
TypePropertyExampleWhen to Use
Primary DIDtc.did (after signIn)did:pkh:eip155:1:0xd8dA...Delegations, user identity
Session Key DIDtc.sessionDiddid:key:z6Mk...#z6Mk...Internal session operations
After signIn, tc.did returns the primary DID — the identity that authenticated (e.g., an Ethereum wallet). Before signIn, tc.did returns the session key DID. The primary DID represents the user’s persistent identity, while the session key DID is ephemeral and rotates on every authentication.
// Correct: use tc.did for delegations (after signIn)
await alice.createDelegation({
  delegateDID: bob.did,  // did:pkh:eip155:1:0x... (Bob's primary DID after signIn)
  actions: ['tinycloud.kv/get'],
});

// Wrong: using sessionDid -- will fail server validation
await alice.createDelegation({
  delegateDID: bob.sessionDid,  // did:key:z6Mk... -- session key, not for delegations
  actions: ['tinycloud.kv/get'],
});

PortableDelegation Format

Delegations are serialized as PortableDelegation for transport between users. This is a JSON string containing the signed delegation chain, which can be verified cryptographically.
// Create and serialize
const delegation = await alice.createDelegation({
  delegateDID: bob.did,  // Bob's primary DID (after signIn)
  actions: ['tinycloud.kv/get'],
  path: 'shared/',
});

// The portable format is a JSON string
const portable = delegation.portable;

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

// Recipient loads it
const access = await bob.useDelegation(portable);
The portable delegation is self-contained. The recipient does not need any additional context to verify and use it — the cryptographic chain of trust is embedded in the payload.
For simpler sharing scenarios, TinyCloud provides a sharing links API that generates a URL containing an embedded delegation. The recipient can access the shared data without being authenticated.
// Generate a share link for a specific key
const result = await tc.sharing.generate({
  path: 'profile',
  expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});

if (result.ok) {
  console.log(result.data);
  // tc1:eyJhbGciOi...

  // Recipient opens the link -- no auth needed
  // This is a static method that works without an active session
  const shareResult = await TinyCloudWeb.receiveShare(result.data);
  if (shareResult.ok) {
    console.log(shareResult.data.data);
    // { name: 'Alice', email: '[email protected]' }
  }
}
Sharing links are ideal for one-off sharing with people who may not have a TinyCloud account. For ongoing collaboration, use standard delegations instead.

Revoking Delegations

Currently, delegations expire automatically based on their expiresIn value. For scenarios where you need immediate revocation, use short-lived delegations and refresh them as needed.
// Short-lived delegation that must be refreshed
const delegation = await alice.createDelegation({
  delegateDID: bob.did,  // Bob's primary DID (after signIn)
  actions: ['tinycloud.kv/get'],
  path: 'shared/',
  expiry: new Date(Date.now() + 60 * 60 * 1000), // Expires in 1 hour
});

// To "revoke", simply stop issuing new delegations
// The existing one becomes invalid after 1 hour

Complete Example

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

Alice creates a delegation for Bob

// Alice's client
const aliceToBob = await alice.createDelegation({
  delegateDID: bob.did,  // Bob's primary DID (after signIn)
  actions: ['tinycloud.kv/get', 'tinycloud.kv/put'],
  path: 'documents/',
  expiry: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});

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

Bob receives and uses the delegation

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

// Bob can now read and write to Alice's documents
const files = await access.kv.list('documents/');
const report = await access.kv.get('documents/report.json');

// Bob can also write (he has kv/put)
await access.kv.put('documents/feedback.json', {
  reviewer: 'Bob',
  comments: 'Looks good!',
});
3

Bob creates a sub-delegation for Carol

// Bob sub-delegates read-only access to a narrower path
const bobToCarol = await bob.createSubDelegation({
  parentDelegation: aliceToBobPortable,
  delegateDID: carol.did,              // Carol's primary DID (after signIn)
  actions: ['tinycloud.kv/get'],       // Read only (subset of Bob's get+put)
  path: 'documents/public/',           // Narrower scope
  expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Shorter than Bob's 30 days
});

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

Carol accesses the shared data

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

// Carol can read Alice's public documents
const doc = await carolAccess.kv.get('documents/public/readme.txt');

// Carol cannot write (only has kv/get)
// Carol cannot access documents outside documents/public/