<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Recent changes to EndpointPermissions</title><link>https://sourceforge.net/p/tradamus/wiki/EndpointPermissions/</link><description>Recent changes to EndpointPermissions</description><atom:link href="https://sourceforge.net/p/tradamus/wiki/EndpointPermissions/feed" rel="self"/><language>en</language><lastBuildDate>Tue, 06 Oct 2015 19:47:20 -0000</lastBuildDate><atom:link href="https://sourceforge.net/p/tradamus/wiki/EndpointPermissions/feed" rel="self" type="application/rss+xml"/><item><title>EndpointPermissions modified by Domhnall101</title><link>https://sourceforge.net/p/tradamus/wiki/EndpointPermissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v6
+++ v7
@@ -1,4 +1,5 @@
-This document describes the access permissions which are required to use the various Tradamus endpoints.  The endpoints themselves are more full described in [Tradamus Server API].
+##Endpoint Permissions
+This document describes the access permissions which are required to use the various Tradamus endpoints.  The endpoints themselves are more full described in [Tradamus Server API](TradamusServerAPI).

 There are five ranked levels of access:  `NONE`, `VIEWER`, `CONTRIBUTOR`, `EDITOR`, and `OWNER`.  If a user has a particular level of access to a resource, they also implicitly have all the lower levels of access.

&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Domhnall101</dc:creator><pubDate>Tue, 06 Oct 2015 19:47:20 -0000</pubDate><guid>https://sourceforge.netb6d1f0851f0f2030b68def763097b8c9a53ad826</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v5
+++ v6
@@ -12,7 +12,7 @@

 These levels are relative to a given resource.  A particular user could have `OWNER` access to one edition while  having no access at all to other editions.

-Permissions can currently be set at three different points:  the edition, the transcription, and the manifest.  When verifying permissions, they are checked at the edition level first; if the user has sufficient permissions to access the edition, permissions are then checked at the transcription or manifest level.  Accordingly, transcription and manuscript permissions can only be used to make access more restrictive.
+Permissions can currently be set at three different points:  the edition, the transcription, and the manifest.  When verifying permissions, they are checked at the edition level first; if the user has sufficient permissions to access the edition, permissions are then checked at the transcription or manifest level.  Accordingly, transcription and manifestt permissions can only be used to make access more restrictive.

 Also new is the notion of annotations being vetted or unvetted.  When a user with `CONTRIBUTOR` access adds an annotation, it is only visible to themselves and to users with `EDITOR` access.  Once an `EDITOR` has approved an annotation, it becomes visible to all users with `VIEWER` access.

&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Thu, 18 Sep 2014 01:08:38 -0000</pubDate><guid>https://sourceforge.net492adb3e87aa923919a1f6251fdd9d2a97945d4c</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v4
+++ v5
@@ -92,7 +92,7 @@
 ### Set edition metadata ###
 Endpoint: /edition/*edID*/metadata
 Method: PUT
-Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify metadata added by the current user.
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any metadata which belong to another user.

 ### Get edition decisions ###
 Endpoint: /edition/*edID*/decisions
@@ -102,7 +102,7 @@
 ### Set edition decisions ###
 Endpoint: /edition/*edID*/decisions
 Method: PUT
-Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify decisions added by the current user.
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any decisions which belong to another user.

 ### Set edition permissions ###
 Endpoint: /edition/*edID*/permissions
@@ -140,7 +140,7 @@
 ### Set witness metadata ###
 Endpoint: /witness/*witID*/metadata
 Method: PUT
-Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify metadata added by the current user.
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any metadata which belong to another user.

 ### Delete a witness ###
 Endpoint:  /witness/*witID*
@@ -191,7 +191,7 @@
 ### Set page annotations ###
 Endpoint:  /page/*pageID*/annotations
 Method: PUT
-Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will only modify annotations added by the current user.
+Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any annotations which belong to another user.

 ### Get page lines ###
 Endpoint:  /page/*pgID*/lines
@@ -201,7 +201,7 @@
 ### Set page lines ###
 Endpoint:  /page/*pageID*/lines
 Method: PUT
-Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will only modify lines added by the current user.
+Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any lines which belong to another user.

 ## 7. Manifests ##
@@ -242,7 +242,7 @@
 ### Set canvas annotations ###
 Endpoint:  /canvas/*canvID*/annotations
 Method: PUT
-Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will only modify annotations added by the current user.
+Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any annotations which belong to another user.

 ### Get canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
@@ -252,7 +252,7 @@
 ### Set canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
 Method: PUT
-Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will only modify lines added by the current user.
+Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify any lines which belong to another user.

 ## 9. Annotations ##
@@ -265,13 +265,13 @@
 ### Set annotation details ###
 Endpoint:  /annotation/*annID*
 Method: PUT
-Access: `CONTRIBUTOR` on the transcription or the manifest.  If the access level is just `CONTRIBUTOR`, the call can only modify annotations added by the current user.
+Access: `CONTRIBUTOR` on the transcription or the manifest.  If access level is just `CONTRIBUTOR`, the call will fail with a 403 if it attempts to modify an annotation which belongs to another user.

 ### Delete an annotation ###
 Endpoint:  /annotation/*annID*
 Method: DELETE
-Access: `CONTRIBUTOR` on the transcription or the manifest.  If the access level is just `CONTRIBUTOR`, the call can only delete annotations added by the current user.
+Access: `CONTRIBUTOR` on the transcription or the manifest.  If the access level is just `CONTRIBUTOR`, the call can only delete an annotation belonging to the current user.

 ## 10. Collation ##
&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Mon, 14 Apr 2014 17:45:13 -0000</pubDate><guid>https://sourceforge.netba8f98d3a3f00b6caa5180cc1bb52c2a57c984e8</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v3
+++ v4
@@ -6,13 +6,15 @@
 ------------- | ------
 `NONE`        | None
 `VIEWER`      | Read-only access.
-`CONTRIBUTOR` | Can add new elements (i.e. annotations) but these will be unvetted.
-`EDITOR`      | Can add vetted elements.  Can vet elements for `CONTRIBUTOR`s.
+`CONTRIBUTOR` | Can add new annotations but these will be unvetted.
+`EDITOR`      | Can add fully-vetted annotations.  Can vet annotations for `CONTRIBUTOR`s.
 `OWNER`       | Can assign permissions.

 These levels are relative to a given resource.  A particular user could have `OWNER` access to one edition while  having no access at all to other editions.

 Permissions can currently be set at three different points:  the edition, the transcription, and the manifest.  When verifying permissions, they are checked at the edition level first; if the user has sufficient permissions to access the edition, permissions are then checked at the transcription or manifest level.  Accordingly, transcription and manuscript permissions can only be used to make access more restrictive.
+
+Also new is the notion of annotations being vetted or unvetted.  When a user with `CONTRIBUTOR` access adds an annotation, it is only visible to themselves and to users with `EDITOR` access.  Once an `EDITOR` has approved an annotation, it becomes visible to all users with `VIEWER` access.

 ## 1. Users ##

@@ -169,30 +171,37 @@
 ### Get page details ###
 Endpoint:  /page/*pageID*
 Method: GET
+Access: `VIEWER` on the transcription.

 ### Set page details ###
 Endpoint:  /page/*pageID*
 Method: PUT
+Access: `EDITOR` on the transcription.

 ### Add a page annotation ###
 Endpoint: /page/*pgID*/annotations
 Method: POST
+Access: `CONTRIBUTOR` on the transcription.

 ### Get page annotations ###
 Endpoint:  /page/*pgID*/annotations
 Method: GET
+Access: `VIEWER` on the transcription.

 ### Set page annotations ###
 Endpoint:  /page/*pageID*/annotations
 Method: PUT
+Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will only modify annotations added by the current user.

 ### Get page lines ###
 Endpoint:  /page/*pgID*/lines
 Method: GET
+Access: `VIEWER` on the transcription.

 ### Set page lines ###
 Endpoint:  /page/*pageID*/lines
 Method: PUT
+Access: `CONTRIBUTOR` on the transcription.  If access level is just `CONTRIBUTOR`, the call will only modify lines added by the current user.

 ## 7. Manifests ##
@@ -200,10 +209,12 @@
 ### Get manifest details ###
 Endpoint:  /manifest/*manID*
 Method:  GET
+Access: `VIEWER` on the manifest.

 ### Set manifest permissions ###
 Endpoint:  /manifest/*manID*/permissions
 Method:  PUT
+Access: `OWNER` on the manifest.

 ## 8. Canvasses ##
@@ -211,30 +222,37 @@
 ### Get canvas details ###
 Endpoint:  /canvas/*canvID*
 Method:  GET
+Access: `VIEWER` on the manifest.

 ### Set canvas details ###
 Endpoint:  /canvas/*canvID*
 Method: PUT
+Access: `EDITOR` on the manifest.

 ### Add a canvas annotation ###
 Endpoint: /canvas/*canvID*/annotations
 Method: POST
