Files API reference
Upload, read, organize and restore files.
Requests go to https://api.revdoku.com. Every path starts with /v1, for example https://api.revdoku.com/v1/buckets. Send Authorization: Bearer $REVDOKU_API_KEY from your server. Tables use JSON names; where TypeScript differs, a field shows json_name / typescriptName. Python uses the JSON names.
All operations · API guide · Errors · OpenAPI
- List files:
GET /v1/buckets/{bucket_id}/files - Save an uploaded file (step 3 of 3):
POST /v1/buckets/{bucket_id}/files - Find a file by path:
GET /v1/buckets/{bucket_id}/files/by_path - Append text to a file:
POST /v1/buckets/{bucket_id}/files/append_text - Read file metadata:
GET /v1/buckets/{bucket_id}/files/{file_id} - Delete a file:
DELETE /v1/buckets/{bucket_id}/files/{file_id} - Get a download link for a file:
GET /v1/buckets/{bucket_id}/files/{file_id}/download - Rename or move a file in the same bucket:
POST /v1/buckets/{bucket_id}/files/{file_id}/rename - Move a file:
POST /v1/buckets/{bucket_id}/files/{file_id}/move - Copy a file:
POST /v1/buckets/{bucket_id}/files/{file_id}/copy - List file versions:
GET /v1/buckets/{bucket_id}/files/{file_id}/versions - Restore a file version:
POST /v1/buckets/{bucket_id}/files/{file_id}/versions/{version_id}/restore - Prepare a file upload (step 1 of 3):
POST /v1/buckets/{bucket_id}/uploads - Start a batch upload:
POST /v1/buckets/{bucket_id}/upload_sessions - Get storage targets for a batch:
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/uploads - Save uploaded files:
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/finalize_batch - Finish or cancel a batch upload:
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/finalize
List files
GET /v1/buckets/{bucket_id}/files
Lists current files, including stored emails under _mailbox/. Listing never marks files read.
| Language | Call |
|---|---|
| TypeScript | client.listFiles({ bucketId }) |
| Python | client.list_files(bucket_id) |
| TypeScript, full API | client.api.listFiles({ bucketId }) |
| Python, full API | client.api.list_files(bucket_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
q |
string | no | Case-insensitive text in the path. |
folder |
string | no | List one folder. An empty value lists the root. |
limit |
integer | no | |
cursor |
string | no | pagination.next_cursor from the previous page. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.listFiles({ bucketId: 'bkt_example' });
for (const file of data.files) {
console.log(file.id, file.path);
}
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
files |
array of BucketFile | |
files[].id |
string | |
files[].bucket_id / bucketId |
string | |
files[].basename |
string | |
files[].path |
string | |
files[].role |
string | |
files[].created_by_system / createdBySystem |
string | |
files[].deleted |
boolean | |
files[].version_reasons / versionReasons |
array of FileVersionReason | Fields: version_id, reason, created_by_id, version_number, created_at. |
files[].lock |
object | |
files[].current_file_version / currentFileVersion |
FileVersion or null | |
files[].created_by / createdBy |
FileActor | Fields: id, name, email. |
files[].created_at / createdAt |
string (date-time) | |
files[].updated_at / updatedAt |
string (date-time) | |
files[].deleted_at / deletedAt |
string (date-time) | |
files[].created_by_agent / createdByAgent |
object | |
files[].updated_by_agent / updatedByAgent |
object | |
files[].created_by_api_key / createdByApiKey |
object | |
files[].updated_by_api_key / updatedByApiKey |
object | |
files[].read |
boolean | |
files[].read_at / readAt |
string (date-time) or null | |
files[].read_by / readBy |
FileActor or null | |
files[].read_by_api_key / readByApiKey |
FileKey or null | |
pagination |
Pagination | One page of a list. Request the next page with cursor=next_cursor and the same filters. Cursors are opaque strings: do not build or decode them. |
pagination.limit |
integer | |
pagination.has_more / hasMore |
boolean | True when another page exists. |
pagination.next_cursor / nextCursor |
string or null | Cursor for the next page; null on the last page. Email lists always return a cursor so that you can poll for new mail. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Save an uploaded file (step 3 of 3)
POST /v1/buckets/{bucket_id}/files
Saves the uploaded blob at path. An existing path gets a new version.
| Language | Call |
|---|---|
| TypeScript | client.uploadFile({ bucketId, path, content }) (runs all three upload steps) |
| Python | client.upload_file(bucket_id, path, content) (runs all three upload steps) |
| TypeScript, full API | client.api.saveUploadedFile({ bucketId, saveUploadedFileRequest }) |
| Python, full API | client.api.save_uploaded_file(bucket_id, save_uploaded_file_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes |
Request body: TypeScript saveUploadedFileRequest: SaveUploadedFileRequest, Python save_uploaded_file_request=SaveUploadedFileRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Destination path. An existing path receives a new version. Paths under _mailbox/ return MAILBOX_PATH_READ_ONLY; paths under _agents/ and _revdoku/ return RESERVED_PATH. |
signed_blob_id / signedBlobId |
string | yes | data.signed_id from POST /v1/buckets/{bucket_id}/uploads. |
name |
string | no | Display name; defaults to the last path segment. |
role |
string: source, reference, generated, agent_output, export, artifact |
no | |
existing_file_policy / existingFilePolicy |
string: replace, skip |
no | |
expected_bucket_version / expectedBucketVersion |
string | no | Bucket version_id from your last read. A different current version returns BUCKET_VERSION_CONFLICT and saves nothing. |
account_id / accountId |
string | no | |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
// uploadFile runs all three steps: prepare, PUT the bytes to storage, save.
const { data } = await client.uploadFile({
bucketId: 'bkt_example',
path: 'notes/todo.txt',
content: 'Call the customer back.',
contentType: 'text/plain',
});
console.log(data.file.id, data.version.id);
Python
=
# upload_file runs all three steps: prepare, PUT the bytes to storage, save.
=
cURL
Response 201, 200
| Field | Type | Description |
|---|---|---|
file |
BucketFile | Fields as in List files. |
version |
FileVersion | |
version.id |
string | |
version.file_id / fileId |
string | |
version.name |
string | |
version.mime_type / mimeType |
string | |
version.sha256 |
string | |
version.reason |
string or null | |
version.created_by_system / createdBySystem |
string | |
version.deleted |
boolean | True for the version that records a deletion. |
version.ready |
boolean | |
version.has_text / hasText |
boolean | |
version.version_number / versionNumber |
integer (int64) | |
version.byte_size / byteSize |
integer (int64) | |
version.created_at / createdAt |
string (date-time) | |
version.created_by / createdBy |
FileActor | Fields: id, name, email. |
version.updated_by / updatedBy |
FileActor | Fields: id, name, email. |
version.created_by_agent / createdByAgent |
object | |
version.updated_by_agent / updatedByAgent |
object | |
version.created_by_api_key / createdByApiKey |
object | |
version.updated_by_api_key / updatedByApiKey |
object | |
version.read |
boolean | |
version.read_at / readAt |
string (date-time) or null | |
version.read_by / readBy |
FileActor or null | |
version.read_by_api_key / readByApiKey |
FileKey or null | |
created |
boolean | |
skipped |
FileUploadSkip | |
skipped.path |
string | |
skipped.name |
string | |
skipped.reason |
string | |
skipped.code |
string | |
skipped.input_index / inputIndex |
integer | |
skipped.details |
object | |
duplicate |
boolean |
Example 201:
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Upload a file
Find a file by path
GET /v1/buckets/{bucket_id}/files/by_path
Returns file metadata only. Read content with the download endpoint. The disposition and content_url parameters return INVALID_ARGUMENT.
| Language | Call |
|---|---|
| TypeScript | client.api.getFileByPath({ bucketId, path }) |
| Python | client.api.get_file_by_path(bucket_id, path) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
path |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.getFileByPath({
bucketId: 'bkt_example',
path: 'notes/todo.txt',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
file |
BucketFile | Fields as in List files. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Error codes named above: INVALID_ARGUMENT.
Guide: Files
Append text to a file
POST /v1/buckets/{bucket_id}/files/append_text
Appends UTF-8 text to an existing text file and creates a new version. A missing file returns BUCKET_FILE_NOT_FOUND.
| Language | Call |
|---|---|
| TypeScript | client.api.appendText({ bucketId, appendTextRequest }) |
| Python | client.api.append_text(bucket_id, append_text_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes |
Request body: TypeScript appendTextRequest: AppendTextRequest, Python append_text_request=AppendTextRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | |
content |
string | yes | |
newline_before / newlineBefore |
boolean | no | |
expected_bucket_version / expectedBucketVersion |
string | no | Bucket version_id from your last read. |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.appendText({
bucketId: 'bkt_example',
appendTextRequest: { path: 'notes/todo.txt', content: 'Call the customer back.' },
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
path |
string | |
appended |
boolean true |
|
appended_bytes / appendedBytes |
integer | |
newline_inserted / newlineInserted |
boolean |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Error codes named above: BUCKET_FILE_NOT_FOUND.
Guide: Files
Read file metadata
GET /v1/buckets/{bucket_id}/files/{file_id}
Reading metadata never marks the file read.
| Language | Call |
|---|---|
| TypeScript | client.api.getFile({ bucketId, fileId }) |
| Python | client.api.get_file(bucket_id, file_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.getFile({
bucketId: 'bkt_example',
fileId: 'df_example',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
file |
BucketFile | Fields as in List files. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Delete a file
DELETE /v1/buckets/{bucket_id}/files/{file_id}
Requires bucket-admin permission.
Files under _mailbox/ return MAILBOX_PATH_READ_ONLY; delete the ticket message instead. Files under _agents/ and _revdoku/ return RESERVED_PATH.
| Language | Call |
|---|---|
| TypeScript | client.api.deleteFile({ bucketId, fileId }) |
| Python | client.api.delete_file(bucket_id, file_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.deleteFile({
bucketId: 'bkt_example',
fileId: 'df_example',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
deleted |
boolean true |
|
id |
string | ID of the deleted resource. A webhook delete returns the bucket ID. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Error codes named above: MAILBOX_PATH_READ_ONLY, RESERVED_PATH.
Guide: Files
Get a download link for a file
GET /v1/buckets/{bucket_id}/files/{file_id}/download
Returns a temporary link and marks this version read. Send version_id for an older version.
| Language | Call |
|---|---|
| TypeScript | client.api.getFileDownload({ bucketId, fileId }) |
| Python | client.api.get_file_download(bucket_id, file_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes | |
version_id / versionId |
string | no | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.getFileDownload({
bucketId: 'bkt_example',
fileId: 'df_example',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
download |
Download | Temporary download link. Fetch url without an API key. Request a new link after it expires. |
download.url |
string (uri) | |
download.filename |
string | |
download.content_type / contentType |
string or null | |
download.expires_in / expiresIn |
integer 900 |
Seconds until the link expires. |
download.size_bytes / sizeBytes |
integer (int64) | |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
previously_read / previouslyRead |
boolean | Read state before this request, when tracking succeeded. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Rename or move a file in the same bucket
POST /v1/buckets/{bucket_id}/files/{file_id}/rename
Changes the path without uploading the content again.
| Language | Call |
|---|---|
| TypeScript | client.api.renameFile({ bucketId, fileId, renameFileRequest }) |
| Python | client.api.rename_file(bucket_id, file_id, rename_file_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes |
Request body: TypeScript renameFileRequest: RenameFileRequest, Python rename_file_request=RenameFileRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
new_path / newPath |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.renameFile({
bucketId: 'bkt_example',
fileId: 'df_example',
renameFileRequest: { newPath: 'notes/done.txt' },
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
operation |
string: rename, move, copy |
|
skipped |
boolean | True when the file already had the requested path. |
old_path / oldPath |
string | |
path |
string | |
source_bucket_id / sourceBucketId |
string | |
target_bucket_id / targetBucketId |
string | |
source_file / sourceFile |
BucketFile | Fields as in List files. |
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Move a file
POST /v1/buckets/{bucket_id}/files/{file_id}/move
Works in the same bucket or into target_bucket_id. Content is not uploaded again. Send target_path for an exact path or target_folder to keep the name.
| Language | Call |
|---|---|
| TypeScript | client.api.moveFile({ bucketId, fileId, moveFileRequest }) |
| Python | client.api.move_file(bucket_id, file_id, move_file_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes |
Request body: TypeScript moveFileRequest: MoveFileRequest, Python move_file_request=MoveFileRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
target_bucket_id / targetBucketId |
string | no | |
target_path / targetPath |
string | no | |
target_folder / targetFolder |
string | no | |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.moveFile({
bucketId: 'bkt_example',
fileId: 'df_example',
moveFileRequest: { targetFolder: 'archive' },
});
console.log(data);
Python
=
=
cURL
Response 201
| Field | Type | Description |
|---|---|---|
operation |
string: rename, move, copy |
|
skipped |
boolean | True when the file already had the requested path. |
old_path / oldPath |
string | |
path |
string | |
source_bucket_id / sourceBucketId |
string | |
target_bucket_id / targetBucketId |
string | |
source_file / sourceFile |
BucketFile | Fields as in List files. |
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Copy a file
POST /v1/buckets/{bucket_id}/files/{file_id}/copy
Works in the same bucket or into target_bucket_id. Content is not uploaded again. Send target_path for an exact path or target_folder to keep the name.
| Language | Call |
|---|---|
| TypeScript | client.api.copyFile({ bucketId, fileId, moveFileRequest }) |
| Python | client.api.copy_file(bucket_id, file_id, move_file_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes |
Request body: TypeScript moveFileRequest: MoveFileRequest, Python move_file_request=MoveFileRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
target_bucket_id / targetBucketId |
string | no | |
target_path / targetPath |
string | no | |
target_folder / targetFolder |
string | no | |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.copyFile({
bucketId: 'bkt_example',
fileId: 'df_example',
moveFileRequest: { targetPath: 'notes/todo-copy.txt' },
});
console.log(data);
Python
=
=
cURL
Response 201
| Field | Type | Description |
|---|---|---|
operation |
string: rename, move, copy |
|
skipped |
boolean | True when the file already had the requested path. |
old_path / oldPath |
string | |
path |
string | |
source_bucket_id / sourceBucketId |
string | |
target_bucket_id / targetBucketId |
string | |
source_file / sourceFile |
BucketFile | Fields as in List files. |
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
List file versions
GET /v1/buckets/{bucket_id}/files/{file_id}/versions
Newest first.
| Language | Call |
|---|---|
| TypeScript | client.api.listFileVersions({ bucketId, fileId }) |
| Python | client.api.list_file_versions(bucket_id, file_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes | |
account_id / accountId |
string | no | Select another granted account for this request. Omit to use the credential default. In JSON bodies, send account_id as a body field instead. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
limit |
integer | no | |
cursor |
string | no | pagination.next_cursor from the previous page. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.listFileVersions({
bucketId: 'bkt_example',
fileId: 'df_example',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
versions |
array of FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
pagination |
Pagination | One page of a list. Request the next page with cursor=next_cursor and the same filters. Cursors are opaque strings: do not build or decode them. Fields as in List files. |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Restore a file version
POST /v1/buckets/{bucket_id}/files/{file_id}/versions/{version_id}/restore
Creates a new current version with the content of the selected version. History is kept. Requires a plan with version restore.
| Language | Call |
|---|---|
| TypeScript | client.api.restoreFileVersion({ bucketId, fileId, versionId, actionRequest }) |
| Python | client.api.restore_file_version(bucket_id, file_id, version_id, action_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
file_id / fileId |
string | yes | |
version_id / versionId |
string | yes |
Request body: TypeScript actionRequest: ActionRequest, Python action_request=ActionRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.restoreFileVersion({
bucketId: 'bkt_example',
fileId: 'df_example',
versionId: 'dfrev_example',
actionRequest: {},
});
console.log(data);
Python
=
=
cURL
Response 201
| Field | Type | Description |
|---|---|---|
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
restored_from / restoredFrom |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Files
Prepare a file upload (step 1 of 3)
POST /v1/buckets/{bucket_id}/uploads
Returns a storage URL. Then PUT the bytes to direct_upload.url with exactly direct_upload.headers and no API key (step 2), and save the file with POST /v1/buckets/{bucket_id}/files (step 3). Identical content at the same path returns skipped=true and duplicate=true without an upload.
| Language | Call |
|---|---|
| TypeScript | client.uploadFile({ bucketId, path, content }) (runs all three upload steps) |
| Python | client.upload_file(bucket_id, path, content) (runs all three upload steps) |
| TypeScript, full API | client.api.prepareFileUpload({ bucketId, prepareFileUploadRequest }) |
| Python, full API | client.api.prepare_file_upload(bucket_id, prepare_file_upload_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes |
Request body: TypeScript prepareFileUploadRequest: PrepareFileUploadRequest, Python prepare_file_upload_request=PrepareFileUploadRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Destination path in the bucket. Paths under _mailbox/ return MAILBOX_PATH_READ_ONLY; paths under _agents/ and _revdoku/ return RESERVED_PATH. |
existing_file_policy / existingFilePolicy |
string: replace, skip |
no | skip returns the existing file without an upload when the path exists. |
blob |
UploadBlob | yes | |
blob.filename |
string | yes | |
blob.byte_size / byteSize |
integer (int64) | yes | |
blob.checksum |
string | yes | |
blob.content_type / contentType |
string | yes | |
blob.sha256 |
string | yes | |
blob.purpose |
string: bucket_file |
yes | |
account_id / accountId |
string | no | |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
// uploadFile runs all three steps: prepare, PUT the bytes to storage, save.
const { data } = await client.uploadFile({
bucketId: 'bkt_example',
path: 'notes/todo.txt',
content: 'Call the customer back.',
contentType: 'text/plain',
});
console.log(data.file.id, data.version.id);
Python
=
# upload_file runs all three steps: prepare, PUT the bytes to storage, save.
=
cURL
# Step 1: prepare. checksum is the base64 MD5 and sha256 the hex SHA-256 of the bytes.
# Step 2: PUT the bytes to direct_upload.url with exactly direct_upload.headers. Send no API key.
# Step 3: save the file with signed_id from step 1.
Response 201, 200
| Field | Type | Description |
|---|---|---|
signed_id / signedId |
string | |
filename |
string | |
byte_size / byteSize |
integer (int64) | |
checksum |
string | |
content_type / contentType |
string | |
direct_upload / directUpload |
StorageUpload | |
direct_upload.url |
string (uri) | |
direct_upload.headers |
object | |
file |
BucketFile | Fields as in List files. |
version |
FileVersion | Fields as in Save an uploaded file (step 3 of 3). |
skipped |
boolean | |
duplicate |
boolean | |
reason |
string | |
details |
object |
Example 201:
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Upload a file
Start a batch upload
POST /v1/buckets/{bucket_id}/upload_sessions
Starts an upload session for many files and returns storage targets for the first files. The session locks the bucket for other writers; incoming email still arrives. Send files in batches with POST …/uploads, PUT the bytes to each upload.url, save each batch with POST …/finalize_batch, then call POST …/finalize. Unsaved uploads are discarded when the session expires.
| Language | Call |
|---|---|
| TypeScript | client.api.createUploadSession({ bucketId, createUploadSessionRequest }) |
| Python | client.api.create_upload_session(bucket_id, create_upload_session_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes |
Request body: TypeScript createUploadSessionRequest: CreateUploadSessionRequest, Python create_upload_session_request=CreateUploadSessionRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
files |
array of UploadSessionFile | no | Optional first batch. Send the rest with POST …/uploads. |
files[].path |
string | yes | Destination path in the bucket. Paths under _mailbox/ return MAILBOX_PATH_READ_ONLY; paths under _agents/ and _revdoku/ return RESERVED_PATH. |
files[].byte_size / byteSize |
integer (int64) | yes | |
files[].content_type / contentType |
string | yes | |
files[].checksum |
string | yes | Base64-encoded MD5 of the bytes. |
files[].sha256 |
string | yes | Hex SHA-256 of the bytes. |
expected_file_count / expectedFileCount |
integer | no | Total files you will upload in this session. Used for progress. |
client_session_key / clientSessionKey |
string | no | Your own key for this batch. Repeating it with the same credential while the session is pending returns that session instead of a new one. |
existing_file_policy / existingFilePolicy |
string: replace, skip |
no | skip keeps existing files at the same paths. |
expected_bucket_version / expectedBucketVersion |
string | no | Bucket version_id from your last read. A different current version returns BUCKET_VERSION_CONFLICT. |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { createHash } from 'node:crypto';
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const bucketId = 'bkt_example';
const bytes = Buffer.from('Quarterly numbers');
// 1. Start the session with the first batch. Send more files later with client.api.addUploadSessionFiles.
const { data: started } = await client.api.createUploadSession({
bucketId,
createUploadSessionRequest: {
expectedFileCount: 1,
files: [{
path: 'reports/q3.txt',
byteSize: bytes.length,
contentType: 'text/plain',
checksum: createHash('md5').update(bytes).digest('base64'),
sha256: createHash('sha256').update(bytes).digest('hex'),
}],
},
});
const sessionId = started.bucketUploadSession.id;
// 2. PUT the bytes to each storage target with exactly its headers. Send no API key.
for (const item of started.uploads ?? []) {
const response = await fetch(item.upload.url, { method: 'PUT', headers: item.upload.headers, body: new Uint8Array(bytes) });
if (!response.ok) throw new Error(`Storage upload failed: HTTP ${response.status}`);
}
// 3. Save the uploaded batch, then finish the session.
await client.api.saveUploadSessionBatch({ bucketId, sessionId });
const { data: finished } = await client.api.finalizeUploadSession({
bucketId,
sessionId,
finalizeUploadSessionRequest: { complete: true },
});
console.log(finished.bucketUploadSession.status);
Python
=
=
= b
# 1. Start the session with the first batch. Send more files later with client.api.add_upload_session_files.
= .
=
# 2. PUT the bytes to each storage target with exactly its headers. Send no API key.
=
# 3. Save the uploaded batch, then finish the session.
=
cURL
# 1. Start the session. checksum is the base64 MD5 and sha256 the hex SHA-256 of each file.
# 2. PUT the bytes to each uploads[].upload.url with exactly uploads[].upload.headers. Send no API key.
# 3. Save the uploaded batch, then finish the session.
Response 201
| Field | Type | Description |
|---|---|---|
bucket_upload_session / bucketUploadSession |
UploadSession | |
bucket_upload_session.id |
string | |
bucket_upload_session.bucket_id / bucketId |
string | |
bucket_upload_session.status |
string: pending, finalized, expired, failed |
pending accepts uploads. failed means canceled; its unsaved files were discarded. |
bucket_upload_session.base_bucket_version_id / baseBucketVersionId |
string or null | Bucket version when the session started. |
bucket_upload_session.target_bucket_version_id / targetBucketVersionId |
string or null | Bucket version created by the final save. |
bucket_upload_session.uploads_expire_at / uploadsExpireAt |
string (date-time) or null | Unsaved uploads are discarded after this time. |
bucket_upload_session.session_closed_at / sessionClosedAt |
string (date-time) or null | |
bucket_upload_session.files_count / filesCount |
integer | |
bucket_upload_session.pending_files_count / pendingFilesCount |
integer | |
bucket_upload_session.uploaded_files_count / uploadedFilesCount |
integer | |
bucket_upload_session.finalized_files_count / finalizedFilesCount |
integer | |
bucket_upload_session.skipped_files_count / skippedFilesCount |
integer | |
bucket_upload_session.skipped_code_counts / skippedCodeCounts |
object | Skipped files per skip code. |
bucket_upload_session.failed_files_count / failedFilesCount |
integer | |
uploads |
array of UploadSessionUpload | Create and uploads only: storage targets for the files that need bytes. |
uploads[].input_index / inputIndex |
integer | |
uploads[].path |
string | |
uploads[].name |
string | |
uploads[].signed_blob_id / signedBlobId |
string | |
uploads[].upload |
StorageUpload | Fields: url, headers. |
uploaded |
array of UploadedFile | Files saved by this request (at most 250). |
uploaded[].input_index / inputIndex |
integer | |
uploaded[].path |
string | |
uploaded[].file |
BucketFile | Fields: see BucketFile in OpenAPI. |
uploaded[].version |
FileVersion | Fields: see FileVersion in OpenAPI. |
uploaded[].created |
boolean | False when the path received a new version. |
uploaded_count / uploadedCount |
integer | |
uploaded_truncated / uploadedTruncated |
boolean | |
created_count / createdCount |
integer | |
updated_count / updatedCount |
integer | |
skipped |
array of FileUploadSkip | Files left unchanged, for example identical content (DUPLICATE_FILE_VERSION). Fields as in Save an uploaded file (step 3 of 3). |
skipped_count / skippedCount |
integer | |
skipped_truncated / skippedTruncated |
boolean | |
finalize_pending / finalizePending |
boolean | Finalize only: true when more files remain. Call finalize again after Retry-After. |
remaining_files_count / remainingFilesCount |
integer | |
message |
string |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Upload many files
Get storage targets for a batch
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/uploads
Returns upload.url and upload.headers for each file that needs bytes. Repeating a path returns a fresh target, for example after a failed PUT. Files with identical content are skipped.
| Language | Call |
|---|---|
| TypeScript | client.api.addUploadSessionFiles({ bucketId, sessionId, addUploadSessionFilesRequest }) |
| Python | client.api.add_upload_session_files(bucket_id, session_id, add_upload_session_files_request) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
session_id / sessionId |
string | yes | bucket_upload_session.id from the create response. |
Request body: TypeScript addUploadSessionFilesRequest: AddUploadSessionFilesRequest, Python add_upload_session_files_request=AddUploadSessionFilesRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
files |
array of UploadSessionFile | yes | |
files[].path |
string | yes | Destination path in the bucket. Paths under _mailbox/ return MAILBOX_PATH_READ_ONLY; paths under _agents/ and _revdoku/ return RESERVED_PATH. |
files[].byte_size / byteSize |
integer (int64) | yes | |
files[].content_type / contentType |
string | yes | |
files[].checksum |
string | yes | Base64-encoded MD5 of the bytes. |
files[].sha256 |
string | yes | Hex SHA-256 of the bytes. |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.addUploadSessionFiles({
bucketId: 'bkt_example',
sessionId: 'bus_example',
addUploadSessionFilesRequest: {
files: [
{
path: 'notes/todo.txt',
byteSize: 23,
contentType: 'text/plain',
checksum: 'BASE64_MD5_OF_CONTENT',
sha256: 'HEX_SHA256_OF_CONTENT',
},
],
},
});
console.log(data);
Python
=
=
cURL
Response 201
| Field | Type | Description |
|---|---|---|
bucket_upload_session / bucketUploadSession |
UploadSession | Fields as in Start a batch upload. |
uploads |
array of UploadSessionUpload | Create and uploads only: storage targets for the files that need bytes. Fields as in Start a batch upload. |
uploaded |
array of UploadedFile | Files saved by this request (at most 250). Fields as in Start a batch upload. |
uploaded_count / uploadedCount |
integer | |
uploaded_truncated / uploadedTruncated |
boolean | |
created_count / createdCount |
integer | |
updated_count / updatedCount |
integer | |
skipped |
array of FileUploadSkip | Files left unchanged, for example identical content (DUPLICATE_FILE_VERSION). Fields as in Save an uploaded file (step 3 of 3). |
skipped_count / skippedCount |
integer | |
skipped_truncated / skippedTruncated |
boolean | |
finalize_pending / finalizePending |
boolean | Finalize only: true when more files remain. Call finalize again after Retry-After. |
remaining_files_count / remainingFilesCount |
integer | |
message |
string |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Upload many files
Save uploaded files
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/finalize_batch
Saves up to limit files whose bytes were uploaded. Call it after each batch to keep each request short. DIRECT_UPLOAD_NOT_FOUND names a file whose bytes are missing in error.details.path; upload it again and retry.
| Language | Call |
|---|---|
| TypeScript | client.api.saveUploadSessionBatch({ bucketId, sessionId }) |
| Python | client.api.save_upload_session_batch(bucket_id, session_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
session_id / sessionId |
string | yes | bucket_upload_session.id from the create response. |
Request body (optional): TypeScript saveUploadSessionBatchRequest: SaveUploadSessionBatchRequest, Python save_upload_session_batch_request=SaveUploadSessionBatchRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
limit |
integer | no | Files to save in this request. |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.saveUploadSessionBatch({
bucketId: 'bkt_example',
sessionId: 'bus_example',
});
console.log(data);
Python
=
=
cURL
Response 200
| Field | Type | Description |
|---|---|---|
bucket_upload_session / bucketUploadSession |
UploadSession | Fields as in Start a batch upload. |
uploads |
array of UploadSessionUpload | Create and uploads only: storage targets for the files that need bytes. Fields as in Start a batch upload. |
uploaded |
array of UploadedFile | Files saved by this request (at most 250). Fields as in Start a batch upload. |
uploaded_count / uploadedCount |
integer | |
uploaded_truncated / uploadedTruncated |
boolean | |
created_count / createdCount |
integer | |
updated_count / updatedCount |
integer | |
skipped |
array of FileUploadSkip | Files left unchanged, for example identical content (DUPLICATE_FILE_VERSION). Fields as in Save an uploaded file (step 3 of 3). |
skipped_count / skippedCount |
integer | |
skipped_truncated / skippedTruncated |
boolean | |
finalize_pending / finalizePending |
boolean | Finalize only: true when more files remain. Call finalize again after Retry-After. |
remaining_files_count / remainingFilesCount |
integer | |
message |
string |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Error codes named above: DIRECT_UPLOAD_NOT_FOUND.
Guide: Upload many files
Finish or cancel a batch upload
POST /v1/buckets/{bucket_id}/upload_sessions/{session_id}/finalize
complete=true saves the remaining uploaded files, creates one bucket version and releases the lock. A 202 response with finalize_pending=true means more files remain: call finalize again after Retry-After. complete=false cancels the session and discards its unsaved files. Finalizing a finished session returns it unchanged.
| Language | Call |
|---|---|
| TypeScript | client.api.finalizeUploadSession({ bucketId, sessionId }) |
| Python | client.api.finalize_upload_session(bucket_id, session_id) |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
bucket_id / bucketId |
string | yes | |
session_id / sessionId |
string | yes | bucket_upload_session.id from the create response. |
Request body (optional): TypeScript finalizeUploadSessionRequest: FinalizeUploadSessionRequest, Python finalize_upload_session_request=FinalizeUploadSessionRequest(...).
| Field | Type | Required | Description |
|---|---|---|---|
complete |
boolean | no | false cancels the session. |
account_id / accountId |
string | no | Select another granted account for this request. |
reason |
string or null | no | Optional purpose for this action. AI agents should explain intentional reads and changes when known; omit when unknown. Do not include secrets, file contents, or transcripts. |
Examples
TypeScript
import { Revdoku } from 'revdoku';
const client = new Revdoku();
const { data } = await client.api.finalizeUploadSession({
bucketId: 'bkt_example',
sessionId: 'bus_example',
finalizeUploadSessionRequest: { complete: true },
});
console.log(data);
Python
=
=
cURL
Response 200, 202
| Field | Type | Description |
|---|---|---|
bucket_upload_session / bucketUploadSession |
UploadSession | Fields as in Start a batch upload. |
uploads |
array of UploadSessionUpload | Create and uploads only: storage targets for the files that need bytes. Fields as in Start a batch upload. |
uploaded |
array of UploadedFile | Files saved by this request (at most 250). Fields as in Start a batch upload. |
uploaded_count / uploadedCount |
integer | |
uploaded_truncated / uploadedTruncated |
boolean | |
created_count / createdCount |
integer | |
updated_count / updatedCount |
integer | |
skipped |
array of FileUploadSkip | Files left unchanged, for example identical content (DUPLICATE_FILE_VERSION). Fields as in Save an uploaded file (step 3 of 3). |
skipped_count / skippedCount |
integer | |
skipped_truncated / skippedTruncated |
boolean | |
finalize_pending / finalizePending |
boolean | Finalize only: true when more files remain. Call finalize again after Retry-After. |
remaining_files_count / remainingFilesCount |
integer | |
message |
string |
Errors
| Status | Description |
|---|---|
| 4xx, 5xx | Request failed: branch on error.code. A 429 or 503 with Retry-After: wait that many seconds, then try the read again. 429 EMAIL_SEND_RATE_LIMITED has no Retry-After. |
Guide: Upload many files