Skip to content

Place a legal hold on a file

PUT
/api/v1/tenants/{tenant_id}/applications/{application_id}/files/{file_id}/hold
curl --request PUT \
--url https://files.centralcodestation.com/api/v1/tenants/example/applications/example/files/example/hold \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "reason": "example", "until": "2026-04-15T12:00:00Z", "acting_user_id": "example" }'

Or change the reason and the end of the one in place. While the hold is in force the file cannot be purged (by anyone, nor by the trash’s retention or emptying), its versions are neither deleted nor pruned, and a storage migration keeps its source copies; it can still be trashed and restored, edited and given new versions. files.manage. The reason is visible to everyone who can view the file.

Legal hold

file_id
required
string

A file id (ULID).

tenant_id
required
string

The Identity Server tenant id.

application_id
required
string

The Identity Server application id: a tenant application, or (platform clients) the client’s own platform application.

Media typeapplication/json
object
reason
required

Why the file is held; shown to everyone who can view the file and kept in the audit log.

string
<= 500 characters
until

A future date when the hold ends by itself; omitted or null: until released.

string format: date-time
nullable
acting_user_id

Machine tokens: the member the change is recorded for.

string

Example generated

{
"reason": "example",
"until": "2026-04-15T12:00:00Z",
"acting_user_id": "example"
}

Success.

Media typeapplication/json
object
type
required
string
Allowed values: file
file_id
required
string
tenant_id
required
string
application_id
required
string
folder_id
required
string
path
required
string
name
required
string
extension
required
string
nullable
mime_type
required
string
kind
required
string
Allowed values: image video audio pdf document spreadsheet presentation text archive other
size
required
number
content_sha256
required
string
nullable
title
required
string
nullable
description
required
string
nullable
category
required
string
nullable
department
required
string
nullable
tags
required
Array<string>
custom
required
object
key
additional properties
string
external_ref
required

Your own reference (for example post:123); a list and search filter.

string
nullable
owner_id
required
string
nullable
created_by
required
object
user_id
required
string
nullable
client_id
required
string
created_at
required
string format: date-time
updated_at
required
string format: date-time
state
required
string
Allowed values: pending_upload available quarantined trashed
status
required
string
Allowed values: draft in_review approved rejected archived
visibility
required
string
Allowed values: private application public
public_url
required

The stable URL of a public file.

string
nullable
current_version_no
required
number
versions_count
required
number
revision
required
number
media
required

Derived from the bytes: width, height, duration_ms, pages, …

object
key
additional properties
any
processing
required

One outcome per processing step.

object
key
additional properties
string
Allowed values: pending done skipped failed clean infected
renditions
required
object
key
additional properties
object
status
required
string
Allowed values: pending ready skipped failed
mime_type
required
string
nullable
size
required
number
nullable
width
required
number
nullable
height
required
number
nullable
duration_ms
required
number
nullable
error

Only on skipped and failed: why, e.g. source_smaller, duration_exceeds_limit, size_exceeds_limit, duration_unknown.

string
public_renditions
required
object
key
additional properties
string
duplicate_of
required
string
nullable
lock
required
object
kind
required
string
Allowed values: checkout edit_session
holder_user_id
required
string
nullable
holder_client_id
required
string
nullable
acquired_at
required
string format: date-time
expires_at
required
string format: date-time
note
required
string
nullable
editable
required
boolean
hold
required

A legal hold: while it is in force the file cannot be purged and its versions are kept.

object
reason
required
string
placed_by
required

The user who placed it, or the client id of a machine caller without acting_user_id.

string
placed_at
required
string format: date-time
until
required

When the hold ends by itself; null: until released.

string format: date-time
nullable
managed
required

Application-managed: the item is a folder an application marked, or lies below one. Read-only for people (403 folder_managed); only the application’s machine clients change it.

boolean
managed_by
required

Who manages it and which folder carries the mark; null when not managed.

object
client_id
required

The machine client that marked the managing folder.

string
name
required

A display name for it: the tenant application’s name, or a platform application’s slug.

string
nullable
managed_folder_id
required

The folder that carries the mark (the item itself, or its nearest marked ancestor). The tenant owner releases this one.

string
managed_at
required
string format: date-time
inherit
required
boolean
acl_scope_id
required
string
entries_count
required
number
trashed_at
required
string format: date-time
nullable
trashed_by
required
string
nullable
my_actions
required
Array<string>
Allowed values: view download edit share delete approve

Example

