Data Read API Documentation

This page is about writing software to retrieve unit and inspection test data and metadata from Instrumental. It assumes that you have access to an Instrumental project with data in it.

Table of Contents

API Keys

To retrieve data, you will need an API Key with “read” permissions. You can read about API Keys and how to obtain them on the API Keys page.

Data Read API

Domain/endpoint

Make an HTTP POST request to https://api.instrumental.ai/api/v0/data.

Please work with your networking teams to unblock TCP on port 443 (HTTPS access) for this domain on whatever network your script will run. The domain does not have a fixed IP address or IP range, so the domain itself must be allowed. If necessary, Instrumental can discuss setting up a proxy with a fixed IP.

Requests must have the following headers:

instrumental-api-key: YOUR_API_KEY
content-type: application/json

Of course, replace YOUR_API_KEY with your actual, full key (it should contain INST:V1 – don’t use only the identifier displayed in the API Keys modal).

Default limits

Clients have a rate limit of 50 ingest requests refilled at a rate of 5 per second, shared across all read API endpoints. This allows some burst capacity if you need to send more requests for a short time. In some circumstances rate limits may be applied per factory site. If the rate limit is exceeded, requests will be rejected with a 429 Too Many Requests error until the rate limit bucket refills enough for new requests to get through.

Requests may be up to 4MB. Larger requests will be rejected with a 413 Too Large error. This is unlikely to happen in normal use, but could happen if you send filters with a very large number of values.

If you expect to exceed these limits, please discuss with your Instrumental representative.

Request

The endpoint accepts a JSON body with this structure. All fields are optional (but if you omit all fields you must still send an empty JSON object {}):

{
    "format": "UNIT_JSON",
    "paging": {
        "count": 100,
        "offsetPageId": "0123456789abcdef"
    },
    "filters": {
        "before": "2022-05-25T00:26:02.901Z",
        "after": "2022-04-25T00:26:02.901Z",
        "serialNumbers": ["SN123"],
        "stationNames": ["Finished Good"],
        "fixtureNames": ["DFX-INST-0101"],
        "lineNames": ["Line 1"]
    }
}

format: UNIT_JSON or UNIT_CSV or INSPECTION_CSV
Indicates how the data will be formatted in the API response (see the “Response” section below). The JSON form (which is the default) is helpful if you want to do something with the response in code. The CSV formats are helpful if you want to make CSV files available for humans to manipulate or sometimes if you want to upload the data to another system that works with flat/tabular data.

paging.count: positive integer
Approximately how many records to return in the response (defaults to 100, max 1000). For the UNIT_JSON and UNIT_CSV formats, we will return this number of units unless there are fewer units available on the requested page. For the INSPECTION_CSV format, we add inspections to the response one unit at a time, and stop when the number of inspections is greater than or equal to the specified count.

paging.offsetPageId: string
Each request will have a nextPageId value as part of the response, unless there are no more pages to return (see the “Response” section below). Pass that value into this parameter for the next request to retrieve the next page of results. Ensure that your filters do not change between requests for subsequent pages.

filters
Each filter applies to the entity specified in the format. For the UNIT_JSON and UNIT_CSV formats, we will return all data for each unit that matches the filters, even if some of the unit’s data does not match the filters. For example, given a unit with two inspections, one on 2022-05-22 and one on 2022-05-24, the request { "format": "UNIT_CSV", "filters": { "before": "2022-05-23" } } will result in a unit record that includes both inspections. For the INSPECTION_CSV format, we will return just the inspections that match the filters (in this example, only the one from 2022-05-22).

filters.before: ISO 8601 datetime string
Only records with data from before this date (exclusive) will be returned. The date must be in ISO 8601 format, i.e.: “YYYY-MM-DDTHH:mm:ss.SSSZ”.

filters.after: ISO 8601 datetime string
Only records with data from after this date (exclusive) will be returned. The date must be in ISO 8601 format, i.e.: “YYYY-MM-DDTHH:mm:ss.SSSZ”.

filters.serialNumbers: array[string]
Only records from units that match these serial numbers will be returned. The serial numbers must match the identifiers Instrumental stores; this filter does not consider serial numbers of related assemblies. If you are not sure which component’s serial number Instrumental uses to represent the final assembly, log into the web app or consult with your Instrumental representative.

filters.stationNames: array[string]
Only records with data from these station names will be returned.

filters.fixtureNames: array[string]
Only records with data from these fixture names will be returned.

filters.lineNames: array[string]
Only records with data from these lines will be returned.

Example request

Here is a basic request via cURL:

curl -v -XPOST --data '{"format": "UNIT_CSV"}' -H 'instrumental-api-key: YOUR_API_KEY' -H 'content-type: application/json' https://api.instrumental.ai/api/v0/data

Here is the same request in Python using the requests library:

import requests
import json

url = "https://api.instrumental.ai/api/v0/data"
json_data = json.dumps({ "format": "UNIT_CSV" })
headers = {
    "content-type": "application/json",
    "instrumental-api-key": "YOUR_API_KEY",
}

response = requests.post(url, data=json_data, headers=headers)

print(response.text)
print(response.status_code)

