Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/docs/content/docs/cli/files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ sim files versions revert <fileId> <version> [options]

| Option | Required | Description |
| --- | --- | --- |
| `--expected-current-version <value>` | No | Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number. |
| `--expected-current-version <value>` | No | Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number, so prefer `expectedRevision` to guard content. |
| `--expected-revision <value>` | No | Revert only while the file still holds the content this revision names, as returned by Get File Metadata or an earlier write; otherwise the request fails with `409`. Unlike a version number, it also catches edits that folded into the current version. |

</CommandTable>

Expand Down Expand Up @@ -354,6 +355,7 @@ sim files edit <fileId> [options]
| Option | Required | Description |
| --- | --- | --- |
| `--edit <json\|@file>` | Yes | One edit object: &#123;"mode":"search_replace","search":"old","content":"new","replaceAll":false&#125;, &#123;"mode":"replace_between","beforeAnchor":"start line","afterAnchor":"end line","content":"new"&#125;, &#123;"mode":"insert_after","anchor":"line","content":"new"&#125;, or &#123;"mode":"delete_between","startAnchor":"first line deleted","endAnchor":"ending line kept"&#125;. Anchored modes also accept occurrence starting at 1 (JSON, or @path / @- to read a file or stdin). |
| `--expected-revision <value>` | No | Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on. |

</CommandTable>

Expand Down Expand Up @@ -613,6 +615,7 @@ sim files set-content <fileId> [options]
| --- | --- | --- |
| `--content <value>` | Yes | Complete replacement content for the file. The 70,000,000-character bound guards the JSON envelope; the decoded bytes must be at most 50 MiB, and a longer base64 payload is rejected with `413`. |
| `--encoding <value>` | No | Content encoding. Accepted values: `utf-8`, `base64`. |
| `--expected-revision <value>` | No | Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on. |

</CommandTable>

Expand Down
5 changes: 4 additions & 1 deletion apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,8 @@ sim files versions revert <fileId> <version> [options]

| Option | Required | Description |
| --- | --- | --- |
| `--expected-current-version <value>` | No | Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number. |
| `--expected-current-version <value>` | No | Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number, so prefer `expectedRevision` to guard content. |
| `--expected-revision <value>` | No | Revert only while the file still holds the content this revision names, as returned by Get File Metadata or an earlier write; otherwise the request fails with `409`. Unlike a version number, it also catches edits that folded into the current version. |

</CommandTable>

Expand Down Expand Up @@ -1103,6 +1104,7 @@ sim files edit <fileId> [options]
| Option | Required | Description |
| --- | --- | --- |
| `--edit <json\|@file>` | Yes | One edit object: &#123;"mode":"search_replace","search":"old","content":"new","replaceAll":false&#125;, &#123;"mode":"replace_between","beforeAnchor":"start line","afterAnchor":"end line","content":"new"&#125;, &#123;"mode":"insert_after","anchor":"line","content":"new"&#125;, or &#123;"mode":"delete_between","startAnchor":"first line deleted","endAnchor":"ending line kept"&#125;. Anchored modes also accept occurrence starting at 1 (JSON, or @path / @- to read a file or stdin). |
| `--expected-revision <value>` | No | Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on. |

</CommandTable>

Expand Down Expand Up @@ -1382,6 +1384,7 @@ sim files set-content <fileId> [options]
| --- | --- | --- |
| `--content <value>` | Yes | Complete replacement content for the file. The 70,000,000-character bound guards the JSON envelope; the decoded bytes must be at most 50 MiB, and a longer base64 payload is rejected with `413`. |
| `--encoding <value>` | No | Content encoding. Accepted values: `utf-8`, `base64`. |
| `--expected-revision <value>` | No | Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on. |

</CommandTable>

Expand Down
8 changes: 8 additions & 0 deletions apps/docs/content/docs/integrations/file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Create a new workspace file, either from text content or from an existing file.
| `fileInput` | file | No | An existing file to store in the workspace, such as one produced by an earlier tool. Use this for anything that is not text — PDFs, images, audio, archives. Provide exactly one of content or fileInput. |
| `contentType` | string | No | MIME type for new files \(e.g., "text/plain"\). Auto-detected from the file extension, or taken from the stored file, if omitted. |
| `overwrite` | boolean | No | Replace the contents of an existing file at the exact target path \(folder and name\) instead of creating a suffixed copy. Creates the file when that path does not exist yet. |
| `expectedRevision` | string | No | Refuse the write unless the file still holds the content this revision names, as returned by Get File or an earlier write. Use it so an edit computed from what you read cannot overwrite someone else’s change. |

#### Output

Expand All @@ -148,6 +149,8 @@ Create a new workspace file, either from text content or from an existing file.
| `name` | string | File name |
| `size` | number | File size in bytes |
| `url` | string | URL to access the file |
| `version` | number | Version number of the content this write recorded |
| `revision` | string | Opaque token for the content this write produced. Pass it back as expectedRevision to make a later write conditional on nothing having changed since. |

