Grendel is a RESTful webservice, using JSON objects for data exchange.
/users
/users/{id}
/users/{id}/documents
/users/{id}/documents/{name}
/users/{id}/documents/{name}/links
/users/{id}/documents/{name}/links/{otherid}
/users/{id}/linked-documents
/users/{id}/linked-documents/{otherid}/{name}
Grendel uses Last-Modified
and ETag
headers, and supports If-None-Match
,
If-Match
, If-Unmodified-Since
, and If-Modified-Since
preconditions.
Your Grendel client should either use these headers or accept the possibility of overwriting fresh data with stale data.
To safely update a document or user:
GET
the existing resource.- Make any changes you need.
PUT
the new resource, usingIf-Match
and the existing resource'sETag
orIf-Unmodified-Since
and the existing resource'sLast-Modified
.- If you receive a
2xx
response, your change was successfully written. If you receive a412 Preconditions Failed
, you should retry the process, starting with Step 1.
To efficiently re-read a document or user:
GET
the existing resource.GET
the possibly-changed resource usingIf-None-Match
and the existing resource'sETag
orIf-Modified-Since
and the existing resource'sLast-Modified
.- If you receive a
200 OK
response, the resource was changed since you last requested it. If you receive a304 Not Modified
response, the resource has not been changed.
This will work for both /users/{id}
and /users/{id}/document
.
All of the operations documented below are demonstrated with shell scripts that you can find in the examples directory. These assume that Grendel is running on localhost, port 8080. If you run them without arguments and they require arguments, a help line will appear showing you how to use them.
The full request and response will be shown as output. All the example scripts
use curl
, a command-line HTTP client.
Most Grendel resources require Basic HTTP Authentication credentials with the
user's id
and password
.
A Grendel user is effectively an id
— an arbitrary identifier — and an OpenPGP
signing key/encryption key pair.
Sending a GET
request to /users/
will return an application/json
object
with a list of all users' ids and URIs:
> GET /users/ HTTP/1.1
> Accept: application/json
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
< "users":[
< {
< "id":"codahale",
< "uri":"http://example.com/users/codahale"
< }
< ]
< }
Sending a POST
request to /users
with an application/json
object will
create a new user and return its URI in the response's Location
header:
> POST /users/ HTTP/1.1
> Content-Type: application/json
>
> {
> "id": "codahale",
> "password": "woowoo"
> }
< HTTP/1.1 201 Created
< Location: http://example.com/users/codahale
Both the id
and the password
properties are required. This action may
take some time (~1s), as Grendel will generate an OpenPGP keyset for the user.
The user's id
property is immutable. Please use an immutable piece of data
(e.g., a primary key) instead of a modifiable piece of data (e.g., a username or
email address).
If the user id
is taken, a 422 Unprocessable Entity
response will be
returned with an explanation.
Sending a GET
request to /users/codahale
will return an application/json
object with information about the user codahale
:
> GET /users/codahale HTTP/1.1
> Accept: application/json
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
< "id":"codahale",
< "modified-at":"20091227T211120Z",
< "created-at":"20091227T211120Z",
< "keys":"[2048-RSA/0A895A19, 2048-RSA/39D1621B]"
< }
The created-at
and modified-at
properties are timestamps in ISO 8601 format.
Requires authentication.
Sending a PUT
request to /users/codahale
with an application/json
object
will change the user codahale
's password:
> PUT /users/codahale HTTP/1.1
> Content-Type: application/json
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
> {
> "password": "secretstuff"
> }
< HTTP/1.1 204 No Content
The password
property is required. The Basic authentication should use the
old password; the next request will require Basic authentication credentials
with the new password.
Requires authentication.
Sending a DELETE
request to /users/codahale
will delete user codahale
and all their documents:
> DELETE /users/codahale HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
< HTTP/1.1 204 No Content
A Grendel document is an arbitrary series of bytes which belongs to a user. Documents are stored as OpenPGP messages, signed by the document's owner, and encrypted for the owner and any linked users. The MIME type of the document is stored along with the document.
Requires authentication.
Sending a GET
request to /users/codahale/documents/
will return an
application/json
object with a list of documents belonging to user codahale
:
> GET /users/codahale/documents/ HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
> Accept: application/json
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
< "documents":[
< {
< "name":"document1.txt",
< "uri":"http://example.com/users/codahale/documents/document1.txt"
< }
< ]
< }
Requires authentication.
Sending a GET
request to /users/codahale/documents/document1.txt
will return
the document named document1.txt
belonging to user codahale
in whatever
content type the document was stored with:
> GET /users/codahale/documents/document1.txt HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
< HTTP/1.1 200 OK
< Content-Length: 10
< Cache-Control: private, no-cache, no-store, no-transform
< Content-Type: text/plain
<
< yay for me
Requires authentication.
Sending a PUT
request to /users/codahale/documents/document1.txt
will store
the request entity as document1.txt
with the specified content type:
> PUT /users/codahale/documents/document1.txt HTTP/1.1
> Content-Type: text/plain
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
> i am super secret
< HTTP/1.1 204 No Content
Sending PUT
request to an existing document will overwrite its contents;
doing so to a non-existent document will create it.
Remember: use If-Match
or If-Unmodified-Since
to avoid clobbering
changes.
Requires authentication.
Sending a DELETE
request to /users/codahale/documents/document1.txt
will
delete the document named document1.txt
belonging to user codahale
:
> DELETE /users/codahale/documents/document1.txt HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
< HTTP/1.1 204 No Content
A Grendel document can be linked by its owner with other users. Doing so provides other users read-only access to the document.
Requires authentication.
Sending a GET
request to /users/codahale/documents/document1.txt/links
will
return a list of links from the document document1.txt
belonging to user
codahale
to other users:
> GET /users/codahale/documents/document1.txt/links HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
> Accept: application/json
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
< "links":[
< {
< "user":{
< "id":"precipice",
< "uri":"http://example.com/users/precipice"
< },
< "uri":"http://example.com/users/codahale/documents/document1.txt/links/precipice"
< }
< ]
< }
Requires authentication.
Sending a PUT
request to
/users/codahale/documents/document1.txt/links/precipice
will link user
precipice
to document document1.txt
belonging to user codahale
:
> PUT /users/codahale/documents/document1.txt/links/precipice HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
> Accept: application/json
>
< HTTP/1.1 204 No Content
User precipice
will now have read-only access to the document.
Requires authentication.
Sending a DELETE
request to
/users/codahale/documents/document1.txt/links/precipice
will unlink user
precipice
to document document1.txt
belonging to user codahale
:
> DELETE /users/codahale/documents/document1.txt/links/precipice HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
> Accept: application/json
>
< HTTP/1.1 204 No Content
User precipice
will no longer have access to the document.
The documents shared with a user are stored in their own namespace to avoid document name collisions. If the document's owner modifies the document, the linked users will see the changes. Likewise, if the document's owner deletes the document (or the owner is deleted), the documents will be removed from the user's linked documents.
Requires authentication.
Sending a GET
request to /users/codahale/linked-documents/
will return a
list of documents to which user codahale
has read-only access:
> GET /users/codahale/linked-documents/ HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
> Accept: application/json
>
< HTTP/1.1 200 OK
< Content-Type: application/json
<
< {
< "linked-documents":[
< {
< "name":"document1.txt",
< "uri":"http://example.com/users/codahale/linked-documents/precipice/document1.txt",
< "owner":{
< "id": "precipice",
< "uri": "http://example.com/users/precipice"
< }
< }
< ]
< }
Requires authentication.
Sending a GET
request to
/users/codahale/linked-documents/precipice/document1.txt
will return the
document named document1.txt
belonging to user precipice
in whatever content
type the document was stored with:
> GET /users/codahale/linked-documents/precipice/document1.txt HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
< HTTP/1.1 200 OK
< Content-Length: 10
< Cache-Control: private, no-cache, no-store, no-transform
< Content-Type: text/plain
<
< yay for me
Requires authentication.
Sending a DELETE
request to
/users/codahale/linked-documents/precipice/document1.txt
will remove the user
codahale
's access to the document named document1.txt
belonging to user
precipice
:
> DELETE /users/codahale/linked-documents/precipice/document1.txt HTTP/1.1
> Authorization: Basic Y29kYWhhbGU6d29vd29v
>
< HTTP/1.1 204 No Content
This will not delete the document itself, it will simply remove the document from the user's list of linked documents. It will also not re-encrypt the document; the next time the document is written to, however, the user will be excluded from the recipients.