[Table of contents](README.md#table-of-contents) # Shared drives A shared drive is a federated sharing in `io.cozy.files` between several Cozy instances. The root can currently be either: - a directory - a single file A member doesn't have the files in their Cozy, but can access them via the stack playing a proxy role. The root kind is exposed on the sharing payload with `drive_root_type`: - `directory` - `file` For drives created on an organization instance, the sharing payload also exposes `org_drive: true`. This is an additive classification flag for clients; it does not change the underlying drive-sharing behavior. ## Creating a shared drive There are two ways to create a shared drive: ### Simple method: Use `POST /sharings/drives` Use the [`POST /sharings/drives`](#post-sharingsdrives) endpoint either to: - convert an existing file or directory into a shared drive with `folder_id` or `file_id` - create a brand new directory-root shared drive with `name` This is the recommended approach as it handles validation and setup automatically. ### Manual method To create a shared drive manually (typically on the organization Cozy), follow these steps: 1. Create the root resource to share in the desired parent directory: - either a folder with the name of the shared drive - or a file that will become the root of the shared drive 2. Create a sharing with the `drive: true` attribute, and one rule for the shared root (with `none` for `add`, `update` and `remove` attributes). New recipient shortcuts are created at the instance root. Updating an existing shortcut preserves its ID and location. ## Managing shared drives ### GET /sharings/drives The `GET /sharings/drives` route returns the list of shared drives. When a drive was created on an organization instance, its attributes include `org_drive: true`. #### Request ```http GET /sharings/drives HTTP/1.1 Host: acme.example.net Accept: application/vnd.api+json ``` #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json ``` ```json { "data": [ { "type": "io.cozy.sharings", "id": "aae62886e79611ef8381fb83ff72e425", "attributes": { "drive": true, "drive_root_type": "directory", "org_drive": true, "owner": true, "description": "Drive for the product team", "app_slug": "drive", "created_at": "2025-02-10T11:08:08Z", "updated_at": "2025-02-10T12:10:43Z", "members": [ { "status": "owner", "public_name": "ACME", "email": "admin@acme.example.net", "instance": "acme.example.net" }, { "status": "pending", "name": "Alice", "email": "alice@example.net" }, { "status": "pending", "name": "Bob", "email": "bob@example.net" } ], "rules": [ { "title": "Product team", "doctype": "io.cozy.files", "values": [ "357665ec-e797-11ef-94fb-f3d08ccb3ff5" ], "add": "none", "update": "none", "remove": "none" } ] }, "meta": { "rev": "1-272ba74b868f" }, "links": { "self": "/sharings/aae62886e79611ef8381fb83ff72e425" } } ] } ``` ### POST /sharings/drives Creates a new shared drive. The endpoint supports three mutually exclusive modes: - pass `folder_id` to convert an existing file or directory into a shared drive - pass `file_id` to convert an existing file or directory into a shared drive - pass `name` to create a new directory at the instance root and share it If the target Cozy is an organization instance, the created sharing is also marked with `org_drive: true`. `folder_id` is the legacy root identifier alias. `file_id` is the preferred identifier alias for clients migrating to the new API. In both cases, the actual root kind is determined from the referenced `io.cozy.files` document and is exposed back as `drive_root_type`. When an existing root is used through `folder_id` or `file_id`, that root must: - Exist - Not already have a sharing itself - Not be inside another shared folder Additional rules for directory roots: - Must not be a system folder (`root`, `trash`, `shared-with-me`, `no-longer-shared`) - Must not be inside the trash - Must not contain any subfolder that already has a sharing Additional rules for file roots: - Must not be trashed #### Request ```http POST /sharings/drives HTTP/1.1 Host: acme.example.net Content-Type: application/vnd.api+json Accept: application/vnd.api+json ``` ```json { "data": { "type": "io.cozy.sharings", "attributes": { "description": "Project Documents", "folder_id": "357665ec-e797-11ef-94fb-f3d08ccb3ff5" }, "relationships": { "recipients": { "data": [ {"id": "contact-id-1", "type": "io.cozy.contacts"}, {"id": "group-id-1", "type": "io.cozy.contacts.groups"} ] }, "read_only_recipients": { "data": [ {"id": "contact-id-2", "type": "io.cozy.contacts"} ] } } } } ``` Or create a brand new shared drive directly: ```json { "data": { "type": "io.cozy.sharings", "attributes": { "name": "Product Team" } } } ``` Or create a file-root shared drive explicitly: ```json { "data": { "type": "io.cozy.sharings", "attributes": { "description": "Quarterly report", "file_id": "357665ec-e797-11ef-94fb-f3d08ccb3ff5" } } } ``` **Attributes:** | Attribute | Required | Description | |---------------|----------|-------------| | `folder_id` | No | Legacy root ID alias. Can reference an existing file or directory to convert into a shared drive | | `file_id` | No | Preferred root ID alias. Can reference an existing file or directory to convert into a shared drive | | `name` | No | The name of the directory to create at the instance root for a new directory-root shared drive | | `description` | No | A description for the shared drive. If not provided, defaults to the root resource name | Exactly one of `folder_id`, `file_id`, or `name` must be provided. **Relationships:** | Relationship | Description | |------------------------|-------------| | `recipients` | Contacts or groups to add as read-write members | | `read_only_recipients` | Contacts or groups to add as read-only members | Both relationships are optional - you can create a shared drive without any recipients and add them later. #### Response (Success) ```http HTTP/1.1 201 Created Content-Type: application/vnd.api+json ``` ```json { "data": { "type": "io.cozy.sharings", "id": "aae62886e79611ef8381fb83ff72e425", "attributes": { "drive": true, "drive_root_type": "directory", "owner": true, "description": "Project Documents", "app_slug": "drive", "created_at": "2025-02-10T11:08:08Z", "updated_at": "2025-02-10T11:08:08Z", "members": [ { "status": "owner", "public_name": "ACME", "email": "admin@acme.example.net", "instance": "acme.example.net" }, { "status": "pending", "name": "Alice", "email": "alice@example.net" } ], "rules": [ { "title": "Project Documents", "doctype": "io.cozy.files", "values": [ "357665ec-e797-11ef-94fb-f3d08ccb3ff5" ], "add": "none", "update": "none", "remove": "none" } ] }, "meta": { "rev": "1-272ba74b868f" }, "links": { "self": "/sharings/aae62886e79611ef8381fb83ff72e425" } } } ``` #### Error Responses | Status | Error | Description | |--------|-------|-------------| | 400 | Bad Request | Invalid JSON body | | 403 | Forbidden | Insufficient permissions to create a sharing | | 404 | Not Found | The file or directory with the given `folder_id` or `file_id` does not exist | | 409 | Conflict | The root already has a sharing, is inside a shared folder, contains a shared subfolder, or the new `name` already exists at the instance root | | 400 | Bad Request | Invalid request: missing all of `folder_id`, `file_id`, and `name`, conflicting attributes, invalid root type, system folder, trashed root, or invalid `name` | **Example error (root already shared):** ```http HTTP/1.1 409 Conflict Content-Type: application/vnd.api+json ``` ```json { "errors": [ { "status": "409", "title": "Conflict", "detail": "Folder already has an existing sharing" } ] } ``` ### POST /sharings/drives/:id/accept Accepts a shared drive on the recipient's instance, without going through the `/auth/authorize/sharing` page. Once the owner's instance has acknowledged the answer, its shortcut is marked as seen and points to the shared drive. If the shared drive was already accepted, for example by auto-accept, only the shortcut is updated. If the owner's instance cannot be reached, the route answers `502 Bad Gateway` and the shortcut is left untouched. The calling app needs the same permissions as for revoking itself from the sharing (`DELETE /sharings/:id/recipients/self`). #### Request ```http POST /sharings/drives/aae62886e79611ef8381fb83ff72e425/accept HTTP/1.1 Host: bob.example.net Accept: application/vnd.api+json ``` #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json ``` The response body is the sharing, as for `GET /sharings/:id`. ## Files and directories Unless stated otherwise, a permission on the whole `io.cozy.files` doctype is required to use the following routes. Authorization is resolved against the **effective access** of the target file or folder, not only the membership of the route's shared drive. The effective access combines every sharing scope applying to the target (the sharing of the route, plus any nested shared folder on its path) where the caller is a member: - read routes targeting a file or folder by its ID (`GET`/`HEAD .../:file-id`, download, thumbnails) require effective read on the target; - `GET /sharings/drives/:id/metadata` requires effective read on the folder or file resolved from its `Path` query parameter, and answers `404 Not Found` when the path resolves outside every sharing scope of the caller (so that the existence of paths outside the drive is not revealed); - write, update and delete routes (`POST`, `PUT`, `PATCH`, `DELETE`) require effective write on the target file or folder; - creation and upload routes require effective write on the destination folder; - `PATCH` (metadata update, rename) requires effective write on the source item; a move (the `parent` relationship) additionally requires effective write on the destination folder; - `POST /sharings/drives/:id/:file-id/copy` requires effective read on the source file and effective write on the destination folder; - open routes (`GET /sharings/drives/:id/notes/:file-id/open`, `GET /sharings/drives/:id/office/:file-id/open`, `GET /sharings/drives/:id/editor/:file-id/open`) require effective read on the target file. The opening is **forced read-only** when the member lacks effective write on the target: the share-interact token carries ALL verbs (including write), so the read-only restriction is enforced at the application layer by rewriting the `ReadOnly` query parameter to `true` before delegating to the note/office/editor handler. A member with effective write access opens the file in read-write mode (unless they explicitly pass `ReadOnly=true`); in that case the returned sharecode is minted from the nearest sharing scope that grants the effective write (e.g. a nested drive), never from the drive whose route was called when that drive only grants read: the share-interact permission set covers the whole drive, and a code scoped beyond the member's effective write would let them write files they cannot write; - trash routes (`POST`/`DELETE /sharings/drives/:id/trash/:file-id`) target files outside every sharing scope: restore and destroy require effective write on the folder the file would be restored to (or on its first existing ancestor when the original folder hierarchy was deleted, as the restore recreates it there); - temporary download links (`POST /sharings/drives/:id/downloads` and `POST /sharings/drives/:id/archive`) require effective read on every target before minting the link. A target outside every sharing scope of the caller is answered `404 Not Found`. The resulting `:secret` URL is not authenticated (by design, so browsers can use it directly), so the link should be treated as a capability: anyone holding it can download the prepared content until it expires. `GET /sharings/drives/:id/_changes` is not resolved per target: it returns the changes of the whole drive to every member. In the additive access mode, every member of the drive can read every file of the drive anyway, so no per-line filtering is needed. A typical consequence: a member who is read-only on the drive root but has write access on a nested shared folder can write inside that folder through the drive routes, and is answered `403 Forbidden` elsewhere. Known limitation: write routes without a file target (`POST /sharings/drives/:id/`, `POST /sharings/drives/:id/upload/metadata`, `POST /sharings/drives/:id/notes`) are authorized against the effective write access on the **drive root**, not on the actual destination folder declared in the request body (`dir_id`). As a consequence, a member who is read-only on the drive root but has write access on a nested shared folder cannot use these routes to target that folder: they get a `403 Forbidden` false negative. Clients in that situation should use a targeted route instead (`POST /sharings/drives/:id/:file-id` with the destination folder ID), which is checked against the effective access of the destination folder. This limitation will be lifted with `limited_access` support. For file-root shared drives (`drive_root_type = file`), iteration 1 currently supports only file-shaped routes. Directory-only routes return `422 Unprocessable Entity`. `_changes` and realtime are available with exact root-file semantics for file-root shared drives. ### GET /sharings/drives/:id/download/:file-id Download a file via a drive share. Identical call to [`GET /files/download/:file-id`](files.md#get-filesdownloadfile-id) but over a shared drive. See there for request and response examples ### POST /sharings/drives/:id/downloads Two-step download of a single file: creates a short-lived secret link (valid 10 minutes) pointing back to the shared drive download endpoint. Useful when the client cannot set an `Authorization` header on the eventual GET (e.g. a plain `` tag). Identical call to [`POST /files/downloads`](files.md#post-filesdownloads) but over a shared drive. The `links.related` in the response will point to `/sharings/drives/:id/downloads/:secret/:filename` instead of `/files/downloads/...`. Drive tokens (share-interact) are checked against the member's effective read access on the target file; a target outside every sharing scope of the caller is answered `404 Not Found`. ### GET /sharings/drives/:id/downloads/:secret/:fake-name Download the file prepared by the `POST /sharings/drives/:id/downloads` call above. The `:fake-name` segment is ignored by the server — it exists solely so browsers use it as the suggested save-as filename. Does not require an `Authorization` header (the secret acts as the credential). ### POST /sharings/drives/:id/archive Create a temporary zip archive of multiple files and/or folders inside a shared drive. Works for both owners and recipients (including read-only recipients). This route is supported only for directory-root shared drives. File-root shared drives return `422 Unprocessable Entity`. The request body follows the same format as [`POST /files/archive`](files.md#post-filesarchive). The `links.related` in the response points to `/sharings/drives/:id/archive/:secret/:name.zip` instead of `/files/archive/...`. Drive tokens (share-interact) are checked against the member's effective read access on every target (`ids` and `files` entries); any target outside every sharing scope of the caller fails the whole request with `404 Not Found`. The `/` and `/files` pseudo-entries are governed by the VFS permission check only (they cover the whole account, outside any drive scope). #### Request ```http POST /sharings/drives/aae62886e79611ef8381fb83ff72e425/archive HTTP/1.1 Authorization: Bearer token Content-Type: application/vnd.api+json ``` ```json { "data": { "attributes": { "name": "project-docs", "ids": ["af1e1b66e92111ef8ddd5fbac4938703", "be2f2c77f03222fg9eee6gcbd5049814"] } } } ``` #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json ``` ```json { "links": { "related": "/sharings/drives/aae62886e79611ef8381fb83ff72e425/archive/4521DC87/project-docs.zip" }, "data": { "type": "io.cozy.archives", "id": "4521DC87", "attributes": { "name": "project-docs", "ids": ["af1e1b66e92111ef8ddd5fbac4938703", "be2f2c77f03222fg9eee6gcbd5049814"] } } } ``` ### GET /sharings/drives/:id/archive/:secret/:fake-name Download the zip archive prepared by `POST /sharings/drives/:id/archive`. The archive is built on-the-fly and streamed directly — it is not stored on disk. Does not require an `Authorization` header (the secret acts as the credential, valid 10 minutes). ### GET /sharings/drives/:id/_changes Get the change feed for a drive. Identical call to [`GET /files/_changes`](files.md#get-files_changes) but over a shared drive. See there for request and response examples, differences are the URL and: - Any item that changed for that owner but isn't under that shared drive is presented as a deletion. - Paths are truncated to the shared drive, and formatted accordingly: eg: `//io.cozy.files.shared-drives-dir/1/ba3b516812f636fc022f3968f991357a/Meetings/Checklist.txt` Schema and it's version, followed by the shared drive ID, and the path within For file-root shared drives, this route returns only changes for the root file. Unrelated file changes are filtered out. ### GET /sharings/drives/:id/:file-id Get a directory or a file informations inside a shared drive. In the case of a directory, it contains the list of files and sub-directories inside it. For a note, its images are included. #### Request ```http GET /sharings/drives/aae62886e79611ef8381fb83ff72e425/af1e1b66e92111ef8ddd5fbac4938703 HTTP/1.1 Accept: application/vnd.api+json ``` #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json ``` ```json { "data": { "type": "io.cozy.files", "id": "af1e1b66e92111ef8ddd5fbac4938703", "meta": { "rev": "1-e36ab092" }, "attributes": { "type": "directory", "name": "Streams", "path": "/Product team/Streams", "created_at": "2016-09-19T12:35:00Z", "updated_at": "2016-09-19T12:35:00Z", "tags": [], "cozyMetadata": { "doctypeVersion": "1", "metadataVersion": 1, "createdAt": "2016-09-20T18:32:47Z", "createdByApp": "drive", "createdOn": "https://cozy.example.com/", "updatedAt": "2016-09-20T18:32:47Z" }, "driveId": "aae62886e79611ef8381fb83ff72e425" }, "relationships": { "contents": { "data": [ { "type": "io.cozy.files", "id": "a2843318f52411ef8e7ab79eae2f09ab" }, { "type": "io.cozy.files", "id": "b1db1642f52411efbe0b3bfc5fc0b437" } ] } }, "links": { "self": "/files/af1e1b66e92111ef8ddd5fbac4938703" } }, "included": [ { "type": "io.cozy.files", "id": "a2843318f52411ef8e7ab79eae2f09ab", "meta": { "rev": "1-ff3beeb456eb" }, "attributes": { "type": "directory", "name": "Authentication", "path": "/Product team/Streams/Authentication", "created_at": "2016-09-19T12:35:08Z", "updated_at": "2016-09-19T12:35:08Z", "cozyMetadata": { "doctypeVersion": "1", "metadataVersion": 1, "createdAt": "2016-09-20T18:32:47Z", "createdByApp": "drive", "createdOn": "https://cozy.example.com/", "updatedAt": "2016-09-20T18:32:47Z" } } }, { "type": "io.cozy.files", "id": "b1db1642f52411efbe0b3bfc5fc0b437", "meta": { "rev": "1-0e6d5b72" }, "attributes": { "type": "file", "name": "REAMDE.md", "trashed": false, "md5sum": "ODZmYjI2OWQxOTBkMmM4NQo=", "created_at": "2016-09-19T12:38:04Z", "updated_at": "2016-09-19T12:38:04Z", "tags": [], "size": 12, "executable": false, "class": "document", "mime": "text/plain", "cozyMetadata": { "doctypeVersion": "1", "metadataVersion": 1, "createdAt": "2016-09-20T18:32:49Z", "createdByApp": "drive", "createdOn": "https://cozy.example.com/", "updatedAt": "2016-09-20T18:32:49Z", "uploadedAt": "2016-09-20T18:32:49Z", "uploadedOn": "https://cozy.example.com/", "uploadedBy": { "slug": "drive" } } } } ] } ``` ### GET /sharings/drives/:id/:file-id/size This endpoint returns the size taken by the files in a directory inside a shared drive, including those in subdirectories. The `files_count` attribute is the number of files in the directory, including those in subdirectories (directories are not counted). If the directory has more than 10 000 subdirectories, only the first 10 000 are taken into account for `size` and `files_count`. This route is supported only for directory-root shared drives. File-root shared drives return `422 Unprocessable Entity`. #### Request ```http GET /sharings/drives/aae62886e79611ef8381fb83ff72e425/af1e1b66e92111ef8ddd5fbac4938703/size HTTP/1.1 Accept: application/vnd.api+json ``` #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json ``` ```json { "data": { "type": "io.cozy.files.sizes", "id": "af1e1b66e92111ef8ddd5fbac4938703", "attributes": { "size": "1234567890", "files_count": 42 }, "meta": {} } } ``` ### POST /sharings/drives/:id/:file-id/copy Duplicates a file. Identical call to [`POST /files/:file-id/copy`](files.md#post-filesfile-idcopy) but over a shared drive. See there for request and response examples, the only difference is the URL. This route is supported only for directory-root shared drives. File-root shared drives return `422 Unprocessable Entity`. ### POST /sharings/drives/move Move or copy a file or a directory between locations (personal drive and/or shared drives). Supported flows: - From a shared drive to another shared drive (same stack or cross-stack) - From a shared drive to a personal drive - From a personal drive to a shared drive At least one side (source or destination) must be a shared drive. The `copy` parameter controls whether the operation is a move (default) or copy: - When `copy: false` (default): The source file/directory is moved to the destination and deleted from the source - When `copy: true`: The source file/directory is copied to the destination but remains in the source location #### Request Body (preferred): ```json { "source": { "instance": "https://alice.localhost:8080", // omit for personal drive "sharing_id": "share_src_id", // required when instance is set "file_id": "file123", // exactly one of file_id or dir_id "dir_id": "" }, "dest": { "instance": "https://bob.localhost:8080", // omit for personal drive "sharing_id": "share_dst_id", // required when instance is set "dir_id": "destDir456" // destination directory id }, "copy": false // optional, defaults to false } ``` Validation rules: - Exactly one of `source.file_id` or `source.dir_id` must be provided. - `dest.dir_id` is required. - If `source.instance` is provided, `source.sharing_id` is required. - If `dest.instance` is provided, `dest.sharing_id` is required. - At least one of `source.sharing_id` or `dest.sharing_id` must be provided. - When one side is a personal drive (no `instance`), whole-type permission on `io.cozy.files` is required. - `copy` is optional and defaults to `false` (move operation). Notes on behavior: - Name conflicts at destination are resolved automatically by appending a conflict suffix. - For directory operations, the subtree is recreated top-down and files are copied, then sources are deleted (only for move operations). - Cross-stack operations perform a remote download/upload and delete the remote source upon success (only for move operations). - When `copy: true`, source files and directories are preserved in their original location. - When `copy: false` (default), source files and directories are deleted after successful copy to destination. - Moves are not atomic: the source is deleted only after the copy succeeded. If that deletion fails, the request returns an error, but the copy remains at the destination (retrying the move creates a suffixed duplicate, e.g. `name (2)`). Additional rules for file-root shared drives: - A file-root shared drive can be used only as a copy source. - `move` is rejected when the source shared drive is file-root. - A file-root shared drive cannot be used as a destination for either move or copy. - When a file-root shared drive is the source, only its root file can be copied. #### Responses - 201 Created, with a JSON object describing the created file or directory on the destination. - 400 Bad Request, when required inputs are missing or invalid. - 403 Forbidden, on permission errors or missing credentials for a shared drive. - 404 Not Found, when referenced files or drives do not exist. Example response (file, abbreviated): ```json { "data": { "type": "io.cozy.files", "id": "new-file-id", "attributes": { "name": "example.txt", "dir_id": "destDir456", "type": "file", "size": 123, "mime": "text/plain", "class": "document", "executable": false, "tags": [], "driveId": "aae62886e7..." // present when destination is a shared drive } } } ``` When the move occurs locally (same stack), the response matches the standard local creation response. For cross-stack moves, the response is built from the remote upload result and includes equivalent attributes. #### Copy Example To copy a file instead of moving it, set the `copy` parameter to `true`: ```json { "source": { "instance": "https://alice.localhost:8080", "sharing_id": "share_src_id", "file_id": "file123" }, "dest": { "instance": "https://bob.localhost:8080", "sharing_id": "share_dst_id", "dir_id": "destDir456" }, "copy": true } ``` This will create a copy of the file in the destination while preserving the original file in the source location. ### PATCH /sharings/drives/:id/:file-id This endpoint can be used to update the metadata of a file or directory, to rename it or to move it within the same shared drive. Some specific attributes of the patch can be used: - `dir_id` attribute can be updated to move a file or directory (the new directory needs to be in the same shared drive as the old one) For file-root shared drives, metadata updates and rename are supported, but changing `dir_id` is rejected with `422 Unprocessable Entity`. #### HTTP headers It's possible to send the `If-Match` header, with the previous revision of the file/directory (optional). #### Request ```http PATCH /sharings/drives/aae62886e79611ef8381fb83ff72e425/9152d568-7e7c-11e6-a377-37cbfb190b4b HTTP/1.1 Accept: application/vnd.api+json Content-Type: application/vnd.api+json ``` ```json { "data": { "type": "io.cozy.files", "id": "9152d568-7e7c-11e6-a377-37cbfb190b4b", "attributes": { "type": "file", "name": "hi.txt", "dir_id": "f2f36fec-8018-11e6-abd8-8b3814d9a465", "tags": ["poem"] } } } ``` #### Status codes - 200 OK, when the file or directory metadata has been successfully updated - 400 Bad Request, when a the destination directory does not exist - 403 Forbidden, when the file or directory cannot be modified or the - destination directory is not accessible - 404 Not Found, when the file/directory does not exist - 412 Precondition Failed, when the `If-Match` header is set and doesn't match the last revision of the file/directory - 422 Unprocessable Entity, when the sent data is invalid (for example, the parent doesn't exist) #### Response ```http HTTP/1.1 200 OK Content-Type: application/vnd.api+json Location: https://cozy.example.com/sharings/drives/aae62886e79611ef8381fb83ff72e425/9152d568-7e7c-11e6-a377-37cbfb190b4b ``` ```json { "data": { "type": "io.cozy.files", "id": "9152d568-7e7c-11e6-a377-37cbfb190b4b", "meta": { "rev": "1-0e6d5b72" }, "attributes": { "type": "file", "name": "hi.txt", "trashed": false, "md5sum": "ODZmYjI2OWQxOTBkMmM4NQo=", "created_at": "2016-09-19T12:38:04Z", "updated_at": "2016-09-19T12:38:04Z", "tags": ["poem"], "size": 12, "executable": false, "class": "document", "mime": "text/plain", "cozyMetadata": { "doctypeVersion": "1", "metadataVersion": 1, "createdAt": "2016-09-20T18:32:49Z", "createdByApp": "drive", "createdOn": "https://cozy.example.com/", "updatedAt": "2016-09-22T13:32:51Z", "uploadedAt": "2016-09-21T04:27:50Z", "uploadedOn": "https://cozy.example.com/", "uploadedBy": { "slug": "drive" } } }, "relationships": { "parent": { "links": { "related": "/files/f2f36fec-8018-11e6-abd8-8b3814d9a465" }, "data": { "type": "io.cozy.files", "id": "f2f36fec-8018-11e6-abd8-8b3814d9a465" } } }, "links": { "self": "/files/9152d568-7e7c-11e6-a377-37cbfb190b4b" } } } ``` ### Similar to /files The following routes are similar to [`/files`](files.md), but for a shared drive. #### GET /sharings/drives/:id/metadata #### POST /sharings/drives/upload/metadata #### DELETE /sharings/drives/:file-id #### POST /sharings/drives/trash/:file-id #### DELETE /sharings/drives/trash/:file-id For file-root shared drives: - `GET /sharings/drives/:id/metadata` is not supported and returns `422` - `POST /sharings/drives/upload/metadata` is not supported and returns `422` - `DELETE /sharings/drives/:file-id` is supported for the root file - trash and restore routes are supported for the root file Trash operations keep the same file metadata shape as `/files`: when an item is moved to the trash, `cozyMetadata.trashedAt` and `cozyMetadata.trashedBy` are exposed on both the caller side and the replicated shared-drive copies. On shared-drive requests, `trashedBy.kind` is `member` and `trashedBy.displayName` / `trashedBy.domain` are derived from the sharing member linked to the `DriveToken`. ## Share-by-link permissions The following routes manage share-by-link permissions scoped to files inside a shared drive: - `GET /sharings/drives/:id/permissions?ids=...` - `POST /sharings/drives/:id/permissions` - `PATCH /sharings/drives/:id/permissions/:perm-id` - `DELETE /sharings/drives/:id/permissions/:perm-id` On every route, the targeted file or folder must be **effectively readable** by the caller (see the effective access rules in [Files and directories](#files-and-directories)). ### GET /sharings/drives/:id/permissions?ids=... Lists the share-by-link permissions for the requested file or folder IDs inside the shared drive. For file-root shared drives, the only valid target is the root file. Authorization rules: - The shared-drive owner can list all matching links. - A write-capable recipient can list writable and read-only links. - A read-only recipient only sees read-only links. Validation: - `ids` is required and must be a comma-separated list of file or folder IDs. - Every requested ID must belong to the shared drive. Status codes: - `200 OK` listed - `403 Forbidden` caller cannot access shared-drive permissions - `422 Unprocessable Entity` missing or invalid `ids` ### POST /sharings/drives/:id/permissions Creates a share-by-link permission for one file or folder in the shared drive. The request body uses the same JSON:API shape as [`POST /permissions`](permissions.md#post-permissions). For file-root shared drives, the only valid target is the root file. Authorization rules: - The shared-drive owner can create a link. - A write-capable recipient can create a link. - A read-only recipient can create a read-only link. Validation: - The permission set must target exactly one file or folder. - The target type must be `io.cozy.files`. - Selectors are not supported. - The target must belong to the shared drive and must be readable by the caller. - A read-only recipient cannot request writable verbs. - Only one share-by-link permission can exist per target. A second creation attempt on the same target returns a conflict, regardless of which member created the existing link. Status codes: - `200 OK` created - `400 Bad Request` invalid permission set or invalid target - `403 Forbidden` caller lacks access to the target or requests a permission set larger than their shared-drive access - `409 Conflict` a share-by-link permission already exists for this target ### PATCH /sharings/drives/:id/permissions/:perm-id Updates an existing share-by-link permission. Authorization rules: - The shared-drive owner can patch any share-by-link permission. - The creator of a share-by-link permission can patch the permission they created. - Creator resolution works for same-stack and cross-stack recipients. - A read-only shared-drive recipient can patch only the read-only permission they created. - Public share tokens (`share`, `share-preview`) cannot patch permissions. Allowed updates: - `password` - `expires_at` - `permissions` (same target only, owner or write-capable creator only) Validation: - `password` must be a string (empty string clears the password). - `expires_at` must be a string (empty string clears expiration, otherwise RFC3339 date-time). - `permissions`, when provided, must still target the same file or folder inside the shared drive. - for file-root shared drives, that target must remain the root file - A write-capable creator or the owner can promote a read-only link to a writable link if their current token grants those verbs. - A read-only shared-drive recipient can only update `password` and `expires_at`. - A read-only shared-drive recipient cannot patch `permissions`, even if the result would stay read-only. Status codes: - `200 OK` updated - `400 Bad Request` invalid payload (for example trying to update `codes`), invalid `password` / `expires_at` attribute format - `403 Forbidden` caller is not owner/creator, caller identity cannot be verified for a shared-drive token, or a read-only recipient attempts to patch `permissions` - `404 Not Found` permission ID does not exist ### DELETE /sharings/drives/:id/permissions/:perm-id Revokes an existing share-by-link permission. Authorization rules: - The shared-drive owner can revoke any share-by-link permission. - The creator of a share-by-link permission can revoke the permission they created. - A read-only shared-drive recipient can revoke the read-only permission they created. - Public share tokens (`share`, `share-preview`) cannot revoke permissions. Status codes: - `204 No Content` revoked - `400 Bad Request` permission is not a share-by-link permission - `403 Forbidden` caller is not owner/creator, or permission does not belong to this shared drive - `404 Not Found` permission ID does not exist ## Delegated email sharing Members of a shared drive add new recipients through the sharing API, not through a drive-specific route: - `POST /sharings/:sharing-id/recipients` When that request is sent from a recipient Cozy, the stack delegates the operation to the owner Cozy internally. Authorization rules: - The shared-drive owner can add recipients as for any other sharing. - A write-capable recipient can invite read-write or read-only recipients. - A read-only recipient can invite only read-only recipients. - A read-only recipient receives `403 Forbidden` for a read-write invite. ## Versions The identifier of the `io.cozy.files.versions` is composed of the `file-id` and another string called the `version-id`, separated by a `/`. So, when a route makes reference to `/something/:file-id/:version-id`, you can use the identifier of the version document (without having to prepend the file identifier). ### POST /sharings/drives/:id/:file-id/versions Create a new version of a file, with the same content but new metadata. Identical call to [`POST /files/:file-id/versions`](files.md#post-filesfile-idversions) but over a shared drive. See there for request and response examples, the only difference is the URL. For file-root shared drives, this route is supported for the root file. ### GET /sharings/drives/:id/download/:file-id/:version-id Downloads an old version of the file content. Identical call to [`GET /files/download/:file-id/:version-id`](files.md#get-filesdownloadfile-idversion-id) but over a shared drive. See there for request and response examples, the only difference is the URL. ### DELETE /sharings/drives/:id/:file-id/:version-id Permanently deletes an old version of a file. The caller must have write access to the shared drive. Identical call to [`DELETE /files/:file-id/:version-id`](files.md#delete-filesfile-idversion-id) but over a shared drive. See there for request and response examples, the only difference is the URL. ## Notes ### POST /sharings/drives/:id/notes Create a note inside a shared drive. Identical to [`POST /notes`](notes.md#post-notes). This route is supported only for directory-root shared drives. File-root shared drives return `422 Unprocessable Entity`. ### GET /sharings/drives/:id/notes/:file-id/open Return the parameters to build the URL where the note can be opened. Identical to [`GET /notes/:file-id/open`](notes.md#get-notesidopen). Drive tokens (share-interact) are checked against the member's effective read access on the target; a target outside every sharing scope of the caller is answered `404 Not Found`. The opening is forced read-only (the sharecode resolves to a `share-preview` permission) when the member lacks effective write access on the target. Otherwise the sharecode is minted from the nearest sharing scope that grants the effective write. ## Office ### GET /sharings/drives/:id/office/:file-id/open Returns the parameters to open an office document. Identical to [`GET /office/:file-id/open`](office.md#get-officeidopen). Drive tokens are checked and the opening forced read-only the same way as the notes open route. ## Editors ### GET /sharings/drives/:id/editor/:file-id/open Return the parameters to open a file from a shared drive with an editor. Identical to [`GET /editor/:file-id/open`](files.md#get-editorfile-idopen), but scoped to the shared drive. Drive tokens are checked and the opening forced read-only the same way as the notes open route. ## Shortcuts ### GET /sharings/drives/:id/shortcuts/:file-id Get information about a shortcut inside a shared drive. Identical to [`GET /shortcuts/:id`](shortcuts.md#get-shortcutsid), with the same two variants depending on the `Accept` header (JSON-API response or redirect). Recipients are transparently proxied to the owner's instance. ## Realtime ### GET /sharings/drives/:id/realtime Get the changes inside a shared drive in real-time from a websocket. Identical to [`GET /realtime`](realtime.md), except subscribing to the shared drive is automatically done. On the recipient side, connecting marks the sharing as seen (its shortcut leaves the `new` status counted by `GET /sharings/news`). Background consumers that watch a drive without the user looking at it, like the dataproxy indexer, must pass the query parameter `background=true` to keep the sharing unseen. This route is currently available only for directory-root shared drives. For file-root shared drives, the websocket stream emits only events whose target is the root file of the drive. Unrelated file events are filtered out. ``` client > {"method": "AUTH", "payload": "xxAppOrAuthTokenxx="} server > {"event": "UPDATED", "payload": {"id": "idB", "rev": "6-457...", "type": "io.cozy.files", "doc": {embeded doc ...}}} ```