### File Append

Expand All @@ -171,6 +174,8 @@ Append content to an existing workspace file. The file must already exist. Conte
| `name` | string | File name |
| `size` | number | File size in bytes |
| `url` | string | URL to access the file |
| `version` | number | Version number of the content this write recorded |
| `revision` | string | Opaque token for the content this write produced. Pass it back as expectedRevision to make a later write conditional on nothing having changed since. |

### Apply File Edit

Expand All @@ -194,6 +199,7 @@ Apply one precise edit to an existing text file without rewriting it. Use search
| `startAnchor` | string | No | For delete_between, the complete first line to delete. The start anchor is removed. |
| `endAnchor` | string | No | For delete_between, the complete ending boundary line. The end anchor remains in the file. |
| `occurrence` | number | No | For anchored edits, which matching anchor occurrence to use, starting at 1. Defaults to 1. |
| `expectedRevision` | string | No | Refuse the edit unless the file still holds the content this revision names, as returned by Get File or an earlier write. Use it so an edit computed from what you read cannot overwrite someone else’s change. |

#### Output

Expand All @@ -203,6 +209,8 @@ Apply one precise edit to an existing text file without rewriting it. Use search
| `name` | string | File name |
| `size` | number | File size in bytes |
| `lineCount` | number | Lines in the file after the edit |
| `version` | number | Version number of the content this edit recorded |
| `revision` | string | Opaque token for the content this edit produced. Pass it back as expectedRevision to make a later write conditional on nothing having changed since. |

### File Compress

