API Documentation Overview

Introduction

The Evident Security Platform API (version 2.0) enables organizations of all sizes to proactively manage cloud security risk — minimizing attack surface and improving overall security posture.

This documentation includes examples of raw JSON responses. For SDK examples, please check out the our SDKs:

The endpoint for our API is located at: https://api.evident.io.

Our API follows the JSON API 1.0 specification.

Authentication Show/Hide

The Evident Security Platform (version 2.0) API uses the authentication method HMAC-SHA1. This is the same authentication method used by Amazon, which requires you to sign your requests with your secret key.

Headers

Date: Mon, 21 Oct 2015 04:20:01 GMT
Content-MD5: Wn+B9XU1p7jk1YmgJmDevA==
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
Authorization: APIAuth abc:fN9pbUcJVoYVcfNEZ8lFPsU3KWI= 

Add the following headers to your request:

Header Note
Date Uses the RFC1123 spec. Note: Must be in the GMT timezone. For example: Mon, 21 Oct 2015 04:20:01 GMT
Content-MD5 This is a MD5 hexdigest of the request body. Use an empty string for GET requests. See details below on how to build the md5.
Authorization Set to the public key and encoded string for the request. See details below on how to build the string. Prefix with ApiAuth.
Content-Type Only supports application/vnd.api+json
Accept Only supports application/vnd.api+json

Content-MD5 Header

On OSX replace md5sum with md5

# Generate MD5
> echo -n '{"data":{"attributes":{"name":"Testing"}}}' | md5sum
5a7f81f57535a7b8e4d589a02660debc

# Convert MD5 to hex dump and then base 64 it
> echo -n '5a7f81f57535a7b8e4d589a02660debc' | xxd -r -p | base64
Wn+B9XU1p7jk1YmgJmDevA==

# Header
Content-MD5: Wn+B9XU1p7jk1YmgJmDevA==

The Content-MD5 header should include a MD5 base64 hexdigest of the request body. If you are making a GET request you may md5 an empty string, or leave the header blank.

Generating the header requires 3 steps:

  1. Generate MD5 sum
  2. Hex dump the MD5 sum
  3. Base64 the hex dump

Be sure to base64 the raw result of the hex dump. It should not be ascii that is converted with base64.

Authorization Header

Building the authorization header takes multiple steps. You must build a canonical string of multiple headers, then take that string and encode it with HMAC-SHA1. Finally take the result of that and place it in the Authorization header.

Build Canonical String

Create a canonical string using your HTTP headers containing the HTTP method, content-type, content-MD5, request URI and the timestamp. The URI should only contain the relative path. The other values should be an exact match to the header sent in the request.

'HTTP method,content-type,content-MD5,URI,timestamp'

Encode String

Use the HMAC-SHA1 algorithm to encode the string with your secret key.

Example:

String: POST,application/vnd.api+json,Wn+B9XU1p7jk1YmgJmDevA==,/api/v2/external_accounts,Mon, 21 Oct 2015 04:20:01 GMT

Secret key: abc123

HMAC-SHA1 encoded string: fN9pbUcJVoYVcfNEZ8lFPsU3KWI=

Add Authorization Header

Add an Authorization header with the ‘APIAuth’, the public key, and the encoded canonical string. Public Key: 'abc’

                Authorization: APIAuth abc:fN9pbUcJVoYVcfNEZ8lFPsU3KWI=
            

Full Example

Assuming we have the following headers:

POST /api/v2/external_accounts
Accept: application/vnd.api+json
Content-Type: application/vnd.api+json
Date: Mon, 21 Oct 2015 04:20:01 GMT
Content-MD5: Wn+B9XU1p7jk1YmgJmDevA==

The canonical string would look like

                POST,application/vnd.api+json,Wn+B9XU1p7jk1YmgJmDevA==,/api/v2/external_accounts,Mon, 21 Oct 2015 04:20:01 GMT
            

Encode the string

> echo -n "POST,application/vnd.api+json,Wn+B9XU1p7jk1YmgJmDevA==,/api/v2/external_accounts,Mon, 21 Oct 2015 04:20:01 GMT" | openssl dgst -sha1 -binary -hmac "abc123" | base64
fN9pbUcJVoYVcfNEZ8lFPsU3KWI=

End Result

Full HTTP Request Example:
Public Key: 'abc'
Secret Key: 'abc123'

POST /api/v2/external_accounts
Authorization: APIAuth abc:fN9pbUcJVoYVcfNEZ8lFPsU3KWI=
Accept: application/vnd.api+json
Content-Type: application/vnd.api+json
Date: Mon, 21 Oct 2015 04:20:01 GMT
Content-MD5: Wn+B9XU1p7jk1YmgJmDevA==

