Test API V2

The following is our complete document for V2 of Tenon's Test API. A complete Postman collection is available to demonstrates all routes.git

General Flow

Tenon's V2 Test API differs significantly from its predecessor in terms of workflow:

  • V1 accepts only POST requests. The V1 API immediately triggers testing and returns the results on completion of that testing.
  • In V2, POST requests do not immediately trigger testing. Instead, V2 will add successful POST requests to a queue. Doing so allows backend processes to handle the ebb and flow of API traffic to improve system stability during periods of high demand.

As a result, client workflow using V2 should now follow the following general pattern:

  1. Submit POST request to the API.
  2. If the POST request was successful, store the responseID returned.
  3. Submit HEAD requests, looking for an HTTP 200 response code
  4. When HEAD returns 200, perform a GET to retrieve the record, using the stored responseID as the id of the record.

Waiting on results

The time necessary to wait to see results depends significantly on a handful of factors - most notable of them is the current queue size. Globally (across millions of test runs), the API takes 3-6 seconds to test a page. However, if the queue is very large, it can take a significantly longer time to test a page.

Client application code should, if possible, make use of the callback parameter in the initial POST request. If supplied, the API will submit a POST to that endpoint with the results of the testing. This approach is strongly preferred over long-polling.

Differences between V1 and V2

Note: this section only mentions the major differences. V2 is, essentially, a completely new system and we cannot catalog all of the differences that will essentially exist.

All changes listed below are discussed further in their relevant sections.

No longer proxied by PHP

  • The V1 API is proxied by PHP. This is due to a legacy situation where the Node app did not do the full range of validation that was needed to validate and process responses
  • V2 is no longer be proxied by PHP and will handle all routing, authorization, and validation on its own.

V2 is RESTful

  • V1 only accepts POST requests
  • V2 is fully RESTful and accepts GET, POST, PUT, DELETE, and HEAD requests.

Changes to Authentication

  • V1 authorizes requests based on the use of an API key within the request.
  • V2 authenticates users via OAuth. Users must authenticate themselves against the API before submitting further requests against it. The authentication process establishes a Bearer token which must accompany subsequent requests.

Changes to response structure