{
"type": "file",
"file_id": "01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"tenant_id": "fitness-co",
"application_id": "fitness-app",
"folder_id": "01J9Z6R2M3N4P5Q6R7S8T9V0W1",
"path": "/posts/IMG_5913.jpg",
"name": "IMG_5913.jpg",
"extension": "jpg",
"mime_type": "image/jpeg",
"kind": "image",
"size": 4194304,
"content_sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"title": "Beach",
"description": null,
"category": "photo",
"department": null,
"tags": [
"beach"
],
"custom": {
"post_id": "123"
},
"external_ref": "post:123",
"owner_id": "u-ana",
"created_by": {
"user_id": "u-ana",
"client_id": "fitness-backend"
},
"created_at": "2026-10-22T09:12:33.120Z",
"updated_at": "2026-10-22T09:12:33.120Z",
"state": "available",
"status": "draft",
"visibility": "application",
"public_url": null,
"current_version_no": 1,
"versions_count": 1,
"revision": 3,
"media": {
"width": 4032,
"height": 3024
},
"processing": {
"sniff": "done",
"policy": "done",
"hash": "done",
"scan": "skipped",
"metadata": "done",
"sanitize": "done",
"renditions": "done",
"text": "skipped",
"publish": "skipped",
"notify": "done"
},
"renditions": {
"thumbnail": {
"status": "ready",
"mime_type": "image/webp",
"size": 8123,
"width": 256,
"height": 192
},
"preview": {
"status": "ready",
"mime_type": "image/webp",
"size": 98123,
"width": 1280,
"height": 960
}
},
"public_renditions": null,
"duplicate_of": null,
"lock": null,
"editable": false,
"hold": null,
"managed": true,
"managed_by": {
"client_id": "fitness-backend",
"name": "Fitness",
"managed_folder_id": "01J9Z6R2M3N4P5Q6R7S8T9V0W1",
"managed_at": "2026-10-22T09:12:33.120Z"
},
"inherit": true,
"acl_scope_id": "root",
"entries_count": 0,
"trashed_at": null,
"trashed_by": null,
"my_actions": [
"view",
"download",
"edit",
"share",
"delete"
]
}

Error. error is one of: invalid_request.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 400,
"error": "invalid_request",
"message": "The request is malformed: a required field is missing, a value has the wrong type or format, or a field the route does not accept was sent",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: invalid_token.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 401,
"error": "invalid_token",
"message": "The bearer token is missing, malformed, expired, signed by an unknown key, or has the wrong audience (`aud` must be `files-api`).",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: not_tenant_member, no_application_access, tenant_not_available, insufficient_scope, client_context_mismatch, files_access_not_granted, missing_capability.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 403,
"error": "not_tenant_member",
"message": "The user is not an active member of the tenant in the URL.",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: application_not_found, not_found, file_not_found.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 404,
"error": "application_not_found",
"message": "The application in the URL is not an active application of the tenant.",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: file_pending.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 409,
"error": "file_pending",
"message": "The file's upload has not been completed yet.",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: rate_limited.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 429,
"error": "rate_limited",
"message": "Too many requests in the current window; `retry_after` (and the `Retry-After` header) says when to retry.",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error. error is one of: identity_unavailable.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 503,
"error": "identity_unavailable",
"message": "The service could not reach the Identity Server and has no fresh cached answer.",
"path": "/api/v1/tenants/fitness-co/applications/fitness-app/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D",
"timestamp": "2026-10-22T09:12:33.120Z"
}

Error: see error for the code.

Media typeapplication/json
object
status_code
required
number
error
required

Stable snake_case error code.

string
Allowed values: invalid_request invalid_cursor invalid_limit invalid_json invalid_name depth_exceeded invalid_move role_locked invalid_redirect_uri invalid_id_token webhook_url_not_allowed invalid_grant invalid_token share_password_required insufficient_scope not_tenant_member no_application_access tenant_not_available client_context_mismatch principal_not_allowed files_access_not_granted missing_capability missing_action platform_admin_required tenant_suspended partial_permission cannot_grant_more_than_held tenant_owner_required folder_managed share_password_invalid not_found application_not_found role_not_found file_not_found folder_not_found upload_not_found version_not_found share_not_found entry_not_found rendition_not_available job_not_found taxonomy_not_found upload_expired upload_not_open name_conflict scan_pending file_quarantined file_trashed file_pending file_locked file_on_hold status_locked version_is_current folder_busy idempotency_key_reused share_gone share_limit_reached revision_mismatch file_too_large quota_exceeded payload_too_large upload_incomplete size_mismatch hash_mismatch type_not_allowed not_previewable too_many_entries too_many_file_ids too_many_scopes too_many_applications taxonomy_value_invalid public_visibility_disabled share_links_disabled webhook_unreachable rate_limited server_error not_implemented identity_unavailable storage_unavailable notifications_unavailable storage_account_not_found storage_migration_not_found storage_account_in_use storage_account_not_verified storage_migration_in_progress storage_account_verification_failed storage_adapter_not_allowed
message
required
One of:
string
path
required
string
timestamp
required
string format: date-time

Example

{
"status_code": 403,
"error": "invalid_request",
"message": "This client is not bound to this tenant/application",
"path": "/api/v1/tenants/t1/applications/a1/files/01J9Z6S9Q5F5R6Y2W2GZ3B3K4D"
}