Skip to content

Icarus API

Each node running Icarus hosts its own FastAPI server for communication with Heimdall.
FastAPI automatically generates API docs, which can be found by going to <node_name>:8000/docs. For example, omaha:8000/docs

This section will offer a more detailed explanation of what each FastAPI route on Icarus is doing and what they are used for.

Routes

IoT Routes

These endpoints are used by the IoT setup and are not meant to be hit normally.

GET /connect

POST /connect

POST /rescan

POST /reset

Heimdall-Accessed Routes

These endpoints are the ones used by Heimdall.

GET /gps

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:

    {
      "lat": 31.416,
      "lon": 42.069
    }
    
    The lat and lon of the node are returned as floats.
  2. Status: 404
    Body:

    {  
        "status": "error",
        "message": "No valid GPS fix available",  
        "gps_response": "<GpsResponse No fix>"  
    }
    
    GPS receiver is unable to determine location accurately.
  3. Status: 404
    Body:

    {  
        "status": "error",
        "message": "Failed to connect to GPSD instance",
        "error": "Unexpected data received as welcome. Is the server a gpsd 3 server?",
    }
    
    Failed to connect to the GPSD instance.
    Troubleshooting:
    Run systemctl status gpsd in the node's terminal. It should look like this: alt text
    If the service is not active, consider restarting it with sudo systemctl restart gpsd and then running systemctl status gpsd again.
    Consider also running gpsmon, check to see if data is showing up. Use ctrl+C to exit it.

Description
Icarus will attempt to get the GPS coordinates of the node it is running on using the GPSD instance.

There could be many reasons why the GPS is unable to determine location accurately:

  1. Buildings, tunnels, or dense urban areas – GPS signals are line-of-sight; tall structures can block or reflect signals (multipath effect).
  2. Indoor or underground use – Most consumer GPS devices struggle without a clear sky view.
  3. Heavy cloud cover, storms, or solar activity – While GPS signals penetrate clouds, severe weather can slightly degrade accuracy.
  4. Ionospheric delays – Solar flares and atmospheric disturbances can slow signal transmission, causing minor errors.
  5. Less than 4 satellites in view – GPS requires at least 4 satellites for a 3D fix (latitude, longitude, altitude). Fewer satellites reduce accuracy or prevent a lock entirely.

For more information about GPSD and possible errors, check out
Python GPSD library
GPSD Documentation

Example
curl -X GET http://omaha:8000/gps

{"lat":40.23232344,"lon":-23.404011797}

GET /sensor

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 501
    This endpoint is not implemented.

Description
It is intended to be a health check for the sensors on the node (water sensor, humidity sensor).

Example
alt text

GET /status

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:
    {
        "status": "online"
    }
    

Description
Heimdall uses this to check if Icarus is running on the node.

Example
alt text

PATCH /config

Parameters
This request requires a body containing valid keys and values for the NodeConfig class.
You can send any number of these key value pairs, meaning you are not required to send a whole serialized NodeConfig class if you are not updating every key.
Example Bodies:

{
    "threshold": 150,
    "video_retention_days": 1,
    "min_event_duration": 3,
    "start_time": "23:15:00",
    "stop_time": "10:30:00"
}
{
    "threshold": 170,
    "stop_time": "10:30:00"
}
{
    "min_event_duration": 5,
}

