Skip to main content

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:

  1. Discover preservable datasources - Identify which datasources support preservation
  2. Search for user identities - Find users to place on hold (custodians)
  3. Create a preservation - Set up the legal hold with query configuration
  4. Trigger smart action check - Start the preservation job to apply rules
  5. Monitor progress - Track the preservation job status
  6. Review details - Check preserved data and custodians
  7. Finish preservation - Mark as complete when ready
  8. 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 started
  • running - Job is currently processing
  • completed - Job finished successfully (check complete: true)
  • error - Job encountered errors (check error_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 before delete_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:

  1. Check error field in status response
  2. Verify datasource connectivity
  3. Review query configuration for errors
  4. 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"]
]
}
]
}
}