Share access to your space with other users and services
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.
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.
Quick
Explained
Copy
// Alice delegates read access to Bobconst 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 Bobconsole.log(delegation.portable);
Copy
const delegation = await alice.createDelegation({ // The recipient's primary DID (e.g., did:pkh:eip155:{chainId}:{address}) // After signIn, tc.did returns the primary DID (the identity that authenticated) delegateDID: bob.did, // Actions the recipient can perform // Available: tinycloud.kv/get, tinycloud.kv/put, tinycloud.kv/del, tinycloud.kv/list actions: ['tinycloud.kv/get', 'tinycloud.kv/put'], // Path prefix the delegation covers // 'shared/' grants access to all keys starting with 'shared/' path: 'shared/', // When the delegation expires (optional) // Pass a Date object for the absolute expiry time expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Earliest time the delegation becomes valid (optional) notBefore: new Date(),});
When you receive a delegation (as a portable JSON string), load it into your SDK to access the delegator’s space.
Copy
// Bob received a portable delegation from Aliceconst portableDelegation = /* received from Alice */;// Load the delegationawait tc.kv.useDelegation(portableDelegation);// Now Bob can access Alice's spaceconst 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.
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.
Copy
const access = await bob.useDelegation(portableDelegation);// access.kv is scoped to Alice's space with the delegated permissionsawait access.kv.get('shared/document'); // Read from Alice's spaceawait access.kv.put('shared/response', { message: 'Thanks!' }); // Write to Alice's spaceawait access.kv.list('shared/'); // List keys in Alice's space
Recipients can further delegate their access to others. Sub-delegations must be strictly narrower than the parent delegation.
Copy
// Bob creates a sub-delegation for Carolconst 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});
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:
Type
Property
Example
When to Use
Primary DID
tc.did (after signIn)
did:pkh:eip155:1:0xd8dA...
Delegations, user identity
Session Key DID
tc.sessionDid
did: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.
Copy
// 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 validationawait alice.createDelegation({ delegateDID: bob.sessionDid, // did:key:z6Mk... -- session key, not for delegations actions: ['tinycloud.kv/get'],});
Delegations are serialized as PortableDelegation for transport between users. This is a JSON string containing the signed delegation chain, which can be verified cryptographically.
Copy
// Create and serializeconst 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 stringconst portable = delegation.portable;// Send via any channel: HTTP API, email, QR code, messaging, etc.sendToRecipient(portable);// Recipient loads itconst 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.
Copy
// Generate a share link for a specific keyconst 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.
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.
Copy
// Short-lived delegation that must be refreshedconst 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
Alice shares a document folder with Bob, who then shares a subset with Carol.
1
Alice creates a delegation for Bob
Copy
// Alice's clientconst 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 BobsendToUser(bob.address, aliceToBob.portable);
2
Bob receives and uses the delegation
Copy
// Bob's clientconst access = await bob.useDelegation(aliceToBobPortable);// Bob can now read and write to Alice's documentsconst 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
Copy
// Bob sub-delegates read-only access to a narrower pathconst 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
Copy
// Carol's clientconst carolAccess = await carol.useDelegation(bobToCarolPortable);// Carol can read Alice's public documentsconst doc = await carolAccess.kv.get('documents/public/readme.txt');// Carol cannot write (only has kv/get)// Carol cannot access documents outside documents/public/