Responses

  1. Status: 200
    Body:

    {
        "accepted_fields": {
            "threshold": 150,
            "min_event_duration": 3,
            "start_time": "23:15:00",
            "stop_time": "10:30:00"
        },
        "rejected_fields": {},
        "patched_config": {
            "dev_name": "/dev/video0",
            "video_retention_days": 1,
            "min_event_duration": 3,
            "start_time": "23:15:00",
            "stop_time": "10:30:00",
            "threshold": 150
        }
    }
    
    The body of a successful response has three parts. accepted_fields are the key value pairs you send in the request body that were accepted and changed in the NodeConfig object. rejected_fields are key value pairs that either cannot be changed or do not exist. pathced_config is what the new NodeConfig object looks like after updating.
    There is another case where a 200 status is returned. If Icarus is currently recording and is sent a request containing updated start_time or stop_time values and other valid keys such as threshold or min_event_duration, then Icarus will update any valid key value pair that is not start_time or stop_time.
    For example if Icarus is currently recording and we send the following body:
    {
        "threshold": 170,
        "start_time": "07:30:00",
        "stop_time": "10:30:00"
    }
    
    The response body will look like this:
    {
        "message": "updated non schedule-related fields",
        "detail": "non-schedule fields updated successfully, but recording schedule cannot be modified while the node is recording",
        "accepted_fields": {
            "threshold": 170
        },
        "rejected_fields": {
            "start_time": "07:30:00",
            "stop_time": "10:30:00",
        },
        "patched_config": {
            ... previous values
            "threshold": 170      
        },
    }
    
    Note how threshold is updated in the NodeConfig but start_time and stop_time were not. This is because Icarus is unable to change the recording schedule while recording.
  2. Status: 503
    Body:

    {
        "message": "failed to change recording schedule",
        "detail": "cannot change recording schedule while node is recording",
        "start_time": <start_time>,
        "stop_time": <stop_time>,
    }
    
    This occurs if Icarus is recording and the only valid keys in the body are start_time and/or stop_time.
  3. Status: 400 Body:
    There are two ways a 400 response could be returned.

    {
        "status": "error",
        "message": "Cannot mutate fields",
        "immutable_fields": [immutable_keys],
    }
    
    Fields in the body of the request were not allowed to be mutated. For example dev_name is a field of the NodeConfig class, but cannot be mutated.
    {
        "status": "error",
        "message": "Error validating data",
        "errors": [validation_errors],
    }
    
    The request's body has malformed or invalid data.
    For example if we sent the following body in the request:
    {
        "threshold": "foobar",
        "foo": "bar",
        "baz": 12414
    }
    
    We would get a 400 response with the following body:
    {
        "status": "error",
        "message": "Error validating data",
        "errors": [
            {
                "type": "int_parsing",
                "field": [
                    "threshold"
                ],
                "input": "foobar",
                "message": "Input should be a valid integer, unable to parse string as an integer"
            },
            {
                "type": "extra_forbidden",
                "field": [
                    "foo"
                ],
                "input": "bar",
                "message": "Extra inputs are not permitted"
            },
            {
                "type": "extra_forbidden",
                "field": [
                    "baz"
                ],
                "input": 12414,
                "message": "Extra inputs are not permitted"
            }
        ]
    }
    
    The possible errors that can occur fall into two categories: Validation and Extra.
    Validation errors occur when the data sent fails to pass the NodeConfig validators. In this example we can see that we sent "threshold": "foobar", but threshold has a validator that requires it to be an integer (or string that is parsed into an integer).
    Extra forbidden errors occur when the data sent has extraneous fields that do not exist in the NodeConfig class. This is due to NodeConfig not allowing extra fields to be given to itself. The keys baz and foo are not actual fields in the NodeConfig class and therefore are rejected.

Description
The description under each response sums up the functionality of this endpoint.
This endpoint is hit when "Apply" is pressed after updating a node's config in its setting page on Heimdall.

alt text

Example
alt text

GET /nights

Parameters
date: Date that you want to get night recordings for. Must be formatted as YYYY-MM-DD.

Responses

  1. Status: 200
    Headers:
    content-type: application/zip
    content-disposition: attachment; filename="<node_name>-<date>.zip"
    Body:
    The zipped night recordings binary data.

  2. Status: 404

    {
        "message": "No folders exist for given date", 
        "date": <date>
    }
    
    No recordings exist for the specified date.
  3. Status: 503

    {
        "message": "Cannot fetch night data while recording"
    }
    
    Icarus is busy recording and cannot check for night recordings.
  4. Status: 400
    Body:

    {
        "message": "Date parameter is required"
    }
    
    date query paramter is missing from the request.
    {
        "message": "Invalid date format. Use YYYY-MM-DD", 
        "date": <date>
    }
    
    date query paramter is not formatted correctly.

Description
This endpoint is internally called by Heimdall after receiving a 200 response code from HEAD /nights. Note that this endpoint is returning a massive (yes the meme is still massive) zip file and will therefore take a very long time to finish execution.

Example
alt text

HEAD /nights

Parameters
date: Date that you are checking for night recordings. Must be formatted as YYYY-MM-DD.

Responses
Since this is a HEAD request, none of the responses have a body.

  1. Status: 200
    There exists recordings for the specified date.

  2. Status: 404
    No recordings exist for the specified date.

  3. Status: 503
    Icarus is busy recording and cannot check for night recordings.

  4. Status: 400
    Either the date query parameter is missing in the request or it is malformed. The date query parameter must be formatted as YYYY-MM-DD.

