Introduction and Documentation Standards

For this documentation we are going to use curl with the headers:

  • Accept: application/json
  • Content type: application/json

If you prefer you can create an alias in your current shell, add to your shell script profile, or set the equivalent in the software you are using, the alias is as follow:

alias curl='curl -s -H "accept: application/json" -H "content-type: application/json"'
                      

Another standard of this documentation is that whenever we share the output it is going to be formatted with the jq command line software.

A simple example of jq is:

$ echo '{"ok":true}' | jq
{
  "ok": true
}
                      

The final standard is: with the exception of the commands in this introduction section all of the other commands are not going to have any shell indicator like $, #, %, or any other.

Authentication

There are several ways to authenticate on the API, including a re-authentication one (or refresh token if you will).

Basic Auth

We recommend basic auth only in development mode or when writing simple scripts (the kind that does not do more than 5 requests). Because this is a slow form of authentication.

For example to get information about your session:

curl \
  -X GET \
  -u [email protected]:password \
  "https://api.simpleserviceorder.com/_session"
                      

The output is:

{
  "ok": true,
  "userCtx": {
    "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
    "roles": [
      "acc-1e36d77db5434f3cbded1bb07db403c1"
    ]
  }
}
                      

Bearer Token

Getting a token means you authenticate with a slow process, and after that you only use the token.

Using username and password is slow because we have algorithms in place to prevent Timing attacks

The endpoint to get a token is POST /_session.

Here you can use Basic Auth for the request:

curl \
  -X POST \
  -u [email protected]:password \
  "https://api.simpleserviceorder.com/_session"
                      

Or add the information within the JSON payload:

curl \
  -X POST \
  "https://api.simpleserviceorder.com/_session" \
  -d '{"username":"[email protected]","password":"password"}'
                      

In both cases the JSON output is:

{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
                      

The JWT token is in the header of the HTTP request, if you add the -i to the curl call it is going to output something like this:

HTTP/2 200
set-cookie: AuthSession=eyJhbGciOi; path=/; expires=Wed, 08 Feb 2023 17:59:47 GMT; HttpOnly; SameSite=Strict

{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
                      

With that token you can use to authenticate the following requests.

For example, to get your session with that token:

curl \
  -X GET \
  -H "Authorization: Bearer eyJhbGciOi" \
  "https://api.simpleserviceorder.com/_session"
                      

And the output is the same as before:

{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
                      

Reauth

This is a particular case in which you have access to the token and wants to get a new one before it expires.

We also built this feature as a transition from the API v3 which had access to the token in the web browser and using JavaScript.

In future versions we are going to deprecate this in favor of calling POST /_session or GET /_session to get a new token in the header.

curl \
  -X POST \
  "https://api.simpleserviceorder.com/_session" \
  -d '{"token":"eyJhbGciOi"}'
                      

And the output is the same as the other POST /_session: token in the header and the session JSON in the body.

The correct way to re-authenticate is to call the endpoint POST /_session with the autorization you already have, be it Basic Auth, a Bearer token in the header, or a request using the HttpOnly cookie.

Bearer example:

curl \
  -X POST \
  -H "Authorization: Bearer eyJhbGciOi" \
  "https://api.simpleserviceorder.com/_session"
                      

Basic Auth example:

curl \
  -X POST \
  -u [email protected]:password \
  "https://api.simpleserviceorder.com/_session"
                      

As a final note, if you know the user id you can replace it instead of using an email address:

curl \
  -X GET \
  -u usr-9fcc027ca99e44468227bef4c4440d5c:password \
  "https://api.simpleserviceorder.com/_session"
                      

Session

In the previous session you saw the Session output as JSON together with the token. Here is a short explanation of its elements.

The Session JSON:

{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
                      

Its attributes are:

ok
Indicates the authentication is ok and a token is expected to be in the header
name
This is the unique ID of the user
roles
roles exposes an array with ID of the Accounts the user has some kind of permission, be it read or write.

Account

An Account is basically an aggregation of data that several users have access to. Some other software may call it Group, Organization, or Team.

Account and User are the only two records that does not depend on anything to exists.

As you could see in the session output, the roles attribute exposes an array with the unique ID of each Account the user has permission.

GET /accounts/:account_id

This endpoint returns the JSON data for one Account. There is no way to request data for more than one Account at the same time.

curl \
  -X GET \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1"
                      

The output is:

{
  "_id": "acc-1e36d77db5434f3cbded1bb07db403c1"
}
                      

As you can see there is not much. That is because our API returns only the fields requested and if nothing is in the list of fields it returns the _id of the record.

To get more data you can use the fields query parameters:

curl \
  -X GET \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1?fields=_id,_rev,name"
                      

And it outputs:

{
  "_id": "acc-1e36d77db5434f3cbded1bb07db403c1",
  "_rev": "9-2b48b88130cde7bec684dc546ca39f36",
  "name": "Simple Corp."
}
                      

You can see more fields in the link to the OpenAPI in the sidebar.

PUT /accounts/:account_id

This endpoint updates the data of one Account.

curl \
  -X PUT \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1" \
  -d '{"name": "Simple Co - Edit"}'
                      

And it outputs:

{
  "error": "conflict",
  "reason": "Document update conflict."
}
                      

To avoid two users editing the same resource we created a locking mechanism similar to the one CouchDB uses. It is a simple incremental per record integer followed for a md5 sum of the record. Which means that in order to update you need to first query the record and then send the current _rev.

Assuming you have the correct _rev you can send it as a query parameter:

curl \
  -X PUT \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1?rev=9-2b48b88130cde7bec684dc546ca39f36" \
  -d '{"name": "Simple Co - Edit"}'
                      

And the output is now going to tell you the update was successful:

{
  "id": "acc-1e36d77db5434f3cbded1bb07db403c1",
  "ok": true
}
                      

If you try to send an empty body or you try to update fields that are not there you are going to get the same reply and nothing is going to change.

All of the three example below produces the same result.

Example #1, request with empty JSON body:

curl \
  -X PUT \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1?rev=11-83ae374f5748c1595e1505ce2cc3800b" \
  -d '{}'
                      

Example #2, request with no body:

curl \
  -X PUT \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1?rev=12-83ae374f5748c1595e1505ce2cc3800b"
                      

Example #3, request with a non-existent field body:

curl \
  -X PUT \
  "https://api.simpleserviceorder.com/accounts/acc-1e36d77db5434f3cbded1bb07db403c1?rev=13-83ae374f5748c1595e1505ce2cc3800b" \
  -d '{"non-existent-field": true}'
                      

CRUD operations

Each user can create up to 5 free groups, we recommend using this feature to test any API integration.

Besides that we also have one kind of record that let you perform tests in the API even in a production environment.

This kind of record lets you manage crystals, each Crystal has the following fields:

  • _id: Unique ID of the record
  • _rev: The locking code lets you perform updates. Auto generated
  • name: The name of the Crystal, because this is a testing endpoing, there is no right or wrong information to store here, but it cannot be an empty string. Text
  • age: The age of the Crystal, the number stored in this field has to be lower than 125. Integer

Create one record with POST /:account_id

When you sign up the _id of your Account and User are random, we do that to prevent Accounts and Users to reuse _ids as this can lead to security vulnerabilities.

But when you manage the records within your organization you must provide the _id yourself.

As you could see from the previous examples the _id field is a Universally unique identifier plus a 3 letter prefix for that particular kind of record.

If you don't want to use a random _id for your records but an incremental one, you can replace all characters with 0 except for the prefix and the - (dash) and place the number to the end, example:

crs-00000000000000000000000000000157
                      

We do very little validation in the _id field, so far the only requirement is for it to be hexadecimal.

Creating a record with valid data:

curl \
 -X POST \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1" \
 -d '{"_id":"crs-041fa18a2f77410d9bf4e07fa33abeeb","name":"John Doe","age":120}'
                      

Returns:

{
  "id": "crs-a9d89d7963a2ee2f1b92f73c2dfb73a0"
}
                      

Creating a record with missing mandatory field:

curl \
 -X POST \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1" \
 -d '{"name":"John Doe", "age": 120}'
                      

Returns:

{
  "error": "unprocessable_entity",
  "reason": {
    "_id": "can't be blank"
  }
}
                      

Creating a record with invalid data:

curl \
 -X POST \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1" \
 -d '{"_id": "crs-ee4371b193ac4b4191cb27624c7442c2", "name":"John Doe", "age": 210}'
                      

Returns:

{
  "error": "unprocessable_entity",
  "reason": {
    "age": "less-than-or-equal-to"
  }
}
                      

Get one record with GET /:account_id/:document_id

With the ID of the record to query you can call the API with:

curl \
 -X GET \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2"
                      

Returns:

{
  "_id": "crs-a9d89d7963a2ee2f1b92f73c2dfb73a0"
}
                      

As you remember from the /accounts endpoint if you don't tell which fields you want in your request we are going to reply only with _id.

Example with the fields query parameters:

curl \
 -X GET \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2?fields=_id,_rev,name,age,created_at,updated_at"
                      

Returns:

{
  "_id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "_rev": "1-25907e94b8cec1aa102595d415fbb36b",
  "name": "John Doe",
  "age": 120,
  "created_at": "2023-05-13T23:16:24.792Z",
  "updated_at": "2023-05-13T23:16:24.792Z"
}
                      

Update one record with PUT /:account_id/:document_id

The same rules you saw with updating using the /accounts endpoint is required here:

  • You have to provide a valid rev in the query parameters
  • Only fields informed are going to be updated the others are going to stay the same
  • If you provide no body in the request the response is ok, but nothing changes in the database
  • If you provide an empty JSON body the response is ok, but nothing changes in the database

An example of updating a Crystal:

curl \
 -X PUT \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2?rev=1-25907e94b8cec1aa102595d415fbb36b" \
 -d '{"age":20}'
                      

And the response is the same as the POST:

{
  "id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "ok": true
}
                      

Delete one record with DELETE /:account_id/:document_id

You also need the rev query parameters to delete a record.

An example request to delete a record is:

curl \
 -X DELETE \
 "https://api.simpleserviceorder.com/acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2?rev=2-f32d250e7d4ce0a07c99dd2d14095582"
                      

And the response is the same as create or update:

{
  "id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "ok": true
}
                      

If you forget or pass on a wrong rev you get the same response as before:

{
  "error": "conflict",
  "reason": "Document update conflict."
}
                      

Find multiple records with POST /:account_id/_find

TODO