1# API version 1 2 3In this document, the Notes API major version 1 and all its minor versions are described. An introduction with general information about versions, capabilities, compatibility between versions, authentication and input parameters can be found in the [README](README.md). 4 5 6## Minor versions 7 8| API version | Introduced with app version | Remarkable Changes | 9|:-----------:|:----------------------------|:-------------------| 10| **1.0** | Notes 3.3 (May 2020) | Separate title, no auto rename based on content | 11| **1.1** | Notes 3.4 (May 2020) | Filter "Get all notes" by category | 12| **1.2** | Notes 4.1 (June 2021) | Preventing lost updates, read-only notes, settings | 13 14 15 16## Note attributes 17 18The app and the API is mainly about notes. So, let's have a look about the attributes of a note. The description of endpoints and operations will refer to this attribute definition. 19 20| Attribute | Type | Description | since API version | 21|:----------|:-----|:------------|:------------------| 22| `id` | integer (read‑only) | Every note has a unique identifier which is created by the server. It can be used to query and update a specific note. | 1.0 | 23| `etag` | string (read‑only) | The note's entity tag (ETag) indicates if a note's attribute has changed. I.e., if the note changes, the ETag changes, too. Clients can use the ETag for detecting if the local note has to be updated from server and for optimistic concurrency control (see section [Preventing lost updates and conflict solution](#preventing-lost-updates-and-conflict-solution)). | 1.2 | 24| `readonly` | boolean (read‑only) | Indicates if the note is read-only. This is `true`, e.g., if a file or folder was shared by another user without allowing editing. If this attribute is `true`, then all read/write attributes become read-only; except for the `favorite` attribute. | 1.2 | 25| `content` | string (read/write) | Notes can contain arbitrary text. Formatting should be done using Markdown, but not every markup can be supported by every client. Therefore, markup should be used with care. | 1.0 | 26| `title` | string (read/write) | The note's title is also used as filename for the note's file. Therefore, some special characters are automatically removed and a sequential number is added if a note with the same title in the same category exists. When saving a title, the sanitized value is returned and should be adopted by your client. | 1.0 | 27| `category` | string (read/write) | Every note is assigned to a category. By default, the category is an empty string (not null), which means the note is uncategorized. Categories are mapped to folders in the file backend. Illegal characters are automatically removed and the respective folder is automatically created. Sub-categories (mapped to sub-folders) can be created by using `/` as delimiter. | 1.0 | 28| `favorite` | boolean (read/write) | If a note is marked as favorite, it is displayed at the top of the notes' list. Default is `false`. | 1.0 | 29| `modified` | integer (read/write) | Unix timestamp for the last modified date/time of the note. If not provided on note creation or content update, the current time is used. | 1.0 | 30 31 32## Settings 33 34Since API version 1.2, it is possible to change app settings using the API. The following settings attributes exist: 35 36| Attribute | Type | Description | since API version | 37|:----------|:-----|:------------|:------------------| 38| `notesPath` | string | Path to the folder, where note's files are stored in Nextcloud. The path must be relative to the user folder. Default is the localized string `Notes`. | 1.2 | 39| `fileSuffix` | string | Newly created note's files will have this file suffix. Only the values `.txt` or `.md` are allowed. Default is `.txt`. | 1.2 | 40 41 42## Endpoints and Operations 43 44The base URL for all calls is: 45 46 https://user:password@yournextcloud.com/index.php/apps/notes/api/v1/ 47 48All defined routes in the specification are appended to this url. To access all notes for instance use this url (here shown as `curl` command): 49 50 curl -u user:password -H "Accept: application/json" https://yournextcloud.com/index.php/apps/notes/api/v1/notes 51 52 53 54### Get all notes (`GET /notes`) 55<details><summary>Details</summary> 56 57#### Request parameters 58| Parameter | Type | Description | since API version | 59|:----------|:-----|:------------|:------------------| 60| `category` | string, optional | Filter the result by category name, e.g. `?category=recipes`. Notes with another category are not included in the result. *Compatibility note:* before API v1.1, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.1 | 61| `exclude` | string, optional | Fields which should be excluded from response, seperated with a comma e.g.: `?exclude=content,title`. You can use this in order to reduce transferred data size if you are interested in specific attributes, only. | 1.0 | 62| `pruneBefore` | integer, optional | All notes without change before of this Unix timestamp are purged from the response, i.e. only the attribute `id` is included. You should use the Unix timestamp value from the last request's HTTP response header `Last-Modified` in order to reduce transferred data size. | 1.0 | 63| `chunkSize` | integer, optional | The response will contain no more than the given number of full notes. If there are more notes, then the result is chunked and the HTTP response header `X-Notes-Chunk-Cursor` is sent with a string value. In order to request the next chunk, a new request have to be made with parameter `chunkCursor` filled with that string value. *Compatibility note:* before API v1.2, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.2 | 64| `chunkCursor` | string, optional | To be used together with the parameter `chunkSize`. You must use the string value from the last request's HTTP response header `X-Notes-Chunk-Cursor` in order to get the next chunk of notes. Don't use this parameter for requesting the first chunk. *Compatibility note:* before API v1.2, this parameter is ignored; i.e., the result contains all notes regardless of this parameter. | 1.2 | 65| `If-None-Match` | HTTP header, optional | Use this in order to reduce transferred data size (see [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)). You should use the value from the last request's HTTP response header `ETag`. | 1.0 | 66 67#### Response 68##### 200 OK 69- **HTTP Header**: 70 - `ETag` (see [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)). 71 - `X-Notes-Chunk-Cursor`: Only if `chunkSize` is provided and not `0` and if the response does not contain all remaining notes. In this case, the response does not contain pruned notes. In order to get the next chunk, you will have to make a new request and use this header value as request parameter `chunkCursor`. The last chunk response will not contain this header but it will contain all pruned notes. In summary: a client have to repeatedly request the notes list from server with the desired `chunkSize` and with updated `chunkCursor` until the response does not contain any `X-Notes-Chunk-Cursor` HTTP header – only this last request can be used to check for deleted notes. 72 - `X-Notes-Chunk-Pending`: number of pending notes that have to be requested using the chunk cursor provided in the HTTP response header `X-Notes-Chunk-Cursor`. 73- **Body**: list of notes (see section [Note attributes](#note-attributes)), example: 74```js 75[ 76 { 77 "id": 76, 78 "etag": "be284e00488c61c101ee28309d235e0b", 79 "readonly": false, 80 "modified": 1376753464, 81 "title": "New note", 82 "category": "sub-directory", 83 "content": "New note\n and something more", 84 "favorite": false 85 }, // etc 86] 87``` 88 89##### 401 Unauthorized 90No valid authentication credentials supplied. 91</details> 92 93 94### Get single note (`GET /notes/{id}`) 95<details><summary>Details</summary> 96 97#### Request parameters 98| Parameter | Type | Description | 99|:------|:-----|:-----| 100| `id` | integer, required (path) | ID of the note to query. | 101| `If-None-Match` | HTTP header, optional | Use this in order to reduce transferred data size (see [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)). You should use the value from the note's attribute `etag` or from the last request's HTTP response header `ETag`. | 1.2 | 102 103#### Response 104##### 200 OK 105- **HTTP Header**: `ETag` (see [HTTP ETag](https://en.wikipedia.org/wiki/HTTP_ETag)). The value is identical to the note's attribute `etag` (see section [Note attributes](#note-attributes)). 106- **Body**: note (see section [Note attributes](#note-attributes)), example: 107```js 108{ 109 "id": 76, 110 "etag": "be284e00488c61c101ee28309d235e0b", 111 "readonly": false, 112 "modified": 1376753464, 113 "title": "New note", 114 "category": "sub-directory", 115 "content": "New note\n and something more", 116 "favorite": false 117} 118``` 119##### 400 Bad Request 120Invalid ID supplied. 121 122##### 401 Unauthorized 123No valid authentication credentials supplied. 124 125##### 404 Not Found 126Note not found. 127</details> 128 129 130### Create note (`POST /notes`) 131<details><summary>Details</summary> 132 133#### Request parameters 134- **Body**: some or all "read/write" attributes (see section [Note attributes](#note-attributes)), example: 135```js 136{ 137 "title": "New note", 138 "category": "Category/Sub Category", 139 "content": "New note\n and something more", 140} 141``` 142 143#### Response 144##### 200 OK 145- **Body**: note (see section [Note attributes](#note-attributes)), example see section [Get single note](#get-single-note-get-notesid). 146 147##### 400 Bad Request 148Invalid ID supplied. 149 150##### 401 Unauthorized 151No valid authentication credentials supplied. 152 153##### 507 Insufficient Storage 154Not enough free storage for saving the note's content. 155</details> 156 157 158### Update note (`PUT /notes/{id}`) 159<details><summary>Details</summary> 160 161#### Request parameters 162| Parameter | Type | Description | 163|:------|:-----|:-----| 164| `id` | integer, required (path) | ID of the note to update. | 165| `If-Match` | HTTP header, optional | Use this for optimistic concurrency control (optional, but strongly recommended in order to prevent lost updates). As value of this HTTP header, the client has to use the last known note's etag (see section [Note attributes](#note-attributes)). If the note has changed in the meanwhile (concurrent change), the update request is blocked with HTTP status 412 (see below). Otherwise, the request will be processed normally. | 1.2 | 166- **Body**: some or all "read/write" attributes (see section [Note attributes](#note-attributes)), example see section [Create note](#create-note-post-notes). 167 168#### Response 169##### 200 OK 170- **Body**: note (see section [Note attributes](#note-attributes)), example see section [Get single note](#get-single-note-get-notesid). 171 172##### 400 Bad Request 173Invalid ID supplied. 174 175##### 401 Unauthorized 176No valid authentication credentials supplied. 177 178##### 403 Forbidden 179The note is read-only. 180 181##### 404 Not Found 182Note not found. 183 184##### 412 Precondition Failed 185*(since API v1.2)* 186Update cannot be performed since the note has been changed on the server in the meanwhile (concurrent change). The body contains the current note's state from server (see section [Note attributes](#note-attributes)), example see section [Get single note](#get-single-note-get-notesid). The client should use this response data in order to perform a conflict solution (see section [Preventing lost updates and conflict solution](#preventing-lost-updates-and-conflict-solution)). 187 188##### 507 Insufficient Storage 189Not enough free storage for saving the note's content. 190</details> 191 192 193### Delete note (`DELETE /notes/{id}`) 194<details><summary>Details</summary> 195 196#### Request parameters 197| Parameter | Type | Description | 198|:------|:-----|:-----| 199| `id` | integer, required (path) | ID of the note to delete. | 200 201#### Response 202##### 200 OK 203Note is deleted. 204 205##### 400 Bad Request 206Invalid ID supplied. 207 208##### 401 Unauthorized 209No valid authentication credentials supplied. 210 211##### 403 Forbidden 212The note is read-only. 213 214##### 404 Not Found 215Note not found. 216</details> 217 218 219### Get settings (`GET /settings`) 220<details><summary>Details</summary> 221 222*(since API v1.2)* 223 224#### Request parameters 225None. 226 227#### Response 228##### 200 OK 229- **Body**: user's app settings (see section [Settings](#settings)), example: 230```js 231{ 232 "notesPath": "Notes", 233 "fileSuffix": ".txt" 234} 235``` 236 237##### 400 Bad Request 238Endpoint not supported by installed notes app version (requires API version 1.2). 239 240##### 401 Unauthorized 241No valid authentication credentials supplied. 242</details> 243 244 245### Change settings (`PUT /settings`) 246<details><summary>Details</summary> 247 248*(since API v1.2)* 249 250#### Request parameters 251- **Body**: some or all settings attributes (see section [Settings](#settings)). 252Omitted settings attributes are not changed. 253Empty values are replaced by the settings attribute's default value. 254All values are sanitized (e.g. prevent path traversal attacks, check allowed suffixes), so the result can differ from the request (the request will still succeed). 255The client may show an information to the user if the response differs from the request. 256Example: 257```js 258{ 259 "fileSuffix": ".md" 260} 261``` 262 263#### Response 264##### 200 OK 265- **Body**: user's app settings after validation (see section [Settings](#settings)), example see section [Get settings](#get-settings-get-settings). 266 267##### 400 Bad Request 268Endpoint not supported by installed notes app version (requires API version 1.2). 269 270##### 401 Unauthorized 271No valid authentication credentials supplied. 272</details> 273 274 275 276## Preventing lost updates and conflict solution 277 278While changing a note using a Notes client, the same note may be changed by another client. 279In order to prevent lost updates of those concurrent changes, the notes API uses a well established mechanism called [optimistic concurrency control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control). 280For this purpose, notes have the attribute `etag` which is an identifier that changes if (and only if) the note changes on the server. 281Clients have to store the `etag` for every note and send its value with every update request (HTTP header `If-Match`, see section [Update note](#update-note-put-notesid)). 282If there was no parallel change on the server (i.e., the `etag` on server is the same as the one send from the client), the update request is performed as usual. 283But if there was a parallel change, the `etag` on the server has changed and the server will refuse the update request. 284 285In this case, the client has to perform a conflict resolution, i.e. the local changes have to be merged with the remote changes. 286In order to compare local changes with remote changes, it is useful that the client stores the full note's state as reference state before performing any local updates. 287If an update conflict occurs, the client can use this reference state in order to merge all changes attribute-wise: 288- Attributes, that have changed only locally or remotely, can be merged by picking the (local resp. remote) change. 289- Attributes, that have changed both localy and remotely, have to be merged (see below). 290 291There are several options on how to merge an attribute: 292- a) *Let the user decide*: ask the user whether i) overwrite local changes, ii) overwrite remote changes, or iii) save local (or remote) changes as new note. 293- b) *Let the user merge*: provide an interface which allows for merging the files (you know it from your version control). 294- c) *Try to merge automatically*: merge all changes automatically, e.g. for the `content` attribute using the [google-diff-match-patch](https://code.google.com/p/google-diff-match-patch/) ([Demo](https://neil.fraser.name/software/diff_match_patch/svn/trunk/demos/demo_patch.html), [Code](https://github.com/bystep15/google-diff-match-patch)) library. 295 296