Preservation
This documentation walks you through creating and managing preservations (legal holds) using the Onna Public API. Preservations allow you to place user data on legal hold for compliance and legal discovery purposes.
Overview
The preservation workflow involves several key steps:
- Discover preservable datasources - Identify which datasources support preservation
- Search for user identities - Find users to place on hold (custodians)
- Create a preservation - Set up the legal hold with query configuration
- Trigger smart action check - Start the preservation job to apply rules
- Monitor progress - Track the preservation job status
- Review details - Check preserved data and custodians
- Finish preservation - Mark as complete when ready
- Delete preservation - Remove after finalization
Prerequisites
- Valid API credentials with preservation permissions
- Existing workspaces with data to preserve
- Understanding of JSON Logic format for queries
Step 1: Check Preservable Datasources
First, identify which datasources in your account can be preserved.
Get Preservable Datasource Types
curl -X GET "https://api.onna.com/v1/preservations/datasource-types" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Response:
{
"types": [
"SlackEDatasource",
"GSuiteEDatasource",
"QuipEDatasource",
"MsTeamsEDatasource"
]
}
List Preservable Datasources
Retrieve all datasources that can be preserved:
curl -X GET "https://api.onna.com/v1/preservations/datasources" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Response:
{
"total": 5,
"items": [
{
"type_name": "SlackEDatasource",
"uuid": "abc123-def456-789",
"path": "/workspaces/example/slack-data",
"title": "Company Slack",
"parent_workspace_title": "Example Workspace"
},
{
"type_name": "GSuiteEDatasource",
"uuid": "xyz789-abc123-456",
"path": "/workspaces/example/gsuite-data",
"title": "Corporate GSuite",
"parent_workspace_title": "Example Workspace"
}
],
"cursor": "next_cursor_value"
}
Step 2: Discover User Identities
Search for user identities to define as custodians for the preservation.
curl -X POST "https://api.onna.com/v1/preservations/identities-discovery" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "john.doe@company.com",
"suggestions": true
}'
Response:
{
"items": [
{
"id": "identity-123-456",
"title": "John Doe",
"email": "john.doe@company.com",
"source_accounts": [
{
"uuid": "sa-123",
"email": "john.doe@company.com",
"config_type": "slack"
},
{
"uuid": "sa-456",
"email": "john.doe@company.com",
"config_type": "gsuite"
}
]
}
],
"matching_source_accounts": 2,
"cursor": null
}
Step 3: Create a Preservation
Create a preservation workspace with legal hold configuration using JSON Logic queries.
Understanding the Query Structure
Preservations use JSON Logic format to define what data to preserve. The following examples show how to structure your query:
Preserve by Identity (User):
{
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-123-456"]
]
}
]
}
}
Preserve by Datasource Path:
{
"advanced": {
"and": [
{
"in": [
{"var": "parent_datasource.path"},
[
"/account/workspace/slack-data",
"/account/workspace/gsuite-data"
]
]
}
]
}
}
Combine Multiple Criteria:
{
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-123-456", "identity-789-012"]
]
},
{
"in": [
{"var": "parent_datasource.path"},
["/account/workspace/slack-data"]
]
}
]
}
}
Create Preservation Request
curl -X POST "https://api.onna.com/v1/preservations" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Legal Hold Case 2024-001",
"description": "Data preservation for legal case 2024-001",
"billing_number": "CASE-2024-001",
"tags": ["legal-hold", "preservation"],
"embeddings_enabled": false,
"legal_hold": {
"complete": false,
"query": {
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-123-456-789"]
]
},
{
"in": [
{"var": "parent_datasource.path"},
["/account/workspace/slack-data"]
]
}
]
}
}
}
}'
Response:
{
"onna_id": "preservation-ws-c45fb2f5d72a41c2b085c2916511c633",
"created_by": "public-api",
"legal_hold": {
"complete": false,
"created_by": "public-api",
"query": {
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-123-456-789"]
]
},
{
"in": [
{"var": "parent_datasource.path"},
["/account/workspace/slack-data"]
]
}
]
}
}
}
}
Step 4: Trigger and Monitor Preservation Job
After creating a preservation, trigger the smart action to start processing.
Trigger Smart Action Check
curl -X POST "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633/smart-action-check" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Monitor Job Status
Regularly check the status of the preservation job:
curl -X GET "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633/smart-action-status?view=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Response:
{
"status": "running",
"complete": false,
"error": false,
"current": 2650,
"total": 10000,
"actions": 2650,
"progress": 26.5,
"when": "2025-12-04T14:50:05.973079+00:00",
"task_id": "task:onna-onna-0df0167f-1e59-4321-b2bc-ea6a6718676d",
"version": 1,
"target_version": 1
}
Status Values:
pending- Job is queued but not startedrunning- Job is currently processingcompleted- Job finished successfully (checkcomplete: true)error- Job encountered errors (checkerror_message)
Step 5: Review Preservation Details
Once the preservation job completes, review the preserved data and custodians.
Get Preservation Details
curl -X GET "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633/details" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Response:
{
"identities": [
{
"id": "identity-123",
"title": "John Doe",
"email": "john.doe@company.com",
"source_accounts": [
{
"uuid": "sa-123",
"email": "john.doe@company.com",
"config_type": "slack"
}
]
}
],
"preservation_query": {
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-123-456-789"]
]
}
]
}
},
"total_identities": 1
}
Get Preserved Datasources
Check which datasources were successfully preserved:
curl -X GET "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633/preserved-datasources" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Response:
{
"datasources": [
{
"@id": "ds-123",
"@type": "SlackEDatasource",
"title": "Company Slack",
"config_type": "slack",
"creation_date": "2024-01-15T10:30:00.000000+00:00"
}
]
}
Step 6: Update Preservation (Optional)
You can update the preservation configuration before finishing it.
curl -X PATCH "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"billing_number": "CASE-2024-001-UPDATED",
"legal_hold": {
"query": {
"advanced": {
"and": [
{
"in": [
{"var": "parent_datasource.path"},
[
"/account/workspace/slack-data",
"/account/workspace/gsuite-data"
]
]
}
]
}
}
}
}'
Step 7: Finish Preservation
When the preservation is complete, mark it as finished. This is required before deletion.
curl -X PATCH "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633/finish" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json"
Important: A preservation must be finished before it can be deleted.
Step 8: Delete Preservation
After finishing the preservation, you can delete it when no longer needed.
curl -X DELETE "https://api.onna.com/v1/preservations/preservation-ws-c45fb2f5d72a41c2b085c2916511c633" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Error if not finished:
{
"error": "cannot_delete_active_preservation",
"message": "The preservation must be marked as complete before deletion."
}
Best Practices
1. Query Design
- Be specific: Use both identity and datasource path filters to narrow scope
- Test queries: Validate your JSON Logic queries before creating preservations
- Document queries: Keep records of what each preservation query targets
2. Monitoring
- Poll regularly: Check status every 10-30 seconds for large preservations
- Set timeouts: Implement reasonable timeout values based on data volume
- Log progress: Track preservation progress for audit purposes
3. Error Handling
try:
preservation = client.create_preservation(
name="My Preservation",
legal_hold_query=query
)
except requests.HTTPError as e:
if e.response.status_code == 409:
print("Preservation already exists")
elif e.response.status_code == 422:
print(f"Invalid request: {e.response.json()}")
else:
raise
4. Workflow Management
- Always finish before deleting: Ensure
finish_preservation()is called beforedelete_preservation() - Track billing numbers: Use meaningful billing numbers for tracking
- Tag appropriately: Use tags for organization and filtering
5. Identity Discovery
- Use specific queries: Search by email or partial names
- Verify results: Check that discovered identities match expected users
- Handle multiple accounts: A single person may have multiple identity records
Common Issues and Solutions
Preservation Job Stuck
If a preservation job appears stuck:
- Check
errorfield in status response - Verify datasource connectivity
- Review query configuration for errors
- Contact support if issue persists
Cannot Delete Preservation
Error: "cannot_delete_active_preservation"
Solution: Call finish endpoint first:
# First finish
curl -X PATCH "https://api.onna.com/v1/preservations/{preservation_id}/finish" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Then delete
curl -X DELETE "https://api.onna.com/v1/preservations/{preservation_id}" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Incorrect Query Format
Error: 422 Unprocessable Entity
Solution: Validate JSON Logic syntax:
// Correct format
{
"advanced": {
"and": [
{
"in": [
{"var": "identity-member"},
["identity-id"]
]
}
]
}
}