Presence Activity
Overview
The Presence Activity API allows users to manage current activities (such as gaming, music, fitness) with automatic expiration via a lease-based system. Activities can be created, updated, and deleted, with support for flexible metadata and both system-generated and user-defined identifiers.
Core Features
- Lease-based expiration: Activities automatically expire within 1-60 minutes unless renewed
- Flexible identifiers: Supports both auto-generated GUIDs and user-defined ManualIds
- Extensible metadata: JSON-stored metadata dictionary for custom developer data
- Soft delete: Activities are soft-deleted and automatically filtered out
- Performance optimized: Cached activities have a 1-minute expiration
- Authentication required: All endpoints require valid user authentication
API Endpoints
Get Activities
Gets all current activities for the authenticated user (non-expired presence activities).
Endpoint: GET /pass/activities
Response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "Gaming",
"manualId": "game-session-1",
"title": "Playing Cyberpunk 2077",
"subtitle": "Night City Exploration",
"caption": "Missions completed: 15",
"meta": {
"appName": "Cyberpunk 2077",
"platform": "Steam",
"customProperty": "additional data"
},
"leaseMinutes": 10,
"leaseExpiresAt": "2024-01-15T14:30:00Z",
"accountId": "user-guid",
"createdAt": "2024-01-15T14:25:00Z",
"updatedAt": "2024-01-15T14:25:00Z",
"deletedAt": null
}
]
Common Response Codes:
200 OK- Success, returns array of activities401 Unauthorized- Invalid or missing authentication
Create New Activity
Creates a new presence activity with a configurable lease duration.
Endpoint: POST /pass/activities
Request Body:
{
"type": "Gaming",
"manualId": "my-game-session",
"title": "Playing Cyberpunk 2077",
"subtitle": "Night City Mission",
"caption": "Currently exploring downtown",
"meta": {
"appName": "Cyberpunk 2077",
"platform": "Steam",
"difficulty": "Hard",
"mods": ["mod1", "mod2"]
},
"leaseMinutes": 15
}
Response: Returns the created SnPresenceActivity object with populated fields.
Field Details:
type: PresenceType enum (Unknown, Gaming, Music, Workout)manualId: Optional user-defined string identifiertitle,subtitle,caption: Display strings (max 4096 characters each)meta: OptionalDictionary<string, object>for custom dataleaseMinutes: 1-60 minutes (default: 5)
Response Codes:
200 OK- Activity created successfully400 Bad Request- Invalid lease minutes or malformed data401 Unauthorized- Invalid authentication
Update Activity
Updates an existing activity using either GUID or ManualId. Only updates provided fields.
Endpoint: PUT /pass/activities
Query Parameters: (one required)
id- System-generated GUID (string)manualId- User-defined identifier (string)
Request Body: (all fields optional)
{
"title": "Updated: Playing Cyberpunk 2077",
"meta": {
"appName": "Cyberpunk 2077",
"platform": "Steam",
"newProperty": "updated data"
},
"leaseMinutes": 20
}
Response: Returns the updated SnPresenceActivity object.
Response Codes:
200 OK- Activity updated successfully400 Bad Request- Missing or invalid ID parameter401 Unauthorized- Invalid authentication404 Not Found- Activity not found or does not belong to user
Example cURL:
# Update via ManualId
curl -X PUT "/pass/activities?manualId=my-game-session" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"leaseMinutes": 20}'
# Update via GUID
curl -X PUT "/pass/activities?id=550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title"}'
Delete Activity
Soft-deletes an activity using either GUID or ManualId.
Endpoint: DELETE /pass/activities
Query Parameters: (one required)
id- System-generated GUID (string)manualId- User-defined identifier (string)
Request Body: None
Response: No content (204)
Response Codes:
204 No Content- Activity deleted successfully400 Bad Request- Missing or invalid ID parameter401 Unauthorized- Invalid authentication404 Not Found- Activity not found or does not belong to user
Example cURL:
# Delete via ManualId
curl -X DELETE "/pass/activities?manualId=my-game-session" \
-H "Authorization: Bearer <token>"
Additional Endpoints
Get Activities by Account ID
Endpoint: GET /pass/activities/{accountId:guid}
Used for administrative or debugging purposes. Returns activities for the specified account ID regardless of authentication status.
Data Model
PresenceType Enum
public enum PresenceType
{
Unknown,
Gaming,
Music,
Workout
}
SnPresenceActivity
public class SnPresenceActivity : ModelBase
{
public Guid Id { get; set; } // System-generated GUID
public PresenceType Type { get; set; }
public string? ManualId { get; set; } // User-defined ID
public string? Title { get; set; }
public string? Subtitle { get; set; }
public string? Caption { get; set; }
public Dictionary<string, object>? Meta { get; set; } // JSON metadata
public int LeaseMinutes { get; set; } // Lease duration
public Instant LeaseExpiresAt { get; set; } // Expiration timestamp
// Inherited from ModelBase
public Guid AccountId { get; set; }
public Instant CreatedAt { get; set; }
public Instant UpdatedAt { get; set; }
public Instant? DeletedAt { get; set; } // Soft delete
}
Behavior and Constraints
Lease Expiration
- Activity automatically expires when
SystemClock.Instance.GetCurrentInstant() > LeaseExpiresAt - Expiration check happens in the database query, so expired activities are filtered in GET operations
- Clients must periodically update/renew to keep activities active
ID Flexibility
- ManualId: User-defined string, unique within the user's activities
- GUID: System-generated, always unique, returned in API responses
- Both can be interchangeably used for updates and deletes
Performance Optimization
- Activities are cached for 1 minute to handle frequent updates
- Create/update/delete operations invalidate the cache
- Database queries automatically filter expired activities
Security
- All operations are scoped to the authenticated user's account
- Users can only manage their own activities
- Invalid or expired authentication tokens return 401 Unauthorized
Data Storage
- Activities are stored in PostgreSQL with JSONB metadata support
- Soft deletes use timestamps rather than hard deletes
- EF Core middleware automatically handles CreatedAt/UpdatedAt timestamps
Usage Examples
Gaming Session Management
// Start gaming session
const activity = await fetch("/pass/activities", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "Gaming",
manualId: "game-session-1",
title: "Playing Cyberpunk 2077",
meta: { appId: "cyberpunk2077", mods: ["photorealistic"] },
leaseMinutes: 15,
}),
});
// Update progress (extend lease)
await fetch("/pass/activities?manualId=game-session-1", {
method: "PUT",
body: JSON.stringify({
title: "Playing Cyberpunk 2077 - Level 25",
leaseMinutes: 15,
}),
});
// End session
await fetch("/pass/activities?manualId=game-session-1", {
method: "DELETE",
});
Metadata Extensions
// Rich metadata support
const activity = {
type: "Music",
manualId: "spotify-session",
title: "Listening to Electronic",
meta: {
spotifyTrackId: "1Je1IMUlBXcx1FzbcXRuWw",
artist: "Purity Ring",
album: "Shrines",
duration: 240000, // milliseconds
custom: { userRating: 5, genre: "Electronic" },
},
leaseMinutes: 30,
};
Error Handling
Common error responses follow REST API conventions:
{
"type": "Microsoft.AspNetCore.Mvc.ValidationProblemDetails",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"leaseMinutes": ["Lease minutes must be between 1 and 60."]
}
}