From 90e5831d8ff127696a053f8c0ec435fa9c258ba1 Mon Sep 17 00:00:00 2001 From: Grotax Date: Mon, 1 Apr 2024 08:53:16 +0000 Subject: Deployed 8c36f1d with MkDocs version: 1.4.2 --- api/api-v2/index.html | 1876 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1876 insertions(+) create mode 100644 api/api-v2/index.html (limited to 'api/api-v2') diff --git a/api/api-v2/index.html b/api/api-v2/index.html new file mode 100644 index 000000000..c1eb2b0e2 --- /dev/null +++ b/api/api-v2/index.html @@ -0,0 +1,1876 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + API v2 - Nextcloud News App + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

External API v2 (Draft)

+

Disclaimer: this API has not been fully implemented yet, help is welcome.

+

The News app offers a RESTful API which can be used to sync folders, feeds and items. The API also supports CORS which means that you can access the API from your browser using JavaScript.

+

In addition, an updater API is exposed which enables API users to run feed updates in parallel using a REST API or Nextcloud console API.

+

Conventions

+

This document uses the following conventions:

+
    +
  • Object aliases as comments
  • +
  • Error objects are omitted
  • +
+

Object Aliases As Comments

+

In order to only specify the JSON objects once, comments are used to alias them.

+

There are two types of aliases:

+
    +
  • Objects
  • +
  • Object arrays
  • +
+

Objects:

+
{
+    "folder": { /* folder object */ },
+}
+
+

means that the folder attributes will be listed inside the folder object

+

Object arrays:

+
{
+    "folders": [ /* array of folder objects */ ],
+}
+
+

means that folder objects will be listed inside the folders array.

+

Error Objects Are Omitted

+

This means that the error object will not be explicitly shown in the examples. All HTTP 400 response status codes contain an error object:

+
{
+    "error": {
+        "code": 1,
+        "message": "error message"
+    }
+}
+
+

API Stability Contract

+

The API level will change if the following occurs:

+
    +
  • a required HTTP request header is added
  • +
  • a required request parameter is added
  • +
  • a field of a response object is removed
  • +
  • a field of a response object is changed to a different datatype
  • +
  • an HTTP response header is removed
  • +
  • an HTTP response header is changed to a different datatype
  • +
  • the meaning of an API call changes (e.g. /sync will not sync any more but show a sync timestamp)
  • +
+

The API level will not change if:

+
    +
  • a new HTTP response header is added
  • +
  • an optional new HTTP request header is added
  • +
  • a new response parameter is added (e.g. each item gets a new field "something": 1)
  • +
  • The order of the JSON attributes is changed on any level (e.g. "id":3 is not the first field anymore, but the last)
  • +
+

You have to design your app with these things in mind!:

+
    +
  • Don't depend on the order of object attributes. In JSON it does not matter where the object attribute is since you access the value by name, not by index
  • +
  • Don't limit your app to the currently available attributes. New ones might be added. If you don't handle them, ignore them
  • +
  • Use a library to compare versions, ideally one that uses semantic versioning
  • +
+

Request Format

+

The base URL for all calls is:

+
https://yournextcloud.com/index.php/apps/news/api/v2
+
+

Unless an absolute Url is specified, the relative Urls in the Specification are appended to this url. To access the route /sync for instance you'd use the following url:

+
https://yournextcloud.com/index.php/apps/news/api/v2/sync
+
+

The required request headers are:

+
    +
  • Accept: application/json
  • +
+

Any request method except GET:

+
    +
  • Content-Type: application/json; charset=utf-8
  • +
+

Any route that allows caching:

+
    +
  • If-None-Match: an Etag, e.g. 6d82cbb050ddc7fa9cbb659014546e59. If no previous Etag is known, this header should be omitted
  • +
+

The request body is either passed in the URL in case of a GET request (e.g.: ?foo=bar&index=0) or as JSON, e.g.:

+
{
+    "foo": "bar",
+    "index": 0
+}
+
+

Note: The current Etag implementation contains a unix timestamp in milliseconds. This is an implementation detail and you should not rely on it.

+

API Level Detection

+

Check the API level route

+

Authentication

+

Because REST is stateless you have to re-send user and password each time you access the API. Therefore running Nextcloud with SSL is highly recommended otherwise everyone in your network can log your credentials.

+

Credentials are passed as an HTTP header using HTTP basic auth:

+
Authorization: Basic $CREDENTIALS
+
+

where $CREDENTIALS is:

+
base64(USER:PASSWORD)
+
+

This authentication/authorization method will be the recommended default until core provides an easy way to do OAuth

+

Note: Even if login cookies are sent back to your client, they will not be considered for authentication.

+

Response Format

+

The status codes are not always provided by the News app itself, but might also be returned because of Nextcloud internal errors.