+Access: `CONTRIBUTOR` on the manifest.

 ### Get canvas annotations ###
 Endpoint:  /canvas/*canvID*/annotations
 Method:  GET
+Access: `VIEWER` on the manifest.

 ### Set canvas annotations ###
 Endpoint:  /canvas/*canvID*/annotations
 Method: PUT
+Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will only modify annotations added by the current user.

 ### Get canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
 Method: GET
+Access: `VIEWER` on the manifest.

 ### Set canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
 Method: PUT
+Access: `CONTRIBUTOR` on the manifest.  If access level is just `CONTRIBUTOR`, the call will only modify lines added by the current user.

 ## 9. Annotations ##
@@ -242,14 +260,18 @@
 ### Get annotation details ###
 Endpoint:  /annotation/*annID*
 Method:  GET
+Access: `VIEWER` on the transcription or the manifest.  In particular, if an annotation is anchored to both the page and the canvas, it is sufficient to have `VIEWER` access on one of the transcription or the manifest.

 ### Set annotation details ###
 Endpoint:  /annotation/*annID*
 Method: PUT
+Access: `CONTRIBUTOR` on the transcription or the manifest.  If the access level is just `CONTRIBUTOR`, the call can only modify annotations added by the current user.
+

 ### Delete an annotation ###
 Endpoint:  /annotation/*annID*
 Method: DELETE
+Access: `CONTRIBUTOR` on the transcription or the manifest.  If the access level is just `CONTRIBUTOR`, the call can only delete annotations added by the current user.

 ## 10. Collation ##
@@ -257,17 +279,21 @@
 ### Full-edition collation ###
 Endpoint:  /collation/*edID*
 Method:  GET
+Access: Only transcriptions with `VIEWER` access will be included in the collation.

 ### Sub-collation ###
 Endpoint:  /collation
 Method:  GET
+Access: Only transcriptions with `VIEWER` access will be included in the collation.

 ## 11. Config ##
 Endpoint:  /config
 Method:  GET
+Access: No restrictions.

 ## 12. Activity Log ##
 Endpoint:  /activity
 Method:  GET
+Access: To be determined.
&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Thu, 03 Apr 2014 16:15:21 -0000</pubDate><guid>https://sourceforge.net81cdb664ba27d517f68cae418c81de3c834cfa13</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v2
+++ v3
@@ -1,45 +1,63 @@
-The following table describes the permission level which is required to access the given endpoint.  The endpoints themselves are more full described in [Tradamus Server API].
-
-There are five ranked levels of permissions:  `NONE`, `VIEWER`, `CONTRIBUTOR`, `EDITOR`, and `OWNER`.  If a user has a particular level of access to a resource, they also implicitly have all the lower levels of access.
-
-Permissions can currently be set at three different points:  the Edition, the Transcription, and the Manifest.  When verifying
+This document describes the access permissions which are required to use the various Tradamus endpoints.  The endpoints themselves are more full described in [Tradamus Server API].
+
+There are five ranked levels of access:  `NONE`, `VIEWER`, `CONTRIBUTOR`, `EDITOR`, and `OWNER`.  If a user has a particular level of access to a resource, they also implicitly have all the lower levels of access.
+
+level         | access
+------------- | ------
+`NONE`        | None
+`VIEWER`      | Read-only access.
+`CONTRIBUTOR` | Can add new elements (i.e. annotations) but these will be unvetted.
+`EDITOR`      | Can add vetted elements.  Can vet elements for `CONTRIBUTOR`s.
+`OWNER`       | Can assign permissions.
+
+These levels are relative to a given resource.  A particular user could have `OWNER` access to one edition while  having no access at all to other editions.
+
+Permissions can currently be set at three different points:  the edition, the transcription, and the manifest.  When verifying permissions, they are checked at the edition level first; if the user has sufficient permissions to access the edition, permissions are then checked at the transcription or manifest level.  Accordingly, transcription and manuscript permissions can only be used to make access more restrictive.

 ## 1. Users ##

 ### Add new user ###
-POST /users
-No restrictions.
+Endpoint: /users
+Method: POST
+Access: No restrictions.

 ### Invite new user ###
-POST /users
-`EDITOR` on associated edition.
+Endpoint: /users
+Method: POST
+Access: `EDITOR` on edition to which user is being invited.

 ### Get user details ###
-GET /user/*userID*
-No restrictions.
+Endpoint: /user/*userID*
+Method: GET
+Access: No restrictions.

 ### Get user details by email ###
-GET /user?mail=*emailAddress*
-No restrictions.
+Endpoint: /user?mail=*emailAddress*
+Method: GET
+Access: No restrictions.

 ### Modify user details ###
-PUT /user/*userID*
-Only *userID* can modify their own details.
+Endpoint: /user/*userID*
+Method: PUT
+Access: Only *userID* can modify their own details.

 ### Reset user password ###
-PUT /user?reset=*emailAddress*
-No restrictions.
+Endpoint: /user?reset=*emailAddress*
+Method: POST
+Access: No restrictions.

 ## 2. Login ##

 ### Log in to Tradamus ###
-POST /login
-No restrictions.
+Endpoint: /login
+Method: POST
+Access: No restrictions.

 ### Check login status ###
-GET /login
-The call always returns the status of the currently logged-in user.
+Endpoint: /login
+Method: GET
+Access: The call always returns the status of the current user.

 ## 3. Editions ##
@@ -47,42 +65,52 @@
 ### Add new edition ###
 Endpoint: /editions
 Method: POST
+Access: No restrictions.  The current user will be granted `OWNER` access to the newly-created edition.

 ### List editions ###
 Endpoint: /editions
 Method: GET
+Access: The call will only return editions for which the current user has `VIEWER` permissions.

 ### Get edition details ###
 Endpoint: /edition/*edID*
 Method: GET
+Access: `VIEWER` on the edition.

 ### Set edition details ###
 Endpoint: /edition/*edID*
 Method: PUT
+Access: `EDITOR` on the edition.

 ### Add an edition metadatum ###
 Endpoint: /edition/*edID*/metadata
 Method: POST
+Access: `CONTRIBUTOR` on the edition.

 ### Set edition metadata ###
 Endpoint: /edition/*edID*/metadata
 Method: PUT
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify metadata added by the current user.

 ### Get edition decisions ###
 Endpoint: /edition/*edID*/decisions
 Method: GET
+Access: `VIEWER` on the edition.

 ### Set edition decisions ###
 Endpoint: /edition/*edID*/decisions
 Method: PUT
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify decisions added by the current user.

 ### Set edition permissions ###
 Endpoint: /edition/*edID*/permissions
 Method: PUT
+Access: `OWNER` on the edition.

 ### Delete an edition ###
 Endpoint: /edition/*edID*
 Method: DELETE
+Access: `OWNER` on the edition.

 ## 4. Witnesses ##
@@ -90,30 +118,37 @@
 ### Create a new witness ###
 Endpoint: /witnesses
 Method: POST
+Access: `EDITOR` on the edition.

 ### Get witness details ###
 Endpoint:  /witness/*witID*
 Method: GET
+Access: `VIEWER` on the edition.

 ### Set witness details ###
 Endpoint:  /witness/*witID*
 Method:  PUT
+Access: `EDITOR` on the edition.

 ### Add a witness metadatum ###
 Endpoint: /witness/*witID*/metadata
 Method: POST
+Access: `CONTRIBUTOR` on the edition.

 ### Set witness metadata ###
 Endpoint: /witness/*witID*/metadata
 Method: PUT
+Access: `CONTRIBUTOR` on the edition.  If access level is just `CONTRIBUTOR`, the call will only modify metadata added by the current user.

 ### Delete a witness ###
 Endpoint:  /witness/*witID*
 Method: DELETE
+Access: `EDITOR` on the edition.

 ### Recursively get all witness annotations ###
 Endpoint:  /witness/*witID*/annotations
 Method: GET
+Access: `VIEWER` on the edition.  The results will be filtered if access is more restricted at the transcription or manifest levels.

 ## 5. Transcriptions ##
@@ -121,10 +156,12 @@
 ### Get transcription details ###
 Endpoint:  /transcription/*transcrID*
 Method: GET
+Access: `VIEWER` on the transcription.

 ### Set transcription permissions ###
 Endpoint:  /transcription/*transcrID*/permissions
 Method:  PUT
+Access: `OWNER` on the transcription.

 ## 6. Pages ##
&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Wed, 02 Apr 2014 23:24:50 -0000</pubDate><guid>https://sourceforge.net0973ea66c6f401e2d9970bc74767bed7479071d6</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v1
+++ v2
@@ -30,6 +30,9 @@
 PUT /user?reset=*emailAddress*
 No restrictions.

