Sharing
Sharing allows users to grant access to their data. Apps and other users can receive controlled access through exposure profiles.
How Sharing Works
- App requests authorization - User is shown what data the app wants
- User selects exposure profile - Determines what's shared
- Share is created - App can now access filtered data
- User can revoke anytime - Removes app's access
Share Structure
{
"id": "share_a1b2c3d4",
"owner_id": "user_123",
"third_party_id": "app_myapp",
"exposure_profile_id": "profile_work",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2025-01-15T10:30:00Z",
"revoked_at": null
}
| Field | Description |
|---|---|
owner_id | User sharing their data |
third_party_id | App receiving access |
exposure_profile_id | What data is accessible |
expires_at | When access automatically ends |
revoked_at | When user manually revoked (null if active) |
For App Developers
Creating a Share
After a user completes OAuth authorization:
# The user has authorized your app via OAuth
# Now create the share with their chosen profile
share = client.sharing.create(
user_id="user-123",
third_party_id="your-app-id", # Your registered app
exposure_profile_id="profile_work",
expires_at="2025-01-15T10:30:00Z" # Optional expiration
)
Checking Active Shares
See which users have authorized your app:
# List shares where your app is the recipient
# This requires your app credentials
shares = client.sharing.list_incoming(
active_only=True
)
for share in shares['items']:
print(f"User {share['owner_id']} authorized on {share['created_at']}")
Accessing Shared Data
Once a share exists, you can access the user's data (filtered by their exposure profile):
# This only returns nodes matching the user's exposure profile
nodes = client.nodes.list(user_id="user-123")
# If the profile only allows "note" type, only notes are returned
# If tags are restricted, only matching nodes are returned
For Users
Users manage shares through their dashboard or API.
View Authorized Apps
# List all apps the user has authorized
shares = client.sharing.list_outgoing(
user_id="user-123",
active_only=True
)
for share in shares['items']:
print(f"App: {share['third_party_id']}")
print(f"Profile: {share['exposure_profile_id']}")
print(f"Since: {share['created_at']}")
Revoke Access
# User revokes an app's access
client.sharing.revoke(
user_id="user-123",
share_id="share_abc"
)
# The app can no longer access this user's data
Update Share Profile
Change what data an app can access:
# Use authorizations endpoint to update
client.authorizations.update(
user_id="user-123",
authorization_id="auth_xyz",
exposure_profile_id="profile_minimal" # Switch to more restrictive profile
)
User-to-User Sharing
Users can also share data with other users:
# Share work data with a colleague
share = client.sharing.create(
user_id="user-123",
recipient_id="user-456", # Another user
exposure_profile_id="profile_work",
expires_at="2024-06-01T00:00:00Z"
)
The recipient can then access filtered data:
# As user-456, access user-123's shared data
shared_nodes = client.nodes.list(
user_id="user-123", # The sharer
# Only returns nodes matching the exposure profile
)
Following (Public Profiles + Follow Requests)
Paradigm also supports Following, which is a social wrapper around user-to-user sharing:
- Public profile (
is_public=true): follow is auto-accepted and grants read-only access to everything - Private profile: follow starts as a request; if accepted, the owner chooses scope:
allspecific_tagsspecific_nodes
See:
Expiring Shares
Set expiration for temporary access:
# Share for 30 days
from datetime import datetime, timedelta
expires = datetime.now() + timedelta(days=30)
share = client.sharing.create(
user_id="user-123",
third_party_id="app_id",
exposure_profile_id="profile_work",
expires_at=expires.isoformat()
)
After expiration:
- Share status changes to expired
- App receives 403 when accessing data
- User can re-authorize if desired
Share Lifecycle
Created ──► Active ──► Expired
│
▼
Revoked
| Status | Description |
|---|---|
| Active | App can access data |
| Expired | expires_at has passed |
| Revoked | User manually revoked |
Audit Trail
All share actions are logged:
# User can see share history
logs = client.audit.list(
user_id="user-123",
resource_type="share"
)
for log in logs['items']:
print(f"{log['action']}: {log['resource_id']} at {log['created_at']}")
Best Practices
1. Request Appropriate Duration
Don't ask for perpetual access if you only need temporary:
# For a one-time export
expires_at = datetime.now() + timedelta(hours=1)
# For ongoing sync
expires_at = datetime.now() + timedelta(days=365)
2. Handle Revocation Gracefully
Your app should handle 403 errors:
try:
nodes = client.nodes.list(user_id="user-123")
except PermissionDenied:
# Share was revoked or expired
prompt_user_to_reauthorize()
3. Re-authorize When Needed
If access expires, prompt users to re-authorize:
def get_user_nodes(user_id):
try:
return client.nodes.list(user_id=user_id)
except PermissionDenied:
# Redirect to OAuth flow
return redirect_to_authorization(user_id)
4. Respect User Privacy
Even with access, only use data for the stated purpose of your app.