{"openapi":"3.0.0","info":{"version":"3.0.0","title":"AML API V3","description":"# Introduction\n**Welcome to the Elliptic API Documentation**\n\nThis version of the API is currently only used to interrogate and manage your risk rules. For example, if you want to view all your organisation's risk rules, you can use the GET /risk_rules endpoint.\n\n## Getting Started\n1. First, request your API keys through your Customer Success Manager or via [customers@elliptic.co](mailto:customers@elliptic.co). For privacy reasons, you will be asked for a keybase username or PGP Public Key;\n2. [Authenticate with the API](#section/Authentication), using the credentials provided in step 1 as this will allow you to pass our API’s user verification;\n3. [Submit your first test request](#tag/Risk-Rules/paths/~1risk_rules/get).\n# Configuration\n### Types\n All timestamps are represented as ISO8601-formatted date strings with time zone.\n### Requests\n All HTTP requests and responses are application/json content type and typical HTTP response status codes for success and failure are used.\n All successful requests will respond with an HTTP 2xx status code and will contain a body (except in the case of a 204). The body varies by endpoint and will be described for each resource below, but will contain a JSON data object or array (for individual and multiple resources, respectively).\n All other requests will respond with an HTTP 4xx or 5xx status code. Furthermore, the body will contain an array of error messages to help with understanding the cause of failure.\n### Rate Limiting\n There is a global limit of 500 requests per minute. This applies to all requests (GET, POST, etc.).\n If the limit is reached an HTTP 429 status code will be returned.\n\n The following headers are returned by each request\n\n Header | Description\n ------ | -----------\n X-RateLimit-Limit | The limit of the current endpoint\n X-RateLimit-Remaining | The remaining rate limit\n X-RateLimit-Reset | The timestamp after which the limit will reset (unix epoch timestamp)\n\n# Authentication\n All HTTP requests to API endpoints require authentication and authorization. For code samples see our [Authenication Cookbooks](#section/Cookbooks/Authentication)\n\n Elliptic will provide an API key and secret which must be used to set HTTP headers. These headers will be used to verify and authorize all requests.\n\n The following headers should be added to all HTTP requests:\n\n Key | Value\n --- | ---\n x-access-key | \\\n x-access-sign | \\\n x-access-timestamp | \\ (in milliseconds)\n\n Examples of how to generate the signature can be found to the below. The following variables are referenced:\n\n Variable | Description\n ------- | ---------\n TIME_OF_REQUEST_IN_MS | The current time formatted as the current unix timestamp (in milliseconds)\n SIGNATURE_OF_REQUEST | A Base64 string encoded HMAC-SHA256 of REQUEST_TEXT signed with the Base64 decoded SECRET\n REQUEST_TEXT | a plain string concatenation of: TIME_OF_REQUEST_IN_MS, HTTP_METHOD (uppercase), HTTP_PATH (lowercase API path including query string), REQUEST_BODY (string encoded JSON object, or “{}” if there is no body)\n\n From now on, all code examples will assume you are attaching the authentication headers\n\n# Cookbooks\n\n## Authentication\nWe've provided Authentication cookbooks for commonly used langauges below. If your language isn't listed here, [let us know](mailto:customers@elliptic.co) and we'll add it\n\n
Javascript\n\n```javascript\nconst crypto = require('crypto');\n\n/*\n* Generate a signature for use when signing a request to the API\n*\n* - secret: your secret supplied by Elliptic - a base64 encoded string\n* - time_of_request: current time, in milliseconds, since 1 Jan 1970 00:00:00 UTC\n* - http_method: must be uppercase\n* - http_path: API endpoint including query string\n* - payload: string encoded JSON object or '{}' if there is no request body\n*/\nfunction get_signature(secret, time_of_request, http_method, http_path, payload) {\n\n // create a SHA256 HMAC using the supplied secret, decoded from base64\n const hmac = crypto.createHmac('sha256', Buffer.from(secret, 'base64'));\n\n // concatenate the request text to be signed\n const request_text = time_of_request + http_method + http_path.toLowerCase() + payload;\n\n // update the HMAC with the text to be signed\n hmac.update(request_text);\n\n // output the signature as a base64 encoded string\n return hmac.digest('base64');\n}\n\nconst SECRET = '894f142d667e8cdaca6822ac173937af'; // Supplied by Elliptic\n// Disclaimer: this secret is just an example\nconst TIME_OF_REQUEST_IN_MS = 1478692862000; // For real world use Date.now()\n\n// Example: GET with empty payload - do not run JSON.stringify with no request body, pass an empty object as string\nconsole.log(get_signature(SECRET, TIME_OF_REQUEST_IN_MS, 'GET', '/v3/risk_rules', '{}'));\n// cN9fRUqeT7UnwwpkBZaNmnwxKAPHkhytdXelfUVvxMI=\n```\n\n
\n
Python 2/3\n\n```python\nimport json, base64, hmac as crypto, hashlib\n\n#\n# Generate a signature for use when signing a request to the API\n#\n# - secret: your secret supplied by Elliptic - a base64 encoded string\n# - time_of_request: current time, in milliseconds, since 1 Jan 1970 00:00:00 UTC\n# - http_method: must be uppercase\n# - http_path: API endpoint including query string\n# - payload: string encoded JSON object or '{}' if there is no body\n#\ndef get_signature(secret, time_of_request, http_method, http_path, payload):\n\n # create a SHA256 HMAC using the supplied secret, decoded from base64\n hmac = crypto.new(base64.b64decode(secret), digestmod=hashlib.sha256)\n\n # concatenate the text to be signed\n request_text = time_of_request + http_method + http_path.lower() + payload\n\n # update the HMAC with the text to be signed\n hmac.update(request_text.encode('UTF-8'))\n\n # output the signature as a base64 encoded string\n return base64.b64encode(hmac.digest()).decode('utf-8')\n\n\n SECRET = '894f142d667e8cdaca6822ac173937af' # Supplied by Elliptic - a base64 encoded string\n # Disclaimer: this secret is just an example\n TIME_OF_REQUEST_IN_MS = '1478692862000' # for real world use str(int(round(time.time() * 1000)))\n\n # Example: GET with empty payload - do not run json.dumps with no request body, pass an empty object as string\n print(get_signature(SECRET, TIME_OF_REQUEST_IN_MS, 'GET', '/v3/risk_rules', '{}'))\n # cN9fRUqeT7UnwwpkBZaNmnwxKAPHkhytdXelfUVvxMI\n```\n\n
\n
Java\n\n```java\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport org.apache.commons.codec.binary.Base64;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\n\npublic class EllipticAuth {\n /*\n * Generate a signature for use when signing a request to the API\n *\n * - secret: your secret supplied by Elliptic - a base64 encoded string\n * - time_of_request: current time, in milliseconds, since 1 Jan 1970 00:00:00 UTC\n * - http_method: must be uppercase\n * - http_path: API endpoint including query string\n * - payload: string encoded JSON object or \"{}\" if there is no request body\n */\n public static String get_signature(String secret, String time_of_request, String http_method, String http_path, String payload) {\n\n try {\n // create a SHA256 HMAC using the supplied secret, decoded from base64\n Mac hmac = Mac.getInstance(\"HmacSHA256\");\n SecretKeySpec secret_key = new SecretKeySpec(Base64.decodeBase64(secret), \"HmacSHA256\");\n hmac.init(secret_key);\n\n // concatenate the request text to be signed\n String request_text = time_of_request + http_method + http_path.toLowerCase() + payload;\n\n // update the HMAC with the text to be signed\n hmac.update(request_text.getBytes());\n\n // output the signature as a base64 encoded string\n return Base64.encodeBase64String(hmac.doFinal());\n } catch(InvalidKeyException | NoSuchAlgorithmException e) {\n throw new RuntimeException(e);\n }\n }\n\n public static String SECRET = \"894f142d667e8cdaca6822ac173937af\"; // Supplied by Elliptic - a base64 encoded string\n // Disclaimer: this secret is just an example\n public static String TIME_OF_REQUEST_IN_MS = \"1478692862000\"; // For real world use currentTimeMillis()\n\n // Example: GET with empty payload\n System.out.println(get_signature(EllipticAuth.SECRET, EllipticAuth.TIME_OF_REQUEST_IN_MS, \"GET\", \"/v3/risk_rules\", \"{}\"));\n // cN9fRUqeT7UnwwpkBZaNmnwxKAPHkhytdXelfUVvxMI=\n return;\n }\n}\n```\n\n
\n\n
Ruby\n\n```ruby\nrequire 'base64'\nrequire 'json'\nrequire 'openssl'\n\n=begin\n Generate a signature for use when signing a request to the API\n\n - secret: your secret supplied by Elliptic - a base64 encoded string\n - time_of_request: current time, in milliseconds, since 1 Jan 1970 00:00:00 UTC\n - http_method: must be uppercase\n - http_path: API endpoint including query string\n - payload: string encoded JSON object or '{}' if there is no request body\n=end\ndef get_signature(secret, time_of_request, http_method, http_path, payload)\n # concatenate the request text to be signed\n request_text = time_of_request + http_method + http_path.downcase + payload\n\n # create a SHA256 HMAC using the supplied secret, decoded from base64, and update it with the request_text\n hmac = OpenSSL::HMAC.digest('SHA256', Base64.decode64(secret), request_text)\n\n # output the signature as a base64 encoded string\n signed = Base64.encode64(hmac).strip.encode('UTF-8')\nend\n\nSECRET = '894f142d667e8cdaca6822ac173937af' # Supplied by Elliptic\n# Disclaimer: this secret is just an example\nTIME_OF_REQUEST_IN_MS = '1478692862000' # For real world use (Time.now.to_i * 1000)\n\n# Example: GET with empty payload - do not run JSON.generate with no request body, pass an empty object as string\nputs(get_signature(SECRET, TIME_OF_REQUEST_IN_MS, 'GET', '/v3/risk_rules', '{}'))\n# cN9fRUqeT7UnwwpkBZaNmnwxKAPHkhytdXelfUVvxMI\n```\n\n
\n\n### Debugging Authentication\nThe `WWW-Authenticate` response header gives useful information to help understand what's going wrong:\n - `error_description=\"invalid signature\"`: The signature generated by your code does not match what we've generated on the API\n - `error_description=\"invalid timestamp 1605268999252\"`: The timestamp you've provided is invalid, it should be milliseconds since epoch\n - No error discription usually indicates that the key you're using is invalid\n"},"servers":[{"url":"https://aml-api.elliptic.co/v3","description":"Production"}],"tags":[{"name":"Risk Rules","description":"Manage risk rules. Currently the same risk rules are used for Navigator and Lens"},{"name":"Criteria","description":"Retrieve all criteria related to triggering risk rules"}],"paths":{"/risk_rules":{"get":{"tags":["Risk Rules"],"summary":"Get team's risk rules","description":"Get the risk rules for your team","parameters":[{"in":"query","name":"include_id_map","schema":{"type":"boolean"},"required":false,"description":"Whether or not to include an id_map in response"}],"responses":{"200":{"description":"Rule objects","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"risk_rules":{"type":"array","items":{"$ref":"#/components/schemas/RiskRule"}}}},{"type":"object","properties":{"risk_rules":{"type":"array","items":{"$ref":"#/components/schemas/RiskRule"}},"id_map":{"type":"object"}}}]}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"500":{"$ref":"#/components/responses/500"}}}},"/criteria/categories":{"get":{"tags":["Criteria"],"summary":"Get all current categories","description":"Get all current categories","responses":{"200":{"description":"Array of Category objects","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"description":"Categories not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}}},"500":{"$ref":"#/components/responses/500"}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"name":{"type":"string","example":"GenericErrorName"},"message":{"type":"string","example":"Something bad happened."}}},"BadRequestError":{"type":"object","properties":{"name":{"type":"string","example":"BadRequestError"},"message":{"type":"string","example":"Invalid id parameter"}}},"NotFoundError":{"type":"object","properties":{"name":{"type":"string","example":"NotFoundError"},"message":{"type":"string","example":"Entity not found."}}},"UnauthorizedError":{"type":"string","example":"Unauthorized"},"ForbiddenError":{"type":"object","properties":{"name":{"type":"string","example":"ForbiddenError"},"message":{"type":"string","example":"You do not have enough privilege to access this path."}}},"ServerError":{"type":"object","example":{}},"RiskRule":{"title":"RiskRule","type":"object","description":"a single risk rule","properties":{"id":{"description":"UUIDv4 identifier of the rule","type":"string","format":"UUIDv4","example":"b7535048-76f8-4f60-bdd3-9d659298f9e7"},"history_id":{"description":"UUIDv4 identifier of the first version of the rule. If the rule has never been updated, will be equal to id","type":"string","format":"UUIDv4","example":"b7535048-76f8-4f60-bdd3-9d659298f9e7"},"name":{"description":"the name of the rule","type":"string","minLength":1,"maxLength":100,"example":"illicit activities"},"version":{"description":"the version of the rule","type":"number","example":3},"is_current":{"description":"flag for whether the rule is in use or not","type":"boolean","example":true},"created_at":{"description":"the date when the rule was created","type":"string","format":"date-time","example":"2015-05-13T10:36:21.000Z"},"application_criteria":{"description":"object containing criteria for applying the given rule","type":"object","example":{"direction":{"forwards":null}}},"trigger_criteria":{"description":"object containing mongo-style statements determining how the risk rule is triggered","type":"object","example":{"$or":[{"label_id":{"$in":["a3535022-76f8-4f60-bdd3-9d659298f3b5"]}}]}},"scoring":{"description":"object containing the rule's scoring method and options (min_score, max_score, etc)","type":"object","example":{"method":"linear","options":{"property":"percentage","min_threshold":0,"max_threshold":100,"min_score":10,"max_score":10}}},"draft_rule_id":{"description":"UUIDv4 identifier of the draft rule","format":"UUIDv4","example":"b7535048-76f8-4f60-bdd3-9d659298f9e7"}}},"Category":{"title":"Category","type":"object","properties":{"id":{"description":"UUIDv4 identifier of the rule","type":"string","format":"UUIDv4","example":"b7535048-76f8-4f60-bdd3-9d659298f9e7"},"history_id":{"description":"UUIDv4 identifier of the first version of the category. If the category has never been updated, will be equal to id","type":"string","format":"UUIDv4","example":"b7535048-76f8-4f60-bdd3-9d659298f9e7"},"name":{"description":"the name of the category","type":"string","minLength":1,"maxLength":100,"example":"illicit activities"}}}},"responses":{"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BadRequestError"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}}},"403":{"description":"Not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForbiddenError"}}}},"404":{"description":"Resource item not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}}},"500":{"description":"Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"securitySchemes":{"bearer":{"description":"Bearer Authentication","type":"http","scheme":"bearer","bearerFormat":"JWT"},"apiKey":{"description":"API Key","type":"apiKey","in":"header","name":"x-access-key"},"signature":{"description":"(Request Time, HTTP Method, Lowercase Path, Request Payload) signed with API Secret","type":"apiKey","in":"header","name":"x-access-sign"},"timestamp":{"type":"apiKey","in":"header","name":"x-access-timestamp"}}},"security":[{"bearer":[]},{"apiKey":[],"signature":[],"timestamp":[]}],"x-tagGroups":[{"name":"Navigator","tags":["Risk Rules"]},{"name":"Lens","tags":["Risk Rules"]}]}