+
+## 2. Login ##
+
 ### Log in to Tradamus ###
 POST /login
 No restrictions.
@@ -38,148 +41,90 @@
 GET /login
 The call always returns the status of the currently logged-in user.

-POST /editions
-GET /editions
-GET /edition/*edID*
-PUT /edition/*edID*
-POST /edition/*edID*/metadata
-PUT /edition/*edID*/metadata
-GET /edition/*edID*/decisions
-PUT /edition/*edID*/decisions
-PUT /edition/*edID*/permissions
-DELETE /edition/*edID*
-
-POST /witnesses
-Method: POST
-Parameters: `edition=`*edID* (required)
-Request content-type: application/json
-Request body: {"title":"My Witness","siglum":"M"}
-Status: 201 on success, 404 if *edID* does not exist.
-Comments:
-This allows the creation of a witness from JSON (non-LD) data.  The "title" and "siglum" fields are required.
-
-### Import JSON-LD witness ###
+
+## 3. Editions ##
+
+### Add new edition ###
+Endpoint: /editions
+Method: POST
+
+### List editions ###
+Endpoint: /editions
+Method: GET
+
+### Get edition details ###
+Endpoint: /edition/*edID*
+Method: GET
+
+### Set edition details ###
+Endpoint: /edition/*edID*
+Method: PUT
+
+### Add an edition metadatum ###
+Endpoint: /edition/*edID*/metadata
+Method: POST
+
+### Set edition metadata ###
+Endpoint: /edition/*edID*/metadata
+Method: PUT
+
+### Get edition decisions ###
+Endpoint: /edition/*edID*/decisions
+Method: GET
+
+### Set edition decisions ###
+Endpoint: /edition/*edID*/decisions
+Method: PUT
+
+### Set edition permissions ###
+Endpoint: /edition/*edID*/permissions
+Method: PUT
+
+### Delete an edition ###
+Endpoint: /edition/*edID*
+Method: DELETE
+
+
+## 4. Witnesses ##
+
+### Create a new witness ###
 Endpoint: /witnesses
 Method: POST
-Parameters: `edition=`*edID* (required)
-Request content-type: application/ld+json
-Request body: single-file IIIF metadata representation
-Status: 201 on success, 404 if *edID* does not exist.
-Comments:
-Currently the only source for this type of file is the JSON-LD export from T-PEN.  The IIIF metadata spec is still under development, and does not appear to yet be supported by any other software.
-
-### Import XML witness ###
-Endpoint: /witnesses
-Method: POST
-Parameters: `edition=`*edID* (required)
-Request content-type: text/xml
-Request body: TEI XML file
-Status:  201 on success, 404 if *edID* does not exist.
-Comments:
-This has so far only been tested with TEI files from Jeffrey Witt's Petrus Plaoul corpus.  The implementation will need to be generalised as we encounter other TEI files.

 ### Get witness details ###
 Endpoint:  /witness/*witID*
 Method: GET
-Request body:  none
-Response body:
-{
-&amp;nbsp;&amp;nbsp;"id":4,
-&amp;nbsp;&amp;nbsp;"author":"Petrus Plaoul",
-&amp;nbsp;&amp;nbsp;"edition":1,
-&amp;nbsp;&amp;nbsp;"title":"Lectio 1, Prologus \[Vatican Transcription]",
-&amp;nbsp;&amp;nbsp;"manifest":3,
-&amp;nbsp;&amp;nbsp;"siglum":"V",
-&amp;nbsp;&amp;nbsp;"transcription":3,
-&amp;nbsp;&amp;nbsp;"metadata":\{
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"editor":"Jeffrey C. Witt",
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"pubPlace":"Boston, MA"
-&amp;nbsp;&amp;nbsp;}
-}
-Status:  200 on success, 404 if *witID* does not exist.
-Comments:

 ### Set witness details ###
 Endpoint:  /witness/*witID*
 Method:  PUT
-Request content-type:  application/json
-Request body:  {"author":"New Author", "title":"New Title", "siglum":"New Siglum"}
-Response body:  none
-Status:  204 on success, 404 if *witID* does not exist.
-Comments:
-The request body should contain a JSON object describing the fields to be changed.  Any fields which are omitted from the request will remain unchanged.

 ### Add a witness metadatum ###
 Endpoint: /witness/*witID*/metadata
 Method: POST
-Request content-type: application/json
-Request body: {"type":"colour","content":"turquoise"}
-Response body: none
-Response location: /annotation/*annID*
-Status: 201 on success, 404 if *witID* does not exist.
-Comments:
-The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created metadatum.

 ### Set witness metadata ###
 Endpoint: /witness/*witID*/metadata
 Method: PUT
-Request content-type: application/json
-Request body: \[{"type":"colour","content":"turquoise"}]
-Request parameters: `merge=true|false` (default `true`)
-Response body: none
-Status: 204 on success, 404 if *witID* does not exist.
-Comments:
-The request body should contain an array of annotation objects. If the `merge` parameter is `false`, the contents of the array will completely replace the witness' existing metadata; if `merge` is `true` or omitted, the PUT will only affect metadata objects included in the request body.  In either case, the call will create any objects found in the request body which don't already exist.
-

 ### Delete a witness ###
 Endpoint:  /witness/*witID*
 Method: DELETE
-Request body:  none
-Response body:  none
-Status:  204 on success.
-Comments:
-Deletes the single witness identified by *witID*.  Associated transcriptions, pages, manifests, canvasses, images, and annotations will also be deleted.

 ### Recursively get all witness annotations ###
 Endpoint:  /witness/*witID*/annotations
 Method: GET
-Request body:  none
-Response body:  
-Status:  200 on success, 404 if *witID* does not exist.
-Comments:
-Convenience function which aggregates the annotations from all canvasses and pages associated with the witness.

 ## 5. Transcriptions ##
-
-Each witness has exactly one corresponding transcription object, which is created automatically when the witness is created.  A transcription contains some metadata (e.g. the editor name), but its main purpose is to serve as a container to group together all the pages.

 ### Get transcription details ###
 Endpoint:  /transcription/*transcrID*
 Method: GET
-Request body:  none
-Response body:  {
-&amp;nbsp;&amp;nbsp;"id":1,"editor":"J. Witt",
-&amp;nbsp;&amp;nbsp;"pages":\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"id":1000,"title":"1r"},
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"id":6001,"title":"1v"}
-&amp;nbsp;&amp;nbsp;]
-}
-Status:  200 on success, 404 if *transcrID* does not exist.
-Comments:
-The "pages" field of the transcription object provides an array with the IDs and titles of all pages associated with said transcription.

 ### Set transcription permissions ###
 Endpoint:  /transcription/*transcrID*/permissions
 Method:  PUT
-Request content-type:  application/json
-Request body:  \[{"user":*uID*, "role":"OWNER"}]
-Request parameters: `merge=true|false` (default `true`)
-Response body:  none
-Status: 204 on success, 404 if *transcrID* does not exist, 409 if the request body refers to a nonexistent user ID.
-Comments:
-The request body should contains an array of user permissions objects.  The "user" field of each object should contain a user ID, and the "role" field one of "NONE", "VIEWER", "CONTRIBUTOR", "EDITOR", or "OWNER", identifying the permissions that will be granted to that user for the given transcription.  If the `merge` parameter is `false`, the contents of the array will completely replace the transcription's existing permissions; if it is `true` or omitted, only permissions mentioned in the request body will be affected.

 ## 6. Pages ##
@@ -187,97 +132,41 @@
 ### Get page details ###
 Endpoint:  /page/*pageID*
 Method: GET
-Request body:  none
-Response body:  {"id":5, "text":"Quantum ad istud…", "title":"S4r.jpg", "index":4, "transcription":1}
-Status:  200 on success, 404 if *pageID* does not exist.
-Comments:
-The "index" field contains the page's index within the transcription as a whole.  If the "annotations" parameter is specified, the response will include an array of all annotation objects attached to the page.  Similarly, specifying the "lines" parameter will include an array of all line objects.

 ### Set page details ###
 Endpoint:  /page/*pageID*
 Method: PUT
-Request body:  {"id":5, "text":"Quantum ad istud…"}
-Response body:  none
-Status:  204 on success, 404 if *pageID* does not exist.
-Comments:
-The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.  Changing the page's "text" property may result in annotations being recalculated.  Changing the page's "index" property can be used to reorder pages within the transcription.

 ### Add a page annotation ###
 Endpoint: /page/*pgID*/annotations
 Method: POST
-Request content-type: application/json
-Request body: {"type":"colour","content":"turquoise"}
-Response body: none
-Response location: /annotation/*annID*
-Status: 201 on success, 404 if *pgID* does not exist.
-Comments:
-The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created annotation.

 ### Get page annotations ###
 Endpoint:  /page/*pgID*/annotations
 Method: GET