Expand Down
165 changes: 159 additions & 6 deletions apps/docs/openapi-v2-files-audit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2744,7 +2744,7 @@
"put": {
"operationId": "updateFileContent",
"summary": "Replace File Content",
"description": "Replace the complete contents of an existing file from UTF-8 or base64 input.\n\nOAuth scope: `api:write`.",
"description": "Replace the complete contents of an existing file from UTF-8 or base64 input. A stale `expectedRevision`, or a write that raced this one, returns `409`; re-read before retrying.\n\nOAuth scope: `api:write`.",
"x-sim-operation": "files.update_content",
"x-oauth-scope": "api:write",
"tags": ["Files"],
Expand Down Expand Up @@ -2791,7 +2791,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/V2FileResponse"
"$ref": "#/components/schemas/V2WrittenFileResponse"
}
}
}
Expand All @@ -2808,6 +2808,9 @@
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/Conflict"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
},
Expand Down Expand Up @@ -4988,10 +4991,15 @@
"description": "Workspace that owns the file."
},
"expectedCurrentVersion": {
"description": "Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number.",
"description": "Revert only while this is still the current version; otherwise the request fails with `409`. Omit to revert whatever is current. Collaborative edits and repeated workflow writes that fold into the current version keep its number, so prefer `expectedRevision` to guard content.",
"type": "integer",
"minimum": 1,
"maximum": 2147483647
},
"expectedRevision": {
"description": "Revert only while the file still holds the content this revision names, as returned by Get File Metadata or an earlier write; otherwise the request fails with `409`. Unlike a version number, it also catches edits that folded into the current version.",
"type": "string",
"minLength": 1
}
},
"required": ["workspaceId"],
Expand Down Expand Up @@ -5315,6 +5323,10 @@
],
"description": "Current public-share state, or null when the file has never been shared."
},
"revision": {
"description": "Opaque token for the file's current content. Send it back as `expectedRevision` so a write or revert is refused when the content moved on. Absent for a file with no recorded content version.",
"type": "string"
},
"currentVersion": {
"type": "integer",
"minimum": 1,
Expand Down Expand Up @@ -5368,7 +5380,8 @@
"updatedAt": "2026-01-15T10:30:00Z",
"deletedAt": null,
"share": null,
"currentVersion": 1
"currentVersion": 1,
"revision": "d2ZfVjFTdEdYUjh6NWpkSGk2Qm15VDkxOjIwMjYtMDEtMTVUMTA6MzA6MDAuMDAwWg"
}
},
{
Expand All @@ -5382,7 +5395,7 @@
"folderPath": "/Engineering",
"uploadedByEmail": "jane@example.com",
"uploadedAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-16T09:12:00Z",
"deletedAt": null,
"share": {
"id": "shr_8Hf3kL9wQ2mNpXr6Tz1Vb",
Expand All @@ -5395,7 +5408,8 @@
"hasPassword": false,
"allowedEmails": []
},
"currentVersion": 3
"currentVersion": 3,
"revision": "d2ZfVjFTdEdYUjh6NWpkSGk2Qm15VDkxOjIwMjYtMDEtMTZUMDk6MTI6MDAuMDAwWg"
Comment thread
waleedlatif1 marked this conversation as resolved.
}
}
]
Expand Down Expand Up @@ -5799,6 +5813,10 @@
"minimum": 0,
"maximum": 9007199254740991,
"description": "Lines the file holds after the edit."
},
"revision": {
"description": "Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.",
"type": "string"
}
},
"required": ["file", "lineCount"],
Expand Down Expand Up @@ -5965,6 +5983,11 @@
}
],
"description": "One exact or anchor-based edit: search_replace, replace_between, insert_after, or delete_between."
},
"expectedRevision": {
"description": "Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on.",
"type": "string",
"minLength": 1
}
},
"required": ["workspaceId", "edit"],
Expand Down Expand Up @@ -6121,6 +6144,131 @@
}
]
},
"V2WrittenFile": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique file identifier.",
"examples": ["wf_V1StGXR8z5jdHi6BmyT91"]
},
"webUrl": {
"type": "string",
"format": "uri",
"description": "Canonical absolute URL for opening this resource in the Sim web application."
},
"name": {
"type": "string",
"description": "Original file name.",
"examples": ["data.csv"]
},
"size": {
"type": "number",
"minimum": 0,
"description": "Size in bytes of the stored file. For a generated document (docx, pptx, pdf, xlsx) this is the generation source, not the rendered document, so it does not predict how many bytes downloading the file returns.",
"examples": [1024]
},
"type": {
"type": "string",
"description": "MIME type of the stored file. For a generated document (docx, pptx, pdf, xlsx) this is the generation source type, not the rendered document type a download serves.",
"examples": ["text/csv"]
},
"key": {
"type": "string",
"description": "Storage key for the file.",
"examples": ["workspace/example/data.csv"]
},
"folderPath": {
"type": "string",
"title": "Folder path",
"description": "Canonical containing-folder path. `/` is the workspace root.",
"maxLength": 4096
},
"uploadedByEmail": {
"type": "string",
"format": "email",
"pattern": "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$",
"description": "Current email address of the uploader.",
"examples": ["jane@example.com"]
},
"uploadedAt": {
"type": "string",
"description": "ISO 8601 timestamp when the file was uploaded.",
"format": "date-time",
"examples": ["2026-01-15T10:30:00Z"]
},
"updatedAt": {
"type": "string",
"description": "ISO 8601 timestamp of the last content or metadata write.",
"format": "date-time",
"examples": ["2026-01-15T10:30:00Z"]
},
"deletedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "ISO 8601 timestamp when the file was archived by deleting it, or null while the file is active. Only an archived-scope file list returns files with a non-null value.",
"format": "date-time",
"examples": ["2026-01-16T09:00:00Z"]
},
"revision": {
"description": "Opaque token for the content this write produced. Send it back as `expectedRevision` on the next write. Absent for a file with no recorded content version.",
"type": "string"
}
},
"required": [
"id",
"webUrl",
"name",
"size",
"type",
"key",
"folderPath",
"uploadedByEmail",
"uploadedAt",
"updatedAt",
"deletedAt"
],
"additionalProperties": false,
"title": "Written file",
"description": "A workspace file after a content replacement, with the revision it produced."
},
"V2WrittenFileResponse": {
"type": "object",
"properties": {
"data": {
"description": "Response data.",
"$ref": "#/components/schemas/V2WrittenFile"
}
},
"required": ["data"],
"additionalProperties": false,
"title": "Written file response",
"description": "A workspace file after a content replacement, with the revision the write produced.",
"examples": [
{
"data": {
"id": "wf_V1StGXR8z5jdHi6BmyT91",
"webUrl": "https://www.sim.ai/workspace/a91c4b2e-6d3f-4e8a-b5c7-0d9e2f1a8c64/files/wf_V1StGXR8z5jdHi6BmyT91",
"name": "data.csv",
"size": 1024,
"type": "text/csv",
"key": "workspace/example/data.csv",
"folderPath": "/Engineering",
"uploadedByEmail": "jane@example.com",
"uploadedAt": "2026-01-15T10:30:00Z",
"updatedAt": "2026-01-15T10:30:00Z",
"deletedAt": null,
"revision": "d2ZfVjFTdEdYUjh6NWpkSGk2Qm15VDkxOjIwMjYtMDEtMTVUMTA6MzA6MDAuMDAwWg"
}
}
]
},
"UpdateFileContentRequest": {
"type": "object",
"properties": {
Expand All @@ -6140,6 +6288,11 @@
"description": "Encoding of the content field.",
"type": "string",
"enum": ["utf-8", "base64"]
},
"expectedRevision": {
"description": "Revision from Get File Metadata or an earlier write; the request is refused with `409` when the content moved on.",
"type": "string",
"minLength": 1
}
},
"required": ["workspaceId", "content"],
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/v2/files/[fileId]/content/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ describe('PUT /api/v2/files/[fileId]/content', () => {
uploadedAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-03T00:00:00.000Z',
deletedAt: null,
/** The token for the content this write produced, for the caller's next conditional write. */
revision: expect.any(String),
},
})
expect(mocks.updateContent).toHaveBeenCalledWith({
Expand Down
Loading
Loading