The response structure for all routes now follows our Tenon Unified API Response Structure:

  • status: Numeric Token. HTTP Status code representing the state of your request. Values will be 200, 400, 401, 402, 403, 404, 405, 500, or 522
  • message: String Token. Human readable text message that corresponds to the numeric HTTP status above
  • code: String Token. A token value that represents the reason for the response.
  • info: String Token.
  • moreInfo: String URL. A fully qualified URL at which you can learn more details explaining the reason for the response.
  • responseExecTime: Float. How long, in seconds, the request took to process
  • responseTime: String. Time of your request, localized to GMT
  • response: Object. An object containing the response. Content within this object depends on what type of request it was. The structure of the test results is described in (Test Results Response Structure)[#Test-Results-Response-Structure]

Example from a successful POST request.


{
    "status": 200,
    "message": "OK",
    "code": "success",
    "info": "Accepted",
    "moreInfo": "https://www.tenon.io/documentation/apiv2/response-codes#success",
    "responseExecTime": 0.255717702,
    "responseTime": "2020-09-15T13:36:35.735Z",
    "data": {
        "responseID": "d5b956a7-e28a-41cf-817c-a0200842a9d0"
    }
}

Changes to POST request

  • V1 requests required the request to be sent as form-data or x-www-form-urlencoded.
  • V2 requests are all made as raw JSON

Addition of context to testing

  • V1 tests whole documents when given a url.
  • V2 allows customers to pass in a special parameter that identifies a specific part of the page as a selector that can be passed to context. When context is provided, the API will limit testing to that part of the page.

Modifications to delay and waitFor

V1 of the API previously had a confusingly named parameter, waitFor that operated like a delay. It was renamed to delay, because the only part we had implemented was a timed delay. However there have been customers who asked for a feature similar to Puppeteer’s waitFor, in that it waits for a specific event to fire.

Puppeteer’s waitFor is more robust and is well documented. We’ll pass-through the user’s settings directly to Puppeteer.

Addition of ignore

Only valid in POST requests, ignore accepts a comma-delimited list of issue signatures that should be ignored during testing.

Addition of metadata

V2 allows customers to pass in a metadata object to both POST and PUT requests. This object can then be used by their consuming applications for any other arbitrary purpose and allows them to customize information relating to the test run.

Addition of headers

V2 allows customers to pass an object containing key:value pairs that will then be used by the API during testing. This allows customers greater control over testing, because it will allow the API to send over HTTP Headers as part of its request that can facilitate things like Content Negotiation or Authentication

Addition of cookies

V2 allows customers to pass an array of objects containing cookies that should be set during the request. Because this information will be part of the request(s) to the API, it can also be persisted across multiple requests the API makes even if multiple Puppeteer instances are used in the process

Not tested immediately

  • In V1, POST requests cause the request to be processed immediately for testing.
  • V2 adds the request into a test queue instead of testing right away. This will allow customers to submit potentially thousands of requests at once without fear of overloading the system.

Addition of other services

The initial release will integrate with two companion services:

  • Source Saver
  • Tenon Screenshot

Successful POST requests to V2 trigger requests for the page to be downloaded via our ‘Source Saver’ service (which saves a copy of the page being tested) and a request for a screenshot of the page to be taken via ‘Tenon Screenshot’. These will be made available via the Tenon.io web UI.

Routes

The following is a full list of all routes supported by V2, their supported parameters, validation, and status responses.

The endpoint for all V2 requests is https://{{domain}}/api/v2/ where domain is the domain of the Tenon instance. For our SaaS customers, the domain is tenon.io.

Auth (POST: /api/v2/auth)

All requests to the API must come from an authenticated user. This is done by sending a POST request to the authentication endpoint with a username and password and retrieving a token that must be used on future requests.

Example Request


{
    "username" : "me@example.com",
    "password" : "xyzpdq123@!"
}
Status Codes Returned
HTTP Status Code Explanation
200 The request was valid and the operation was successful
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
500 There was a problem on our end and the request could not be processed
Processing

Upon successful request, a JSON response is returned. Of the returned data, the two most important values are access_token and expires_in.

  • access_token: This value must be passed through on subsequent requests to the API. It must be passed as the Authorization request header in the form Bearer {{access_token}}
  • expires_in: Represents the total time, in seconds, that the token will remain valid. We advise that your application code should be aware of this time and, if needed, be set to reauthenticate when the token expires.
Sample Response

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlljM0IzRDBoeTF0bzNZaVA5ZWpSQyJ9.eyJpc3MiOiJodHRwczovL2RlbW8tdGVub25pby5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8MSIsImF1ZCI6Imh0dHBzOi8vZGVtby10ZW5vbmlvLmF1dGgwLmNvbS9hcGkvdjIvIiwiaWF0IjoxNTk4MzYzMzEwLCJleHAiOjE1OTg0NDk3MTAsImF6cCI6IjJwSzNaYUpqOEpKWU1YdXVDQ0ZvbHRUcmE3TmY2YThaIiwic2NvcGUiOiJyZWFkOmN1cnJlbnRfdXNlciB1cGRhdGU6Y3VycmVudF91c2VyX21ldGFkYXRhIGRlbGV0ZTpjdXJyZW50X3VzZXJfbWV0YWRhdGEgY3JlYXRlOmN1cnJlbnRfdXNlcl9tZXRhZGF0YSBjcmVhdGU6Y3VycmVudF91c2VyX2RldmljZV9jcmVkZW50aWFscyBkZWxldGU6Y3VycmVudF91c2VyX2RldmljZV9jcmVkZW50aWFscyB1cGRhdGU6Y3VycmVudF91c2VyX2lkZW50aXRpZXMiLCJndHkiOiJwYXNzd29yZCJ9.DkWJFxSvH164-nCBaozMJiV-OmjbChCwbT84OBrazvHK4EBPc6LQaxp3TGYOwxLT3VZ4TGK7gYbx2eGv1ji7dEWmXiwplhYVnv24flKn2jvOAyqdMcwC9TR76urbMX9Tz0RtdVMUqQ0XjxfHX26MeHYJnwPxKqaMj2BwE6b887nbx14yKMBn7QAqMzs2VihL8mdL9i9wbeGL1Rq7Hv7r4UbnU5R9dxWzaYLIC1Ov2DXttWhioSEG47bheCPHzlwesmiOnjcXMOARYB8by7gfqi3I_cIfkJaIbQcBoMttecvvVDv708F3-2YN9GrN6kakzA3NhnTlSPcMSUyNNaR",
    "scope": "read:current_user update:current_user_metadata delete:current_user_metadata create:current_user_metadata create:current_user_device_credentials delete:current_user_device_credentials update:current_user_identities",
    "expires_in": 86400,
    "token_type": "Bearer"
}