-Request body:  none
-Response body:   \[{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]
-Status:  200 on success, 404 if *pgID* does not exist.
-Comments:
-Response contains an array with all annotations found on the given page.  This includes multi-page annotations whose page range includes the page in question.  Since lines are annotations, they will be included in the response.

 ### Set page annotations ###
 Endpoint:  /page/*pageID*/annotations
 Method: PUT
-Request body:  \[{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]
-Request parameters:  `merge=true|false` (default `true`)
-Response body:  none
-Status:  204 on success, 404 if *pageID* does not exist.
-Comments:
-The JSON array in the request body should contain new values for the page's annotations.  If the `merge` parameter is `false`, the existing annotations will be completely replaced; if it is `true` or omitted, only annotations mentioned in the request body will be affected.  For the purposes of this endpoint, a page's annotations are considered to be all annotations whose "startPage" to "endPage" range includes *pageID*.

 ### Get page lines ###
 Endpoint:  /page/*pgID*/lines
 Method: GET
-Request body:  none
-Response body: \[
-&amp;nbsp;&amp;nbsp;{"id":34796,"startOffset":0,"endOffset":55,"selector":null,"type":"line","purpose":"LB","content":"episcopum fuisse aut esse neque ipsos qui ab eo ordina-","attributes":null,"startPage":6118,"endPage":6118,"bounds":{"height":94,"width":662,"y":9,"x":53},"canvas":6118},
-&amp;nbsp;&amp;nbsp;…
-]
-Status:  200 on success, 404 if *pgID* does not exist.
-Comments:
-Response contains an array with all line annotations found on the given page.

 ### Set page lines ###
 Endpoint:  /page/*pageID*/lines
 Method: PUT
-Request body:  \[{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]
-Request parameters:  `merge=true|false` (default `true`)
-Response body:  none
-Status:  204 on success, 404 if *pageID* does not exist.
-Comments:
-The JSON array in the request body should contain new values for the page's lines.  If the `merge` parameter is `false`, the existing lines will be completely replaced; if it is `true` or omitted, only lines mentioned in the request body will be affected.  For the purposes of this endpoint, a page's lines are considered to be all annotations whose "purpose" is "LB" and whose "startPage" is *pageID*.

 ## 7. Manifests ##
-
-Each witness has exactly one corresponding manifest object, which is created automatically when the witness is created.  A manifest will eventually contain some metadata, but its main purpose is to serve as a container to group together all the canvasses.

 ### Get manifest details ###
 Endpoint:  /manifest/*manID*
 Method:  GET
-Request body:  none
-Response body:  {"id":3,"canvasses":\[9,10,11,12]}
-Status:  200 on success, 404 if *manID* does not exist.
-Comments:
-The "canvasses" field of the manifest object provides an array with the IDs of all canvasses associated with said manifest.

 ### Set manifest permissions ###
 Endpoint:  /manifest/*manID*/permissions
 Method:  PUT
-Request content-type:  application/json
-Request body:  \[{"user":*uID*, "role":"OWNER"}]
-Request parameters: `merge=true|false` (default `true`)
-Response body:  none
-Status: 204 on success, 404 if *manID* does not exist, 409 if the request body refers to a nonexistent user ID.
-Comments:
-The request body should contains an array of user permissions objects.  The "user" field of each object should contain a user ID, and the "role" field one of "NONE", "VIEWER", "CONTRIBUTOR", "EDITOR", or "OWNER", identifying the permissions that will be granted to that user for the given manifest.  If the `merge` parameter is `false`, the contents of the array will completely replace the manifest's existing permissions; if it is `true` or omitted, only permissions mentioned in the request body will be affected.

 ## 8. Canvasses ##
@@ -285,69 +174,30 @@
 ### Get canvas details ###
 Endpoint:  /canvas/*canvID*
 Method:  GET
-Request body:  none
-Response body:  {"id":12,"title":"R2v.jpg","page":12,"manifest":3}
-Status:  200 on success, 404 if *canvID* does not exist.
-Comments:
-The "index" field contains the canvas' index within the manifest as a whole.

 ### Set canvas details ###
 Endpoint:  /canvas/*canvID*
 Method: PUT
-Request body:  {"id":5,"title":"Frontispiece"}
-Response body:  none
-Status:  204 on success, 404 if *canvID* does not exist.
-Comments:
-The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.  Changing the canvas' "index" property can be used to reorder canvasses within the manifest.

 ### Add a canvas annotation ###
 Endpoint: /canvas/*canvID*/annotations
 Method: POST
-Request content-type: application/json
-Request body: {"type":"colour","content":"turquoise"}
-Response body: none
-Response location: /annotation/*annID*
-Status: 201 on success, 404 if *canvID* does not exist.
-Comments:
-The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created annotation.

 ### Get canvas annotations ###
 Endpoint:  /canvas/*canvID*/annotations
 Method:  GET
-Request body:  none
-Response body:  \[{"id":36748,"startOffset":0,"endOffset":58,"type":"line","purpose":"LB","content":"de adulterio accussant et non habent latentia peccata uin-","motivation":"sc:painting","canvas":2033,"bounds":{"height":66,"width":601,"y":64,"x":31},"startPage":2033,"endPage":2033},…]
-Status:  200 on success, 404 if *canvID* does not exist.
-Comments:
-Response contains an array with all annotations found on the given canvas.  Since lines are annotations, they will be included in the response.

 ### Set canvas annotations ###
 Endpoint:  /canvas/*canvID*/annotations
 Method: PUT
-Request body:  \[{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]
-Request parameters:  `merge=true|false` (default `true`)
-Response body:   none
-Status:  204 on success, 404 if *canvID* does not exist.
-Comments:
-The JSON array in the request body should contain new values for the canvas' annotations.  If the `merge` parameter is `false`, the existing annotations will be completely replaced; if it is `true` or omitted, only annotations mentioned in the request body will be affected.

 ### Get canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
 Method: GET
-Request body:  none
-&lt;s&gt;Response body: \[{"id":37484,"index":0,"annotation":38042,"endOffset":46,"content":"fratres nostri quae acta sunt et quae difinita","startOffset":0,"page":2085},…]
-Status:  200 on success, 404 if *canvID* does not exist.
-Comments:
-Response contains an array with all lines found on the given canvas.  A line is actually an annotation, but with a slightly different set of fields.  The "annotation" field of the line object can be used to retrieve the full set of annotation fields.&lt;/s&gt;

 ### Set canvas lines ###
 Endpoint:  /canvas/*canvID*/lines
 Method: PUT
-Request body:  \[{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]
-Request parameters:  `merge=true|false` (default `true`)
-Response body:  none
-Status:  204 on success, 404 if *canvID* does not exist.
-Comments:
-The JSON array in the request body should contain new values for the canvas' lines.  If the `merge` parameter is `false` the existing lines will be completely replaced; if it is `true` or omitted, only lines mentioned in the request body will be affected.  For the purposes of this endpoint, a canvas' lines are considered to be all annotations whose "purpose" is "line" and whose "canvas" is *canvID*.

 ## 9. Annotations ##
@@ -355,27 +205,14 @@
 ### Get annotation details ###
 Endpoint:  /annotation/*annID*
 Method:  GET
-Request body:  none
-Response body:  {"id":81538,"startOffset":1938,"endOffset":1938,"target":null,"selector":null,"type":"g","purpose":"NONE","content":null,"attributes":"{\"ref\":\"#slash\"}","modifiedBy":0,"approvedBy":0,"startPage":17000,"endPage":17000,"bounds":null,"canvas":null}
-Status:  200 on success, 404 if *annID* does not exist.

 ### Set annotation details ###
 Endpoint:  /annotation/*annID*
 Method: PUT
-Request body:  {"content":"/"}
-Response body:  none
-Status:  204 on success, 404 if *annID* does not exist.
-Comments:
-The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.

 ### Delete an annotation ###
 Endpoint:  /annotation/*annID*
 Method: DELETE
-Request body:  none
-Response body:  none
-Status:  204 on success.
-Comments:
-Deletes the single annotation identified by *annID*.

 ## 10. Collation ##
@@ -383,122 +220,17 @@
 ### Full-edition collation ###
 Endpoint:  /collation/*edID*
 Method:  GET
-Request body:  none
-Response content-type:  application/json
-Response body:  an array of arrays of mote annotations:
-\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#2","endPage":2000,"startPage":2000,"startOffset":0},
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#1","endPage":1000,"startPage":1000,"startOffset":0}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"\[Reims","endOffset":25,"target":"#1","endPage":1000,"startPage":1000,"startOffset":19}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"\[Sorbonne","endOffset":28,"target":"#2","endPage":2000,"startPage":2000,"startOffset":19}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;…
-]
-Status:  200 on success, 404 if *edID* does not exist.
-Comments:  This provides a full collation of all the witnesses in the specified edition.  The response contains an array of arrays of mote annotations.  Each of the subarrays contains mote annotations which the collator has determined to be in correspondence.  The `target` field of the mote annotations is used to indicate the witness whose content provides support for the annotation.
-

 ### Sub-collation ###
 Endpoint:  /collation
 Method:  GET
-Request content-type:  application/json
-Request body:  an array of text ranges:
-\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;{"startPage":1000,"startOffset":0,"endPage":1000,"endOffset":477},
-&amp;nbsp;&amp;nbsp;&amp;nbsp;{"startPage":2000,"startOffset":0,"endPage":2000,"endOffset":360}
-]
-Response content-type:  application/json
-Response body:  an array of arrays of mote annotations:
-\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#2","endPage":2000,"startPage":2000,"startOffset":0},
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#1","endPage":1000,"startPage":1000,"startOffset":0}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"\[Reims","endOffset":25,"target":"#1","endPage":1000,"startPage":1000,"startOffset":19}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"\[Sorbonne","endOffset":28,"target":"#2","endPage":2000,"startPage":2000,"startOffset":19}
-&amp;nbsp;&amp;nbsp;&amp;nbsp;],
-&amp;nbsp;&amp;nbsp;&amp;nbsp;…
-]
-Status:  200 on success, 400 if the text ranges are not consistent (e.g. a range has a negative length, or refers to pages from two separate witnesses).
-Comments:  The request body must contain an array of JSON objects specifying the text ranges to be collated.  In practice, this may be an array of annotations, but that need not be the case.  The response contains an array of arrays of mote annotations.  Each of the subarrays contains mote annotations which the collator has determined to be in correspondence.  The `target` field of the mote annotations is used to indicate the witness whose content provides support for the annotation.
-

 ## 11. Config ##
 Endpoint:  /config
 Method:  GET
-Request body:  none
-Response content-type:  application/json
-Response body:  {"version":"0.9.1", "revision":189}
-Status: 200 on success.
-Comments: The response's "version" property contains the "version-num" property from `build.xml`.  The "revision" property contains the SVN revision which was used to build the WAR file.  At present, this endpoint does not require authentication.

 ## 12. Activity Log ##
-
 Endpoint:  /activity
 Method:  GET
-Parameters:  user=*userID* to retrieve activities of a given user
-table=*table*&amp;amp;id=*id* to retrieve activities associated with a given entity.  The `table` parameter can be one of `annotations`, `canvasses`, `decisions`, `editions`, `images`, `manifests`, `pages`, `transcriptions`, `users`, or `witnesses`.  The optional `id` parameter identifies the particular entity of that type.
-Request body:  none
-Response body:  An array of activity records:
-\[
-&amp;nbsp;&amp;nbsp;&amp;nbsp;{
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"content":"Lectio 1, Prologus \[Reims Transcription]",
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"operation":"INSERT",
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time":1381535382000,
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"entity":"witness/1",
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"parent":"edition/1",
-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"user":2
-&amp;nbsp;&amp;nbsp;&amp;nbsp;},
-&amp;nbsp;&amp;nbsp;&amp;nbsp;…
-]
-Status:  200 on success
-Comments: The endpoint returns an array of activity entries. The `operation` property can be `INSERT`, `UPDATE`, `DELETE`, or `VIEW`.  The entity which changed is identified by the `entity` property.  The time of the activity and the user responsible are specified by the `time` and `user` properties.  For insertions, there will also be a `parent` property which identifies where the newly-created entity was inserted.
-
-The `content` property describes the change that occurred.  The values are summarised in this table:
-
-operation | type      | content
---------- | --------- | -------
-INSERT    | editions  | Title of newly-created edition
-UPDATE    | editions  | JSON object containing changes sent by the client&lt;br /&gt;For /editions sub-endpoints, it may be a JSON array of motes, or permissions.
-INSERT    | users     | Name of newly-created user
-UPDATE    | users     | "Account confirmed."
-INSERT    | witnesses | Title of newly-created witness
-UPDATE    | witnesses | "Witness updated."
-UPDATE    | *other*   | JSON object containing changes sent by the client
-DELETE    | *all*     | none
-
-
-## 13. T-PEN proxy ##
-
-Endpoint: /tpen?config
-Method: GET
-Request body:  none
-Response content-type:  application/json
-Response body:  {"tpen":"http://t-pen.org/TPEN"}
-Status: 200 on success.
-Comments: This method is used to retrieve the address of the T-PEN server being used by Tradamus, as configured in Tradamus' `web.xml`.
-
-
-Endpoint: /tpen
-Method: GET or POST (as appropriate)
-Comments:  Special endpoint for proxying calls to T-PEN.  The portion of the request following /tpen will be forwarded to the T-PEN instance configured in the "server" init-param entry of the Tradamus web.xml file.  Has been tested with the following T-PEN endpoints:
-
-&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/login
-&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/projects
-&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/project/*projID*
-
-Authentication can be done in two ways:  by cookie or by white-list.
-
-In the cookie method, first call the T-PEN login endpoint to get a JSESSIONID cookie and pass that to subsequent calls.
-
-The white-list method depends on the Tradamus user-name (typically an email address) also existing in T-PEN.  The line TRADAMUS=*TradamusIPAddress* must be added to the T-PEN version.properties file, to tell T-PEN that requests from that Tradamus instance are allowed to bypass the usual T-PEN login process.
&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Wed, 02 Apr 2014 22:37:53 -0000</pubDate><guid>https://sourceforge.net5a72aa95b7125b3fa99d492a2c301340662e4d9f</guid></item><item><title>Endpoint Permissions modified by Eric Smith</title><link>https://sourceforge.net/p/tradamus/wiki/Endpoint%2520Permissions/</link><description>&lt;div class="markdown_content"&gt;&lt;p&gt;The following table describes the permission level which is required to access the given endpoint.  The endpoints themselves are more full described in &lt;a class="alink" href="/p/tradamus/wiki/Tradamus%20Server%20API/"&gt;[Tradamus Server API]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are five ranked levels of permissions:  &lt;code&gt;NONE&lt;/code&gt;, &lt;code&gt;VIEWER&lt;/code&gt;, &lt;code&gt;CONTRIBUTOR&lt;/code&gt;, &lt;code&gt;EDITOR&lt;/code&gt;, and &lt;code&gt;OWNER&lt;/code&gt;.  If a user has a particular level of access to a resource, they also implicitly have all the lower levels of access.&lt;/p&gt;
&lt;p&gt;Permissions can currently be set at three different points:  the Edition, the Transcription, and the Manifest.  When verifying&lt;/p&gt;
&lt;h2 id="1-users"&gt;1. Users&lt;/h2&gt;
&lt;h3 id="add-new-user"&gt;Add new user&lt;/h3&gt;
&lt;p&gt;POST /users&lt;br /&gt;
No restrictions.&lt;/p&gt;
&lt;h3 id="invite-new-user"&gt;Invite new user&lt;/h3&gt;
&lt;p&gt;POST /users&lt;br /&gt;
&lt;code&gt;EDITOR&lt;/code&gt; on associated edition.&lt;/p&gt;
&lt;h3 id="get-user-details"&gt;Get user details&lt;/h3&gt;
&lt;p&gt;GET /user/&lt;em&gt;userID&lt;/em&gt;&lt;br /&gt;
No restrictions.&lt;/p&gt;
&lt;h3 id="get-user-details-by-email"&gt;Get user details by email&lt;/h3&gt;
&lt;p&gt;GET /user?mail=&lt;em&gt;emailAddress&lt;/em&gt;&lt;br /&gt;
No restrictions.&lt;/p&gt;
&lt;h3 id="modify-user-details"&gt;Modify user details&lt;/h3&gt;
&lt;p&gt;PUT /user/&lt;em&gt;userID&lt;/em&gt;&lt;br /&gt;
Only &lt;em&gt;userID&lt;/em&gt; can modify their own details.&lt;/p&gt;
&lt;h3 id="reset-user-password"&gt;Reset user password&lt;/h3&gt;
&lt;p&gt;PUT /user?reset=&lt;em&gt;emailAddress&lt;/em&gt;&lt;br /&gt;
No restrictions.&lt;/p&gt;
&lt;h3 id="log-in-to-tradamus"&gt;Log in to Tradamus&lt;/h3&gt;
&lt;p&gt;POST /login&lt;br /&gt;
No restrictions.&lt;/p&gt;
&lt;h3 id="check-login-status"&gt;Check login status&lt;/h3&gt;
&lt;p&gt;GET /login&lt;br /&gt;
The call always returns the status of the currently logged-in user.&lt;/p&gt;
&lt;p&gt;POST /editions&lt;br /&gt;
GET /editions&lt;br /&gt;
GET /edition/&lt;em&gt;edID&lt;/em&gt;&lt;br /&gt;
PUT /edition/&lt;em&gt;edID&lt;/em&gt;&lt;br /&gt;
POST /edition/&lt;em&gt;edID&lt;/em&gt;/metadata&lt;br /&gt;
PUT /edition/&lt;em&gt;edID&lt;/em&gt;/metadata&lt;br /&gt;
GET /edition/&lt;em&gt;edID&lt;/em&gt;/decisions&lt;br /&gt;
PUT /edition/&lt;em&gt;edID&lt;/em&gt;/decisions&lt;br /&gt;
PUT /edition/&lt;em&gt;edID&lt;/em&gt;/permissions&lt;br /&gt;
DELETE /edition/&lt;em&gt;edID&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;POST /witnesses&lt;br /&gt;
Method: POST&lt;br /&gt;
Parameters: &lt;code&gt;edition=&lt;/code&gt;&lt;em&gt;edID&lt;/em&gt; (required)&lt;br /&gt;
Request content-type: application/json&lt;br /&gt;
Request body: {"title":"My Witness","siglum":"M"}&lt;br /&gt;
Status: 201 on success, 404 if &lt;em&gt;edID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
This allows the creation of a witness from JSON (non-LD) data.  The "title" and "siglum" fields are required.&lt;/p&gt;
&lt;h3 id="import-json-ld-witness"&gt;Import JSON-LD witness&lt;/h3&gt;
&lt;p&gt;Endpoint: /witnesses&lt;br /&gt;
Method: POST&lt;br /&gt;
Parameters: &lt;code&gt;edition=&lt;/code&gt;&lt;em&gt;edID&lt;/em&gt; (required)&lt;br /&gt;
Request content-type: application/ld+json&lt;br /&gt;
Request body: single-file IIIF metadata representation&lt;br /&gt;
Status: 201 on success, 404 if &lt;em&gt;edID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Currently the only source for this type of file is the JSON-LD export from T-PEN.  The IIIF metadata spec is still under development, and does not appear to yet be supported by any other software.&lt;/p&gt;
&lt;h3 id="import-xml-witness"&gt;Import XML witness&lt;/h3&gt;
&lt;p&gt;Endpoint: /witnesses&lt;br /&gt;
Method: POST&lt;br /&gt;
Parameters: &lt;code&gt;edition=&lt;/code&gt;&lt;em&gt;edID&lt;/em&gt; (required)&lt;br /&gt;
Request content-type: text/xml&lt;br /&gt;
Request body: TEI XML file&lt;br /&gt;
Status:  201 on success, 404 if &lt;em&gt;edID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
This has so far only been tested with TEI files from Jeffrey Witt's Petrus Plaoul corpus.  The implementation will need to be generalised as we encounter other TEI files.&lt;/p&gt;
&lt;h3 id="get-witness-details"&gt;Get witness details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /witness/&lt;em&gt;witID&lt;/em&gt;&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:&lt;br /&gt;
{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"id":4,&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"author":"Petrus Plaoul",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"edition":1,&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"title":"Lectio 1, Prologus [Vatican Transcription]",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"manifest":3,&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"siglum":"V",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"transcription":3,&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"metadata":{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"editor":"Jeffrey C. Witt",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"pubPlace":"Boston, MA"&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;witID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;/p&gt;
&lt;h3 id="set-witness-details"&gt;Set witness details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /witness/&lt;em&gt;witID&lt;/em&gt;&lt;br /&gt;
Method:  PUT&lt;br /&gt;
Request content-type:  application/json&lt;br /&gt;
Request body:  {"author":"New Author", "title":"New Title", "siglum":"New Siglum"}&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;witID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contain a JSON object describing the fields to be changed.  Any fields which are omitted from the request will remain unchanged.&lt;/p&gt;
&lt;h3 id="add-a-witness-metadatum"&gt;Add a witness metadatum&lt;/h3&gt;
&lt;p&gt;Endpoint: /witness/&lt;em&gt;witID&lt;/em&gt;/metadata&lt;br /&gt;
Method: POST&lt;br /&gt;
Request content-type: application/json&lt;br /&gt;
Request body: {"type":"colour","content":"turquoise"}&lt;br /&gt;
Response body: none&lt;br /&gt;
Response location: /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Status: 201 on success, 404 if &lt;em&gt;witID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created metadatum.&lt;/p&gt;
&lt;h3 id="set-witness-metadata"&gt;Set witness metadata&lt;/h3&gt;
&lt;p&gt;Endpoint: /witness/&lt;em&gt;witID&lt;/em&gt;/metadata&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request content-type: application/json&lt;br /&gt;
Request body: [{"type":"colour","content":"turquoise"}]&lt;br /&gt;
Request parameters: &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body: none&lt;br /&gt;
Status: 204 on success, 404 if &lt;em&gt;witID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contain an array of annotation objects. If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the contents of the array will completely replace the witness' existing metadata; if &lt;code&gt;merge&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; or omitted, the PUT will only affect metadata objects included in the request body.  In either case, the call will create any objects found in the request body which don't already exist.&lt;/p&gt;
&lt;h3 id="delete-a-witness"&gt;Delete a witness&lt;/h3&gt;
&lt;p&gt;Endpoint:  /witness/&lt;em&gt;witID&lt;/em&gt;&lt;br /&gt;
Method: DELETE&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success.&lt;br /&gt;
Comments:&lt;br /&gt;
Deletes the single witness identified by &lt;em&gt;witID&lt;/em&gt;.  Associated transcriptions, pages, manifests, canvasses, images, and annotations will also be deleted.&lt;/p&gt;
&lt;h3 id="recursively-get-all-witness-annotations"&gt;Recursively get all witness annotations&lt;/h3&gt;
&lt;p&gt;Endpoint:  /witness/&lt;em&gt;witID&lt;/em&gt;/annotations&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;witID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Convenience function which aggregates the annotations from all canvasses and pages associated with the witness.&lt;/p&gt;
&lt;h2 id="5-transcriptions"&gt;5. Transcriptions&lt;/h2&gt;
&lt;p&gt;Each witness has exactly one corresponding transcription object, which is created automatically when the witness is created.  A transcription contains some metadata (e.g. the editor name), but its main purpose is to serve as a container to group together all the pages.&lt;/p&gt;
&lt;h3 id="get-transcription-details"&gt;Get transcription details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /transcription/&lt;em&gt;transcrID&lt;/em&gt;&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  {&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"id":1,"editor":"J. Witt",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;"pages":[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"id":1000,"title":"1r"},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"id":6001,"title":"1v"}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;]&lt;br /&gt;
}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;transcrID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The "pages" field of the transcription object provides an array with the IDs and titles of all pages associated with said transcription.&lt;/p&gt;
&lt;h3 id="set-transcription-permissions"&gt;Set transcription permissions&lt;/h3&gt;
&lt;p&gt;Endpoint:  /transcription/&lt;em&gt;transcrID&lt;/em&gt;/permissions&lt;br /&gt;
Method:  PUT&lt;br /&gt;
Request content-type:  application/json&lt;br /&gt;
Request body:  [{"user":&lt;em&gt;uID&lt;/em&gt;, "role":"OWNER"}]&lt;br /&gt;
Request parameters: &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status: 204 on success, 404 if &lt;em&gt;transcrID&lt;/em&gt; does not exist, 409 if the request body refers to a nonexistent user ID.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contains an array of user permissions objects.  The "user" field of each object should contain a user ID, and the "role" field one of "NONE", "VIEWER", "CONTRIBUTOR", "EDITOR", or "OWNER", identifying the permissions that will be granted to that user for the given transcription.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the contents of the array will completely replace the transcription's existing permissions; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only permissions mentioned in the request body will be affected.&lt;/p&gt;
&lt;h2 id="6-pages"&gt;6. Pages&lt;/h2&gt;
&lt;h3 id="get-page-details"&gt;Get page details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pageID&lt;/em&gt;&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  {"id":5, "text":"Quantum ad istud…", "title":"S4r.jpg", "index":4, "transcription":1}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;pageID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The "index" field contains the page's index within the transcription as a whole.  If the "annotations" parameter is specified, the response will include an array of all annotation objects attached to the page.  Similarly, specifying the "lines" parameter will include an array of all line objects.&lt;/p&gt;
&lt;h3 id="set-page-details"&gt;Set page details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pageID&lt;/em&gt;&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  {"id":5, "text":"Quantum ad istud…"}&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;pageID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.  Changing the page's "text" property may result in annotations being recalculated.  Changing the page's "index" property can be used to reorder pages within the transcription.&lt;/p&gt;
&lt;h3 id="add-a-page-annotation"&gt;Add a page annotation&lt;/h3&gt;
&lt;p&gt;Endpoint: /page/&lt;em&gt;pgID&lt;/em&gt;/annotations&lt;br /&gt;
Method: POST&lt;br /&gt;
Request content-type: application/json&lt;br /&gt;
Request body: {"type":"colour","content":"turquoise"}&lt;br /&gt;
Response body: none&lt;br /&gt;
Response location: /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Status: 201 on success, 404 if &lt;em&gt;pgID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created annotation.&lt;/p&gt;
&lt;h3 id="get-page-annotations"&gt;Get page annotations&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pgID&lt;/em&gt;/annotations&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:   [{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;pgID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Response contains an array with all annotations found on the given page.  This includes multi-page annotations whose page range includes the page in question.  Since lines are annotations, they will be included in the response.&lt;/p&gt;
&lt;h3 id="set-page-annotations"&gt;Set page annotations&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pageID&lt;/em&gt;/annotations&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  [{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]&lt;br /&gt;
Request parameters:  &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;pageID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON array in the request body should contain new values for the page's annotations.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the existing annotations will be completely replaced; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only annotations mentioned in the request body will be affected.  For the purposes of this endpoint, a page's annotations are considered to be all annotations whose "startPage" to "endPage" range includes &lt;em&gt;pageID&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="get-page-lines"&gt;Get page lines&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pgID&lt;/em&gt;/lines&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body: [&lt;br /&gt;
&amp;nbsp;&amp;nbsp;{"id":34796,"startOffset":0,"endOffset":55,"selector":null,"type":"line","purpose":"LB","content":"episcopum fuisse aut esse neque ipsos qui ab eo ordina-","attributes":null,"startPage":6118,"endPage":6118,"bounds":{"height":94,"width":662,"y":9,"x":53},"canvas":6118},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;…&lt;br /&gt;
]&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;pgID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Response contains an array with all line annotations found on the given page.&lt;/p&gt;
&lt;h3 id="set-page-lines"&gt;Set page lines&lt;/h3&gt;
&lt;p&gt;Endpoint:  /page/&lt;em&gt;pageID&lt;/em&gt;/lines&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  [{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]&lt;br /&gt;
Request parameters:  &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;pageID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON array in the request body should contain new values for the page's lines.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the existing lines will be completely replaced; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only lines mentioned in the request body will be affected.  For the purposes of this endpoint, a page's lines are considered to be all annotations whose "purpose" is "LB" and whose "startPage" is &lt;em&gt;pageID&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="7-manifests"&gt;7. Manifests&lt;/h2&gt;
&lt;p&gt;Each witness has exactly one corresponding manifest object, which is created automatically when the witness is created.  A manifest will eventually contain some metadata, but its main purpose is to serve as a container to group together all the canvasses.&lt;/p&gt;
&lt;h3 id="get-manifest-details"&gt;Get manifest details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /manifest/&lt;em&gt;manID&lt;/em&gt;&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  {"id":3,"canvasses":[9,10,11,12]}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;manID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The "canvasses" field of the manifest object provides an array with the IDs of all canvasses associated with said manifest.&lt;/p&gt;
&lt;h3 id="set-manifest-permissions"&gt;Set manifest permissions&lt;/h3&gt;
&lt;p&gt;Endpoint:  /manifest/&lt;em&gt;manID&lt;/em&gt;/permissions&lt;br /&gt;
Method:  PUT&lt;br /&gt;
Request content-type:  application/json&lt;br /&gt;
Request body:  [{"user":&lt;em&gt;uID&lt;/em&gt;, "role":"OWNER"}]&lt;br /&gt;
Request parameters: &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status: 204 on success, 404 if &lt;em&gt;manID&lt;/em&gt; does not exist, 409 if the request body refers to a nonexistent user ID.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contains an array of user permissions objects.  The "user" field of each object should contain a user ID, and the "role" field one of "NONE", "VIEWER", "CONTRIBUTOR", "EDITOR", or "OWNER", identifying the permissions that will be granted to that user for the given manifest.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the contents of the array will completely replace the manifest's existing permissions; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only permissions mentioned in the request body will be affected.&lt;/p&gt;
&lt;h2 id="8-canvasses"&gt;8. Canvasses&lt;/h2&gt;
&lt;h3 id="get-canvas-details"&gt;Get canvas details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  {"id":12,"title":"R2v.jpg","page":12,"manifest":3}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The "index" field contains the canvas' index within the manifest as a whole.&lt;/p&gt;
&lt;h3 id="set-canvas-details"&gt;Set canvas details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  {"id":5,"title":"Frontispiece"}&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.  Changing the canvas' "index" property can be used to reorder canvasses within the manifest.&lt;/p&gt;
&lt;h3 id="add-a-canvas-annotation"&gt;Add a canvas annotation&lt;/h3&gt;
&lt;p&gt;Endpoint: /canvas/&lt;em&gt;canvID&lt;/em&gt;/annotations&lt;br /&gt;
Method: POST&lt;br /&gt;
Request content-type: application/json&lt;br /&gt;
Request body: {"type":"colour","content":"turquoise"}&lt;br /&gt;
Response body: none&lt;br /&gt;
Response location: /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Status: 201 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The request body should contain a single annotation object.  The Location header of the response gives the caller the ID of the newly-created annotation.&lt;/p&gt;
&lt;h3 id="get-canvas-annotations"&gt;Get canvas annotations&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;/annotations&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  [{"id":36748,"startOffset":0,"endOffset":58,"type":"line","purpose":"LB","content":"de adulterio accussant et non habent latentia peccata uin-","motivation":"sc:painting","canvas":2033,"bounds":{"height":66,"width":601,"y":64,"x":31},"startPage":2033,"endPage":2033},…]&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Response contains an array with all annotations found on the given canvas.  Since lines are annotations, they will be included in the response.&lt;/p&gt;
&lt;h3 id="set-canvas-annotations"&gt;Set canvas annotations&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;/annotations&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  [{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]&lt;br /&gt;
Request parameters:  &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:   none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON array in the request body should contain new values for the canvas' annotations.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt;, the existing annotations will be completely replaced; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only annotations mentioned in the request body will be affected.&lt;/p&gt;
&lt;h3 id="get-canvas-lines"&gt;Get canvas lines&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;/lines&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
&lt;s&gt;Response body: [{"id":37484,"index":0,"annotation":38042,"endOffset":46,"content":"fratres nostri quae acta sunt et quae difinita","startOffset":0,"page":2085},…]&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
Response contains an array with all lines found on the given canvas.  A line is actually an annotation, but with a slightly different set of fields.  The "annotation" field of the line object can be used to retrieve the full set of annotation fields.&lt;/s&gt;&lt;/p&gt;
&lt;h3 id="set-canvas-lines"&gt;Set canvas lines&lt;/h3&gt;
&lt;p&gt;Endpoint:  /canvas/&lt;em&gt;canvID&lt;/em&gt;/lines&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  [{"id":38042,"startOffset":0,"endOffset":46,"type":"line","purpose":"LB","content":"fratres nostri quae acta sunt et quae difinita","motivation":"sc:painting","canvas":2085,"bounds":{"height":50,"width":627,"y":76,"x":40},"startPage":2085,"endPage":2085},…]&lt;br /&gt;
Request parameters:  &lt;code&gt;merge=true|false&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;)&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;canvID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON array in the request body should contain new values for the canvas' lines.  If the &lt;code&gt;merge&lt;/code&gt; parameter is &lt;code&gt;false&lt;/code&gt; the existing lines will be completely replaced; if it is &lt;code&gt;true&lt;/code&gt; or omitted, only lines mentioned in the request body will be affected.  For the purposes of this endpoint, a canvas' lines are considered to be all annotations whose "purpose" is "line" and whose "canvas" is &lt;em&gt;canvID&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="9-annotations"&gt;9. Annotations&lt;/h2&gt;
&lt;h3 id="get-annotation-details"&gt;Get annotation details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  {"id":81538,"startOffset":1938,"endOffset":1938,"target":null,"selector":null,"type":"g","purpose":"NONE","content":null,"attributes":"{\"ref\":\"#slash\"}","modifiedBy":0,"approvedBy":0,"startPage":17000,"endPage":17000,"bounds":null,"canvas":null}&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;annID&lt;/em&gt; does not exist.&lt;/p&gt;
&lt;h3 id="set-annotation-details"&gt;Set annotation details&lt;/h3&gt;
&lt;p&gt;Endpoint:  /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Method: PUT&lt;br /&gt;
Request body:  {"content":"/"}&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success, 404 if &lt;em&gt;annID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:&lt;br /&gt;
The JSON object in the request body should contain all fields which are to be modified; fields which aren't being changed can be omitted.&lt;/p&gt;
&lt;h3 id="delete-an-annotation"&gt;Delete an annotation&lt;/h3&gt;
&lt;p&gt;Endpoint:  /annotation/&lt;em&gt;annID&lt;/em&gt;&lt;br /&gt;
Method: DELETE&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  none&lt;br /&gt;
Status:  204 on success.&lt;br /&gt;
Comments:&lt;br /&gt;
Deletes the single annotation identified by &lt;em&gt;annID&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="10-collation"&gt;10. Collation&lt;/h2&gt;
&lt;h3 id="full-edition-collation"&gt;Full-edition collation&lt;/h3&gt;
&lt;p&gt;Endpoint:  /collation/&lt;em&gt;edID&lt;/em&gt;&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response content-type:  application/json&lt;br /&gt;
Response body:  an array of arrays of mote annotations:&lt;br /&gt;
[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#2","endPage":2000,"startPage":2000,"startOffset":0},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#1","endPage":1000,"startPage":1000,"startOffset":0}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"[Reims","endOffset":25,"target":"#1","endPage":1000,"startPage":1000,"startOffset":19}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"[Sorbonne","endOffset":28,"target":"#2","endPage":2000,"startPage":2000,"startOffset":19}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;…&lt;br /&gt;
]&lt;br /&gt;
Status:  200 on success, 404 if &lt;em&gt;edID&lt;/em&gt; does not exist.&lt;br /&gt;
Comments:  This provides a full collation of all the witnesses in the specified edition.  The response contains an array of arrays of mote annotations.  Each of the subarrays contains mote annotations which the collator has determined to be in correspondence.  The &lt;code&gt;target&lt;/code&gt; field of the mote annotations is used to indicate the witness whose content provides support for the annotation.&lt;/p&gt;
&lt;h3 id="sub-collation"&gt;Sub-collation&lt;/h3&gt;
&lt;p&gt;Endpoint:  /collation&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request content-type:  application/json&lt;br /&gt;
Request body:  an array of text ranges:&lt;br /&gt;
[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;{"startPage":1000,"startOffset":0,"endPage":1000,"endOffset":477},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;{"startPage":2000,"startOffset":0,"endPage":2000,"endOffset":360}&lt;br /&gt;
]&lt;br /&gt;
Response content-type:  application/json&lt;br /&gt;
Response body:  an array of arrays of mote annotations:&lt;br /&gt;
[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#2","endPage":2000,"startPage":2000,"startOffset":0},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"Lectio 1, Prologus","endOffset":18,"target":"#1","endPage":1000,"startPage":1000,"startOffset":0}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"[Reims","endOffset":25,"target":"#1","endPage":1000,"startPage":1000,"startOffset":19}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{"content":"[Sorbonne","endOffset":28,"target":"#2","endPage":2000,"startPage":2000,"startOffset":19}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;],&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;…&lt;br /&gt;
]&lt;br /&gt;
Status:  200 on success, 400 if the text ranges are not consistent (e.g. a range has a negative length, or refers to pages from two separate witnesses).&lt;br /&gt;
Comments:  The request body must contain an array of JSON objects specifying the text ranges to be collated.  In practice, this may be an array of annotations, but that need not be the case.  The response contains an array of arrays of mote annotations.  Each of the subarrays contains mote annotations which the collator has determined to be in correspondence.  The &lt;code&gt;target&lt;/code&gt; field of the mote annotations is used to indicate the witness whose content provides support for the annotation.&lt;/p&gt;
&lt;h2 id="11-config"&gt;11. Config&lt;/h2&gt;
&lt;p&gt;Endpoint:  /config&lt;br /&gt;
Method:  GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response content-type:  application/json&lt;br /&gt;
Response body:  {"version":"0.9.1", "revision":189}&lt;br /&gt;
Status: 200 on success.&lt;br /&gt;
Comments: The response's "version" property contains the "version-num" property from &lt;code&gt;build.xml&lt;/code&gt;.  The "revision" property contains the SVN revision which was used to build the WAR file.  At present, this endpoint does not require authentication.&lt;/p&gt;
&lt;h2 id="12-activity-log"&gt;12. Activity Log&lt;/h2&gt;
&lt;p&gt;Endpoint:  /activity&lt;br /&gt;
Method:  GET&lt;br /&gt;
Parameters:  user=&lt;em&gt;userID&lt;/em&gt; to retrieve activities of a given user&lt;br /&gt;
table=&lt;em&gt;table&lt;/em&gt;&amp;amp;id=&lt;em&gt;id&lt;/em&gt; to retrieve activities associated with a given entity.  The &lt;code&gt;table&lt;/code&gt; parameter can be one of &lt;code&gt;annotations&lt;/code&gt;, &lt;code&gt;canvasses&lt;/code&gt;, &lt;code&gt;decisions&lt;/code&gt;, &lt;code&gt;editions&lt;/code&gt;, &lt;code&gt;images&lt;/code&gt;, &lt;code&gt;manifests&lt;/code&gt;, &lt;code&gt;pages&lt;/code&gt;, &lt;code&gt;transcriptions&lt;/code&gt;, &lt;code&gt;users&lt;/code&gt;, or &lt;code&gt;witnesses&lt;/code&gt;.  The optional &lt;code&gt;id&lt;/code&gt; parameter identifies the particular entity of that type.&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response body:  An array of activity records:&lt;br /&gt;
[&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"content":"Lectio 1, Prologus [Reims Transcription]",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"operation":"INSERT",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time":1381535382000,&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"entity":"witness/1",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"parent":"edition/1",&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"user":2&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;…&lt;br /&gt;
]&lt;br /&gt;
Status:  200 on success&lt;br /&gt;
Comments: The endpoint returns an array of activity entries. The &lt;code&gt;operation&lt;/code&gt; property can be &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, or &lt;code&gt;VIEW&lt;/code&gt;.  The entity which changed is identified by the &lt;code&gt;entity&lt;/code&gt; property.  The time of the activity and the user responsible are specified by the &lt;code&gt;time&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; properties.  For insertions, there will also be a &lt;code&gt;parent&lt;/code&gt; property which identifies where the newly-created entity was inserted.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;content&lt;/code&gt; property describes the change that occurred.  The values are summarised in this table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;operation&lt;/th&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;th&gt;content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;editions&lt;/td&gt;
&lt;td&gt;Title of newly-created edition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;editions&lt;/td&gt;
&lt;td&gt;JSON object containing changes sent by the client&lt;br /&gt;For /editions sub-endpoints, it may be a JSON array of motes, or permissions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;users&lt;/td&gt;
&lt;td&gt;Name of newly-created user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;users&lt;/td&gt;
&lt;td&gt;"Account confirmed."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;witnesses&lt;/td&gt;
&lt;td&gt;Title of newly-created witness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;witnesses&lt;/td&gt;
&lt;td&gt;"Witness updated."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;&lt;em&gt;other&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;JSON object containing changes sent by the client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;&lt;em&gt;all&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="13-t-pen-proxy"&gt;13. T-PEN proxy&lt;/h2&gt;
&lt;p&gt;Endpoint: /tpen?config&lt;br /&gt;
Method: GET&lt;br /&gt;
Request body:  none&lt;br /&gt;
Response content-type:  application/json&lt;br /&gt;
Response body:  {"tpen":"http://t-pen.org/TPEN"}&lt;br /&gt;
Status: 200 on success.&lt;br /&gt;
Comments: This method is used to retrieve the address of the T-PEN server being used by Tradamus, as configured in Tradamus' &lt;code&gt;web.xml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Endpoint: /tpen&lt;br /&gt;
Method: GET or POST (as appropriate)&lt;br /&gt;
Comments:  Special endpoint for proxying calls to T-PEN.  The portion of the request following /tpen will be forwarded to the T-PEN instance configured in the "server" init-param entry of the Tradamus web.xml file.  Has been tested with the following T-PEN endpoints:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/login&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/projects&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;GET /tpen/project/&lt;em&gt;projID&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Authentication can be done in two ways:  by cookie or by white-list.&lt;/p&gt;
&lt;p&gt;In the cookie method, first call the T-PEN login endpoint to get a JSESSIONID cookie and pass that to subsequent calls.&lt;/p&gt;
&lt;p&gt;The white-list method depends on the Tradamus user-name (typically an email address) also existing in T-PEN.  The line TRADAMUS=&lt;em&gt;TradamusIPAddress&lt;/em&gt; must be added to the T-PEN version.properties file, to tell T-PEN that requests from that Tradamus instance are allowed to bypass the usual T-PEN login process.&lt;/p&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Eric Smith</dc:creator><pubDate>Wed, 02 Apr 2014 22:24:57 -0000</pubDate><guid>https://sourceforge.net711206786951f0b7c966928e480fc64fa57ed6e7</guid></item></channel></rss>