+

The following status codes can always be returned by Nextcloud:

+
    +
  • 401: The provided credentials to log into Nextcloud are invalid.
  • +
  • 403: The user is not allowed to access the route. This can happen if for instance of only users in the admin group can access the route and the user is not in it.
  • +
  • 404: The route can not be found or the resource does not exist. Can also happen if for instance you are trying to delete a folder which does not exist.
  • +
  • 5xx: An internal server error occurred. This can happen if the server is in maintenance mode or because of other reasons.
  • +
+

The following status codes are returned by News:

+
    +
  • 200: Everything went fine
  • +
  • 304: In case the resource was not modified, contains no response body. This means that you can ignore the request since everything is up to date.
  • +
  • 400: There was an app related error, check the error object if specified
  • +
  • 409: Conflict error which means that the resource exists already. Can be returned when updating (PATCH) or creating (POST) a resource, e.g. a folder
  • +
+

The response headers are:

+
    +
  • Content-Type: application/json; charset=utf-8
  • +
  • Etag: A string containing a cache header of maximum length 64, e.g. 6d82cbb050ddc7fa9cbb659014546e59. The etag value will be assembled using the number of feeds, folders and the highest last modified timestamp in milliseconds, e.g. 2-3-123131923912392391239. However consider that a detail and dont rely on it.
  • +
+

The response body is a JSON structure that looks like this, which contains the actual data on the first level. The key is the resource in singular if it's a single resource or plural if its a collection. In case of HTTP 400, an error object is also present to help distinguishing between different error types:

+
{
+    "error": {
+        "code": 1,
+        "message": "error message"
+    }
+}
+
+
    +
  • error: Only present when an HTTP 400 is returned to help distinguishing between error causes
  • +
  • code: A unique error code
  • +
  • message: A translated error message. The user's configured locale is used.
  • +
+

In case of an 4xx or 5xx error the request was not successful and has to be retried. For instance marking items as read locally and syncing should send the same request again the next time the user syncs in case an error occurred.

+

Security Guidelines

+

Read the following notes carefully to prevent being subject to security exploits:

+
    +
  • You should always enforce SSL certificate verification and never offer a way to turn it off. Certificate verification is important to prevent MITM attacks which is especially important in the mobile world where users are almost always connected to untrusted networks. In case a user runs a self-signed certificate on his server ask him to either install his certificate on his device or direct him to one of the many ways to sign his certificate for free (most notably letsencrypt.com)
  • +
  • All string fields in a JSON response expect an item's body are not sanitized. This means that if you do not escape it properly before rendering you will be vulnerable to XSS attacks
  • +
  • Basic Auth headers can easily be decrypted by anyone since base64 is an encoding, not an encryption. Therefore only send them if you are accessing an HTTPS website or display an easy to understand warning if the user chooses HTTP
  • +
  • When creating a feed you can choose to add basic auth authentication credentials. These must be stored in clear text so anyone with access to your database (however they might have achieved it, think of Sql injection) can read them and use them to access the website. You should warn the user about this.
  • +
  • If you are building a client in JavaScript or are using a link with target="blank", remember to set the window.opener property to null and/or add a rel="noreferrer" to your link to prevent your app from being target by an XSS attack
  • +
+

Syncing

+

All routes are given relative to the base API url, e.g.: /sync becomes https://yourNextcloud.com/index.php/apps/news/api/v2/sync

+

There are two usecases for syncing:

+
    +
  • Initial sync: the user does not have any data at all
  • +
  • Syncing local and remote changes: the user has synced at least once and wants to submit and receive changes
  • +
+

Initial Sync

+

The intial sync happens when a user adds an Nextcloud account in your app. In that case you want to download all folders, feeds and unread/starred items. To do this, make the following request:

+
    +
  • Method: GET
  • +
  • Route: /sync
  • +
  • Authentication: required
  • +
  • HTTP headers:
  • +
  • Accept: "application/json"
  • +
+

This will return the following status codes:

+
    +
  • 200: Success
  • +
+

and the following HTTP headers:

+
    +
  • Content-Type: application/json; charset=utf-8
  • +
  • Etag: A string containing a cache header, maximum size 64 ASCII characters, e.g. 6d82cbb050ddc7fa9cbb659014546e59
  • +
+

and the following request body:

+
{
+    "folders": [ /* array of folder objects */ ],
+    "feeds": [ /* array of feed objects */ ],
+    "items": [ /* array of item objects */ ]
+}
+
+

Note: Each object is explained in more detail in a separate section:

+ +

Sync Local And Remote Changes

+

After the initial sync the app has all folders, feeds and items. Now you want to push changes and retrieve updates from the server. To do this, make the following request:

+
    +
  • Method: POST
  • +
  • Route: /sync
  • +
  • Authentication: required
  • +
  • HTTP headers:
  • +
  • Content-Type: "application/json; charset=utf-8"
  • +
  • Accept: "application/json"
  • +
  • If-None-Match: "6d82cbb050ddc7fa9cbb659014546e59" (Etag from the previous request to the /sync route)
  • +
+

with the following request body:

+
{
+    "items": [{
+            // read and starred
+            "id": 5,
+            "isStarred": false,
+            "isUnread": true,
+            "contentHash": "08ffbcf94bd95a1faa6e9e799cc29054"
+        }, {
+            // only read
+            "id": 6,
+            "isUnread": true,
+            "contentHash": "09ffbcf94bd95a1faa6e9e799cc29054"
+        }, {
+            // only starred
+            "id": 7,
+            "isStarred": false,
+            "contentHash": "18ffbcf94bd95a1faa6e9e799cc29054"
+    }, /* etc */]
+}
+
+

If no items have been read or starred, simply leave the items array empty, e.g.:

+
{
+    "items": []
+}
+
+

The response matches the GET call, except there can be two different types of item objects:

+
    +
  • Full: Contains all attributes
  • +
  • Reduced: Contains only id, isUnread and isStarred
  • +
+

The deciding factor whether a full or reduced item object is being returned depends on the contentHash in the request: If the contentHash matches the record in the database a reduced item object is being returned, otherwise a full object is used. Both can occur in the same items array at the same time.

+

The idea behind this special handling is that if the contentHash matches the record in the database, the actual item content did not change. Therefore it is enough to know the item status. This greatly reduces the amount sent over the Net which is especially important for mobile apps.

+

This also applies to folders and feeds, however the reduced folder and feed objects will only include the id element. The deciding factor whether only an id or the full object will be returned is the last modified modified timestamp which is included in the sent etag.

+

If you push a list of items to be marked read/starred, there can also be less items in the response than the ones which were initially sent. This means that the item was deleted by the cleanup job and should be removed from the client device.

+

For instance let's take a look at the following example. You are POSTing the following JSON:

+
{
+    "items": [{
+            "id": 5,
+            "isStarred": false,
+            "isUnread": true,
+            "contentHash": "08ffbcf94bd95a1faa6e9e799cc29054"
+        }, {
+            "id": 6,
+            "isUnread": true,
+            "contentHash": "09ffbcf94bd95a1faa6e9e799cc29054"
+        }, {
+            "id": 7,
+            "isStarred": false,
+            "contentHash": "18ffbcf94bd95a1faa6e9e799cc29054"
+    }]
+}
+
+

and receive the following output in return:

+
{
+    "items": [{
+            "id": 5,
+            "isStarred": false,
+            "isUnread": true
+        }, {
+            "id": 6,
+            "isUnread": true,
+            "isStarred": false
+    }]
+}
+
+

The item with the id 7 is missing from the response. This means that it was deleted on the server.

+

For folders and feeds all ids will be returned so you can compare the existing ids with your locally available feeds and folders and remove the difference.

+

Folders

+

Folders are represented using the following data structure:

+
{
+    "id": 3,
+    "name": "funny stuff"
+}
+
+

The attributes mean the following:

+
    +
  • id: 64bit Integer, id
  • +
  • name: Abitrary long text, folder's name
  • +
+

Deleting A Folder

+

To delete a folder, use the following request:

+
    +
  • Method: DELETE
  • +
  • Route: /folders/{id}
  • +
  • Route Parameters:
  • +
  • {id}: folder's id
  • +
  • Authentication: required
  • +
+

The following response is being returned:

+

Status codes:

+
    +
  • 200: Folder was deleted successfully
  • +
  • 404: Folder does not exist
  • +
+

In case of an HTTP 200, the deleted folder is returned in full in the response, e.g.:

+
{
+    "folder": { /* folder object */ }
+}
+
+

Note: Deleted folders will not appear during the next sync so you also need to delete the folder locally afterwards. Folders should only be deleted locally if an HTTP 200 or 404 was returned.

+

Note: If you delete a folder locally, you should also delete all feeds whose folderId attribute matches the folder's id attribute and also delete all items whose feedId attribute matches the feeds' id attribute. This is done automatically on the server and will also be missing on the next request.

+

Creating A Folder

+

To create a folder, use the following request:

+
    +
  • Method: POST
  • +
  • Route: /folders
  • +
  • Authentication: required
  • +
+

with the following request body:

+
{
+    "name": "Folder name"
+}
+
+

The following response is being returned:

+

Status codes:

+
    +
  • 200: Folder was created successfully
  • +
  • 400: Folder creation error, check the error object:
  • +
  • code: 1: folder name is empty
  • +
+

In case of an HTTP 200, the created or already existing folder is returned in full in the response, e.g.:

+