Error Codes Show/Hide

The Evident Security Platform (version 2.0) uses the following error codes:

Code Name Meaning
400 Bad Request Something is wrong with your request.
401 Unauthorized Your API key is wrong or you do not have access to the resource requested.
404 Not Found The specified resource could not be found.
406 Not Acceptable The Accept/Content-Type headers were not set or have an unsupported value.
422 Unprocessable Entity The requested change could not be made. See errors in the response body for more details.
429 Too Many Requests The user has sent too many requests in a given amount of time (“rate limiting”).
500 Internal Server Error We had a problem with our server. Try again later.
503 Service Unavailable We’re temporarily offline for maintenance. Please try again later.

422 Errors

{
  "errors": [
    {
      "meta": {
        "name": "can't be blank"
      },
      "status": "422",
      "title": "Name can't be blank"
    }
  ]
}

Errors that are returned with a 422 status code will include a message in the body to help diagnose the error. Look for an errors array that may contain multiple objects. Each object will have a title key containing a human-readable error message.

429 Errors

{
    errors:
    [
      {
        status: 429,
        title: 'Request limit exceeded. Please try again later.
      }
    ]
  } 

Errors that are returned with a 429 status code will include a message in the body to help diagnose the error. Look for an errors array that will have a error object. The object will have a title key containing a human-readable error message. In addition to this, we will provide you with the following three headers to assist you in calculating proper retry/backoff logic.

Response Headers

Header Type Description Example
X-RateLimit-Limit String How many total transactions you are allowed to send within your timeframe. “120”
X-RateLimit-Remaining String How many transactions you have remaining. Using all of the remaining transactions prior to the rate limit resetting will cause you to receive a 429 error response. “0”
X-RateLimit-Reset String ISO 8601 timestamp of when your rate limit will be reset. “2016-12-13T18:38:00Z”

The sample responses in this documentation provide a sample errors node along with the data node, but be aware that only 1 or the other will actually be returned, not both in the same response.

Pagination Show/Hide

{
  "data": [],
  "links": {
    "first": "https://api.evident.io/api/v2/users.json?page%5Bnumber%5D=1&page%5Bsize%5D=20",
    "last": "https://api.evident.io/api/v2/users.json?page%5Bnumber%5D=70&page%5Bsize%5D=20",
    "next": "https://api.evident.io/api/v2/users.json?page%5Bnumber%5D=4&page%5Bsize%5D=20",
    "prev": "https://api.evident.io/api/v2/users.json?page%5Bnumber%5D=2&page%5Bsize%5D=20",
    "self": "https://api.evident.io/api/v2/users.json?page%5Bnumber%5D=3&page%5Bsize%5D=20"
  }
} 

All top-level endpoints will be automatically paginated. Pass page[number] to specify the page number to return, and page[size] to specify how many records to return per page. Page size defaults to 20 and can range between 1 and 100 items.

Requests to the top-level endpoints will include a links object at the root of the response. Links will include the first, previous, self, next, and last links when available.

Including Objects Show/Hide

Many objects contain the ID of a related object in their response properties. For example, an Alert will have an associated External Account ID. Those objects can be included inline with the include request parameter. Objects that can be included are noted in this documentation. Some endpoints may automatically include related objects. Those endpoints are noted as well. This parameter is available on all API requests that returns an object that has related objects and applies to the response of that request only.

You can nest include requests with the dot property. For example, requesting external_account.team on an alert will expand the external_account property into a full External Account object, and will then expand the team property on that external account into a full Team object. Deep nesting is available as well: external_account.team.sub_organization.organization

You can include multiple objects at once by identifying multiple items comma separated in the include parameter.

When including objects, the main object will be in the data node, and all included objects will be in the included node of the response. The relationships that are included will also contain data nodes with the id and type of the related object. This can be used to retreive the full object from the included node.

