Skip to content

Table of contents

Proxy for a remote NextCloud

The nextcloud konnector can be used to create an io.cozy.account for a NextCloud. Then, the stack can be used as a client for this NextCloud account. Currently, it supports files operations via WebDAV.

GET /remote/nextcloud/:account/*path

This route can be used to list the files and subdirectories inside a directory of NextCloud.

With Dl=1 in the query-string, it can also be used to download a file.

The :account parameter is the identifier of the NextCloud io.cozy.account. It is available with the cozyMetadata.sourceAccount of the shortcut file for example.

The *path parameter is the path of the file/directory on the NextCloud.

Note: a permission on GET io.cozy.files is required to use this route.

Request (list)

GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/Documents HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response (list)

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
  "data": [
    {
      "type": "io.cozy.remote.nextcloud.files",
      "id": "192172",
      "attributes": {
        "type": "directory",
        "name": "Images",
        "path": "/Documents/Images",
        "updated_at": "Thu, 02 May 2024 09:29:53 GMT",
        "etag": "\"66335d11c4b91\""
      },
      "meta": {},
      "links": {
        "self": "https://nextcloud.example.net/apps/files/files/192172?dir=/Documents"
      }
    },
    {
      "type": "io.cozy.remote.nextcloud.files",
      "id": "208937",
      "attributes": {
        "type": "file",
        "name": "BugBounty.pdf",
        "path": "/Documents/BugBounty.pdf",
        "size": 2947,
        "mime": "application/pdf",
        "class": "pdf",
        "updated_at": "Mon, 14 Jan 2019 08:22:21 GMT",
        "etag": "\"dd1a602431671325b7c1538f829248d9\""
      },
      "meta": {},
      "links": {
        "self": "https://nextcloud.example.net/apps/files/files/208937?dir=/Documents"
      }
    },
    {
      "type": "io.cozy.remote.nextcloud.files",
      "id": "615827",
      "attributes": {
        "type": "directory",
        "name": "Music",
        "name": "/Documents/Music",
        "updated_at": "Thu, 02 May 2024 09:28:37 GMT",
        "etag": "\"66335cc55204b\""
      },
      "meta": {},
      "links": {
        "self": "https://nextcloud.example.net/apps/files/files/615827?dir=/Documents"
      }
    },
    {
      "type": "io.cozy.remote.nextcloud.files",
      "id": "615828",
      "attributes": {
        "type": "directory",
        "name": "Video",
        "path": "/Documents/Video",
        "updated_at": "Thu, 02 May 2024 09:29:53 GMT",
        "etag": "\"66335d11c2318\""
      },
      "meta": {},
      "links": {
        "self": "https://nextcloud.example.net/apps/files/files/615828?dir=/Documents"
      }
    }
  ],
  "meta": {
    "count": 5
  }
}

Request (download)

GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/Documents/Wallpaper.jpg?Dl=1 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response (download)

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 12345
Content-Disposition: attachment; filename="Wallpaper.jpg"

...

Status codes

  • 200 OK, for a success
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the directory is not found on the NextCloud

PUT /remote/nextcloud/:account/*path

This route can be used to create a directory, or upload a file, on the NextCloud. The query-string parameter Type should be file when uploading a file.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file/directory on the NextCloud.

Note: a permission on POST io.cozy.files is required to use this route.

Request (directory)

PUT /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/Documents/Images/Clouds?Type=directory HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response (directory)

HTTP/1.1 201 Created
Content-Type: application/json
{
  "ok": true
}

Request (file)

PUT /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/Documents/Images/sunset.jpg?Type=file HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...
Content-Type: image/jpeg
Content-Length: 54321

...

Response (file)

HTTP/1.1 201 Created
Content-Type: application/json
{
  "ok": true
}

Status codes

  • 201 Created, when the directory has been created
  • 400 Bad Request, when the account is not configured for NextCloud
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the parent directory is not found on the NextCloud
  • 409 Conflict, when a directory or file already exists at this path on the NextCloud.

DELETE /remote/nextcloud/:account/*path

This route can be used to put a file or directory in the NextCloud trash.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file/directory on the NextCloud.

Note: a permission on DELETE io.cozy.files is required to use this route.

Request

DELETE /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/Documents/Images/Clouds HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

Status codes

  • 204 No Content, when the file/directory has been put in the trash
  • 400 Bad Request, when the account is not configured for NextCloud, or the To parameter is missing
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file/directory is not found on the NextCloud

POST /remote/nextcloud/:account/move/*path

This route can be used to move or rename a file/directory on the NextCloud. The new path must be given with the To parameter in the query-string.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file on the NextCloud.

Note: a permission on POST io.cozy.files is required to use this route.

Request

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/move/Documents/wallpaper.jpg?To=/Wallpaper.jpg HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

Status codes

  • 204 No Content, when the file/directory has been moved
  • 400 Bad Request, when the account is not configured for NextCloud
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file/directory is not found on the NextCloud
  • 409 Conflict, when a file already exists with the new name on the NextCloud.

POST /remote/nextcloud/:account/copy/*path

This route can be used to create a copy of a file in the same directory, with a copy suffix in its name. The new name can be optionaly given with the Name parameter in the query-string, or the full path can be given with Path parameter.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file on the NextCloud.

Note: a permission on POST io.cozy.files is required to use this route.

Request

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/copy/Documents/wallpaper.jpg?Path=/Images/beach.jpg HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 201 Created
Content-Type: application/json
{
  "ok": true
}

Status codes

  • 201 Created, when the file has been copied
  • 400 Bad Request, when the account is not configured for NextCloud
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file/directory is not found on the NextCloud
  • 409 Conflict, when a file already exists with the new name on the NextCloud.

POST /remote/nextcloud/:account/downstream/*path

This route can be used to move/copy a file from the NextCloud to the Cozy.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file on the NextCloud.

The To parameter in the query-string must be given, as the ID of the directory on the Cozy where the file will be put.

By default, the file will be moved, but using Copy=true in the query-string will makes a copy.

By default, if a file with the same name already exists in the destination directory, a new name will be automatically generated (e.g., “file (2).txt”). Using FailOnConflict=true in the query-string will make the route return a 409 Conflict error instead of auto-renaming the file.

Note: a permission on POST io.cozy.files is required to use this route.

Request

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/downstream/Documents/Images/sunset.jpg?To=b3ecbc00f4ba013c2bf418c04daba326 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Query parameters

  • To (required): The ID of the directory on the Cozy where the file will be put.
  • Copy (optional): Set to true to make a copy instead of moving the file. Default is false (move).
  • FailOnConflict (optional): Set to true to return a 409 Conflict error if a file with the same name already exists, instead of auto-renaming. Default is false (auto-rename).

Example with FailOnConflict

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/downstream/Documents/Images/sunset.jpg?To=b3ecbc00f4ba013c2bf418c04daba326&FailOnConflict=true HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
  "data": {
    "type": "io.cozy.files",
    "id": "7b41fb7c31e87eeaf13a54bc32001830",
    "attributes": {
      "type": "file",
      "name": "sunset.jpg",
      "dir_id": "b3ecbc00f4ba013c2bf418c04daba326",
      "created_at": "2024-05-15T09:24:39.460655706+02:00",
      "updated_at": "2024-05-15T09:24:39.460655706+02:00",
      "size": "54321",
      "md5sum": "1B2M2Y8AsgTpgAmY7PhCfg==",
      "mime": "image/jpeg",
      "class": "image",
      "executable": false,
      "trashed": false,
      "encrypted": false,
      "cozyMetadata": {
        "doctypeVersion": "1",
        "metadataVersion": 1,
        "createdAt": "2024-05-15T09:24:38.971901347+02:00",
        "updatedAt": "2024-05-15T09:24:38.971901347+02:00",
        "createdOn": "https://cozy.example.net/",
        "uploadedAt": "2024-05-15T09:24:38.971901347+02:00",
        "uploadedOn": "https://cozy.example.net/"
      }
    },
    "meta": {
      "rev": "1-cfed435c4ad72b911b31ed775e3024df"
    },
    "links": {
      "self": "/files/7b41fb7c31e87eeaf13a54bc32001830"
    },
    "relationships": {
      "parent": {
        "links": {
          "related": "/files/b3ecbc00f4ba013c2bf418c04daba326"
        },
        "data": {
          "id": "b3ecbc00f4ba013c2bf418c04daba326",
          "type": "io.cozy.files"
        }
      },
      "referenced_by": {
        "links": {
          "self": "/files/7b41fb7c31e87eeaf13a54bc32001830/relationships/references"
        }
      }
    }
  }
}

Status codes

  • 201 Created, when the file has been moved from the NextCloud to the Cozy
  • 400 Bad Request, when the account is not configured for NextCloud or the To parameter is missing
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file is not found on the NextCloud
  • 409 Conflict, when a file with the same name already exists in the destination directory and FailOnConflict=true is set

POST /remote/nextcloud/:account/upstream/*path

This route can be used to move/copy a file from the Cozy to the NextCloud.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file on the NextCloud.

The From parameter in the query-string must be given, as the ID of the file on the Cozy that will be moved.

By default, the file will be moved, but using Copy=true in the query-string will makes a copy.

Note: a permission on POST io.cozy.files is required to use this route.

Request

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/upstream/Documents/Images/sunset2.jpg?From=7b41fb7c31e87eeaf13a54bc32001830 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

Status codes

  • 204 No Content, when the file has been moved from the Cozy to the NextCloud
  • 400 Bad Request, when the account is not configured for NextCloud
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file is not found on the Cozy

GET /remote/nextcloud/:account/trash/*

This route can be used to list the files and directories inside the trashbin of NextCloud.

Request (list)

GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/trash/ HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response (list)

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
  "data": [
    {
      "type": "io.cozy.remote.nextcloud.files",
      "id": "613281",
      "attributes": {
        "type": "directory",
        "name": "Old",
        "path": "/trash/Old.d93571568",
        "updated_at": "Tue, 25 Jun 2024 14:31:44 GMT",
        "etag": "1719326384",
        "restore_path": "/Old"
      },
      "meta": {},
      "links": {
        "self": "https://nextcloud.example.net/apps/files/trashbin/613281?dir=/Old"
      }
    }
  ]
}

Status codes

  • 200 OK, for a success
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the directory is not found on the NextCloud

POST /remote/nextcloud/:account/restore/*path

This route can be used to restore a file/directory from the trashbin on the NextCloud.

The :account parameter is the identifier of the NextCloud io.cozy.account.

The *path parameter is the path of the file on the NextCloud.

Note: a permission on POST io.cozy.files is required to use this route.

Request

POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/restore/trash/Old.d93571568 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

Status codes

  • 204 No Content, when the file/directory has been restored
  • 400 Bad Request, when the account is not configured for NextCloud
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file/directory is not found on the NextCloud
  • 409 Conflict, when a directory or file already exists where the file/directory should be restored on the NextCloud.

DELETE /remote/nextcloud/:account/trash/*

This route can be used to delete a file in the trash.

Request

DELETE /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/trash/document-v1.docx.d64283654 HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

Status codes

  • 204 No Content, when the file/directory has been put in the trash
  • 400 Bad Request, when the account is not configured for NextCloud, or the To parameter is missing
  • 401 Unauthorized, when authentication to the NextCloud fails
  • 404 Not Found, when the account is not found or the file/directory is not found on the NextCloud

DELETE /remote/nextcloud/:account/trash

This route can be used to empty the trash bin on NextCloud.

Request

DELETE /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/trash HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 204 No Content

GET /remote/nextcloud/:account/size/*path

This route returns the recursive size in bytes of a folder (or a single file) on the NextCloud account. It is backed by a single Depth:0 PROPFIND asking for the oc:size property that NextCloud maintains in its metadata table, so it runs in constant time regardless of how many files are in the tree. Pass the account root by using an empty *path (just /size/).

The :account parameter is the identifier of the NextCloud io.cozy.account.

Note: a permission on GET io.cozy.files is required to use this route.

Request (sub-folder)

GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/size/Photos HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Response

HTTP/1.1 200 OK
Content-Type: application/json
{
  "size": 5656463
}

Request (account root)

GET /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/size/ HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

Status codes

  • 200 OK, with the recursive byte total of the target path
  • 401 Unauthorized, when the NextCloud credentials are rejected
  • 404 Not Found, when the account or the target path does not exist

POST /remote/nextcloud/migration

This route triggers a one-shot bulk migration of a user’s Nextcloud files into their Cozy. The Stack validates the credentials, persists an io.cozy.accounts document, creates an io.cozy.nextcloud.migrations tracking document in pending state, and publishes a nextcloud.migration.requested command to the migration RabbitMQ exchange. The actual transfer is performed by an external migration service that consumes the command and drives the existing /remote/nextcloud/:account/* routes, updating the tracking document as it progresses.

Before persisting anything, the Stack probes the supplied credentials against the Nextcloud instance via the OCS user_status endpoint, so wrong passwords and unreachable hosts surface synchronously instead of being deferred to the migration service. The probe also resolves the WebDAV user ID, which is cached on the account document so the migration service does not need to re-fetch it.

When an existing io.cozy.accounts document for the same account_type: "nextcloud" + auth.url + auth.login triplet is found, it is reused with its stored password and webdav_user_id refreshed from the request. Only one migration can be in flight per instance at a time: if a pending or running tracking document already exists, the Stack returns 409 Conflict. Failed migrations do not block new attempts.

Note: a permission on POST io.cozy.nextcloud.migrations is required to use this route.

Request

POST /remote/nextcloud/migration HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...
Content-Type: application/json
{
  "nextcloud_url": "https://nextcloud.example.com",
  "nextcloud_login": "alice",
  "nextcloud_app_password": "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx",
  "source_path": "/"
}

source_path is optional and defaults to /. The nextcloud_app_password should be a Nextcloud app password, not the user’s main account password.

Response

HTTP/1.1 201 Created
Content-Type: application/vnd.api+json
{
  "data": {
    "id": "d4e5f6a7b8c94d0ea1b2c3d4e5f6a7b8",
    "type": "io.cozy.nextcloud.migrations",
    "attributes": {
      "status": "pending",
      "target_dir": "/Nextcloud",
      "progress": {
        "files_imported": 0,
        "files_total": 0,
        "bytes_imported": 0,
        "bytes_total": 0
      },
      "errors": [],
      "skipped": [],
      "started_at": null,
      "finished_at": null
    }
  }
}

Status codes

  • 201 Created, when the migration has been queued and the tracking document is returned
  • 401 Unauthorized, when the Nextcloud credentials are rejected by the remote host
  • 409 Conflict, when a pending or running migration already exists
  • 500 Internal Server Error, when the conflict check, account upsert, or tracking document creation fails
  • 502 Bad Gateway, when the Nextcloud instance is unreachable
  • 503 Service Unavailable, when the migration command cannot be published to RabbitMQ. The tracking document is marked failed before returning

POST /remote/nextcloud/migration/:id/cancel

This route asks the migration service to stop an in-flight migration. The Stack publishes a nextcloud.migration.canceled command to the migration RabbitMQ exchange; the migration service then stops the migration between files and transitions the tracking document to a new terminal state canceled with progress up to the last completed file preserved.

The Stack does not write to the tracking document on cancel: that terminal state transition is owned by the migration service so there is a single writer for it. A 202 Accepted means “cancel requested, poll the tracking document for the terminal state”, not “migration stopped”. Worst-case delay from request to observed terminal state is roughly one file transfer.

Note: a permission on POST io.cozy.nextcloud.migrations is required to use this route.

Request

POST /remote/nextcloud/migration/d4e5f6a7b8c94d0ea1b2c3d4e5f6a7b8/cancel HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...

No request body.

Response

HTTP/1.1 202 Accepted

Empty body.

Status codes

  • 202 Accepted, when the cancel command has been published
  • 401 Unauthorized, when the bearer token is missing or invalid
  • 403 Forbidden, when the token lacks the POST io.cozy.nextcloud.migrations permission
  • 404 Not Found, when no tracking document with the given id exists on the instance
  • 409 Conflict, when the migration has already reached a terminal state (completed, failed, or canceled)
  • 503 Service Unavailable, when the cancel command cannot be published to RabbitMQ. Unlike the trigger endpoint, the tracking document is not marked failed: a broker glitch must not invalidate a migration that is still running. Retry the cancel.