Skip to main content

Sharing

Sharing allows users to grant access to their data. Apps and other users can receive controlled access through exposure profiles.

How Sharing Works

  1. App requests authorization - User is shown what data the app wants
  2. User selects exposure profile - Determines what's shared
  3. Share is created - App can now access filtered data
  4. 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
}
FieldDescription
owner_idUser sharing their data
third_party_idApp receiving access
exposure_profile_idWhat data is accessible
expires_atWhen access automatically ends
revoked_atWhen 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:
    • all
    • specific_tags
    • specific_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
StatusDescription
ActiveApp can access data
Expiredexpires_at has passed
RevokedUser 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.

Next Steps

  • Proposals - Modify shared data with user approval
  • Webhooks - Get notified of data changes