GET

GET requests retrieve results from the API.

Get All (GET: /api/v2/)

GET requests without an id parameter will return an array of objects representing all requests made against the Test API

Status Codes Returned
HTTP Status Code Explanation
200 The request was valid and the operation was successful
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
500 There was a problem on our end and the request could not be processed
Processing

Upon passing validation, all records retrieved will be returned in the data object of the response

Optional Parameters
Name Type Description
limit numeric Represents the maximum number of items to be returned in the result set. If provided, value must be between 0 and '1000'. The default value is 1000. If invalid, return status:400 and code:""
offset numeric Represents the index number from which the limit will begin. If provided, value must be lower than the total number of records available for the user. If invalid, return status:400 and code:""
projectID string ID of the project from which to fetch the results. If unspecified, the API will return results from all projects. If invalid, return status:400 and code:""
startDate string The first date from which to return results. Required if endDate is supplied. If invalid (or if endDate not supplied), return status:400 and code:""
endDate string The last date from which to return results. Required if startDate is supplied. Must be a date after startDate. If invalid (or if startDate not supplied), return status:400 and code:""
Understanding limit and offset

limit and offset function the same here as they do in MySQL and PostgreSQL.

More information on limit and offset

Get By ID (GET: /api/v2/:id)

When an ID parameter is supplied in the request, the API returns one single result: the response that corresponds to the supplied id

Validation
  • id must exist in the request. If not, the API will return status:400 and code:"required_param_missing"
  • id must match the responseID of a record that is owned by the user is making the request. If not, the API will return status:404 and code:"not_found"
    • Note: we return 404 here as a security measure. Even if the id is otherwise valid, if it isn’t owned by the user making the request, it essentially does not exist.
Status Codes Returned
HTTP Status Code Explanation
200 The request was valid and the operation was successful
202 The request was valid but the test run has not yet been completed (the item is still in queue)
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
404 The id provided was not valid or was not owned by the user
500 There was a problem on our end and the request could not be processed
Processing

Upon passing validation, the retrieved record will be returned in the response object of the response

HEAD (HEAD: /api/v2/:id)

A HEAD request allows the end user to determine whether a resource exists before attempting to perform a GET request to retrieve the resource.

Validation

  • id must exist in the request. If not, the API will return status:400 and code:"required_param_missing"
  • id must match the responseID of a record that is owned by the user who is making the request. If not, the API will return If not, return status:404 and code:"not_found"
    • Note: we return 404 here as a security measure. Even if the id is otherwise valid, if it isn’t owned by the user making the request, it essentially does not exist

Status Codes Returned

HTTP Status Code Explanation
200 The request was valid and the operation was successful
202 The request was valid but the test run has not yet been completed (the request is still in queue)
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
404 The id provided was not valid or was not owned by the user
500 There was a problem on our end and the request could not be processed

Response

Because this is a HEAD request, no content will be sent with the response.

POST (POST: /api/v2/)

POST Request Structure

In V2, POST requests differ in one important characteristic than V1: in V1, POST requests must be made as form-data or as x-www-form-urlencoded. This is no longer the case for V2 which uses a JSON body.

A successful POST request will queue testing of the URL or supplied document sourcecode.

HTTP Status Code Explanation
202 The POST request was accepted for processing
400 One (or more) of the parameters supplied was invalid
401 The user is not authenticated
402 The user does not have enough credits available to do testing
415 The user has attempted to test a URL that is not a web page
429 The user has been making too many requests in too short of a period of time
500 There was a problem on our end and the request could not be processed

Note: in cases where the API returns 429, the API will also return a retry-after header with a numeric value indicating the number of seconds to wait before retrying the requests. The retry-after has a default value of 30

