Shared drives¶
A shared drive is a folder that is shared between several cozy instances. A member doesn’t have the files in their Cozy, but can access them via the stack playing a proxy role.
Creating a shared drive¶
There are two ways to create a shared drive:
Simple method: Convert an existing folder¶
Use the POST /sharings/drives endpoint to convert any
existing folder into a shared drive. This is the recommended approach as it
handles all validation and setup automatically.
Manual method¶
To create a shared drive manually (typically on the organization Cozy), follow these steps:
- Ensure that the
/Drivesfolder exists in the cozy instance with thePOST /files/shared-drivesroute. - Create a folder inside it, with the name of shared drive.
- Create a sharing with the
drive: trueattribute, and one rule for shared folder (withnoneforadd,updateandremoveattributes).
Managing shared drives¶
GET /sharings/drives¶
The GET /sharings/drives route returns the list of shared drives.
Request¶
GET /sharings/drives HTTP/1.1
Host: acme.example.net
Accept: application/vnd.api+json
Response¶
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": [
{
"type": "io.cozy.sharings",
"id": "aae62886e79611ef8381fb83ff72e425",
"attributes": {
"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 from an existing folder. This is an alternative to
the manual process of creating a sharing with drive: true - it automatically
validates the folder and creates the sharing with appropriate rules.
The folder must:
- Exist and be a directory (not a file)
- Not be a system folder (root, trash, shared-with-me, shared-drives, no-longer-shared)
- Not be inside the trash
- Not already have a sharing (directly or via a parent folder)
- Not contain any subfolder that already has a sharing
Request¶
POST /sharings/drives HTTP/1.1
Host: acme.example.net
Content-Type: application/vnd.api+json
Accept: application/vnd.api+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"}
]
}
}
}
}
Attributes:
| Attribute | Required | Description |
|---|---|---|
folder_id |
Yes | The ID of the existing folder to convert into a shared drive |
description |
No | A description for the shared drive. If not provided, defaults to the folder name |
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/1.1 201 Created
Content-Type: application/vnd.api+json
{
"data": {
"type": "io.cozy.sharings",
"id": "aae62886e79611ef8381fb83ff72e425",
"attributes": {
"drive": true,
"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 folder with the given folder_id does not exist |
| 409 | Conflict | The folder already has a sharing, is inside a shared folder, or contains a shared subfolder |
| 422 | Unprocessable Entity | Missing folder_id, folder is a file, or folder is a system folder |
Example error (folder already shared):
HTTP/1.1 409 Conflict
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "409",
"title": "Conflict",
"detail": "Folder already has an existing sharing"
}
]
}
Files and directories¶
Unless stated otherwise, a permission on the whole io.cozy.files doctype is
required to use the following routes.
GET /sharings/drives/:id/download/:file-id¶
Download a file via a drive share.
Identical call to GET /files/download/:file-id but over a shared drive.
See there for request and response examples
GET /sharings/drives/:id/_changes¶
Get the change feed for a drive.
Identical call to 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
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¶
GET /sharings/drives/aae62886e79611ef8381fb83ff72e425/af1e1b66e92111ef8ddd5fbac4938703 HTTP/1.1
Accept: application/vnd.api+json
Response¶
HTTP/1.1 200 OK
Content-Type: application/vnd.api+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.
Request¶
GET /sharings/drives/aae62886e79611ef8381fb83ff72e425/af1e1b66e92111ef8ddd5fbac4938703/size HTTP/1.1
Accept: application/vnd.api+json
Response¶
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"data": {
"type": "io.cozy.files.sizes",
"id": "af1e1b66e92111ef8ddd5fbac4938703",
"attributes": {
"size": "1234567890"
},
"meta": {}
}
}
POST /sharings/drives/:id/:file-id/copy¶
Duplicates a file.
Identical call to POST /files/:file-id/copy but over a shared drive.
See there for request and response examples, the only difference is the URL.
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):
{
"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_idorsource.dir_idmust be provided. dest.dir_idis required.- If
source.instanceis provided,source.sharing_idis required. - If
dest.instanceis provided,dest.sharing_idis required. - At least one of
source.sharing_idordest.sharing_idmust be provided. - When one side is a personal drive (no
instance), whole-type permission onio.cozy.filesis required. copyis optional and defaults tofalse(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.
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):
{
"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:
{
"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_idattribute can be updated to move a file or directory (the new directory needs to be in the same shared drive as the old one)
HTTP headers¶
It’s possible to send the If-Match header, with the previous revision of the
file/directory (optional).
Request¶
PATCH /sharings/drives/aae62886e79611ef8381fb83ff72e425/9152d568-7e7c-11e6-a377-37cbfb190b4b HTTP/1.1
Accept: application/vnd.api+json
Content-Type: application/vnd.api+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-Matchheader 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/1.1 200 OK
Content-Type: application/vnd.api+json
Location: https://cozy.example.com/sharings/drives/aae62886e79611ef8381fb83ff72e425/9152d568-7e7c-11e6-a377-37cbfb190b4b
{
"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, 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¶
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/permissionsPATCH /sharings/drives/:id/permissions/:perm-idDELETE /sharings/drives/:id/permissions/:perm-id
GET /sharings/drives/:id/permissions?ids=…¶
Lists the share-by-link permissions for the requested file or folder IDs inside the shared drive.
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:
idsis 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 OKlisted403 Forbiddencaller cannot access shared-drive permissions422 Unprocessable Entitymissing or invalidids
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.
Authorization rules:
- The shared-drive owner can create a link.
- A write-capable recipient can create a link.
- A read-only recipient cannot create a 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.
- 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 OKcreated400 Bad Requestinvalid permission set or invalid target403 Forbiddencaller lacks access to the target or is read-only on the shared drive409 Conflicta 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.
- Public share tokens (
share,share-preview) cannot patch permissions.
Allowed updates:
passwordexpires_atpermissions(same target only)
Validation:
passwordmust be a string (empty string clears the password).expires_atmust 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.- 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 cannot patch a permission set to add writable verbs.
Status codes:
200 OKupdated400 Bad Requestinvalid payload (for example trying to updatepermissionsorcodes), invalidpassword/expires_atattribute format403 Forbiddencaller is not owner/creator, or caller identity cannot be verified for a shared-drive token404 Not Foundpermission 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 cannot revoke a permission.
- Public share tokens (
share,share-preview) cannot revoke permissions.
Status codes:
204 No Contentrevoked400 Bad Requestpermission is not a share-by-link permission403 Forbiddencaller is not owner/creator, or permission does not belong to this shared drive404 Not Foundpermission 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 Forbiddenfor 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).
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
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.
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.
Office¶
GET /sharings/drives/:id/office/:file-id/open¶
Returns the parameters to open an office document. Identical to
GET /office/:file-id/open.
Realtime¶
GET /sharings/drives/:id/realtime¶
Get the changes inside a shared drive in real-time from a websocket.
Identical to GET /realtime, except subscribing to the shared drive is automatically done.
client > {"method": "AUTH",
"payload": "xxAppOrAuthTokenxx="}
server > {"event": "UPDATED",
"payload": {"id": "idB", "rev": "6-457...", "type": "io.cozy.files", "doc": {embeded doc ...}}}