Below is an example of a call to the alerts endpoint requesting the response to include the tags and external_account relationships. Additionally, it's requesting the team object, related to the external account. The external account relationship includes a data node.

                
                    "external_account": {
                        "data": {
                          "id": "1",
                          "type": "external_accounts"
                        },
                        "links": {
                          "related": "https://api.evident.io/api/v2/external_accounts/1.json"
                        }
                
                

Use id and type to match the external_account object from the included node. The team relationship on the external account object in the included node also has a data node which can be used to match the team object from the included node. This alert has not tags, so the data node of the tags relationship is an empty array, and no tag object is in the include node.

            
                curl -G https://api.evident.io/api/v2/alerts/1 \
               -H "Authorization: ApiAuth abc123:abc123" \
               -H "Accept: application/vnd.api+json" \
               -d include=tags,external_account.team
            {
              "data": {
                "id": "1",
                "type": "alerts",
                "attributes": {
                  "created_at": "2015-09-23T15:04:02.000Z",
                  "status": "pass",
                  "resource": "",
                  "updated_at": "2015-09-23T15:04:02.000Z",
                  "started_at": "2015-09-23T15:04:02.000Z",
                  "ended_at": null
                },
                "relationships": {
                  "external_account": {
                    "data": {
                      "id": "1",
                      "type": "external_accounts"
                    },
                    "links": {
                      "related": "https://api.evident.io/api/v2/external_accounts/1.json"
                    }
                  },
                  "region": {
                    "links": {
                      "related": "https://api.evident.io/api/v2/regions/1.json"
                    }
                  },
                  "signature": {
                    "links": {
                      "related": "https://api.evident.io/api/v2/signatures/9.json"
                    }
                  },
                  "custom_signature": {
                    "links": {
                      "related": null
                    }
                  },
                  "suppression": {
                    "links": {
                      "related": null
                    }
                  },
                  "metadata": {
                    "links": {
                      "related": "https://api.evident.io/api/v2/alerts/1/metadata.json"
                    }
                  },
                  "cloud_trail_events": {
                    "links": {
                      "related": "https://api.evident.io/api/v2/alerts/1/cloud_trail_events.json"
                    }
                  },
                  "tags": {
                    "data": [
                    ]
                  }
                }
              },
              "included": [
                {
                  "id": "1",
                  "type": "external_accounts",
                  "attributes": {
                    "created_at": "2015-09-23T14:43:47.000Z",
                    "name": "Dev",
                    "throttle_level": "none",
                    "updated_at": "2015-09-23T14:43:47.000Z"
                  },
                  "relationships": {
                    "team": {
                      "data": {
                        "id": "1",
                        "type": "teams"
                      },
                      "links": {
                        "related": "https://api.evident.io/api/v2/teams/1.json"
                      }
                    },
                    "credentials": {
                        "links": {
                            "related": "https://api.evident.io/api/v2/external_accounts/1/amazons.json_api"
                        }
                    }
                  }
                },
                {
                  "id": "1",
                  "type": "teams",
                  "attributes": {
                    "name": "Default Team",
                    "created_at": "2015-09-23T14:37:48.000Z",
                    "updated_at": "2015-09-23T14:37:48.000Z"
                  },
                  "relationships": {
                    "sub_organization": {
                      "links": {
                        "related": "https://api.evident.io/api/v2/sub_organizations/1.json"
                      }
                    },
                    "external_accounts": {
                      "links": {
                        "related": "https://api.evident.io/api/v2/external_accounts.json?filter%5Bteam_id_eq%5D=1"
                      }
                    }
                  }
                }
              ]
            }
            

The sample responses in this documentation provide a sample included node to show an example of what the data in the node might look like. Be aware that the included node will only be in the response if the include parameter is sent with the request and related objects are actually returned.

Request Parameters Show/Hide

When making a request there are two different ways to send parameters, which depends on the type of request. With GET requests you will add parameters to the URL in query form, however with POST/PATCH requests the parameters go in the body according to the JSON API specification.

GET Requests

For GET requests, any attributes will appear in the URL for the request. Depending on the parameter, it may be part of the base url, otherwise the parameter will be added as a query parameter.

In the example below we are requesting the alerts for report 123 while also requesting the second page of alerts in batches of 50.

/api/v2/reports/123/alerts.json?page[number]=2&page[size]=50

POST/PATCH Requests

When not referencing an existing object, such as a create...

{
    "data": {
        "type": "sub_organizations",
        "attributes": {
            "name": "Demo Account"
        }
    }
} 

or, in reference to an existing object, such as an update...

{
    "data": {
        "id": 5,
        "type": "sub_organizations",
        "attributes": {
            "name": "Demo Account"
        }
    }
} 

For POST and PATCH requests the attributes go in the body of the request. The body must be JSON formatted and include a data object. The data object should include a type key referencing the object type and an attributes object which may contain the attributes necessary for the object you are creating or updating. If the request is for an existing object, the id of the object should be included as well at the same level as type.

In the JSON example we are creating a Sub Organization which only requires the parameter name.

Searching Lists Show/Hide

When returning a collection of objects from many of the list endpoints, parameters can be passed in order to filter and sort the list.

Filtering

For endpoints that allow it, parameters can be passed that will filter the results based on the search criteria specified. All search criteria must be within a filter parameter. The criteria that can be specified depends on the endpoint. Each endpoint in this documentation that allows searching, has columns in the Attributes table for that endpoint that indicates which attributes can be added to the filter parameter to filter the list.

Searching

The primary method of searching is by using what is known as predicates.

Predicates are used within Evident.io API search queries to determine what information to match. For instance, the cont predicate will check to see if an attribute called "name" contains a value using a wildcard query. So adding that to the filter parameter:

/api/v2/signatures.json?filter[name_cont]=dns

will return signatures where name LIKE '%dns%'

Conditions on Relationships

The syntax for queries on an associated relationship is to just append the association name to the attribute:

/api/v2/suppressions.json?filter[regions_code_eq]=us_east_1

will return suppressions that have a region relationship where code = 'us_east_1'

Complex Filtering

Add multiple attributes and predicates to form complex queries:

/api/v2/suppressions.json?filter[created_by_email_eq]=bob@mycompany.com&filter[regions_code_start]=us&filter[resource_not_null]=1

will return suppressions that have a region relationship where code LIKE 'us%' and created_by relationship where email = 'bob@mycompany.com' and the resource IS NOT NULL

You can also change the combinator for complex queries from the default AND to OR by adding the m: 'or' parameter

/api/v2/suppressions.json?filter[created_by_email_eq]=bob@mycompany.com&filter[m]=or&filter[regions_code_start]=us&filter[resource_not_null]=1

will return suppressions that have a region relationship where code LIKE 'us%' OR created_by relationship where email = 'bob@mycompany.com' OR resource IS NOT NULL

Using PUT

Complex queries will run into trouble because of URL length limits in most browsers. For this reason, you can optionally switch to a PUT request instead, and place the search criteria in the body placing the criteria in the filter parameter.

curl https://api.evident.io/api/v2/suppressions \
     -X PUT \
     -H "Authorization: ApiAuth abc123:abc123" \
     -H "Accept: application/vnd.api+json" \
     -d 'filter[regions_code_start]=us' \
     -d 'filter[created_by_email_eq]=bob@mycompany.com' \
     -d 'filter[resource_not_null]=1'

Bad Attributes

Please note: any attempt to use a predicate for an attribute that does not exist will return a 422 (Unprocessable Entity) response. For instance, this will not work:

/api/v2/suppressions.json?filter[bad_attribute_eq]=something

will return {"errors":[{"status":422,"title":"Invalid search term bad_attribute_eq"}]}

Also note: any attempt to use a predicate for an attribute that exists on the object, but is not in the Attributes table will silently fail and will be excluded from the search criteria.

Available Predicates

Below is a list of the available predicates and their opposites.

Predicate Opposite Example Description
eq not_eq ?filter[regions_code_eq]=us_east_1 The eq predicate returns all records where a field is exactly equal to a given value.
lt gt ?filter[created_at_lt]=2015-11-11+16:25:30 The lt predicate returns all records where a field is less than a given value.
lteq gteq ?filter[created_at_lteq]=2015-11-11+16:25:30 The lteq predicate returns all records where a field is less than or equal to a given value.
in not_in ?filter[id_in][]=1&filter[id_in][]=2 The in predicate returns all records where a field is within a specified list.
not_in in ?filter[id_not_in][]=1&filter[id_not_in][]=2 The not_in predicate returns all records where a field is not within a specified list.
present blank ?filter[identifier_present]=1 The present predicate returns all records where a field is present (not null and not a blank string).
null not_null ?filter[identifier_null]=1 The null predicate returns all records where a field is null.

Please note: The predicates below are only available on attributes that are in the Matching Searchable set in the filter description under a given operation.

Predicate Opposite Example Description
cont not_cont ?filter[name_cont]=IAM The cont predicate returns all records where a field contains a given value.
cont_any not_cont_any ?filter[name_cont_any][]=IAM&filter[name_cont_any][]=EC2 The cont_any predicate returns all records where a field contains a given value.
start not_start ?filter[name_start]=AWS The start predicate returns all records where a field begins with a given value.
end not_end ?filter[name_end]=dns The end predicate returns all records where a field ends with a given value.

Sorting

Lists can also be sorted by adding the sorts parameter with the field to sort by to the filter parameter.

/api/v2/signatures.json?filter[name_cont]=dns&filter[sorts]=risk_level+desc

will return signatures where name LIKE '%dns%' sorted by risk_level in descending order.

Lists can be sorted by multiple fields by specifying an ordered array.

/api/v2/signatures.json?filter[name_cont]=dns&filter[sorts][]=risk_level+desc&filter[sorts][]=created_at

will return signatures where name LIKE '%dns%' sorted by risk_level in descending order and then by created_at in ascending order.

Endpoints