Required Parameters

Parameter Type Description
url or src String Only one is required. url must be a full-qualified URL to a publicly available URL to be tested. src must be a payload containing only HTML/ CSS/ JS.

Optional Parameters

Additional parameters can be passed in the POST body to add specificity to the testing or to override the settings that have been stored in the corresponding project.

Parameter Type Description
src String Required if url not provided. It must be a payload of HTML/ CSS/ JS source to be tested
pageTitle String Title of the page. If not provided, the API will retrieve it during processing.
context String String representing a DOM node that can be used in document.querySelector() to select a specific portion of a page to be tested
level String WCAG Level to be used for testing. Must be one of A, AA, or AAA. If not supplied, will default to the value stored in the corresponding project.
certainty Numeric Filter. Minimum certainty score for issues in the resultset. Valid values must be one of 0,20,40,60,80,100. If not supplied, will default to the value stored in the corresponding project.
priority Numeric Filter. Valid values must be one of 0,20,40,60,80,100. If not supplied, will default to the value stored in the corresponding project.
docID String User-supplied string allowing the user to provide a meaningful ID for the document being tested.
projectID String If not supplied, will default to the user’s default project.
viewportHeight Numeric If not supplied, will default to the value stored in the corresponding project.
viewportWidth Numeric If not supplied, will default to the value stored in the corresponding project.
store Boolean If not supplied, will default to the value stored in the corresponding project.
appID String User-supplied string documenting the application from which the request was made.
waitFor String If not supplied, will default to the value stored in the corresponding project.
ignore String A comma-delimited list of issue signatures that must be ignored during testing
metadata Array/ Object Object conveying custom key:value pairs that the customer can use to add additional information to their records. If not supplied, will default to the value stored in the corresponding project.
headers Array / Object Array of objects conveying custom key:value pairs that the customer can use for their own consumption. If not supplied, will default to the value stored in the corresponding project.
cookies Array/ Object Array of objects conveying the necessary cookie details that the customer can use to pre-set cookies for use in testing. Each object must contain the following keys: name, value, host, path, expires, secure, and HttpOnly, and must conform to RFC 6265
callback Object The callback object allows you to define a URL, method, and headers that we will use to send you results of your POST requests.
  1. The callback will be run twice: when the initial POST is made to the API and when the accessibility testing is complete. (Though not documented, this is happening now)
  2. callbackUrl is going away and will be replaced with callback
  3. callback must be an object. For example:

    "callback": {
        "url": "http://www.foo.com",
        "method": "POST",
        "headers": {
            "X-My-Header": "Value here"
        }
    }
    

  4. callback, if supplied, must be an object. If it isn’t an object, the API will return a 400 response
  5. callback, if supplied, must contain url. All other properties
    are optional
  6. If callback.method is not supplied, it will default to POST
  7. callback.method will support POST, PUT, PATCH, and DELETE methods.
  8. If Content-Type header is not supplied, we will default it to application/json

POST Request Validation

User must be authenticated
  • If the user is not authenticated, the API returns status:401 and code:"api_credentials_invalid"
  • The account owner must have > 0 API calls available
    • This is the only route that requires the user to have API calls available.
    • If the user does not have API calls available, the API will return status:402 and code:"payment_required"
url: Required if src not supplied
  • If supplied, it must be a URL that returns HTTP status 200.
  • If the URL does not return HTTP status 200, the API will return status:400 and code:"url_request_failed" and log the URL’s status code in the response. This also includes 3xx responses. The user must be responsible for supplying the final URL as the API will not follow redirects.
src: Required if url not supplied
  • Must not be supplied if url supplied.
    • Supplying both url and src will result in the API returning status:400 and code:"invalid_param"
  • Must be markup.
    • If not, the API will return status:400 and code:"improper_content_type"
context
  • If supplied, must be a valid selector that can be passed to document.querySelector()
    • If document.querySelector(context) returns null during testing, the API will return status:400 and code:"context_not_found"
pageTitle
  • If supplied, it must be a string greater than 0 characters
    • If empty or if typeOf not a string, the API will return status:400 and code:"invalid_param"
  • The API will accept any pageTitle length, but will only store the first 255 characters
  • If not supplied, the API will attempt to retrieve it from the DOM during testing.
