Skip to content

Allsky Server API

The Allsky Server is a separate process that runs alongside Allsky its primary job is to handle longer running processes and to manage GPIO Coontrol. Its core functions are

  • The Allsky dashboard whcich displays information about Allsky - currently under development
  • The Allsky focuser which allows a camera with a focus motor to be focused - currently under development
  • The Pi's GPIO pins, both digital and PWM

Authentication

The API requires authentication under the following circumstances

  • You are unsing the inbuilt Dashboard, in this case the authentication will be via a login screen using the same login and password as Allaky
  • You are using the api from an external machine. In this case you will need to request a JWT token from the API

If you are using the API from the local machine then no authentication is required for any API

Requesting a JWT token

To get a JWT token use the following API endpoint

POST /login

This expects a username and password and returns a JWT token

TODO: Add Hoppscotch examples TODO: Creating users

The Available API's

allsky

allsky_status()

GET /allsky/status

Retrieve the current Allsky system status.

This endpoint queries backend logic (get_allsky_status) and returns a full status dictionary, plus a rendered HTML fragment used by the web frontend.

Authentication

Requires: api_auth_required("allsky", "read")

Returns:

Name Type Description
dict

A JSON object containing: - All fields provided by get_allsky_status() - status_html: Rendered HTML snippet for UI display

Source code in server/modules/allsky/__init__.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@allsky_bp.route('/status', methods=['GET'])
@api_auth_required("allsky", "read")
def allsky_status():
    """
    GET /allsky/status

    Retrieve the current Allsky system status.

    This endpoint queries backend logic (get_allsky_status) and returns a full
    status dictionary, plus a rendered HTML fragment used by the web frontend.

    Authentication:
        Requires: ``api_auth_required("allsky", "read")``

    Returns:
        dict: A JSON object containing:
            - All fields provided by ``get_allsky_status()``
            - ``status_html``: Rendered HTML snippet for UI display
    """
    status = get_allsky_status()
    status_html = render_template("_partials/_allskyStatus.html", status=status)
    status['status_html'] = status_html
    return status

restart_allsky()

GET /allsky/restart

Restart the Allsky service.

Authentication

Requires: api_auth_required("allsky", "update")

Returns:

Name Type Description
JSON

{"status": "Allsky started"}

Source code in server/modules/allsky/__init__.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@allsky_bp.route('/restart', methods=['GET'])
@api_auth_required("allsky", "update")
def restart_allsky():
    """
    GET /allsky/restart

    Restart the Allsky service.

    Authentication:
        Requires: ``api_auth_required("allsky", "update")``

    Returns:
        JSON: ``{"status": "Allsky started"}``
    """
    subprocess.run(['sudo', 'systemctl', 'restart', 'allsky'])
    return jsonify({'status': 'Allsky started'})

start_allsky()

GET /allsky/start

Start the Allsky service.

Authentication

Requires: api_auth_required("allsky", "update")

Returns:

Name Type Description
JSON

{"status": "Allsky started"}

Source code in server/modules/allsky/__init__.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
@allsky_bp.route('/start', methods=['GET'])
@api_auth_required("allsky", "update")
def start_allsky():
    """
    GET /allsky/start

    Start the Allsky service.

    Authentication:
        Requires: ``api_auth_required("allsky", "update")``

    Returns:
        JSON: ``{"status": "Allsky started"}``
    """
    subprocess.run(['sudo', 'systemctl', 'start', 'allsky'])
    return jsonify({'status': 'Allsky started'})

stop_allsky()

GET /allsky/stopallsky

Stop the Allsky service and immediately start the AllskyServer service.

This is commonly used during debugging or maintenance when the capture component needs to be restarted but the web interface should remain active.

Authentication

Requires: api_auth_required("allsky", "update")

Returns:

Name Type Description
JSON

{"status": "Allsky stopped"}

