Web Services Guide

This is a client guide to using Sondra’s automatically generated REST APIs. The primary audience for this guide are application developers who wish to understand the conventions that Sondra uses. Each API has full autogenerated documentation and self-describing schemas that support standard REST access for objects (documents), filtering collections, and “method calls” which implement more complex server-side application logic than simple CRUD operations support, e.g. login and logout support for Authentication.

Making calls

The following section describes conventions for making web-service requests and interpreting their responses.

Requests

Requests are made using standard HTTP and HTTPS calls. Most browsers, jQuery, and as well as cURL and wget should support all manner of calls supported by these. When a body is required (or optional) for a call, the body is assumed to be JSON. All calls return JSON by default, if no format specifier is supplied as part of the call.

Alternative Request Syntax

Query parameters can always be supplied to modify the call being made, even if the call also supplies a JSON object in the body. Additionally, long query strings may be overwhelming or poorly supported on some browsers. For the situation in which a query string is too long to be manageable, a special body JSON can be supplied as the body of a POST request. The JSON must adhere to the following schema:

{ "type": "object",
  "required": ["__q"],
  "properties": {
    "__q": {
      "type": "object",
      "description": "Query string parameters"
    },
    "__method": {"enum": ["GET","POST","PUT","PATCH","DELETE"]},
    "__objs": {
      "description": "The object or list of objects that constitute the request body",
      "oneOf": [
        {"type": "object"},
        {"type":"array", "items": {"type": "object"}}
      ]
    }
  }

Responses

Except for autogenerated help, all formats return some manner of JSON. Some methods and aggregations return “bare” values e.g. a string, integer, or number. Bare values are not valid JSON, so these values will be wrapped in a dummy object according to their type:

{"_": <value>}

In addition, because empty responses are not handled well by most browsers, a null return from a service call will result in an empty JSON object, {}.

In general, all webservices are self-describing with auto-generated help and JSON-schemas. JSON-schemas are very slightly enhanced to improve the interpretation of linkages across collections. See Schemas for more information.

Errors

Errors in production (non DEBUG) code will be returned as JSON in the very near future. For now they are returned in whatever form the underlying web framework (e.g. Flask, Django, etc) returns exceptions. JSON encoded errors may have their own schemas defined at the Suite level, however at a minimum they conform to this schema:

{
  "type": "object",
  "properties": {
    "errorName": {"type": "string"},
    "requestedUrl": {
      "type": "string",
      "format": "url"
    },
    "format": {
      "enum": ["json", "geojson", "help", "schema"]
    }
    "requestType": {
      "enum": [
        "application", "collection", "document",
        "application_method", "collection_method", "document_method"
      ]
    }
    "requestMethod": {"enum": ["GET", "POST", "PATCH", "PUT", "DELETE"]},
    "message": {"type": "string"}
  }
}

Authentication

Please make sure to expose and call APIs over HTTPS. JWT security relies on encryption to be effective.

APIs that support authentication as supplied by Sondra must include the auth app as part of their API. The auth app has auto-generated documentation that covers it entirely, and it is not in the scope of this document to replicate that here. However, there are a few basic points covered again here for login, logout, and making authenticated webservice calls.

Logging In

This method issues a new JSON Web Token and caches it as valid for the time being (300 seconds by default).

URL Form:

http://localhost:5000/auth.login

Request object properties

username (str)
The username (often the same as email address).
password (str)
The user’s password.

Returns an encoded JSON Web Token as a bare object {"_": <jwt>}

Logging Out

This method revokes a JSON Web Token, invalidating it.

URL Form:

http://localhost:5000/auth.logout

Request object properties

token (string, JWT)
The currently valid JWT

Returns an empty response, the blank object {}.

Renewing a Token

This method renews the token for a currently logged in user.

URL Form:

http://localhost:5000/auth.renew

Returns a new JWT that has been freshly issued.

Making Authenticated Requests

Once you obtain a JWT by logging in, you may use this JWT to authenticate requests. JWTs in Sondra by default must be renewed (see above, Renewing a Token every five minutes (300 seconds). This can be changed on the server by deriving a new subclass of the Auth app.

Query Parameters

_auth (string, JWT)
This is valid on all requests for authenticated application, suites, collections, and documents. The JWT, as returned by auth.login (Logging In) is the value of _auth.

Authentication Service Exceptions

AuthenticationError [1]

Occurs when a request is made that requires authentication to complete, and the call was made anonymously.

ValidationError

Occurs when a request is made with a token that is expired or whose issuer is not recognized.

ParseError

Occurs when a request is made with a token that is corrupted or unreadable for any reason.

AuthorizationError [1]

Occurs when a request is made by an authenticated user who has insufficient permissions to perform the operation.

Schemas

Schemas follow JSON-Schema format and use the jsonschema package to validate documents that will be added to persistent collections. JSON-Schema is strictly followed, with only a few minor additions.

refersTo

Applies to schema type “string” and provides a prefix for linking foreign keys to other API endpoints, specifically other collections. The value of refersTo is itself a string in URL format and must refer to a collection. Single objects will be validated against this string in the same manner as JSON-schema’s pattern specifier. The difference between refersTo and pattern is that refersTo must be a valid URL and must also be a collection API endpoint. This allows code to make certain assumptions (such as formats and metadata endpoints) about the endpoint, whereas a pattern can be any valid regular expression.

See Collection Python documentation for more details.

Suite schema

URL Form:

http://localhost:5000/schema

The suite schema is a “dummy schema” that contains suite-wide definitions that are referenced by other schemas within a suite’s set of enclosed applications. Additionally, it contains another property:

  • applications - whose value is a list of URLs to the schema of all applications in the suite.

Application schema

URL Form:

http://localhost:5000/application;schema

The application schema is a “dummy schema” that contains application-wide definitions that are referenced by schemas within a application’s set of enclosed collections and documents. Additionally, it contains two properties:

  • collections - whose value is a list of URLs to the schema of all collections in the suite.
  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the application level.

Collection schema

URL Form:

http://localhost:5000/application/collection;schema

The collection schema defines the validation set for all documents within a single collection. Additionally, it contains two properties:

  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the collection level.
  • documentMethods - whose value is an object whose property names are method slugs that can be called at the individual document level, and whose property values are the request and response schema of all methods that attach at the document level.

Document schema

URL Form:

http://localhost:5000/application/collection/primary-key;schema

The document schema mirrors the collection schema and mainly exists for completeness. Generally you should use the collection schema. In addition, it contains a property:

  • methods - whose value is an object whose property names are method slugs, and whose property values are the request and response schema of all methods that attach at the document level.

Method schemas

URL Form:

http://localhost:5000/application.method;schema
http://localhost:5000/application/collection.method;schema
http://localhost:5000/application/collection/primary-key.method;schema

Requesting a schema directly on the method call returns the named schema from the “methods” (or documentMethods) section of the schema for that class (application, collection, or document).

Auto-generated help

URL Form:

http://localhost:5000/help
http://localhost:5000/application;help
http://localhost:5000/application.method;help
http://localhost:5000/application/collection;help
http://localhost:5000/application/collection.method;help
http://localhost:5000/application/collection/primary-key;help
http://localhost:5000/application/collection/primary-key.method;help

Help for every level of an API heirarchy can be retrieved by using the format specifier ;help at the end of the URL. Help is completely auto-generated from docstrings (using Markdown, reStructuredText, or Google formats, see suite docs) in the definition of each Python class definition and each schema that makes up the API. Help is available in HTML format. Currently localized help is not supported for APIs, but this may happen in the future.

Operations on Collections

Collections support the standard REST CRUD operations: Create, List, Update, and Delete.

URL Form:

http://localhost:5000/application/collection;format

POST: Create

Create a new document. Raises an error if the document already exists in the database.

Request

Accepts JSON in the post body. If the JSON is an array, then each document in the array is added in turn. If it is an document, then that document will be added.

Response

The response will be a JSON array of the URLs of added documents.

Errors

  • KeyError if any of the POSTed documents already exist in the database by primary key.
  • ValidationError if any of the POSTed documents do not fit the collection schema.

GET: List and Filter

Retrieve a listing of documents in the given format, or retrieve an HTML form for entering a new document. Note that filtering parameters described here can also be accessed via POST using the Alternative Request Syntax.

Request

flt (JSON object or list, see Simple filters)
A (list) of simple filter(s), which will be performed in sequence to narrow the result.
geo (JSON object, see Spatial filters)
A spatial filter which will be used to limit the spatial extent of results.
agg (JSON object, see Aggregations)
An object that describes aggregations to perform on the query
start (int)
The index of the first document to return.
limit (int)
The maximum number of documents to return.
end (int)
The index of the last document to return.
Simple filters

Simple filters are JSON objects that narrow down the documents being returned. Currently these do not support the use of indexes, but they may in the future. They are performed by successive applications of the “filter” method in ReQL.

Each filter must conform to the following JSON specification:

{ "op": "<operator>",
  "args": { "lhs": <field-name>, "rhs": <value> }}

Operators

  • ==: Equality check. Equivalent to .filter(r.row(lhs).eq(rhs))
  • !=: Inequality check. Equivalent to .filter(r.row(lhs).eq(rhs))
  • >: Strictly greater than. Equivalent to .filter(r.row(lhs).gt(rhs))
  • >=: Greater or equal to. Equivalent to .filter(r.row(lhs).ge(rhs))
  • <: Strictly less than. Equivalent to .filter(r.row(lhs).lt(rhs))
  • <=: Less than or equal to. Equivalent to .filter(r.row(lhs).le(rhs))
  • match: Regex match. Equivalent to .filter(r.row(lhs).match(rhs))
  • contains: Containment check. Equivalent to .filter(r.row(lhs).contains(rhs))
  • has_fields: Field presence check. Equivalent to .filter(r.row(lhs).has_fields(rhs))
Spatial filters

Spatial filters narrow down the returned documents using RethinkDB’s geographic operators. There must be at least one spatial property defined on the Collection or the inclusion of a spatial filter will result in an error. Spatial filters must conform to the following JSON schema:

{
  "type": "object",
  "required": ["op", "test"],
  "properties": {
    "against": {"type": "string"},
    "op": {"$ref": "#/definitions/op"},
    "test": {"type": "object", "description": "GeoJSON geometry or distance object"}
  },
  "definitions": {
    "op": {
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": {"enum": ["distance", "get_intersecting", "get_nearest"])
        "args": {"type": "array"},
        "kwargs": {"type": "object"}
      }
  }
}

Spatial operators

Spatial operators all work exactly the same as their corresponding RethinkDB commands. The “test” is a geometry to test against. Geometries should be described in GeoJSON. Distance isn’t very useful yet, since it returns distances without primary keys. Against is the geometric index to test against. If left blank, it is the default geometry field.

Only one spatial filter may be supplied.

Aggregations

Aggregations transform returned documents and often supply summaries of data. Aggregtions must conform to the following JSON schema:

{
  "type": "object",
  "required": ["name"],
  "properties": {
    "name": {"enum": [
      "with_fields",
      "count",
      "max",
      "min",
      "avg",
      "sample",
      "sum",
      "distinct",
      "contains",
      "pluck",
      "without",
      "has_fields",
      "order_by",
      "between"]
    },
    "args": {"type": "array"},
    "kwargs": {"type": "object"}
  }
}

Again, these operators work the same way as their RethinkDB counterparts. Consult the ReQL API documentation for more information. Only one aggregation may be supplied.

Response

Unless an aggregation was specified, the response is a list of documents that conform to the collection schema. If an aggregation (or distance) was specified, then the result will be a bare JSON object, {"_": <value>}.

PUT. Replace

Replace an document currently in the database with a new document.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.

Request

Accepts JSON in the PUT body. If the JSON is an array, then each document in the array is replaced in turn. If it is an document, then that document will be added.

Response

The response will be the same as the RethinkDB response to an insert.

Errors

  • KeyError if any of the PUT documents do not already exist in the database by primary key.
  • ValidationError if any of the PUT documents do not fit the collection schema.

PATCH. Update

Update document(s) in place. Raises an error if a supplied document does not already exist in the database. The semantic difference between a PATCH and a PUT request is that the PATCH request retrieves the document first and updates that document with the values provided in the PATCH. The primary key must be present in each replacement document for a lookup to occur.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.

Request

Accepts JSON in the PATCH body. Like PUT and POST, PATCH can be, if the JSON is an array, then each document in the array is updated in turn. If it is an document, then that document will be updated.

Response

The response will be the same as the RethinkDB response to an insert.

Errors

  • KeyError if any of the PATCH documents do not already exist in the database by primary key, or if a primary key is not supplied.
  • ValidationError if any of the PUT documents do not fit the collection schema.

DELETE. Delete

Delete documents in place. Raises an error if the document doesn’t exist in the first place, or if the entire collection would be deleted, unless specifically requested to delete everything.

Request

Accepts a JSON list of primary keys or index keys in the body, OR accepts simple and spatial filters.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.
delete_all (true | false)
Set this flag on the query string if you want to
flt (JSON object or list, see Simple filters)
A (list) of simple filter(s), which will be performed in sequence to narrow the deletion.
geo (JSON object, see Spatial filters)
A spatial filter which will be used to limit the spatial extent of the deletion.
index (str index name)
If index is supplied then the keys to be deleted are assumed to be index keys instead of primary keys.

Response

The same as the RethinkDB delete response.

Errors

  • KeyError if any of the DELETE documents do not already exist in the database by primary key.
  • PermissionError if no filters or primary keys have been supplied (all documents in the collection would be deleted) and delete_all is not true.

Operations on Documents

Documents support the standard REST CRUD operations: Create, Detail, Update, Replace, and Delete.

URL Form:

http://localhost:5000/application/collection/primary-key;format

POST and PUT. Replace

Replace the referenced document with the given document. Raises an error if the primary key exists and conflicts with the primary key of the document.

Request

Accepts a single JSON object in the body.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.

Response

The response will be the same as RethinkDB’s save method.

Errors

  • KeyError if the POSTed document already exists in the database by primary key.
  • ValidationError if the POSTed document does not fit the collection schema.

PATCH. Update

Update document in place. Raises an error if a supplied document does not already exist in the database or if a primary key is present in the supplied document and conflicts with the document key. The semantic difference between a PATCH and a PUT or POST request is that the PATCH request retrieves the document first and updates that document with the values provided in the PATCH.

Query Params

durability (hard | soft)
Same as RethinkDB save.
return_changes (boolean=True)
Same as RethinkDB save.

Request

Accepts a single JSON in the PATCH body.

Response

The response will be the same as the RethinkDB response to an save.

Errors

  • KeyError if the PATCH document does not already exist in the database by primary key or conflicts with the existing key.
  • ValidationError if the PATCH document does not fit the collection schema.

DELETE. Delete

Delete an document. Raises an error if the document doesn’t exist to be deleted.

Request

The request body should be bare.

Query Params

durability (hard | soft)
Same as RethinkDB insert.
return_changes (boolean=True)
Same as RethinkDB insert.

Response

The same as the RethinkDB delete response.

Errors

  • KeyError if the DELETE document does not already exist in the database by primary key.

Complete URL scheme reference

/schema
GET. Suite-wide schema. Typically this includes definitions that are common for the entire suite, including valid filter names, dates, times, geography, and localization.
/help
GET. Suite-wide help with links to applications. Help is autogenerated from schema definitions and from docstrings in the class.
/{application};schema
GET. Application-wide schema.
/{application};help
GET. Application-wide help with links to collections. Help is autogenerated from schema definitions and from docstrings in the class.
/{application}.{method-name};schema
GET. Method schema for application.
/{application}.{method-name};help
GET. Autogenerated help for the method.
/{application}.{method-name};json
GET, POST. Applicationlication method call.
/{application}/{collection};schema
GET. Collection schema.
/{application}/{collection};help
GET. Autogenerated help for the collection.
/{application}/{collection};json
GET, POST, PUT, DELETE. Get, Add, Update, or Delete documents of the collection, respectively.
/{application}/{collection};geojson
GET, DELETE. Get or Delete documents of the collection, respectively, but as GeoJSON FeatureCollections.
/{application}/{collection}.{method-name};schema
GET. Method schemas for a collection-wide method.
/{application}/{collection}.{method-name};help
GET. Autogenerated help for the collection method.
/{application}/{collection}.{method-name};json
GET, POST. Call a collection method.
/{application}/{collection}/{document};schema
GET. Same as /{application}/{collection};schema, generally speaking.
/{application}/{collection}/{document};help
GET. Autogenerated help for document methods.
/{application}/{collection}/{document};json
GET, POST, PUT, DELETE. Get, Replace, Update, or Delete document, respectively.
/{application}/{collection}/{document};geojson
GET, DELETE. Get or Delete document, respectively, but as a GeoJSON Feature.
/{application}/{collection}/{document}.{method-name};schema
GET. Document method schema.
/{application}/{collection}/{document}.{method-name};help
GET. Document method help.
/{application}/{collection}/{document}.{method-name};json
GET, POST. Document method call.
[1](1, 2) The following exceptions are not yet supported, but will be very soon. Currently all authorization and authentication errors raise a standard Python PermissionError on exception.