level
  • If supplied, must be one of A, AA, or AAA.
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
certainty
  • If supplied, must be one of 0,20,40,60,80,100
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
priority
  • If supplied, must be one of 0,20,40,60,80,100
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
docID
  • If supplied, must be a string with no whitespace characters
  • The only non-alphanumeric characters allowed are - and _
  • String length must be greater than 0 characters and less than 255 characters.
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
projectID
  • If supplied, must be a UUID
  • If supplied, must be a projectID for a project owned by the user making the request.
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
viewPortHeight
  • If supplied, must be numeric
  • No additional validation necessary
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
viewPortWidth
  • If supplied, must be numeric
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
store
  • If supplied, must be boolean
  • If typeOf store is not boolean, the API will return status:400 and code:"invalid_param"
appID
  • Must be a string greater than 0 characters and less than 255 characters in length.
    • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
waitFor
  • Must be a string greater than 0 characters and less than 255 characters in length.
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
metadata
  • Must be an object
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
headers
  • Must be an object
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
ignore
  • Must be a comma-delimited list of alphanumerics (md5s)
  • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
callback
  • NOTE: callback url is not validated in any way

Processing

Upon Request
  1. The API validates the request according to the rules described above.
  2. The API generates a UUID to represent the responseID
  3. If src was supplied, it will:
    1. Automagically detect if the src is a fragment
    2. Save the src to file. That file will be accessible to the Test API via a fully qualified URL. This will, in turn, become the final url that is tested.
  4. The API validates that the user has enough API calls available (NOTE: POST requests are the only requests where the Test API validates that the user has API calls available)
  5. The API validates the URL’s MIME type
Upon Passing Validation
  1. The API sends a request to tenon-screenshot to take a screenshot of the page being tested
  2. The API sends a request to tenon-sourcesaver to save the page source and all assets for storage
  3. The API forwards the request to tenon-queue for processing.
Upon entering the request into the queue.

The API responds to the request with a responseID with which the user can retrieve results via GET, PUT, and HEAD request types.

PUT (PUT: /api/v2/:id)

PUT requests are severely limited in what they can do in Tenon’s Test API. POST requests initiate testing of a URL with a specific set of configuration parameters and are considered a snapshot of the URL at a specific point in time under the specific request parameters. Therefore, the PUT request can only modify:

  • docID
  • pageTitle
  • projectID
  • metadata

Validation

  • Validation for those parameters is identical to their validation when they’re included in POST requests
  • Given that PUT requests are meant to update a specific record, the request must also contain an id parameter.
  • id must exist in the request. If not, the API will return status:400 and code:"required_param_missing"
  • id must match the responseID of a record that is owned by the user who has made the request. If not, the API will return status:404 and code:not_found
  • If supplied, the projectID parameter must be a projectID for a project owned by the owner of the supplied key
    • If parameter out of bounds, the API will return status:400 and code:"invalid_param"
Status Codes Returned
HTTP Status Code Explanation
200 The request was valid and the operation was successful
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
404 The id provided was not valid or was not owned by the user
500 There was a problem on our end and the request could not be processed

Processing

Once validation has passed, the corresponding record is updated using the new information and the API responds with the full record in the data object as if this was a GET by ID request.

DELETE (DELETE: /api/v2/)

DELETE requests are made by users who wish to remove a record. Records can only be deleted one at a time.

DELETE Validation

  • id must exist in the request. If not, the API will return status:400 and code:"required_param_missing"
  • id must match the responseID of a record that is owned by the user who has made the request. If not, the API will return status:404 and code:not_found
    • Note: we return 404 here as a security measure. Even if the id is otherwise valid, if it isn’t owned by the user making the request, it essentially does not exist
Status Codes Returned
HTTP Status Code Explanation
200 The request was valid and the operation was successful
400 One (or more) of the parameters supplied was missing or invalid
401 The user is not authenticated
404 The id provided was not valid or was not owned by the user
500 There was a problem on our end and the request could not be processed

Processing

Once validation has passed, processing the request will cause the record and all other associated information relating to the record will be deleted.

Test Results Response Structure