Source code in server/modules/allsky/__init__.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@allsky_bp.route('/stopallsky', methods=['GET'])
@api_auth_required("allsky", "update")
def stop_allsky():
    """
    GET /allsky/stopallsky

    Stop the Allsky service and immediately start the AllskyServer service.

    This is commonly used during debugging or maintenance when the capture
    component needs to be restarted but the web interface should remain active.

    Authentication:
        Requires: ``api_auth_required("allsky", "update")``

    Returns:
        JSON: ``{"status": "Allsky stopped"}``
    """
    subprocess.run(['sudo', 'systemctl', 'stop', 'allsky'])
    subprocess.run(['sudo', 'systemctl', 'start', 'allskyserver'])
    return jsonify({'status': 'Allsky stopped'})

stop_allsky_all()

GET /allsky/stopall

Stop the Allsky service.

This fully stops the "allsky" systemd service.

Authentication

Requires: api_auth_required("allsky", "update")

Returns:

Name Type Description
JSON

{"status": "Allsky stopped"}

Source code in server/modules/allsky/__init__.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@allsky_bp.route('/stopall', methods=['GET'])
@api_auth_required("allsky", "update")
def stop_allsky_all():
    """
    GET /allsky/stopall

    Stop the Allsky service.

    This fully stops the "allsky" systemd service.

    Authentication:
        Requires: ``api_auth_required("allsky", "update")``

    Returns:
        JSON: ``{"status": "Allsky stopped"}``
    """
    subprocess.run(['sudo', 'systemctl', 'stop', 'allsky'])
    return jsonify({'status': 'Allsky stopped'})

gpio

all_gpio_status()

GET /gpio/all

Return the status of every board GPIO pin.

Authentication

Requires @api_auth_required("gpio", "update")

Returns:

Name Type Description
JSON Response

A dictionary of all pins (D0, D1, D2...) with details on whether they are unused, digital I/O, or PWM.

Source code in server/modules/gpio/__init__.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@gpio_bp.route("/all", methods=["GET"])
@api_auth_required("gpio", "update")
def all_gpio_status() -> Response:
    """
    GET /gpio/all

    Return the status of every board GPIO pin.

    Authentication:
        Requires @api_auth_required("gpio", "update")

    Returns:
        JSON: A dictionary of all pins (D0, D1, D2...) with details on
              whether they are unused, digital I/O, or PWM.
    """
    try:
        with gpio_lock:
            all_status = get_gpio_status()

        return jsonify(all_status)

    except Exception as e:
        return (
            jsonify({
                "error": "Failed to retrieve all GPIO status",
                "type": type(e).__name__,
                "message": str(e),
            }),
            500,
        )

get_board_pin(gpio_str)

Convert a GPIO pin number into a board-specific pin object.

Parameters:

Name Type Description Default
gpio_str str | int

Pin number, e.g. "18".

required

Returns:

Type Description

object | None: board.Dxx pin object, or None if invalid.

Source code in server/modules/gpio/__init__.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def get_board_pin(gpio_str):
    """
    Convert a GPIO pin number into a board-specific pin object.

    Args:
        gpio_str (str | int): Pin number, e.g. "18".

    Returns:
        object | None: board.Dxx pin object, or None if invalid.
    """
    try:
        return getattr(board, f"D{int(gpio_str)}")
    except (AttributeError, ValueError):
        return None

get_gpio_status()

Inspect all available board pins and return their current usage.

This includes
  • Unused pins
  • Digital pins (input/output, on/off)
  • PWM pins (frequency, duty cycle)

Returns:

Name Type Description
dict dict

Status for each Dxx pin on the board.

Source code in server/modules/gpio/__init__.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def get_gpio_status() -> dict:
    """
    Inspect all available board pins and return their current usage.

    This includes:
      - Unused pins
      - Digital pins (input/output, on/off)
      - PWM pins (frequency, duty cycle)

    Returns:
        dict: Status for each Dxx pin on the board.
    """
    all_status = {}
    for attr in dir(board):
        if not attr.startswith("D"):
            continue

        hr_pin = attr        # e.g. "D18"
        pin_num = hr_pin[1:] # e.g. "18"
        board_pin = getattr(board, attr)

        status = {"mode": "unused"}

        if pin_num in digital_pins:
            dio = digital_pins[pin_num]
            direction = (
                "output" if dio.direction == digitalio.Direction.OUTPUT else "input"
            )
            status["mode"] = f"digital-{direction}"
            status["value"] = "on" if dio.value else "off"

        elif pin_num in pwm_pins:
            pwm = pwm_pins[pin_num]
            status["mode"] = "pwm"
            status["frequency"] = pwm.frequency
            status["duty"] = pwm.duty_cycle

        all_status[hr_pin] = status

    return all_status