(Make sure to replace YOUR_API_KEY with your actual API key if you want to test this.)

Response

If the request is successful, the response will have an HTTP status code of 200. Failed requests will receive one of the following HTTP status codes:

  • 400 – Failure to parse the input body, or invalid values (e.g. a negative value for paging.count)
  • 401 – Invalid API key in the header
  • 413 – Request is too large; see the Default limits section
  • 429 – Rate limits exceeded; see the Default limits section
  • 500 – Server error

The response body for a successful request (status code 200) depends on the format specified in the request.

For UNIT_CSV and INSPECTION_CSV formats, the response will have the text/csv content type (you could save it directly to a file and open it in Excel). All the same data will be present as in the JSON format (unit metadata, timestamps, test results, etc.), but presented in a flat table. The value you need to pass to offsetPageId to get the next page of results is returned in the Instrumental-Next-Page-Id header.

The UNIT_JSON format produces a response like this:

{
    "units": [
        {
            "id": "0123456789abcdef",
            "serialNumber": "SN123",
            "buildName": "PVT",
            "configName": "SKU1",
            "subassemblies": [
                {
                    "serialNumber": "PCB456",
                    "relationshipName": "PCB",
                    "subassemblySerialNumbers": ["BATT789"]
                },
                {
                    "serialNumber": "BATT789",
                    "relationshipName": "Battery"
                }
            ],
            "lineName": "Line 1",
            "url": "https://app.instrumental.ai/explore/inspect?project=your-project&serial=SN123",
            "data": [
                {
                    "id": "123456789abcdef0",
                    "inspectionId": "23456789abcdef01",
                    "name": "Battery test",
                    "value": {
                        "boolean": true
                    },
                    "limits": {
                        "in.datatypes.measurementlimit.BooleanMeasurementLimit": {
                            "trueIsPassing": true
                        }
                    }
                },
                {
                    "id": "3456789abcdef012",
                    "parentId": "123456789abcdef0",
                    "inspectionId": "23456789abcdef01",
                    "name": "Battery voltage",
                    "value": {
                        "double": 3.791
                    },
                    "limits": {
                        "in.datatypes.measurementlimit.NumberMeasurementLimit": {
                            "passingUpperBound": 3.9,
                            "passingLowerBound": 3.75
                        }
                    }
                },
                {
                    "id": "456789abcdef0123",
                    "inspectionId": "56789abcdef01234",
                    "name": "Enclosure gap 1",
                    "value": {
                        "dfx.schemas.api.searchdata.APISearchDataImageFeature": {
                            "value": 0.17,
                            "measuredFileId": "6789abcdef012345",
                            "featureType": "PERPENDICULAR"
                        }
                    }
                },
                {
                    "id": "789abcdef0123456",
                    "inspectionId": "56789abcdef01234",
                    "name": "Missing screw",
                    "value": {
                        "dfx.schemas.api.searchdata.APISearchDataMonitorResult": {
                            "pass": false,
                            "analyzedFileId": "6789abcdef012345",
                            "executor": "STATION"
                        }
                    }
                }
            ],
            "imageConfigs": {
                "Color": "Blue",
                "Bluetooth": "Yes"
            },
            "tags": ["Modified by Joe"],
            "files": [
                {
                    "id": "6789abcdef012345",
                    "inspectionId": "56789abcdef01234",
                    "name": "PCB Photo",
                    "type": "PHOTO"
                }
            ],
            "inspections": [
                {
                    "id": "23456789abcdef01",
                    "timestamp": "2022-05-25T03:22:28.556Z",
                    "stationName": "Final test"
                },
                {
                    "id": "56789abcdef01234",
                    "timestamp": "2022-05-25T02:21:28.553Z",
                    "stationName": "PCB",
                    "subassemblySerialNumber": "PCB456"
                }
            ]
        }
    ],
    "paging": {
        "nextPageId": "89abcdef01234567"
    }
}

If the request is not a valid shape, the response body will describe what is wrong. For example, if you try to send a string to paging.count instead of a number, you may see a message like this:

{"error":"Could not parse request body as the expected JSON format.","reason":"ERROR :: /paging/count :: 1 cannot be coerced to Integer"}

If the request is a valid shape but the values are not valid, you will get a different format:

{"code":"INVALID_INPUT","msg":"Negative value for count"}

Sort order and paging

Results are sorted roughly by “newest first.” Specifically, inspections are sorted in the following order:

  1. Image and video inspections before data inspections
  2. Timestamp (newest first)

Units are sorted by the time of their highest-sorted inspection (e.g. their newest inspection) based on the order above.

You can think of nextPageId/offsetPageId as a hash of the last timestamp + inspection type seen on the previous page. Subsequent pages basically use this to filter out previous pages. This filtering is done at request time, so the results may be unstable if the data changes. Specifically, it is possible to miss a unit or inspection if new data comes in while you are paging, but units/inspections will not show up more than once.

Reviewing errors

API requests that authenticate successfully (i.e. they have a valid instrumental-api-key header and are not rate limited) but fail other validation (e.g. the request body is invalid) will be temporarily accessible for debugging through the API key modal. You can read more about this on the API Keys page.