The results from accessibility testing can be retrieved only through a GET by id request. As described in Changes to response structure, the data object will contain the relevant data for the response. In the case of a GET by id the data object will contain the following information.

(Explanation follows the code example)


{
    "apiErrors": [],
    "documentSize": 113805,
    "globalStats": {
        "errorDensity": "145",
        "warningDensity": "11",
        "allDensity": "156",
        "stdDev": "375"
    },
    "resultSet": [
        {
            "bpID": 106,
            "certainty": 100,
            "errorDescription": "Link found which contains no text. Because Accessibility APIs use a link's text do determine its accessible name, this link will have no name and will not be announced by assistive technologies.",
            "errorSnippet": "",
            "errorTitle": "Blank link text found",
            "position": {
                "column": 71,
                "line": 24
            },
            "priority": 100,
            "ref": "https://tenon.io/bestpractice.php?bpID=106&tID=57",
            "resultTitle": "Ensure link text (and alternate text for images, when used as links) describes the destination or purpose of the link.",
            "signature": "18c1e927a023f074aa9dce6df3092e51",
            "standards": [
                "Web Content Accessibility Guidelines (WCAG) 2.0, Level A: 2.4.4 Link Purpose (In Context)",
                "Web Content Accessibility Guidelines (WCAG) 2.0, Level AAA: 2.4.9 Link Purpose (Link Only)"
            ],
            "tID": 57,
            "xpath": "/html/body/div[1]/div[2]/div[1]/div[2]/div[1]/div[2]/div[2]/div[1]/a[1]"
        },
    ],
    "resultSummary": {
        "density": {
            "allDensity": 5,
            "errorDensity": 5,
            "warningDensity": 0
        },
        "issues": {
            "totalErrors": 6,
            "totalIssues": 6,
            "totalWarnings": 0
        },
        "issuesByLevel": {
            "A": {
                "count": 5,
                "pct": 83.33
            },
            "AA": {
                "count": 1,
                "pct": 16.67
            },
            "AAA": {
                "count": 1,
                "pct": 16.67
            }
        },
        "tests": {
            "failing": 6,
            "passing": 50,
            "total": 56
        }
    },
    "sourceHash": "605ce048930a2c07bbac7255b8fe5de8",
    "urlHttpCode": 200,
    "clientScriptErrors": [],
    "log": []
}
  • apiErrors: This array will list out any errors on the Tenon API itself. We hope that this array is always empty. However, if you do see anything here, we’d appreciate it if you’d pass on the following additional information in this section. Each item in the list will include:
    • line
    • message
    • sourceId
    • tID
  • documentSize: size, in bytes of the document you gave us to test
  • globalStats: The global stats object exists to allow you to compare your document against others that have been tested. Global Stats are a calculation of all tested documents. These stats relate only to “density” - that is, per KB of code, what percentage contain accessibility issues.
    • allDensity: Density, as a percentage, of both errors and warnings
    • errorDensity: Density, as a percentage, of errors only
    • warningDensity: Density, as a percentage, of warnings only
    • stdDev: Standard Deviation
  • resultSet: An array of issue objects. Issue objects are described at https://tenon.io/documentation/understanding-issue-reports.php
  • resultSummary: The resultSummary object provides a high level overview of our test results.
    • density: As mentioned above in globalStats the density is a calculation of the issue density in your tested document
      • allDensity
      • errorDensity
      • warningDensity
    • issues: This is a count of the issues in your document
      • totalErrors
      • totalIssues
      • totalWarnings
    • issuesByLevel: This is a count of the issues in your document, separated out by WCAG Level. Each level has a count and a pct
      • tests: this is a count of how many tests Tenon API ran (total), how many passed, how many failed, and how many total tests were run
  • sourceHash: an MD5 hash of the source for the page that was tested
  • urlHttpCode: The HTTP status code returned by the URL we were given to test
  • clientScriptErrors: an array that includes any errors on the tested page that may impact Tenon’s ability to test the page
    • message: The exception message
    • stacktrace: This is a full stack trace of the error
      • file: the file where the error exists
      • line: the line where the error exists
      • function: the function where the error exists
  • log: An array of messages piped to the log that may assist in debugging any errors that occurred during testing