Exact SRXP API v1 basic principles

This document describes the basic principles of the SRXP API, and how you can get started using it. Please note our API is still in beta, and while we do not expect major changes there might be some minor breaking changes in the near future.

Authentication

Most API operations require an authenticated user. These credentials can be provided in the HTTP Authorization header, using either Basic authentication, or using an access key.

Obtaining an access key

While it is in theory possible to use basic authentication for every request, we strongly suggest only using basic auth to obtain an access key. To obtain the access key, you thus provide an SRXP e-mail and password in a Basic Authorization header. For example, using cURL:

curl -X POST -H "Content-Type: application/json" -d "{}" --user test@test.com:password https://portal.srxp.com/api/1/access_keys

Provided the e-mail and password are correct, this will return a HTTP 201 Created with a JSON response like:

{
    "access_key": "access_key_40_characters_long",
    "expires": 123467890,
    "user_id": 1,
    "id": 6
}

The access_key returned from this request can be used to authenticate consecutive requests by specifying the HTTP Authorization header:

curl -H "Authorization: SRXPAPI accesskey=access_key_40_characters_long" http://portal.srxp.com/api/1/accounts/1

URI format

Below a resource is defined as "an item with an 'id' field".

Creating resources

Generally resources are created by sending a POST request to the plural version of its resource name with the desired data. However, most resources in our system are subresources of another resource. When this is the case, and the resource cannot exist outside of its parent resource, its creation URI can be generated by appending the standard URI format to its parent resource identifier. To clarify: an expense is always a subresource of an account, and must therefore be created with a POST to /accounts/{account_id}/expenses.

Requesting resources

We try to be as REST as possible here, but with a few exceptions. Resource listings are generally available at the plural of their resource name, like /currencies. Subresources often (also) have shortcut URLs, e.g. you could use /customers/{customer_id}/categories to retrieve all categories for the customer with ID customer_id.

Single resources are only available at the plural of their resource name plus an ID. For instance, an expense with ID 32 can be accessed at the URI /expenses/32. We don't nest these resource identifiers - so this is true even for resources that are created as subresources. That means that /accounts/{account_id}/expenses/{id} does not exists.

Updating resources