Description
Because GET /nights could potentially take a long time to download the whole night's recording, this endpoint is used by Heimdall to check if the date has a recording. Heimdall uses the status code of the response to either do nothing or to hit GET /nights. Heimdall will only hit GET /nights if a 200 status code is returned from this endpoint.
Heimdall hits this endpoint when "Download Night" is pressed in the "NightView" page.

alt text

Example
alt text

GET /snapshot

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Headers:
    content-type: image/jpeg
    content-disposition: attachment; filename="<node_name>-snapshot-<timestamp>.jpg"
    Body:
    The snapshot binary data.

  2. Status: 503
    Service Unavailable.
    Icarus cannot get a snapshot while it is recording because the camera can only be accessed by one video capture.

  3. Status: 500
    Body:
    There are serveral bodies that could be returned in a 500 status.

    {
        "message": "Failed to open camera device"
    }
    
    Icarus failed to open the camera device to get a snapshot. This could be because a process still hasn't released the camera video capture, or because the camera device is not properly set up.
    {
        "message": "Failed to grab frame"
    }
    
    To get a snapshot, Icarus will grab a few seconds of frames in order for the camera exposure to be properly set. If a frame fails to grab during this exposure set time, this response is returned.
    {
        "message": "Failed to capture snapshot"
    }
    
    Icarus failed to read the frame that would be used for the snapshot.
    {
        "message": "Failed to save snapshot"
    }
    
    Icarus failed to write the frame that it captured to a temporary jpg file. This could possibly happen if the node doesn't have file write permissions.
    For further debugging help, check out cv2.imwrite(), the function used to write snapshots.

Description
Icarus opens up the camera device whose path is specified in NodeConfig.dev_name. In order to make sure that the snapshot is clear (not super bright or super dark), Icarus captures 2 seconds of frames before saving one frame to use as the snapshot. The snapshot is saved to a temporary file in the /tmp directory and then sent to Heimdall. After the snapshot is sent to Heimdall successfully, the temporary snapshot in the /tmp directory is deleted.

Example
alt text

POST /serq/trigger

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:

    {
        "message": "Send event retry queue processed",
        "queue_size": 0,
    }
    
    Icarus successfully processed the retry queue.
    The size of the queue after it was processed is returned.
    Note that the queue size could possible be greater than 0, this would happen if an event fails to send again and would therefore be put back into the queue.
  2. Status: 503
    Body:

    {
        "message": "Cannot process send event retry queue while node is recording"
    }
    
    If the node is recording then Icarus cannot process the event retry queue.

Description
This endpoint is triggered from the "Force Sync Events" button located in a node's settings page.

alt text
Attempts to process the whole send event retry queue for the node. This means that any event that the node had previously failed to send to Heimdall will try to be sent again.
If the resending of an event fails, the event is placed back into the retry queue and waits until the queue is processed again.

Example
alt text

Debugging Routes

These endpoints are not used by IoT or by Heimdall, they are only used for tests and for debugging.

GET /

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:
    {
        "name": "Bolide Node API",
        "description": "This API provides access to information about this node",
        "documentation": "http://<node_name>:8000/docs"
    }
    

Description
This root endpoint returns general information and where to find more docs.

Example
alt text

GET /config

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:
    {
        "config": {
            "dev_name": "/dev/video0",
            "video_retention_days": 1,
            "min_event_duration": 3,
            "start_time": "23:55:59",
            "stop_time": "10:43:52",
            "threshold": 150
        }
    }
    

Description
Returns the current NodeConfig object as JSON.
This endpoint should only be used for debugging and testing.
NOTE: Do not use this endpoint to update the node's NodeConfig record in Heimdall's database.

Example
alt text

GET /config/backup

Parameters
This endpoint does not require a request body.

Responses

  1. Status: 200
    Body:
    {
        "config": {
            "dev_name": "/dev/video0",
            "video_retention_days": 1,
            "min_event_duration": 3,
            "start_time": "23:55:59",
            "stop_time": "10:43:52",
            "threshold": 150
        }
    }
    

Description
Returns the current NodeConfig as JSON that is stored in the config.bak file.
This endpoint should only be used for debugging and testing.
NOTE: Do not use this endpoint to update the node's NodeConfig record in Heimdall's database.

Example
alt text