read_digital(pin)

GET /gpio/digital/

Read a digital GPIO pin.

If the pin has not previously been configured, the API automatically initialises it as a digital input.

Parameters:

Name Type Description Default
pin str

Pin number, e.g. "18".

required
Authentication

Requires @api_auth_required("gpio", "update")

Returns:

Name Type Description
JSON Response

{ "pin": "", "value": "on" | "off" }

Source code in server/modules/gpio/__init__.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@gpio_bp.route("/digital/<pin>", methods=["GET"])
@api_auth_required("gpio", "update")
def read_digital(pin) -> Response:
    """
    GET /gpio/digital/<pin>

    Read a digital GPIO pin.

    If the pin has not previously been configured, the API automatically
    initialises it as a digital input.

    Args:
        pin (str): Pin number, e.g. "18".

    Authentication:
        Requires @api_auth_required("gpio", "update")

    Returns:
        JSON: { "pin": "<number>", "value": "on" | "off" }
    """
    pin = str(pin).strip()
    board_pin = get_board_pin(pin)

    if board_pin is None:
        return jsonify({"error": f"Invalid GPIO pin {pin}"}), 400

    try:
        with gpio_lock:
            if pin in digital_pins:
                digital_io = digital_pins[pin]
            else:
                digital_io = digitalio.DigitalInOut(board_pin)
                digital_io.direction = digitalio.Direction.INPUT
                digital_pins[pin] = digital_io

            value = digital_io.value

        return jsonify({"pin": pin, "value": "on" if value else "off"})

    except Exception as e:
        return (
            jsonify({
                "error": "Failed to read digital pin",
                "type": type(e).__name__,
                "message": str(e),
            }),
            500,
        )

set_digital()

POST /gpio/digital

Set a digital GPIO pin ON or OFF.

JSON Body

pin (str/int): GPIO pin number. state (str): "on" or "off". Defaults to "off".

Behaviour

• If the pin is currently configured for PWM, PWM is deinitialised. • Pin is configured as a digital output if not already.

Authentication

Requires @api_auth_required("gpio", "update")

Returns:

Name Type Description
JSON Response

{ "pin": , "state": "on" | "off" }

Source code in server/modules/gpio/__init__.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@gpio_bp.route("/digital", methods=["POST"])
@api_auth_required("gpio", "update")
def set_digital() -> Response:
    """
    POST /gpio/digital

    Set a digital GPIO pin ON or OFF.

    JSON Body:
        pin   (str/int): GPIO pin number.
        state (str):     "on" or "off". Defaults to "off".

    Behaviour:
        • If the pin is currently configured for PWM, PWM is deinitialised.
        • Pin is configured as a digital output if not already.

    Authentication:
        Requires @api_auth_required("gpio", "update")

    Returns:
        JSON: { "pin": <pin>, "state": "on" | "off" }
    """
    try:
        pin = str(request.json.get("pin")).strip()
        state = str(request.json.get("state", "off")).lower()

        board_pin = get_board_pin(pin)
        if board_pin is None:
            return jsonify({"error": f"Invalid GPIO pin {pin}"}), 400

        with gpio_lock:
            if pin in pwm_pins:
                pwm_pins[pin].deinit()
                del pwm_pins[pin]

            if pin not in digital_pins:
                digital_io = digitalio.DigitalInOut(board_pin)
                digital_io.direction = digitalio.Direction.OUTPUT
                digital_pins[pin] = digital_io

            digital_pins[pin].value = state == "on"

        return jsonify({"pin": pin, "state": "on" if digital_pins[pin].value else "off"})

    except Exception as e:
        return (
            jsonify({
                "error": "Failed to set digital pin",
                "type": type(e).__name__,
                "message": str(e),
            }),
            500,
        )