A resource can be updated with a PUT or PATCH to its request URI. Note that due to the mediocre browser support for PATCH, PUT and PATCH are not differentiated by our API and both partially update a resource (so they're both PATCH!). See "Data format" for more info on how we deal with deleting fields.

Updating relations

Resource relations are both specified and updated using {resource_name}_id fields on the child resource. If the relation is optional, it might be possible to delete the relation by sending a null value (see "Creating and updating resources"). Because we do not nest resource URLs, it is always straightforward to construct the resource URI from the {resource_name}_id field; simply pluralize the relation name and append the ID to get the URI. Most resources must have a parent - e.g. a Category always belongs to a Customer. These resources are created using parent URLs (in this example POST /customers/{customer_id}/categories), which will set the parent ID automatically. It is not possible to update these parent relations.

Data format

Creating and updating resources

Resources are created and updated by PUTing or POSTing a JSON object with their properties to the appropriate URL. The details of these objects can be found in our API specification. Because we interpret both PUT and PATCH as PATCH, optional fields cannot be deleted by simply PUTing the resource without the field. We therefore employ a different mechanism to do this: if you want to delete an optional field from a resource, you can PUT or PATCH to the resource with a null value for the field. This is interpreted as removing the field from the resource, so you might get validation errors regarding the field being required if that action is not supported / allowed.

Retrieving resources

Resources are retrieved in a standard format similar to what is proposed by JSONAPI. The only difference is the way we specify our resource relations.

Single resources

Let's say we do GET /expenses/1. Assuming the resource is found, we get something like this:

{
    "expenses": [{"id": 1, ...}],
    "categories": [{"id": 1, ...}, ...],
    ...
}

The expenses top level key will contain the requested expense. Other top-level keys will contain directly related models of this expense, which are specified using {related}_id fields on the expense or its amounts.

Lists of resources

The return format of resource lists is very similar to that of single resources. Say we perform GET /currencies, we'll get something like this:

{
    "meta": {"count": 115},
    "currencies": [{id: 1, ...}, ...]
}

The only difference being the meta top level key, which includes information about how many items could be returned in total using this request. By default, resource lists are limited to 25 items at a time. You can specify a min parameter in the query string to indicate the offset from which you want to retrieve items in the total list. A limit parameter lets you specify the number of items you want to retrieve. This value has an upper limit, which is set at 200 unless specified otherwise.

Sorting

Resources can be sorted by using the sort query parameter. The syntax of this parameter is as follows:

field:direction,field2:direction,field3:direction,....

For instance

GET /accounts/99/expenses?sort=date:desc,id:asc

which sorts expenses by date descending. Different resources support different sorting fields. Specifying an unsupported sorting order results in a 400 Bad Request response.

Filtering

Most list calls support some kind of filtering. Filters are specified using a filter string in a query parameter, for instance:

GET /accounts/99/expenses?filter=filterString

A filter string can consist of field/value pairs, free searches, logical conditions or both. In its most simple form, a filter string is just a search term

term

which will try to return relevant documents corresponding to that term. Most resource support searching through specific fields or with specific conditions, with the syntax:

field:value

At the top level, the filter string is split on whitespace. If you want to include whitespace in your filter strings, you can quote the values:

field:"value with spaces"

Logical conditions are also supported. This means you can either combine multiple fields,

field:("value 1" OR "value 2")

or combine field conditions:

field1:value OR field2:value

For a lot of fields, matching is fuzzy for values. If you want a field value to be matched exactly, you can prefix it with a plus sign:

field:+"exactly this"

You can also negate an entire search term:

field:-("value 1" OR "value 2")

To conclude, a filter string which uses a combination of the above looks like:

(field_a:-("value 1" OR "value 2") OR field_b:(+"value 1" OR "value 2")) "some other value"

Which means: look for the resources which match the search string "some other value", where field_a does not contain the term "value 1" or "value 2" and field_b either equals "value 1" or contains "value 2".

Different resources use different fields / indicator terms, this is specified with the resource. Specifying a malformed filter string, or a filter with unsupported fields results in a 400 Bad Request status.

Response codes

Successful requests

Successful requests result either in a 200 Ok or 201 Created status code. Unless specified otherwise, POST requests return a 201, whereas any other request results in a 200.

Recoverable errors

If your requests contains malformed JSON you will receive a 400 Bad Request, whereas posting with a different content-type than "application/json" will result in a 415 Unsupported Media Type error. Requesting or posting to a resource that doesn't exist will return a 404 Not Found. If you request a resource that requires authorization without specifying a valid access key this will result in a 401 Unauthorized. If you have insufficient permissions to view the resource you requested this could result in either a 403 Forbidden or 404 Not Found - the latter of which can be returned instead of a 403 to prevent information leakage. When creating or updating a resource with data invalid for that resource you will get a 422 Unprocessable Entity, accompanied with information about the errors in the request body.

Rate limiting

The API call limit operates using a "leaky bucket" algorithm as a controller. This allows for infrequent bursts of calls, and allows your app to continue to make an unlimited amount of calls over time. The default bucket size is 40 calls (which cannot be exceeded at any given time), with a "leak rate" of 2 calls per second that continually empties the bucket. If your app averages 2 calls per second, it will never trip a 429 error ("Too Many Requests"). To learn more about the algorithm in general, click here.

Your API calls will be processed almost instantly if there is room in your "bucket". Unlike some integrations of the leaky bucket algorithm that aim to "smooth out" (slow down) operations, you can make quick bursts of API calls that exceed the leak rate. The bucket analogy is still a limit that we are tracking, but your processing speed for API calls is not directly limited to the leak rate of 2 calls per second.

How to avoid the 429 error

Some things to remember:

You can check how many calls you've already made using the headers sent in response to your API call:

  • X-RateLimit-Limit: The size of the bucket
  • X-RateLimit-Remaining: The number of API calls you can make before hitting the limit
  • X-Retry-After: The time in seconds before you can make another API request

Keep in mind that the number of remaining API call will increase over time. If you see you only have 10 calls remaining, and wait 10 seconds, you'll be up to 30 calls.

EN Select your country