Developer Docs

Introduction

ParaIO.com is the hosted service of Para - the open source backend framework. Para is a simple and flexible backend service that allows you to focus on your front-end. It is backed by the AWS cloud and runs on top of Elasticsearch and DynamoDB. Para can persist any object easily, while caching it automatically on every write request. Additionally, it supports full text search and allows you to build complex queries for finding your stored objects.

Quick start

  1. Sign in
  2. Create a new App by clicking the “New App” button in Apps
  3. Take note of the security credentials - access and secret keys
  4. Use one of the client libraries below or use the API directly

The quickest way to interact with Para is through the command-line tool (CLI):

$ npm install -g para-cli
# run setup and leave endpoint blank or enter 'https://paraio.com'
$ para-cli setup
$ para-cli ping
$ echo "{\"type\":\"todo\", \"name\": \"buy milk\"}" > todo.json
$ para-cli create todo.json --id todo1 --encodeId false
$ para-cli read --id todo1
$ para-cli search "type:todo"

In your own project you can create a new ParaClient instance like so:

ParaClient pc = new ParaClient("ACCESS_KEY", "SECRET_KEY");
// Set this to true if you want ParaClient to throw exceptions on HTTP errors
pc.throwExceptionOnHTTPError(false);
// send a test request - this should return a JSON object of type 'app'
pc.me();

API clients

 
 

Quickstart examples

So you want to build something quickly? So by now you should have created an App through the web UI and got your access keys. You can use these to initialize the API client like so:

// example for server-side JavaScript
var ParaClient = require('para-client-js');
var pc = new ParaClient('ACCESS_KEY', 'SECRET_KEY');

Note: Never include your secret key in client-side code. To create a frontend client in JavaScript you can either use JWT tokens and leave the secret key blank or use the paraClient.signin() method to authenticate your users first. Once your client is initialized you can start doing real work like persisting objects and querying them.

Here’s a short list of starter projects:

Apps

Apps allow you to have separate namespaces and data models for different purposes. Each app lives in its own separate database table and is independent from apps.

Each app has a unique identifier, like “my-custom-app”. When an object is created, Para will attach the app identifier to it automatically. Apps also have a set of data types, as set of permissions and validation constraints. Data types can be created on-the-fly, for example you can create a type called “article” and it will have be available as a new API resource at /v1/article (and in plural form /v1/articles).

Creating apps

You can create apps through the web interface. Each app is independent and cannot interact with the data of other apps.

Currently Para organizes objects in one table per app and uses a single shared search index unless that app sets shared = false. If this is the case then a separate search index is created for that app. It is possible to make Para use a single database table for all apps by prefixing id fields (e.g. app1_id1: {data}) but this is not yet implemented.

You can set custom settings to each app through the settings API /v1/_settings using GET, PUT and DELETE. These can be OAuth credentials for social apps or other configuration details that are specific for the app.

Social sign-in for apps

In v1.19 apps can have their own separate credentials for social sign-in, like an OAuth app_id and secret. These are stored within the app object and can be used to create/login users to the app that has these credentials. For example, we can send a request to /facebook_auth?appid=myapp where myapp is not the root app. This will tell Para to look for Facebook keys inside that app. This allows for each Para app to have a corresponding Facebook app (or any other app on the supported social networks, see JWT sign in).

Additionally, we’ve added two custom settings which can tell Para where to redirect users after a successful login or a login failure. These are signin_success and signin_failure (see Custom Settings). Here’s an example of all settings combined:

{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

Note that these settings will work for the traditional authentication flow through the browser and the standard endpoints /facebook_auth?appid=myapp, /github_auth?appid=myapp, /google_auth?appid=myapp, and the rest. The other way of authenticating users is through the JWT sign in API which requires an OAuth access token and doesn’t require stored OAuth credentials - these are not even checked because we are supplied with a ready-to-use token.

Teams

Teams allow you to collaborate with your team mates on the same apps. Each team can have multiple apps and its own billing.

Each account can have up to 5 teams with up to 25 members per team.

To create a team, go to Account > Teams and click “New Team”. You can then invite your team members by entering their email address.

There are two types of team users currently - admins and regular members. Admins can do everything and can manage billing. Regular members can activate or deactivate apps, view all app data and change app settings. Regular team members cannot do the following operations:

  • view or change app secret keys
  • create or delete apps
  • create or delete users
  • update or cancel team subscription
  • add or remove payment methods for the team

Teams are available for all pricing plans. A newly created team doesn’t have any app credits so it requires you to set up team billing in order to be able to create apps in that team. The pricing plans are exactly the same for teams as for personal accounts.

Core objects

All objects in Para extend ParaObject which gives them basic common properties like id, timestamp, name, etc. Let’s say you have a plain old Java object like this:

This allows you do call create(), update(), delete() on that object and enables search indexing automatically.

User u = new User();
u.setName("Gordon Freeman");
u.setAge(40);
// generates a new id and persists the object
String id = u.create();

And here’s what a Para object looks like as JSON when returned from the REST API:

{
  "id" : "572040968316915712",
  "timestamp" : 1446469779546,
  "type" : "user",
  "appid" : "para",
  "updated" : 1446469780024,
  "name" : "Gordon Freeman",
  "votes" : 0,
  "identifier" : "fb:1000123456789",
  "groups" : "admins",
  "active" : true,
  "email" : "g.freeman@blackmesa.com",
  "objectURI" : "/users/572040968316915712",
  "plural" : "users"
}

Fine-tuning backend operations

From version 1.18 Para objects have three new flags - stored, indexed and cached. These flags turn on and off the three main operations - persistence, indexing and caching. Developers can choose to switch off caching on a number of objects that change very often, for example. Also some objects my be hidden from search by setting the indexed: false flag. And finally you can turn off persistence completely with stored: false and thus have objects that live only in memory (and search index) but are never stored in the database.

Optimistic locking

The ParaObject interface contains a version field which can be used to store information about the current version of each object and it’s value should be incremented by the database, if an update operation succeeds. The locking mechanism is simple - the database checks if the supplied object version matches the one stored in the database. If the two versions match, the update operation is executed, otherwise it fails. A version value of -1 indicates a failed update and it’s the responsibility of the API clients to handle such failures. If the version value is positive, the update has been successful, and 0 means the field is unused or the object has not been persisted yet.

Optimistic locking is enabled for each ParaObject individually by setting its version field to a positive value different than 0. To disable, set the version field back to 0.

Core types

The list below describes all of the core types that are built into Para. You can use them for your objects but you’re always free to create your own. To do that, simply POST a new object with type: mytype through the API and your new type will be automatically registered.

Note: Type definitions cannot contain the symbols / and #.

classdescription

User

Defines a basic user with a name, email, password. Used for user registration and security.

Address

Defines an address with optional geographical coordinates. Used for location based searching.

App

Defines an application within Para. Usually there’s only one existing (root) app.

Tag

Simple tag class which contains a tag and its frequency count. Basically any Para object can be tagged. Used for searching.

Vote

Defines a user vote - negative or positive. Useful for modeling objects which can be voted on (likes, +1s, favs, etc).

Translation

Holds a translated string. Can be used for collecting translations from users.

Sysprop

System class used as a general-purpose data container. It’s basically a map.

Linker

System class used for implementing a many-to-many relationship between objects.

Webhook

System class used for storing webhook metadata.

Voting

Para implements a simple voting mechanism for objects - each object can be voted up and down an has a votes property. Voting is useful for many application which require sorting by user votes. When a vote is cast, a new object of type Vote is created to store the vote in the database. Users have a configurable time window to amend their vote, they can no longer vote on that particular object.

User-defined objects

Let’s say you have an object of type Article in your application that you wish to persist. You can define your custom type through the REST API by issuing a simple POST request.

POST /v1/articles

{
 "appid": "myapp",
 "type": "article",
 "author": "Gordon Freeman"
}

You can create all sorts of objects with custom types and fields (properties) through the REST API. Note: When doing search on custom fields, add the “properties” prefix to them, like properties.myfield. Also keep in mind that the following are reserved words and they should not be used for naming your types (plural form included): “search(es)”, “util(s)”.

This creates a new Article and indexes all fields including the custom field author. To search for objects through the API, containing the author field we can do a request like this:

GET /v1/articles/search?q=properties.author:Gordon*

Note that we have q=properties.author:... instead of q=author:.... This is due to the fact that custom fields are stored in Para using a nested Map called properties (see the Sysprop class).

Resource permissions

When creating new users, we usually want to specify which resources they can access. This is why we added a few methods for adding and removing resource permissions. Each app can have any number of users and each user can have a set of permissions for a given resource. Resources are identified by name, for example the _batch resource would represent requests going to /v1/_batch.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Let’s look at a few example scenarios where we give users permission to access the _batch. We have two users - user one with id = 1 and user two with id = 2. We’ll use the following methods:

boolean grantResourcePermission(String subjectid, String resourcePath, String[] permission);

boolean revokeResourcePermission(String subjectid, String resourcePath);

These methods allow the use of wildcards * for subjectid and resourcePath arguments.

Scenario 1: Give all users permission to READ - this allows them to make GET requests:

paraClient.grantResourcePermission("*", "_batch", ["GET"]);

Scenario 2: Give user 1 WRITE permissions - allow HTTP methods POST, PUT, PATCH, DELETE:

paraClient.grantResourcePermission("1", "_batch", ["POST", "PUT", "PATCH", "DELETE"]);

Also you could grant permissions on specific objects like so:

paraClient.grantResourcePermission(urlencode("user1"), urlencode("posts/123"), ["DELETE"]);

This will allow user1 to delete only the post object with an id of 123.

Scenario 3: Give user 2 permission ot only make POST requests:

paraClient.grantResourcePermission("2", "_batch", ["POST"]);

Note that all users still have the READ permissions because permissions are compounded. However, when grantResourcePermission() is called again on the same subject and resource, the new permission will overwrite the old one.

Scenario 4: Revoke all permissions for user 1 except READ:

paraClient.revokeAllResourcePermissions("1");

Scenario 5: Grant full access or deny all access to everyone:

paraClient.grantResourcePermission("*", "*", ["*"]);
paraClient.grantResourcePermission("*", "*", ["-"]);

Scenario 6: Grant full access to user 1 but only to the objects he/she created. In this case user 1 will be able to create, edit, delete and search todo objects but only those which he/she created, i.e. creatorid == 1.

paraClient.grantResourcePermission("1", "todo", ["*", "OWN"]);
paraClient.grantResourcePermission("1", "todo/*", ["*", "OWN"]);

To get all permissions for both users call:

paraClient.getAllResourcePermissions("1", "2");

The default initial policy for all apps is “deny all” which means that new users won’t be able to access any resources, except their own object and child objects, unless given explicit permission to do so.

You can also create “anonymous” permissions to allow unauthenticated users to access certain resources:

paraClient.grantResourcePermission("*", "public/resource", ["GET", "?"]);

This resource is now public but to access it you still need to specify your access key as a parameter:

GET /v1/public/resource?accessKey=app:myapp

Alternatively, on the client-side, you can set the Authorization header to indicate that the request is anonymous:

Authorization: Anonymous app:myapp

The special permission method ? means that anyone can do a GET request on public/resource.

To check if user 1 is allowed to access a particular resource call:

// returns 'false'
paraClient.isAllowedTo("1", "admin", "GET");

Validations

Para supports JSR-303 validation annotations but it also allows you to define validation constraints using the constraints API. This method is more flexible as it allows you to validate any property of any object.

The built-in constraints are:

required, min, max, size, email, digits, pattern, false, true, future, past, url.

Note: Objects are validated on create() and update() operations only.

User-defined validation constraints

Annotations work fine for most objects but are less useful when we want to define objects through the API. For this purpose we can use the constraints API:

paraClient.addValidationConstraint(String type, String field, Constraint c);

paraClient.removeValidationConstraint(String type, String field, String constraintName);

To create a constraint you can use the static methods provided by the Constraint class. For example calling Constraint.email() will return a new constraint object for checking email addresses.

We can use these methods to define constraints on custom types and fields that are not yet defined or are created by the API.

Integration with the client-side

You can easily implement client-side validation by getting the JSON object containing all validation constraints for all Para classes.

// return a JSON object with all validation constraints
paraClient.validationConstraints();

This returned JSON is in the following format (note that type names are all in lowercase):

'user': {
    'email': {
        'email': {
            'message': 'messages.email'
        },
        'required': {
            'message': 'messages.required'
        }
    },
    'identifier': {
        'required': {
            'message': 'messages.required'
        }
    }
    ...
},
...

This format used by the excellent JavaScript validation tool Valdr but can easily be integrated with other client-side validators.

Pager

The Pager class is used for pagination. It holds data about a page request. For example you can call a search method like this:

Pager pager = new Pager();
// limit results to 5
pager.setLimit(5);
// sort by the 'tag' field
pager.setSortby("tag");
// descending order
pager.setDesc(true);
// last key special field
pager.setLastKey("1234");
List<Tag> tags = search.findTags("tag1", pager);
// the total number of tags for the query
int tagCount = pager.getCount();

The Pager class has a special string field lastKey for storing the last id for scrolling pagination queries. Usually scrolling is used in NoSQL databases, where in order to get a page of results, you have to provide the last key from the previous page.

Pager objects are used primarily in combination with search queries and allow you to limit the results of a query.

Search query pagination and sorting

Para supports two modes of pagination for query results. The standard mode works with the page parameter:

GET /v1/users?limit=30&page=2

The other mode is “search after” and uses a stateless cursor to scroll through the results. To activate “search after”, append the lastKey query parameter to the search request like so:

GET /v1/users?limit=10000&sort=_docid&lastKey=835146458100404225

Important: For consistent results when doing “search after” scrolling, set pager.setSortby("_docid") to sort on the _docid field. Additionally, there’s a limit to the result window imposed by Elasticsearch of maximum 10000 documents. See the docs for index.max_result_window.

The “search after” method works well for deep pagination or infinite scrolling or search results. The lastKey field is returned in the body of the response for each search query. It represents the _docid value for a document - a unique, time-based long. You may have to rebuild your index for “search after” to work.

Sorting is done on the timestamp field by default, in desc (descending) order. To sort on a different field, set pager.setSortBy(field). Sorting on multiple fields is also possible by separating them with a comma. For example:

GET /v1/users?sort=name,timestamp
// or
pager.setSortBy("name:desc,timestamp:asc");

And here’s an example for reading all object of type “cat” using deep pagination (“search after”):

Pager pager = new Pager(1, "_docid", false, 10_000);
List<Cat> cats = new LinkedList<>();
List<Cat> catsPage;
do {
    catsPage = Para.getSearch().findQuery("cat", "*", pager);
    cats.addAll(catsPage);
} while (!catsPage.isEmpty());

Webhooks

Webhook support is fully asynchronous and uses a queue for decoupling the message publishing from the actual processing and delivery of payloads. A worker thread periodically pulls messages from the queue and forwards them to their destinations.

The Webhook class is used for storing webhook metadata. Each Webhook object represents a destination which will receive a POST request with a certain payload. The payload is determined by the type of event to which that webhook is subscribed to. For example, a webhook might be a subscribed to all update events in Para, and also it might only be interested in updated user objects. So we can register a new webhook like so:

POST /v1/webhooks
{
    "urlEncoded": true,
    "update": true,
    "targetUrl": "https://destination.url",
    "secret": "secret",
    "typeFilter":"user"
}

The urlEncoded parameter sets the Content-Type header for the payload. By default that’s application/x-www-form-urlencoded, but if urlEncoded is false, the content type will be application/json.

There are 6 event types: update, create, delete, updateAll, createAll, deleteAll. You can also register webhooks which are subscribed to all events on all types in Para:

POST /v1/webhooks
{
    "active": true,
    "urlEncoded": true,
    "update": true,
    "create": true,
    "delete": true,
    "updateAll": true,
    "createAll": true,
    "deleteAll": true,
    "targetUrl": "https://destination.url",
    "secret": "secret",
    "typeFilter":"*"
}

If the typeFilter is either blank or *, all selected events will be sent to the destination, regardless of the object type.

If the webhook’s response code is not 2xx, it is considered as failed. Webhooks with too many failed deliveries (10 by default) will be disabled automatically. The number of maximum failed attemts can be adjusted like so:

para.max_failed_webhook_attempts = 15

Events are intercepted on each mutating request (POST, PUT, PATCH, DELETE) and a message is sent to the queue for all registered Webhook objects. Each message is signed with HmacSHA256 which is sent to the destination in a X-Webhook-Signature header. The signature takes the webhook secret and uses it to sign only the payload JSON string (whitespace removed). The worker node (might be the same machine) then pulls messages from the queue and sends them to their destinations using a POST request. Here’s an example POST request:

POST /test HTTP/1.1
Host: parawebhooks.requestcatcher.com
Accept-Encoding: gzip,deflate
Connection: Keep-Alive
Content-Length: 327
Content-Type: application/json
User-Agent: Para Webhook Dispacher 1.32.0
X-Para-Event: update
X-Webhook-Signature: FFEma4/Uc5gGCs974cJNa873kzsumFgqPGVIGOEexmY=

{"appid":"scoold","event":"update","items":[{"id":"tag:acpi","timestamp":1486848081865,"type":"tag","appid":"scoold","updated":1561644547996,"name":"ParaObject tag:acpi","votes":2,"version":0,"stored":true,"indexed":true,"cached":true,"tag":"acpi","count":1,"objectURI":"/tags/acpi","plural":"tags"}],"timestamp":1561644548430}

The format of the payload is this:

{
    "appid": "myapp",
    "event": "create",
    "timestamp": 1486848081865,
    "items": [{}, ...]
}

The receiving party should verify the signature of each payload by computing Base64(HmacSHA256(payload, secret)).

Custom events

In addition to the standard create, update, delete events you can create webhooks which are subscribed to custom events. For example you can have a webhook which is subscribed to events like post.like or user.mention. These events are triggered from the code of your application using the Para API. Let’s say we have a post which is liked by someone - the code which handles the like event will notify Para that post.like has occured along with a custom payload, in this case the post object and the object of the user who liked the post. Para will then dispatch that payload to the appropriate target URLs (subscribers). Custom events allow you to create applications which follow the best practices of RESTHooks and this makes it easy to integrate them with other applications (see Zapier). Here’s an example request which would trigger a custom event post.like via the API:

POST /v1/webhooks
{
    "triggeredEvent": "post.like",
    "customPayload": {
        "post_id": "5129509320",
        "title": "Hello world",
        "liked_by": {
            "user_id": "581703234",
            "name": "Gordon"
        }
    }
}

The response object returned from this request should be ignored.

One-to-many

Object relationships are defined by the Linkable interface. All Para objects are linkable, meaning that they can be related to other Para objects.

Para supports one-to-many relationships between objects with the parentid field. It contains the id of the parent object. For example a user might be linked to their father like this:

+--------+
| Darth  |
| id: 5  |  Parent
+---+----+
    |
+---+---------+
| Luke        |
| id: 10      |  Child
| parentid: 5 |
+-------------+

This allows us to have a parent objects with many children which have the same parentid set. Now we can get all children for a given object by calling parent.getChildren(Class<P> clazz). This will return the list of objects that have a parentid equal to that object’s id. For example:

// assuming we have the parent object...
// this will return its first child of type User
User luke = client.getChildren(parent, "user").get(0);
User darth = client.read(luke.getParentid()); // parent

Many-to-many

Many-to-many relationships are implemented in Para with Linker objects. This object contains information about a link between two objects. This is simply the id and type of both objects. Linker objects are just regular Para objects - they can be persisted, indexed and cached.

+--------+
|  tag1  |
+---+----+
    |
+---+------------+
|post:10:tag:tag1|  Linker
+---+------------+
    |
+---+------+
|  Post1   |
|  id:10   |
+----------+

Note: The following methods are only used when creating “many-to-many” links. Linking and unlinking two objects, object1 and object2, is done like this:

client.link(object1, object2.getId());
client.unlink(object1, object2.getType(), object2.getId());
// delete all links to/from object1
client.unlinkAll(object1);

To check if two objects are linked use:

client.isLinked(object1, object2.getType(), object2.getId())

Also you can count the number of links by calling:

client.countLinks(object1, object2.getType())

Finally, to read all objects that are linked to object1, use:

client.getLinkedObjects(object1, object2.getType(), Pager... pager)

API Authentication

Para uses the AWS Signature Version 4 algorithm for signing API requests. We chose this algorithm instead of OAuth because it is less complicated and is already implemented inside the core AWS Java SDK, which we have as direct dependency. In terms of security, both algorithms are considered very secure so there’s no compromise in that aspect.

Para offers two ways of authentication - one for apps using API keys and one for insecure clients (mobile, JS) using JWT. Apps authenticated with a secret key have full access to the API. Users authenticated with social login are issued JWT tokens and have limited access to the API, for example they can’t generate new API keys and they are authorized by specific resource permissions (see Resource permissions).

Full access for apps

In order to make a request to the API you need to have a pair of access and secret keys. Access keys are part of the HTTP request and secret keys are used for signing only and must be kept safe.

We recommend that you choose one of our API client libraries to handle the authentication for you.

Note: when a resource has public permissions you can access it without setting the Authorization header. Simply specify your access key as a parameter:

GET /v1/public/resource?accessKey=app:myapp

Changing keys

Call POST /v1/_newkeys to generate a new secret key (the request must be signed with the old keys).

For more information see the AWS documentation for REST authentication.

JSON Web Tokens - client access based on permissions

Para apps can create new users and grant them specific permissions by implementing social login (identity federation). First a user authenticates with their social identity provider such as Facebook, then comes back to Para with the access_token and is issued a new JSON Web Token that allows him to access the REST API.

JWT tokens are a new standard for authentication which is similar to cookies but is more secure, compact and stateless. An encoded token looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG
4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

When decoded the token looks like this:

// HEADER:
{
  "alg": "HS256",
  "typ": "JWT"
}
// PAYLOAD:
{
  "sub": "587408205806571520",
  "appid": "app:para",
    "refresh": 1450137214490,
  "nbf": 1450133614,
    "exp": 1450738414,
    "iat": 1450133614
}

To authenticate with users with social login use the Para client:

paraClient.signIn(String provider, String providerToken);

Supported providers are facebook, google, twitter, github, linkedin, microsoft, slack, password, oauth2, oauth2second, oauth2third, ldap, passwordless.

For example calling paraClient.signIn("facebook", "facebook_access_token") should return a new User object and would store the JWT token in memory. To get an access token from Facebook, use their JavaScript SDK.

After you call paraClient.signIn() and the request succeeds, the client caches the access token and all subsequent requests to the API will include that token until paraClient.signOut() is called. This is different from the normal operation using access and secret keys. Usually, tokens are used to authenticate users, unless they are “super” tokens, which can authenticate apps (see below).

To get or set access tokens use:

paraClient.getAccessToken();
paraClient.setAccessToken(String token);

To sign out and clear the JWT access token use:

paraClient.signOut();

Tokens can also be revoked by calling paraClient.revokeAllTokens() but this only works for authenticated users. The Para client takes care of refreshing the JWT tokens every hour and by default all tokens are valid for a week.

Creating “super” tokens

Since v1.18.3 we’ve added the support for “super” JSON web tokens. These are just like normal tokens for users, but instead of authenticating users we can authenticate apps with them. The give clients full access to the API, bypassing permissions. You don’t need to connect to a social identity provider like Facebook or Twitter - you simply generate the tokens on the client-side. Your code will need both the access key and secret key for this purpose.

For example, lets assume we have some JavaScript app running in the browser and we need admin access to our Para app. We could use the JavaScript client for Para but putting the secret key inside client-side code on the browser is not a smart move. So we pull in a library for generating JWT, like jsrsasign and we create the token ourselves. Here’s a snippet:

function getJWT(appid, secret) {
    var now = Math.round(new Date().getTime() / 1000);
    var sClaim = JSON.stringify({
        exp: now + (7 * 24 * 60 * 60), // expires at
        iat: now, // issued at
        nbf: now, // not valid before
        appid: appid // app id must be present
    });
    var sHeader = JSON.stringify({'alg': 'HS256', 'typ': 'JWT'});
    return KJUR.jws.JWS.sign(null, sHeader, sClaim, secret);
}

Your JS app could ask the user for the access keys, create a JWT and then discard the keys and use the newly generated “super” token. Once we have this we can attach it to every request as a header:

Authorization: Bearer eyJhbGciOiVCJ9.eyJzdWIiOi0._MKAH6SGiKSoqgwmqUaxuMyE

When calling the Para API from JavaScript in the browser, make sure you are running a web server and not as file:/// or your browser might not allow CORS requests.

Calling the API from Postman

You can access the protected API from Postman since it has support for the AWS Signature v4 algorithm, used in Para. Open up Postman and in the “Authorization” tab choose “AWS Signature”:

Access Key

Your Para access key, e.g. app:myapp.

Secret Key

Your Para secret key.

Region

Not used. It should always be us-east-1.

Service Name

This should always be para.

Make sure you don’t have extra headers in your requests because Postman is known to calculate invalid signatures for such requests.

Custom authentication

Para supports custom authentication providers through its “passwordless” filter. This means that you can send any user info to Para and it will authenticate that user automatically without passwords.

The default URL for this filter is /passwordless_auth and all requests to this location will be intercepted and processed by it. It accepts the following parameters:

  • token - a JWT generated on an external backend (for example your login server).
  • appid - the Para app identifier
  • redirect - if set to false redirects are disabled and JWT is returned in response body

Also see the configuration properties para.security.signin_success and para.security.signin_failure in section Config. These can be set for each app individually as signin_success and signin_failure in the app’s settings. For apps other than the root app use /passwordless_auth?appid=myapp.

You can disable redirects and configure the filter to return the authentication JWT directly in the response body with the request parameter ?redirect=false. If the authentication is succesful, a text/plain response is returned containing the actual Para JWT which can later be presented to the /v1/_me endpoint for checking if a user is logged in.

Custom authentication flow:

  1. A user wants to sign in to Para and clicks a button
  2. The button redirects the user to a remote login page you or your company set up.
  3. The user enters their credentials and logs in.
  4. If the credentials are valid, you send back a special JSON Web Token (JWT) to Para with the user’s basic information.
  5. Para verifies the token and if it’s valid, the request is redirected to the signin_success URL, otherwise to the signin_failure URL

The JWT must contain the following claims:

  • email - user’s email address
  • name - user’s display name
  • identifier - some unique ID for that user
  • appid - the app id (optional)

The JWT signature is verified using the secret key value which you provide in your configuration:

  • for the root app app:para set para.app_secret_key = "long_random_string"
  • for child apps - add a property to the app’s settings field:
    {
      "id": "app:myapp",
      "settings": {
          "app_secret_key": "long_random_string"
      }
    }
    This key must be at least 32 symbols in length and random. This key is different from the Para secret key for your app. The JWT should have a short validity period (e.g. 10 min). The JWT should also contain the claims iat and exp and optionally nbf.

Once you generate the JWT on your backend (step 4 above), redirect the successful login request back to Para:

GET https://para-host/passwordless_auth?appid=myapp&token=eyJhbGciOiJIUzI1NiI..

Basic authentication

Para implements several authentication mechanisms which you can integrate in your application and make it easy to handle user registrations and logins.

The classic way of logging users in is with usernames and passwords. It takes a username or email and a password and tries to find that user in the database. If the user exists then it validates that the hash of the given password matches the hash in the database. The hashing algorithm is BCrypt.

The default URL for this filter is /password_auth and all requests to this location will be intercepted and processed by it. The default parameters to pass to it are email and password.

You can set for each app individually signin_success and signin_failure in the app’s settings, for controlling where should the user be redirected to on success or failure. For apps other than the root app use /password_auth?appid=myapp.

Here’s an example HTML form for initiating password-based authentication:

<form method="post" action="/password_auth">
    <input type="email" name="email">
    <input type="password" name="password">
    <input type="submit">
</form>

Creating users programmatically

You can create users from your Java code by using a ParaClient. By default, users are created with active = false, i.e. the account is locked until the email address is verified. To get around this, “verify” the user manually, like so:

// user is created but account is locked
paraClient.signIn("password", "user@example.com:Morgan Freeman:pass123");
// read identifier first to get the user id
ParaObject identifier = paraClient.read("user@example.com");
User user = paraClient.read(identifier.getCreatorid());
user.setActive(true);
User updated = paraClient.update(user); // user is now active

After executing the code above, any subsequent calls to paraClient.signIn() will be successful and the authenticated user object will be returned.

SAML support

Para can act as a SAML service provider and connect to a specified identity provider (IDP). The implementation uses OneLogin’s SAML Java Toolkit. Here’s a summary of all the steps required to authenticate users with SAML:

  1. Generate a X509 certificate and private key for your SP
  2. Convert the private key to PKCS#8 and Base64-encode both public and private keys
  3. Specify the metadata URL for your IDP in your config file
  4. Register Para with your IDP as trusted SP by copying the metadata from /saml_metadata/{appid}
  5. Send users to /saml_auth/{appid} and Para will take care of the rest

The SP metadata endpoint is https://paraio.com/saml_metadata/{appid} where appid is the app id for your Para app. For example, if your appid is scoold, then the metadata is available at https://paraio.com/saml_metadata/scoold as an XML file.

SAML authentication is initiated by sending users to the Para SAML authentication endpoint https://paraio.com/saml_auth/{appid}. For example, if your appid is scoold, then the user should be sent to https://paraio.com/saml_auth/scoold. Para (the service provider) will handle the request and redirect to the SAML IDP. Finally, upon successful authentication, the user is redirected back to https://paraio.com/saml_auth/scoold which is also the assertion consumer service (ACS).

Note: The X509 certificate and private key must be encoded as Base64 in the configuration file. Additionally, the private key must be in the PKCS#8 format (---BEGIN PRIVATE KEY---). To convert from PKCS#1 to PKCS#8, use this:

openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem

There are lots of configuration options but Para needs only a few of those in order to successfully authenticate with your SAML IDP (listed in the first rows below).

propertydefault value

security.saml.sp.entityid

blank
The SP entityId, a URL in the form of https://paraio.com/saml_auth/{appid}.

security.saml.sp.assertion_consumer_service.url

blank
The URL where users will be redirected back to, from the IDP. Same value as the entityId above.

security.saml.sp.nameidformat

blank
Specifies constraints on the name identifier to be used to represent the requested subject.

security.saml.sp.x509cert

blank
The X509 certificate for the SP, encoded as Base64.

security.saml.sp.privatekey

blank
The private key for the X509 certificate, in PKCS#8 format, encoded as Base64.

security.saml.idp.entityid

blank
The IDP entityId, a URL in the form of https://idphost/idp/metadata.xml

security.saml.idp.single_sign_on_service.url

blank
SSO endpoint URL of the IdP.

security.saml.idp.x509cert

blank
The x509 certificate for the IDP, encoded as Base64.

security.saml.idp.metadata_url

blank
The location of IDP’s metadata document. Para will fetch it and the IDP will be auto-configured.

security.saml.security.authnrequest_signed

false
Enables/disables signing of auth requests to the IDP.

security.saml.security.want_messages_signed

false
Enables/disables signing of messages to the IDP.

security.saml.security.want_assertions_signed

false
Enables/disables the requirement for signed assertions.

security.saml.security.want_assertions_encrypted

false
Enables/disables the requirement for encrypted assertions.

security.saml.security.want_nameid_encrypted

false
Enables/disables the requirement for encrypted nameId

security.saml.security.sign_metadata

false
Enables/disables signing of SP’s metadata.

security.saml.security.want_xml_validation

true
Enables/disables XML validation by the SP.

security.saml.security.signature_algorithm

blank
Algorithm that the SP will use in the signing process.

security.saml.attributes.id

blank
Mapping key for the id attribute.

security.saml.attributes.picture

blank
Mapping key for the picture attribute.

security.saml.attributes.email

blank
Mapping key for the email attribute.

security.saml.attributes.name

blank
Mapping key for the name attribute. If this is set, the values for attributes firstname and lastname below will be ignored.

security.saml.attributes.firstname

blank
Mapping key for the firstname attribute.

security.saml.attributes.lastname

blank
Mapping key for the lastname attribute.

security.saml.domain

blank
Domain name for users who don’t have a valid email.

As a bare minimum, you should have the following SAML configuration:

# minimal setup
# IDP metadata URL, e.g. https://idphost/idp/shibboleth
security.saml.idp.metadata_url = ""

# SP endpoint, e.g. https://paraio.com/saml_auth/scoold
security.saml.sp.entityid = ""

# SP public key as Base64(x509 certificate)
security.saml.sp.x509cert = ""

# SP private key as Base64(PKCS#8 key)
security.saml.sp.privatekey = ""

# attribute mappings (usually required)
# e.g. urn:oid:0.9.2342.19200300.100.1.1
security.saml.attributes.id = ""
# e.g. urn:oid:0.9.2342.19200300.100.1.3
security.saml.attributes.email = ""
# e.g. urn:oid:2.5.4.3
security.saml.attributes.name = ""

You want to either configure security.saml.attributes.name or security.saml.attributes.firstname, but not both.

You must configure the SAML authentication filter through the app settings API:

{
    "security.saml.sp.entityid": "https://paraio.com/saml_auth/scoold",
    "security.saml.idp.metadata_url": "https://idphost/idp/shibboleth",
    "security.saml.sp.x509cert": "LS0tLS1CRUdJTiBDRVJUSUZJQ0...",
    "security.saml.sp.privatekey": "LS0tLS1CRUdJTiBQUklWQVRF...",
    ...
    "signin_success": "http://success.url",
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

You can also put all of the settings above in a configuration file, but this only works if your app is the root app (see the config).

LDAP support

Users can be authenticated through an LDAP server, including Active Directory. The implementation uses the UnboundID SDK in combination with Spring Security. The user supplies a uid and password and Para connects to the LDAP server and tries to bind that user. Then, upon successful login, a new User object is created and the user is signed in. The user’s profile data (email, name, uid) is read from the LDAP directory. It is important to note that emails are not validated and are assumed valid.

The LDAP filter responds to requests at https://paraio.com/ldap_auth. The filter takes two query parameters username and password and answers to any HTTP method. Example: GET https://paraio.com/ldap_auth?username=bob&password=secret

These are the configuration options for this filter:

propertydescription

security.ldap.server_url

URL of the LDAP server, including scheme and port, defaults to ldap://localhost:8389/.

security.ldap.base_dn

The base DN, aka domain (default is dc=springframework,dc=org).

security.ldap.bind_dn

The initial bind DN for a user with search privileges. The value of this property cannot contain whitespaces. Those will automatically be escaped with %20 (default is blank). Usually this value is left blank.

security.ldap.bind_pass

The password for a user with search privileges (default is blank). Usually this value is left blank.

security.ldap.user_search_base

Search base for user searches (default is blank).

security.ldap.user_search_filter

Search filter for user searches. This is combined with user_search_base to form the full DN path to a person in the directory. A user search is performed when the person cannot be found directly using user_dn_pattern + base_dn. (default is (cn={0})).

security.ldap.user_dn_pattern

DN pattern for finding users directly. This is combined with base_dn to form the full DN path to a person in the directory. This property can have multiple values separated by |, e.g. uid={0}|cn={0}. (default is uid={0}).

security.ldap.password_attribute

The password attribute in the directory (default is userPassword).

security.ldap.username_as_name

Use a person’s username as their name on login. (default is false).

security.ldap.active_directory_domain

The domain name for AD server. (default is blank, AD is disabled, unless ad_mode_enabled is true).

security.ldap.ad_mode_enabled

Explicitly enables support for authenticating with Active Directory. If true AD is enabled.

security.ldap.mods_group_node

Maps LDAP node like cn=Mods to Para user group mods. (default is blank).

security.ldap.admins_group_node

Maps LDAP node like cn=Admins to Para user group admins. (default is blank).

security.ldap.compare_passwords

If set to any value, will switch to password comparison strategy instead of default “bind” method. (default - not set).

Note: Active Directory support is enabled when active_directory_domain is set. For AD LDAP, the search filter defaults to (&(objectClass=user)(userPrincipalName={0})). The syntax for this allows either {0} (replaced with username@domain) or {1} (replaced with username only). For regular LDAP, only {0} is a valid placeholder and it gets replaced with the person’s username.

You must configure all LDAP properties through the app settings API:

{
    "security.ldap.server_url": "ldap://localhost:8389/",
    "security.ldap.base_dn": "dc=springframework,dc=org",
    "security.ldap.user_dn_pattern": "uid={0}"
    ...
    "signin_success": "http://success.url",
    "signin_failure": "http://failure.url"
}

OAuth 2.0 support

A generic OAuth 2.0 filter is available for situations where you need to use your own authentication server. It follows the standard OAuth 2.0 flow and requires that you redirect users to a login page first (on your server). Then, upon successful login, the user is redirected back to /oauth2_auth and an access token is obtained. Finally, the filter tries to get the user’s profile with that token, from a specified server, which could be the same server used for authentication.

The generic OAuth 2.0 filter responds to requests at https://paraio.com/oauth2_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

These are the configuration options for this filter:

propertydescription

security.oauth.profile_url

API endpoint for user profile.

security.oauth.token_url

The URL from which the access token will be requested (via POST).

security.oauth.scope

The scope parameter of the access token request payload.

security.oauth.accept_header

The Accept header - if blank, the header won’t be set. (default is blank).

security.oauth.download_avatars

If true, Para will fetch profile pictures from IDP and store them locally or to a cloud storage provider. (default is false).

security.oauth.parameters.id

The id parameter for requesting the user id (default is sub).

security.oauth.parameters.picture

The picture parameter for requesting the user’s avatar (default is picture).

security.oauth.parameters.email

The email parameter for requesting the user’s email (default is email).

security.oauth.parameters.name

The name parameter for requesting the user’s full name (default is name).

security.oauth.parameters.given_name

The given_name parameter for requesting the user’s first name (default is given_name).

security.oauth.parameters.family_name

The family_name parameter for requesting the user’s last name (default is family_name).

security.oauth.domain

This domain name is used if a valid email can’t be obtained (optional).

security.oauth.token_delegation_enabled

Enable/disable access token delegation. If enabled, access tokens will be saved inside the user object’s password field and sent for validation to the IDP on each authentication request (using JWTs). (default is false).

You can configure the URLs for authentication success and failure in the configuration file (see the config)

You must set all configuration properties through the app settings API:

{
    "oa2_app_id": "some_appid",
    "oa2_secret": "some_secret",
    "security.oauth.token_url": "https://myidp.com/oauth/token",
    "security.oauth.profile_url": "https://myidp.com/oauth/userinfo",
    "security.oauth.scope": "openid email profile",
    ...
    "signin_success": "http://success.url",
    "signin_failure": "http://failure.url"
}

You can add two additional custom OAuth 2.0/OpenID connect providers called “second” and “third”. Here’s what the settings look like for the “second” provider (“third” is identical but replace “second” with “third”):

{
    "oa2second_app_id": "some_appid",
    "oa2second_secret": "some_secret",
    "security.oauthsecond.token_url": "https://myidp.com/oauth/token",
    "security.oauthsecond.profile_url": "https://myidp.com/oauth/userinfo",
    "security.oauthsecond.scope": "openid email profile",
    ...
}

The endpoints for the “second” and “third” OAuth 2.0 providers are /oauth2second_auth and /oauth2third_auth, respectively.

Facebook support

This describes the web authentication flow with Facebook. You could also login with an existing access token from Facebook through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app in the Facebook Dev Center. Then set these through the app settings API:

{
    "fb_app_id": "..."
    "fb_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/facebook_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Facebook just redirect the user to the Facebook OAuth endpoint:

facebook.com/dialog/oauth

Pass the parameter redirect_uri=/facebook_auth?state=myapp so Para can handle the response from Facebook.

Note: You need to register a new application with Facebook in order to obtain an access and secret keys.

Below is an example Javascript code for a Facebook login button:

$("#facebookLoginBtn").click(function() {
        window.location = "https://www.facebook.com/dialog/oauth?" +
                "response_type=code&client_id={FACEBOOK_APP_ID}" +
                "&scope=email&state=" + APPID +
                "&redirect_uri=https://paraio.com/facebook_auth";
        return false;
});

GitHub support

This describes the web authentication flow with GitHub. You could also login with an existing access token from GitHub through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on GitHub. Then set these through the app settings API:

{
    "gh_app_id": "..."
    "gh_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/github_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with GitHub just redirect the user to the GitHub OAuth endpoint:

github.com/login/oauth/authorize

Pass the parameter redirect_uri=/github_auth?state=myapp so Para can handle the response from GitHub.

Note: You need to register a new application with GitHub in order to obtain an access and secret keys.

Below is an example Javascript code for a GitHub login button:

$("#githubLoginBtn").click(function() {
        window.location = "https://github.com/login/oauth/authorize?" +
                "response_type=code&client_id={GITHUB_APP_ID}" +
                "&scope=user&state=" + APPID +
                "&redirect_uri=https://paraio.com/github_auth";
        return false;
});

Google support

This describes the web authentication flow with Google. You could also login with an existing access token from Google through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app in the Google Dev Console. Then set these through the app settings API:

{
    "gp_app_id": "..."
    "gp_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request. This authentication filter responds to requests at https://paraio.com/google_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Google just redirect the user to the Google OAuth endpoint:

accounts.google.com/o/oauth2/v2/auth

Pass the parameter redirect_uri=/google_auth?state=myapp so Para can handle the response from Google.

Note: You need to register a new application with Google in order to obtain an access and secret keys.

Below is an example Javascript code for a Google login button:

$("#googleLoginBtn").click(function() {
        var baseUrl = window.location.origin;
        window.location = "https://accounts.google.com/o/oauth2/v2/auth?" +
                "client_id={GOOGLE_APP_ID}&response_type=code" +
                "&scope=openid%20email&redirect_uri=https://paraio.com/google_auth" +
                "&state=" + APPID + "&" + "openid.realm=" + baseUrl;
        return false;
});

LinkedIn support

This describes the web authentication flow with LinkedIn. You could also login with an existing access token from LinkedIn through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on LinkedIn. Then set these through the app settings API:

{
    "in_app_id": "..."
    "in_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/linkedin_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with LinkedIn just redirect the user to the LinkedIn OAuth endpoint:

www.linkedin.com/oauth/v2/authorization

Pass the parameter redirect_uri=/linkedin_auth?state=myapp so Para can handle the response from LinkedIn.

Note: You need to register a new application with LinkedIn in order to obtain an access and secret keys.

Below is an example Javascript code for a LinkedIn login button:

$("#linkedinLoginBtn").click(function() {
        window.location = "https://www.linkedin.com/oauth/v2/authorization?" +
                "response_type=code&client_id={LINKEDIN_APP_ID}" +
                "&scope=r_liteprofile%20r_emailaddress&state=" + APPID +
                "&redirect_uri=https://paraio.com/linkedin_auth";
        return false;
});

Microsoft support

This describes the web authentication flow with Microsoft. You could also login with an existing access token from Microsoft through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Microsoft. Then set these through the app settings API:

{
    "ms_app_id": "..."
    "ms_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/microsoft_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Microsoft just redirect the user to the Microsoft OAuth endpoint:

login.microsoftonline.com/common/oauth2/v2.0/authorize

Pass the parameter redirect_uri=/microsoft_auth?state=myapp so Para can handle the response from Microsoft.

Note: You need to register a new application with Microsoft in order to obtain an access and secret keys.

Below is an example Javascript code for a Microsoft login button:

$("#microsoftLoginBtn").click(function() {
        window.location = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" +
                "response_type=code&client_id={MICROSOFT_APP_ID}" +
                "&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&state=" + APPID +
                "&redirect_uri=https://paraio.com/microsoft_auth";
        return false;
});

Slack support

This describes the web authentication flow with Slack. You could also login with an existing access token from Slack through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Slack. Then set these through the app settings API:

{
    "sl_app_id": "..."
    "sl_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/slack_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Slack just redirect the user to the Slack OAuth endpoint:

slack.com/oauth/authorize

Pass the parameter redirect_uri=/slack_auth?state=myapp so Para can handle the response from Slack.

Note: You need to register a new application with Slack in order to obtain an access and secret keys.

Below is an example Javascript code for a Slack login button:

$("#slackLoginBtn").click(function() {
        window.location = "https://slack.com/oauth/authorize?" +
                "response_type=code&client_id={SLACK_APP_ID}" +
                "&scope=identity.basic%20identity.email%20identity.team%20identity.avatar&state=" + APPID +
                "&redirect_uri=https://paraio.com/slack_auth";
        return false;
});

Twitter support

This describes the web authentication flow with Twitter. You could also login with an existing access token from Twitter through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Twitter. Then set these through the app settings API:

{
    "tw_app_id": "..."
    "tw_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/twitter_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Twitter just redirect the user to the https://paraio.com/twitter_auth?state=myapp. This will redirect the user to Twitter for authentication.

Note: You need to register a new application with Twitter in order to obtain an access and secret keys. The Twitter API does not share users’ emails by default, you have to ask Twitter to whitelist your app first.

Below is an example Javascript code for a Twitter login button:

$("#twitterLoginBtn").click(function() {
        window.location = "https://paraio.com/twitter_auth?state=" + APPID;
        return false;
});

Amazon support

This describes the web authentication flow with Amazon. You could also login with an existing access token from Amazon through the API. This web flow sets a cookie, the API returns a JWT instead.

First of all you need to have your API credentials ready by creating an app on Amazon. Then set these through the app settings API:

{
    "az_app_id": "..."
    "az_secret": "..."
    "signin_success": "http://success.url"
    "signin_failure": "http://failure.url"
}

Para can return a short-lived ID token back to the client which initiated the request. Add jwt=id to your signin_success url. For example { "signin_success": "http://success.url?jwt=id" }. The id value of the parameter will be replaced with the actual JWT ID token, which you can use to call paraClient.signIn("passwordless", idToken) to get the long-lived session token.

If you want Para to generate a JWT token upon successful authentication, add the jwt=? parameter to your signin_success url. For example { "signin_success": "http://success.url?jwt=?" }. Para will redirect the user back to your host URL with the generated access token. Warning: this is less secure and not recommended because the JWT is present in the URL of a GET request.

This authentication filter responds to requests at https://paraio.com/amazon_auth.

The endpoint expects an appid value from the ‘state’ parameter, e.g. ?state={appid}. If that parameter is missing, the default (root) app will be used as authentication target.

To initiate a login with Amazon just redirect the user to the Amazon OAuth endpoint:

www.amazon.com/ap/oa

Pass the parameter redirect_uri=/amazon_auth?state=myapp so Para can handle the response from Amazon.

Note: You need to register a new application with Amazon in order to obtain an access and secret keys.

Below is an example Javascript code for a Amazon login button:

$("#amazonLoginBtn").click(function() {
        window.location = "https://www.amazon.com/ap/oa?" +
                "response_type=code&client_id=" + AMAZON_APP_ID +
                "&scope=profile&state=" + APPID +
                "&redirect_uri=" + ENDPOINT + "/amazon_auth";
        return false;
});

API methods

All API methods below require authentication by default, unless it’s written otherwise.

Limiting which fields are returned by the API

Field limiting is supported on all requests by using the query parameter select=xxx,yyy. This parameter takes a comma separated list of fields to include. For example:

GET /myobjects?select=id,name,custom_field1,custom_field2



▾▴ collapse/expand all

POST /jwt_auth Sign in (JWT, public)

Takes an identity provider access token and fetches the user data from that provider. A new User object is created if that user doesn’t exist and is then returned. Access tokens are returned upon successful authentication using one of the SDKs from Facebook, Google, Twitter, etc.

Note: Twitter uses OAuth 1 and gives you a token and a token secret so you must concatenate them first - {oauth_token}:{oauth_token_secret}, and then use that as the provider access token. Also if you use the password provider, the token parameter must be in the format {email}:{full_name}:{password} or {email}::{password} (must be :: if name is empty). For LDAP the token looks similar - {uid}:{password} (single :).

Also keep in mind that when a new user signs in with a password and unverified email, through /jwt_auth, Para will create the user but will return an error 400 indicating that the user is not active and cannot be authenticated. Once the email is verified and the user is set to active: true, subsequent sign in attempts will be successful.

Request

  • body - a JSON object containing appid, provider and token properties (required).

Request body example for authenticating with email and password:

{
    "appid": "app:myapp",
    "provider": "password",
    "token": "user@domain.com::password123"
}

Request body example for Facebook:

{
    "appid": "app:myapp",
    "provider": "facebook",
    "token": "eyJhbGciOiJIUzI1NiJ9.eWIiO..."
}

The appid is the id of your own app that you’re trying to sign in to. The provider field a string and can be one of the following values:

  • facebook - sign in with Facebook account,
  • google - sign in with Google account,
  • twitter - sign in with Twitter account,
  • github - sign in with GitHub account,
  • linkedin - sign in with LinkedIn account,
  • microsoft - sign in with Microsoft account,
  • slack - sign in with Slack account,
  • amazon - sign in with Amazon account,
  • password - sign in with email and password.
  • oauth2 - sign in with generic OAuth 2.
  • oauth2second - sign in with “second” generic OAuth 2 provider.
  • oauth2third - sign in with “third” generic OAuth 2 provider.
  • ldap - sign in with LDAP.
  • passwordless - sign in with an external authentication provider (custom SSO)

Response

Returns a JSON object containing JWT properties and a User object. The returned JWT properties are:

  • access_token - the JWT access token.

  • expires - a Java timestamp of when the token will expire.

  • refresh - a Java timestamp indicating when API clients should refresh their tokens, usually 1 hour after token has been issued.

  • status codes - 200, 400

Example response:

{
    "jwt": {
        "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJ...",
        "expires": 1450137214490,
        "refresh": 1450137216490
    },
    "user": {
        "id":"user1",
        "timestamp": 1399721289987,
        "type":"user",
        "appid":"myapp",
        ...
    }
}

GET /jwt_auth Token refresh (JWT)

Refreshes the access token if and only if the provided token is valid and not expired. Tokens should be refreshed periodically in order to keep users logged in for longer periods of time.

Request

Request should include an Authorization: Bearer {JWT_TOKEN} header containing a valid access token. (required) As an alternative you could provide the token as query parameter instead of a header.

Response

Returns a JSON object containing a new JWT token and the same User object. The returned JWT properties are:

  • access_token - the JWT access token.

  • expires - a Java timestamp of when the token will expire.

  • refresh - a Java timestamp indicating when API clients should refresh their tokens.

  • status codes - 200, 400

Example response:

{
    "jwt": {
        "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJ...",
        "expires": 1450137214490,
        "refresh": 1450137216490
    },
    "user": {
        "id":"user1",
        "timestamp": 1399721289987,
        "type":"user",
        "appid":"myapp",
        ...
    }
}

DELETE /jwt_auth Revoke all tokens (JWT)

Revokes all user tokens for the user that is currently logged in. This would be equivalent to “logout everywhere”. Note: Generating a new API secret on the server will also invalidate all client tokens.

Request

Request should include an Authorization: Bearer {JWT_TOKEN} header containing a valid access token. (required) As an alternative you could provide the token as query parameter instead of a header.

Response

Returns a JSON object containing a new JWT token and the same User object. The returned JWT properties are:

  • access_token - the JWT access token.

  • expires - a Java timestamp of when the token will expire.

  • refresh - a Java timestamp indicating when API clients should refresh their tokens.

  • status codes - 200, 400

Example response (response is empty):

200 OK

POST /v1/{type} Create objects

Creates new objects of type {type}. You can also create objects with custom types and fields (since v1.4.0).

Request

  • body - the JSON object to create
  • {type} - the plural form of the object’s type, e.g. “users”

Example request body:

{
    "type":"tag",
    "plural":"tags",
    "tag":"tag1"
}

Notice how the type field is in singular form and the plural field is the plural form of the type’s name. These are required for mapping types to URLs.

Response

  • status codes - 201, 400

Example response for a Tag:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":0
}

GET /v1/{type}/{id} Read objects

Returns objects of type {type}.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Note: If {id} is omitted then the response will be a list of all objects of the specified type.

Response

  • status codes - 200, 404

Example response for a Tag:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":0
}

PUT /v1/{type}/{id} Overwrite objects

Overwrites an object of type {type}. If the object with this {id} doesn’t exist then a new one will be created.

Request

  • body - the JSON data to merge with the stored object
  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Response

  • status codes - 200, 400, 404, 500

Example response for a Tag with updated count:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":55
}

PATCH /v1/{type}/{id} Update objects

Updates objects of type {type}. Partial objects are supported.

Vote requests: these are a special kind of PATCH request, which has a body like {"_voteup": "user123"} or {"_votedown": "user123"}. Here user123 is the id of the voter. A successful vote request either increments or decrements the votes field by 1.

Request

  • body - the JSON object to merge with the stored object OR a vote request body like {"_voteup": "user123"}
  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

A vote request body can look like this:

{
    "_voteup": "obj123",
    "_vote_locked_after": 60,
    "_vote_expires_after": 2592000
}

Response

  • status codes - 200, 400, 404, 412, 500, vote requests return true or false

If optimistic locking is enabled and the DAO implementation supports it, failed updates will result in 412 Precondition Failed.

Example response for a Tag with updated count:

{
    "id":"tag:tag1",
    "timestamp":1399721289987,
    "type":"tag",
    "appid":"para",
    "name":"tag tag:tag1",
    "votes":0,
    "plural":"tags",
    "objectURI":"/tags/tag1",
    "tag":"tag1",
    "count":55
}

DELETE /v1/{type}/{id} Delete objects

Deletes objects of type {type}.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {id} - the id

Response

  • status codes - 200, 400

No content.

POST /v1/_batch Batch create

Creates multiple objects with a single request. PUT requests to this resource are equivalent to POST.

Request

  • body - a JSON array of objects to create (required).

Maximum request size is 1 megabyte.

Response

  • status codes - 200, 400

Example response for creating 3 objects (returns a list of the created objects):

[ { "id":"id1", ... }, { "id":"id2", ... }, { "id":"id3", ... } ]

GET /v1/_batch Batch read

Returns a list of objects given their id fields.

Parameters

  • ids - a list of ids of existing objects (required).

Example: GET /v1/_batch?ids=id1&ids=id2&ids=id3

Response

  • status codes - 200, 400 (if no ids are specified)

Example response for reading 3 objects:

[ { "id":"id1", ... }, { "id":"id2", ... }, { "id":"id3", ... } ]

PATCH /v1/_batch Batch update

Updates multiple objects with a single request. Partial objects are supported. Note: These objects will not be validated as this would require us to read them first and validate them one by one.

Request

  • body - a JSON array of objects to update (required). The fields id and type are required for each object.

Maximum request size is 1 megabyte.

Response

  • status codes - 200, 400, 412

If optimistic locking is enabled and the DAO implementation supports it, failed updates will be ignored and omitted from the response array. Error 412 is returned only if all object failed to update due to version locking.

Example response for updating 3 objects (returns a list of the updated objects):

[ {
    "id":"id1",
    "type":"type1",
    "name":"newName1", ...
  }, {
    "id":"id2",
    "type":"type2",
    "name":"newName2", ...
  }, {
     "id":"id3",
     "type":"type3",
     "name":"newName3", ...
} ]

DELETE /v1/_batch Batch delete

Deletes multiple objects with a single request.

Parameters

  • ids - a list of ids of existing objects (required).

Example: DELETE /v1/_batch?ids=1&ids=2 will delete the two objects with an id of 1 and 2, respectively.

Response

  • status codes - 200, 400 (if request maximum number of ids is over the limit of ~30)

No content.

GET /v1/{type} Basic search

Searches for objects of type {type}.

Note: Requests to this path and /v1/{type}/search/{querytype} are handled identically. Also, note that custom fields must be used in search queries as properties.myfield.

For detailed syntax of the query string see Lucene’s query string syntax.

Request

  • {type} - the plural form of the object’s type, e.g. “users”
  • {querytype} - the type of query to execute (optional, see Search)

Parameters

  • q - a search query string (optional). Defaults to * (all).
  • desc - sort order - true for descending (optional). Default is true.
  • sort - the field to sort by (optional).
  • limit - the number of results to return. Default is 30.
  • page - starting page for results (optional). (note: page size is 30 items by default)

Response

  • status codes - 200, 400 if query string syntax is invalid

Example response for querying all tags GET /v1/tags?q=*&limit=3:

{
    "page":0,
    "totalHits":3,
    "items":[{
        "id":"tag:tag3",
        "timestamp":1400077389250,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag3",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag3",
        "tag":"tag3",
        "count":0
    }, {
        "id":"tag:tag1",
        "timestamp":1400077383588,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag1",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag1",
        "tag":"tag1",
        "count":0
    }, {
        "id":"tag:tag2",
        "timestamp":1400077386726,
        "type":"tag",
        "appid":"para",
        "name":"tag tag:tag2",
        "votes":0,
        "plural":"tags",
        "objectURI":"/tags/tag2",
        "tag":"tag2",
        "count":0
    }]
}

GET /v1/search/{querytype} Advanced search

Executes a search query.

Note: custom fields must be used in search queries as properties.myfield.

Request

  • {querytype} - the type of query to execute (optional), use one of types below:

The querytype parameter switches between the different query types. If this parameter is missing then the generic findQuery() method will be executed by default.

id query

Finds the object with the given id from the index or null. This executes the method findById() with these parameters:

  • id - the id to search for

ids query

Finds all objects matchings the given ids. This executes the method findByIds() with these parameters:

  • ids - a list of ids to search for

nested query

Searches through objects in a nested field named nstd. Used internally for joining search queries on linked objects. This executes the method findNestedQuery() with these parameters:

  • q - a search query string
  • field - the name of the field to target within a nested object
  • type - a type to search for

nearby query

Location-based search query. Relies on Address objects for coordinates. This executes the method findNearby() with these parameters:

  • latlng - latitude and longitude of the center of the search perimeter
  • radius - radius of the search perimeter in kilometers.

prefix query

Searches for objects containing a field (property) that starts with the given prefix. This executes the method findPrefix() with these parameters:

  • field - the field to search on
  • prefix - the prefix

similar query

“More like this” search query. This executes the method findSimilar() with these parameters:

  • fields - a list of fields, for example:
    GET /v1/search/similar?fields=field1&fields=field2
  • filterid - an id filter; excludes a particular object from the results
  • like - the source text to use for comparison. If the like parameter starts with id:, e.g. id:123, then the source of the “like” text is read from the object with id 123 and extracted from fields fields on the server. This operation can be done on the server in situations where the supplied text in like is too long and causes “URI too long” exceptions.

tagged query

Search for objects tagged with a set of tags. This executes the method findTagged() with these parameters:

  • tags - a list of tags, for example:
    GET /v1/search/tagged?tags=tag1&tags=tag2

in query

Searches for objects containing any of the terms in the given list (matched exactly). This executes the method findTermInList() with these parameters:

  • field - the field to search on
  • terms - a list of terms (values)

terms query

Searches for objects containing all of the specified terms (matched exactly) This executes the method findTerms() with these parameters:

  • matchall - if true executes an AND query, otherwise an OR query
  • terms - a list of field:term pairs, for example:
    GET /v1/search/terms?terms=field1:term1&terms=field2:term2
  • count - if present will return 0 objects but the “totalHits” field will contain the total number of results found that match the given terms.

Since v1.9, the terms query supports ranges. For example if you have a pair like 'age':25 and you want to find objects with higher age value, you can modify the key to have a relational operator 'age >':25. You can use the >, <, >=, <= operators by appending them to the keys of the terms map.


wildcard query

A wildcard query like “example*“. This executes the method findWildcard() with these parameters:

  • field - the field to search on

count query

Returns the total number of results that would be returned by a query. This executes the method getCount() and has no additional parameters.

Request parameters

  • q - a search query string (optional). Defaults to * (all).
  • type - the type of objects to search for (optional).
  • desc - sort order - true for descending (optional). Default is true.
  • sort - the field to sort by (optional).
  • page - starting page for results (optional). (note: page size is 30 items by default)
  • limit - the number of results to return

Response

  • status codes - 200, 400 if query string syntax is invalid

Example response for counting all objects (just three for this example):

{
    "page":0,
    "totalHits":3,
    "items":[
    ]
}

GET /v1/{type}/{id}/links/{type2}/{id2} Find linked objects

Call this method to search for objects that linked to the object with the given {id}.

Note: When called with the parameter ?childrenonly, the request is treated as a “one-to-many” search request. It will do asearch for child objects directly connected to their parent by the parentid field. Without ?childrenonly the request is treated as a “many-to-many” search request.

Request

  • {type} - the type of the first object, e.g. “users” (required)
  • {id} - the id of the first object (required)
  • {type2} - the type of the second object (required)
  • {id2} - the id field of the second object (optional)

Parameters

  • childrenonly - if set and {id2} is not set, will return a list of child objects (these are the objects with parentid equal to {id} above). Also if field and term parameters are set, the results are filtered by the specified field and the value of that field (term).
  • count - if set will return no items an the total number of linked objects. If childrenonly is set, this will return only the count of child objects.
  • q - query string, if set, all linked/child objects will be searched and those that match the query are returned. To search only child objects that are linked by parentid use q in combination with childrenonly, otherwise the Linker objects will be searched. Since v1.19 Linker objects contain a copy of the two objects they connect. This enables Para to execute more complex “joined” search queries. The effectiveness of these is determined by how up-to-date the data inside a Linker is.

Response

  • If the {id2} parameter is specified, the response will be a boolean text value - true if objects are linked.

  • If the {id2} parameter is missing, the response will be a list of linked objects. (pagination parameters are applicable)

  • childrenonly - if set, the response will be a list of child objects (pagination parameters are applicable)

  • status codes - 200, 400 (if type parameter is missing)

Example response if id2 is missing:

{
    "page":X,
    "totalHits":Y,
    "items":[
        ...
    ]
}

Response if id2 is specified: true or false

POST /v1/{type}/{id}/links/{id2} Link objects

This will link the object with {id} to another object with the specified id in the id parameter. The created link represents a many-to-many relationship (see also one-to-many relationships).

Don’t use this method for “one-to-many” links. Creating one-to-many links is trivial - just set the parentid of an object (child) to be equal to the id field of another object (parent).

PUT requests to this resource are equivalent to POST.

Request

  • {type} - the type of the first object, e.g. “users”
  • {id} - the id of the first object
  • {id2} - the id field of the second object (required)

Response

Returns the id of the Linker object - the linkId - which contains the types and ids of the two objects.

  • status codes - 200, 400 (if any of the parameters are missing)

Example response:

"type1:id1:type2:id2"

DELETE /v1/{type}/{id}/links/{type2}/{id2} Unlink objects

Unlinks or deletes the objects linked to the object with the specified {id}.

Request

  • {type} - the type of the first object, e.g. “users”
  • {id} - the id of the first object
  • {type2} - the type of the second object (not required, if this and {id2} are missing, it will unlink everything)
  • {id2} - the id field of the second object (optional)

Parameters

  • all - setting this will delete all linked objects (be careful!)
  • childrenonly - if set, all child objects will be deleted rather than unlinked (be careful!)

Note:

  • If both {type2} and {id2} are not set, all linked objects will be unlinked from this one.
  • If id is set - the two objects are unlinked.
  • If all and id are not set, but childrenonly is set then the child objects with type type are deleted! (these are the objects with parentid equal to {id} above)

Response

  • status codes - 200

No content.

PUT /v1/_constraints/{type}/{field}/{cname} Add constraint

Adds a new validation constraint to the list of constraints for the given field and type.

Request

  • body - the JSON payload of the constraint (see the table below)

  • {type} - the object type to which the constraint applies (required)

  • {field} - the name of the field to which the constraint applies (required)

  • {cname} - the constraint name (required), one of the following:

    NamePayload (example)

required

none

email

none

false

none

true

none

past

none

present

none

url

none

min

{ "value": 123 }

max

{ "value": 123 }

size

{ "min": 123, "max": 456 }

digits

{ "integer": 4, "fraction": 2 }

pattern

{ "value": "^[a-zA-Z]+$" }

Response

Returns a JSON object containing the validation constraints for the given type.

  • status codes - 200, 400

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

GET /v1/_constraints/{type} List constraints

Returns an object containing all validation constraints for all defined types in the current app. This information can be used to power client-side validation libraries like valdr.

Request

  • {type} - when supplied, returns only the constraints for this type (optional). If this parameter is omitted, all constraints for all types will be returned.

Response

Returns a JSON object with all validation constraints for a given type. The message field is a key that can be used to retrieve a localized message.

  • status codes - 200

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

DELETE /v1/_constraints/{type}/{field}/{cname} Remove constraint

Removes a validation constraint from the list of constraints for the given field and type.

Request

  • {type} - the object type to which the constraint applies (required)
  • {field} - the name of the field to which the constraint applies (required)
  • {cname} - the constraint name (required, see the table above)

Response

Returns a JSON object containing the validation constraints for the given type.

  • status codes - 200, 400

Example response:

{
    "User" : {
        "identifier" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "groups" : {
            "required" : {
                "message" : "messages.required"
            }
        },
        "email" : {
            "required" : {
                "message" : "messages.required"
            },
            "email" : {
                "message" : "messages.email"
            }
        }
    },
    ...
}

GET /v1/_id/{id} Read by id

Returns the object for the given {id}.

Request

  • {id} - the id

Response

Returns a JSON object.

  • status codes - 200, 404

Example response:

{
    "id" : "417283630780387328",
  "timestamp" : 1409572755025,
  "type" : "user",
    "name" : "Gordon Freeman"
    ...
}

GET /v1/_me Me object

Returns the currently authenticated User or App object. If the request is unauthenticated 401 error is returned.

Request

No parameters.

Response

Returns the JSON object for the authenticated User or App.

  • status codes - 200, 401

Example response:

{
    "id" : "417283630780387328",
  "timestamp" : 1409572755025,
  "type" : "user",
    "name" : "Gordon Freeman"
    ...
}

GET /v1/_types List types

Returns a list of all known types for this application, including core types and user-defined types. User-defined types are custom types which can be defined through the REST API and allow the users to call the standard CRUD methods on them as if they were defined as regular Para objects. See User-defined classes for more details.

Request

No parameters.

Response

Returns a list of all types that are defined for this application.

  • status codes - 200

Example response for querying all types:

[
    "addresses":"address",
    "apps":"app",
    "sysprops":"sysprop",
    "tags":"tag",
    "translations":"translation",
    "users":"user",
    "votes":"vote"
]

GET /v1/_permissions/{subjectid}/{resource}/{method} Check permission

This checks if a subject is allowed to execute a specific type of request on a resource.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Request

  • {subjectid} - the subject/user id to grant permissions to, or wildcard *. (required)
  • {resource} - the resource path or object type (URL encoded), or wildcard *. (required)
  • {method} - an HTTP method or flag, listed above. (required)

Response

Returns a boolean plain text response - true or false.

  • status codes - 200, 400, 404

Example response:

true

GET /v1/_permissions/{subjectid} List permissions

Returns a permissions objects containing all permissions. If {subjectid} is provided, the returned object contains only the permissions for that subject.

Request

  • {subjectid} - the subject/user id (optional)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "*": {
        "*": ["GET"]
    },
  "user1": [],
  "user2": {
        "posts": ["GET", "POST"]
    },
  "user3": {
    "*": ["*"]
  }
}

PUT /v1/_permissions/{subjectid}/{resource} Grant permissions

Grants a set of permissions (allowed HTTP methods) to a subject for a given resource.

There are several methods and flags which control which requests can go through. These are:

  • GET, POST, PUT, PATCH, DELETE - use these to allow a certain method explicitly
  • ? - use this to enable public (unauthenticated) access to a resource
  • - - use this to deny all access to a resource
  • * - wildcard, allow all request to go through
  • OWN - allow subject to only access objects they created

Request

  • body - a JSON array of permitted HTTP methods/flags, listed above (required).
  • {subjectid} - the subject/user id to grant permissions to, or wildcard * (required)
  • {resource} - the resource path or object type (URL encoded), or wildcard *. For example posts corresponds to /v1/posts, posts%2F123 corresponds to /v1/posts/123 (required)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "user2": {
        "posts": ["GET", "POST"]
    }
}

DELETE /v1/_permissions/{subjectid}/{resource} Revoke permissions

Revokes all permissions for a given subject and resource. If {resource} is not specified, revokes every permission that has been granted to that subject.

Request

  • {subjectid} - the subject/user id to grant permissions to (required)
  • {resource} - the resource path or object type (URL encoded). If omitted,
  • *all permissions** for that subject will be revoked. (optional)

Response

Returns a JSON object containing the resource permissions for the given user.

  • status codes - 200, 400, 404

Example response:

{
  "user1": [],
}

GET /v1/_settings List custom settings

Lists all custom app settings. These can be user-defined key-value pairs and are stored withing the app object.

Request

No parameters.

Response

Returns an map of keys and values.

  • status codes - 200

Example response:

{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

PUT /v1/_settings/{key} Add custom setting

Adds a new custom app setting or overwrites an existing one. To overwrite all app settings, make a PUT request without providing the key parameter, like so:

PUT /v1/_settings
{
    "fb_app_id": "123U3VTNifLPqnZ1W2",
    "fb_secret": "YXBwOnBhcmE11234151667",
    "signin_success": "/dashboard",
    "signin_failure": "/signin?error"
}

This will replace all app-specific settings with the JSON object in that request.

Request

  • body - a JSON object with a single value field: { "value": "setting_value" }, or an object containing all app-specific configuration properties.
  • {key} - a key from the settings map (optional). If {key} is missing, all app settings will be overwritten by the JSON in the body of the request.

Response

Returns an empty response.

  • status codes - 200

POST /v1/_newkeys Reset API keys

This will reset your API secret key by generating a new one. Make sure you save it and use it for signing future requests.

Request

No parameters.

Response

Returns the access and secret keys for this application which will be used for request signing.

  • status codes - 200

Example response:

{
    "secretKey": "U3VTNifLPqnZ1W2S3pVVuKG4HOVbimMocdDMl8T69BB001AXGZtwZw==",
    "accessKey": "YXBwOnBhcmE=",
    "info": "Save the secret key! It is showed only once!"
}

GET /v1/utils/{method} Utilities

Utility functions which can be accessed via the REST API include (listed by the method’s short name):

  • newid - calls Utils.getNewId()
  • timestamp - calls Utils.timestamp()
  • formatdate - calls Utils.formatDate(format, locale), additional parameters: format (e.g. “dd MM yyyy”), locale.
  • formatmessage - calls Utils.formatMessage(msg, params), additional parameters: message (the message to format)
  • nospaces - calls Utils.noSpaces(str, repl), additional parameters: string (the string to process)
  • nosymbols - calls Utils.stripAndTrim(str), additional parameters: string (the string to process)
  • md2html - calls Utils.markdownToHtml(md), additional parameters: md (a Markdown string)
  • timeago - calls HumanTime.approximately(delta), additional parameters: delta (time difference between now and then)

Example: GET /v1/utils?method=nospaces&string=some string with spaces

Request

  • {method} - the name of the method to call (one of the above)

Response

Returns a JSON object.

  • status codes - 200, 400 (if no method is specified)

Example response - returns the result without envelope:

"result"

PUT /v1/_import Data Import

Imports a ZIP backup archive into an app, overwriting all existing objects.

Request

Accepts only a application/zip request body which should be a previously exported ZIP backup file.

Parameters

  • filename - the name of the file which is being restored. This will be saved as a record of the executed import job.

Response

Returns an import summary object as JSON.

  • status codes - 200, 400 (if import job failed)

Example response - returns the result without envelope:

{
  "id": "1232047097503551488",
  "timestamp": 1603827503065,
  "type": "paraimport",
  "appid": "test1",
  "count": 4502,
  "name": "myapp_20201026_155306.zip"
}

GET /v1/_export Data Export

Exports a ZIP backup archive of all the data in a Para app. The archive contains multiple JSON files containing the Para objects.

Response

Returns application/zip response - the ZIP backup file.

  • status codes - 200