set_pwm()

POST /gpio/pwm

Configure or update PWM on a GPIO pin.

JSON Body

pin (str/int): GPIO number. frequency (int): PWM frequency in Hz. Defaults to 1000. duty (int): Duty cycle 0–65535. Defaults to 0.

Behaviour

• If the pin was digital, digital mode is removed. • Creates PWMOut if necessary, otherwise updates existing PWMOut.

Authentication

Requires @api_auth_required("gpio", "update")

Returns:

Name Type Description
JSON Response

{ "pin": , "frequency": , "duty": , "duty_percent": <0-100>,

Response

}

Source code in server/modules/gpio/__init__.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
@gpio_bp.route("/pwm", methods=["POST"])
@api_auth_required("gpio", "update")
def set_pwm() -> Response:
    """
    POST /gpio/pwm

    Configure or update PWM on a GPIO pin.

    JSON Body:
        pin        (str/int): GPIO number.
        frequency  (int):     PWM frequency in Hz. Defaults to 1000.
        duty       (int):     Duty cycle 0–65535. Defaults to 0.

    Behaviour:
        • If the pin was digital, digital mode is removed.
        • Creates PWMOut if necessary, otherwise updates existing PWMOut.

    Authentication:
        Requires @api_auth_required("gpio", "update")

    Returns:
        JSON: {
            "pin": <pin>,
            "frequency": <hz>,
            "duty": <value>,
            "duty_percent": <0-100>,
        }
    """
    try:
        pin = str(request.json.get("pin")).strip()
        frequency = int(request.json.get("frequency", 1000))
        duty = int(request.json.get("duty", 0))

        if not (0 <= duty <= 65535):
            return jsonify({"error": "Duty must be between 0 and 65535"}), 400

        board_pin = get_board_pin(pin)
        if board_pin is None:
            return jsonify({"error": f"Invalid GPIO pin {pin}"}), 400

        with gpio_lock:
            if pin in digital_pins:
                digital_pins[pin].deinit()
                del digital_pins[pin]

            if pin not in pwm_pins:
                pwm = pwmio.PWMOut(board_pin, frequency=frequency, duty_cycle=duty)
                pwm_pins[pin] = pwm
            else:
                pwm = pwm_pins[pin]
                pwm.frequency = frequency
                pwm.duty_cycle = duty

        return jsonify({
            "pin": pin,
            "frequency": frequency,
            "duty": duty,
            "duty_percent": round((duty / 65535) * 100, 2),
        })

    except Exception as e:
        return (
            jsonify({
                "error": "Failed to set PWM",
                "type": type(e).__name__,
                "message": str(e),
            }),
            500,
        )

status()

GET /gpio/status

Return a compact summary of active digital and PWM pins.

Unlike /gpio/all, this endpoint only reports pins currently in use.

Authentication

Requires @api_auth_required("gpio", "update")

Returns:

Name Type Description
JSON Response

{ "digital": { pin: "on" | "off" }, "pwm": { pin: { frequency, duty } }

Response

}

Source code in server/modules/gpio/__init__.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
@gpio_bp.route("/status", methods=["GET"])
@api_auth_required("gpio", "update")
def status() -> Response:
    """
    GET /gpio/status

    Return a compact summary of active digital and PWM pins.

    Unlike `/gpio/all`, this endpoint only reports pins currently in use.

    Authentication:
        Requires @api_auth_required("gpio", "update")

    Returns:
        JSON: {
            "digital": { pin: "on" | "off" },
            "pwm": { pin: { frequency, duty } }
        }
    """
    try:
        with gpio_lock:
            digital_status = {
                pin: "on" if dio.value else "off"
                for pin, dio in digital_pins.items()
            }
            pwm_status = {
                pin: {"frequency": pwm.frequency, "duty": pwm.duty_cycle}
                for pin, pwm in pwm_pins.items()
            }

        return jsonify({"digital": digital_status, "pwm": pwm_status})

    except Exception as e:
        return (
            jsonify({
                "error": "Failed to retrieve status",
                "type": type(e).__name__,
                "message": str(e),
            }),
            500,
        )