Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 148 additions & 13 deletions src/api/rest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,26 @@ REST Reference
--------------

.. note::
This reference is highly incomplete. For an interactive view of the API, try out the API playground running on your local server at: http://localhost:5600/api/
This reference covers the endpoints watchers and clients use most. For an interactive view of the API, try out the API playground running on your local server at: http://localhost:5600/api/

All endpoints live under ``/api/0/``. Request and response bodies are JSON unless noted.
Timestamps must be RFC 3339 with a timezone (``2026-10-01T10:00:00Z``).
Durations are in seconds.

.. note::
There are two server implementations: ``aw-server-rust`` (the default) and ``aw-server`` (Python).
They agree on the happy path but differ on error handling. Differences are called out below.
Do not rely on error response bodies: aw-server-rust returns an HTML page for 400/422 errors,
and some invalid inputs cause a 500 on the Python server.

Server info
~~~~~~~~~~~

.. code-block:: shell

GET /api/0/info

Returns ``{"hostname": ..., "version": ..., "testing": ..., "device_id": ...}``.

Buckets API
~~~~~~~~~~~
Expand All @@ -45,58 +64,174 @@ Buckets are data containers used to group data together which shares some metada
Get Bucket Metadata
^^^^^^^^^^^^^^^^^^^

Will return 404 if bucket does not exist

.. code-block:: shell

GET /api/0/buckets/<bucket_id>

Status codes: ``200``, ``404`` if the bucket does not exist.

List
^^^^

.. code-block:: shell

GET /api/0/buckets/

Returns an object mapping bucket id to bucket metadata.

Create
^^^^^^

Will return 304 if bucket already exists

.. code-block:: shell

POST /api/0/buckets/<bucket_id>

Body (all three fields are required):

.. code-block:: json

{"client": "aw-watcher-example", "type": "app.example", "hostname": "my-host"}

Status codes: ``200`` created, ``304`` if the bucket already exists (the existing bucket is left unchanged),
``400`` for malformed JSON, ``422`` for missing fields (aw-server-rust; the Python server returns ``500``).

Delete
^^^^^^

.. code-block:: shell

DELETE /api/0/buckets/<bucket_id>?force=1

Deletes the bucket **and all its events**. The ``force=1`` parameter is required.
Status codes: ``200``, ``404`` if the bucket does not exist.

Events API
~~~~~~~~~~

The most common API used by ActivityWatch clients is the API providing read and append `Events <../buckets-and-events>` to buckets.
Buckets are data containers used to group data together which shares some metadata (such as client type, hostname or location).
The events API provides read and append access to `Events <../buckets-and-events>` in a bucket.

Get events
^^^^^^^^^^

.. code-block:: shell

GET /api/0/buckets/<bucket_id>/events
GET /api/0/buckets/<bucket_id>/events?start=<rfc3339>&end=<rfc3339>&limit=<n>

Create event
^^^^^^^^^^^^
All parameters are optional. Events are returned newest first, as a list of
``{"id": 1, "timestamp": "...", "duration": 10.0, "data": {...}}``.
A negative ``limit`` means no limit.

Status codes: ``200``, ``400`` if ``start``/``end`` cannot be parsed, ``404`` if the bucket does not exist.

Create events
^^^^^^^^^^^^^

.. code-block:: shell

POST /api/0/buckets/<bucket_id>/events

Body: a JSON **list** of events, each with ``timestamp``, ``duration`` and ``data`` (an object):

.. code-block:: json

[{"timestamp": "2026-10-01T10:00:00Z", "duration": 10, "data": {"app": "firefox"}}]

Always send a list, even for a single event. aw-server-rust rejects a bare object with ``422``,
while the Python server accepts it, so a client tested only against the Python server can break on the default one.

Status codes: ``200``, ``404`` if the bucket does not exist, ``422`` for an invalid body (aw-server-rust).

Count events
^^^^^^^^^^^^

.. code-block:: shell

GET /api/0/buckets/<bucket_id>/events/count?start=<rfc3339>&end=<rfc3339>

Returns the number of events as a bare JSON number.

Get / delete a single event
^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: shell

GET /api/0/buckets/<bucket_id>/events/<event_id>
DELETE /api/0/buckets/<bucket_id>/events/<event_id>

``GET`` returns ``404`` for an unknown id on the Python server but ``500`` on aw-server-rust.
``DELETE`` returns ``200`` even if no event with that id exists.

Heartbeat API
~~~~~~~~~~~~~

The `heartbeat <heartbeats>` API is one of the most useful endpoints for writing watchers.

.. code-block:: shell

POST /api/0/buckets/<bucket_id>/heartbeat
POST /api/0/buckets/<bucket_id>/heartbeat?pulsetime=<seconds>

The ``pulsetime`` query parameter is **required** and must be a number: it is the maximum gap, in seconds,
between two heartbeats with identical ``data`` for them to be merged into one event.
Requests without it are rejected (``400`` on the Python server, an HTML ``422`` on aw-server-rust).

Body: a single event object (not a list), with ``timestamp``, ``duration`` (usually ``0``) and ``data``:

.. code-block:: json

{"timestamp": "2026-10-01T11:00:05Z", "duration": 0, "data": {"app": "firefox"}}

Returns the resulting (possibly merged) event. Status codes: ``200``, ``404`` if the bucket does not exist,
``400``/``422`` for an invalid body or ``pulsetime``.

Query API
~~~~~~~~~~~~~
`Writing Queries <./../examples/querying-data.html>`_
~~~~~~~~~

See `Writing Queries <./../examples/querying-data.html>`_ for the query language.

.. code-block:: shell

POST /api/0/query/

.. code-block:: json

{
"timeperiods": ["2026-10-01T00:00:00Z/2026-10-02T00:00:00Z"],
"query": ["events = query_bucket(\"my-bucket\");", "RETURN = events;"]
}

``timeperiods`` is a list of ``start/end`` RFC 3339 intervals and ``query`` is a list of statements
(a single string is rejected). The response is a list with one result per time period.

Status codes: ``200``, ``422`` for a malformed request body (aw-server-rust), ``400`` for a query that fails to parse (Python server).
aw-server-rust reports query errors (syntax errors, undefined variables, unknown buckets) as ``500`` with a JSON ``message``.

Export and import
~~~~~~~~~~~~~~~~~

.. code-block:: shell

GET /api/0/buckets/<bucket_id>/export
GET /api/0/export
POST /api/0/import

Per-bucket export returns ``{"buckets": {<bucket_id>: {..., "events": [...]}}}``, or ``404`` for an unknown bucket.
``/api/0/import`` takes that same format (JSON body, or a multipart file upload as the web UI does)
and returns ``400`` for malformed input.
Importing into a bucket that already exists is not consistent between servers: aw-server-rust (master) merges
the events, while the Python server fails with ``500``.

Settings API
~~~~~~~~~~~~

Key-value storage for client settings (used by the web UI).

.. code-block:: shell

GET /api/0/settings
GET /api/0/settings/<key>
POST /api/0/settings/<key>
DELETE /api/0/settings/<key>

``GET`` of an unset key returns ``null``. ``POST`` takes any JSON value as the body and returns ``201`` on aw-server-rust
(``200`` on the Python server), and ``400`` for invalid JSON.
``DELETE`` exists only on aw-server-rust; on the Python server it returns ``405`` and a falsy ``POST`` value deletes the key instead.
Loading