things-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- things_api-0.1.0/.gitignore +7 -0
- things_api-0.1.0/CHANGELOG.md +28 -0
- things_api-0.1.0/LICENSE +21 -0
- things_api-0.1.0/PKG-INFO +134 -0
- things_api-0.1.0/README.md +104 -0
- things_api-0.1.0/com.things-api.server.plist +58 -0
- things_api-0.1.0/docs/api-reference.md +207 -0
- things_api-0.1.0/docs/configuration.md +32 -0
- things_api-0.1.0/docs/deployment.md +87 -0
- things_api-0.1.0/docs/development.md +49 -0
- things_api-0.1.0/docs/plans/2026-04-03-things-api-design.md +198 -0
- things_api-0.1.0/docs/plans/2026-04-03-things-api-plan.md +1529 -0
- things_api-0.1.0/docs/plans/2026-04-03-things-api-plan.md.tasks.json +21 -0
- things_api-0.1.0/env.example +6 -0
- things_api-0.1.0/pyproject.toml +44 -0
- things_api-0.1.0/src/things_api/__init__.py +1 -0
- things_api-0.1.0/src/things_api/app.py +98 -0
- things_api-0.1.0/src/things_api/auth.py +26 -0
- things_api-0.1.0/src/things_api/config.py +29 -0
- things_api-0.1.0/src/things_api/models.py +117 -0
- things_api-0.1.0/src/things_api/ratelimit.py +55 -0
- things_api-0.1.0/src/things_api/routers/__init__.py +0 -0
- things_api-0.1.0/src/things_api/routers/areas.py +16 -0
- things_api-0.1.0/src/things_api/routers/lists.py +51 -0
- things_api-0.1.0/src/things_api/routers/projects.py +111 -0
- things_api-0.1.0/src/things_api/routers/search.py +61 -0
- things_api-0.1.0/src/things_api/routers/tags.py +22 -0
- things_api-0.1.0/src/things_api/routers/todos.py +133 -0
- things_api-0.1.0/src/things_api/services/__init__.py +0 -0
- things_api-0.1.0/src/things_api/services/reader.py +73 -0
- things_api-0.1.0/src/things_api/services/writer.py +86 -0
- things_api-0.1.0/tests/__init__.py +0 -0
- things_api-0.1.0/tests/conftest.py +57 -0
- things_api-0.1.0/tests/test_app.py +54 -0
- things_api-0.1.0/tests/test_auth.py +41 -0
- things_api-0.1.0/tests/test_config.py +72 -0
- things_api-0.1.0/tests/test_models.py +75 -0
- things_api-0.1.0/tests/test_ratelimit.py +32 -0
- things_api-0.1.0/tests/test_reader.py +94 -0
- things_api-0.1.0/tests/test_router_areas.py +33 -0
- things_api-0.1.0/tests/test_router_lists.py +69 -0
- things_api-0.1.0/tests/test_router_projects.py +57 -0
- things_api-0.1.0/tests/test_router_search.py +46 -0
- things_api-0.1.0/tests/test_router_tags.py +36 -0
- things_api-0.1.0/tests/test_router_todos.py +57 -0
- things_api-0.1.0/tests/test_writer.py +94 -0
- things_api-0.1.0/uv.lock +616 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-04-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- FastAPI REST API for Things 3 with bearer token authentication
|
|
12
|
+
- Full CRUD for todos and projects via Things URL scheme
|
|
13
|
+
- Read-only endpoints for smart lists (inbox, today, upcoming, anytime, someday, logbook)
|
|
14
|
+
- Tag and area listing endpoints
|
|
15
|
+
- Full-text and advanced filtered search
|
|
16
|
+
- Health endpoint with database connectivity check
|
|
17
|
+
- Configuration via environment variables and `.env` file
|
|
18
|
+
- Rate limiting on failed authentication attempts (10 per minute per IP)
|
|
19
|
+
- Timing-safe token comparison and `SecretStr` for token storage
|
|
20
|
+
- launchd plist template for running as a persistent service
|
|
21
|
+
- 69 unit tests covering auth, config, services, routers, and rate limiting
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- Bearer tokens stored as `SecretStr` — never exposed in logs, repr, or stack traces
|
|
26
|
+
- Auth token redacted in log output (both raw and URL-encoded forms)
|
|
27
|
+
- Subprocess stderr logged server-side only — never returned to API consumers
|
|
28
|
+
- Swagger/ReDoc UI disabled to prevent schema reconnaissance
|
things_api-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT Licence
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jayden Kerr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: things-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: REST API for Things 3 — expose your tasks over HTTP
|
|
5
|
+
Project-URL: Homepage, https://github.com/jaydenk/things-api
|
|
6
|
+
Project-URL: Documentation, https://github.com/jaydenk/things-api/tree/main/docs
|
|
7
|
+
Project-URL: Issues, https://github.com/jaydenk/things-api/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/jaydenk/things-api/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Jayden Kerr
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,gtd,rest,tasks,things,things3
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: MacOS X
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Office/Business :: Scheduling
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: fastapi<1,>=0.135
|
|
22
|
+
Requires-Dist: pydantic-settings<3,>=2.13
|
|
23
|
+
Requires-Dist: things-py<2,>=1.0
|
|
24
|
+
Requires-Dist: uvicorn[standard]<1,>=0.34
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: httpx>=0.28; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.25; extra == 'test'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Things API
|
|
32
|
+
|
|
33
|
+
REST API for [Things 3](https://culturedcode.com/things/) — expose your tasks over HTTP.
|
|
34
|
+
|
|
35
|
+
Things API reads directly from the Things SQLite database via [things.py](https://github.com/thingsapi/things.py) and writes back through the [Things URL scheme](https://culturedcode.com/things/support/articles/2803573/). It runs as a lightweight FastAPI service on any Mac where Things is installed, giving you full programmatic access to your tasks from tools like [n8n](https://n8n.io/), [curl](https://curl.se/), or any HTTP client.
|
|
36
|
+
|
|
37
|
+
## Getting started
|
|
38
|
+
|
|
39
|
+
**Requirements:** macOS with [Things 3](https://culturedcode.com/things/) installed, Python 3.12+, and [uv](https://docs.astral.sh/uv/).
|
|
40
|
+
|
|
41
|
+
### 1. Clone and install
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
git clone https://github.com/jaydenk/things-api.git
|
|
45
|
+
cd things-api
|
|
46
|
+
uv venv
|
|
47
|
+
uv pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Configure
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
cp env.example .env
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Open `.env` and set your API token — this is the bearer token that authenticates every request:
|
|
57
|
+
|
|
58
|
+
```dotenv
|
|
59
|
+
THINGS_API_TOKEN=choose-a-secure-random-string
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
To enable write operations (creating, updating, completing todos), you also need a Things URL scheme auth token. To get one, open **Things > Settings > General > Enable Things URLs** and copy the token:
|
|
63
|
+
|
|
64
|
+
```dotenv
|
|
65
|
+
THINGS_AUTH_TOKEN=your-things-url-scheme-token
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Without `THINGS_AUTH_TOKEN`, the API runs in **read-only mode**.
|
|
69
|
+
|
|
70
|
+
See [docs/configuration.md](docs/configuration.md) for all configuration options.
|
|
71
|
+
|
|
72
|
+
### 3. Run
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
uv run things-api
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The server starts on `http://localhost:5225`.
|
|
79
|
+
|
|
80
|
+
### 4. Try it
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
# Health check
|
|
84
|
+
curl http://localhost:5225/health \
|
|
85
|
+
-H "Authorization: Bearer YOUR_TOKEN"
|
|
86
|
+
|
|
87
|
+
# List today's tasks
|
|
88
|
+
curl http://localhost:5225/today \
|
|
89
|
+
-H "Authorization: Bearer YOUR_TOKEN"
|
|
90
|
+
|
|
91
|
+
# Create a todo (requires THINGS_AUTH_TOKEN)
|
|
92
|
+
curl -X POST http://localhost:5225/todos \
|
|
93
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
94
|
+
-H "Content-Type: application/json" \
|
|
95
|
+
-d '{"title": "Buy milk", "when": "today"}'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To run the server persistently (auto-start at login, auto-restart on crash), see the [deployment guide](docs/deployment.md).
|
|
99
|
+
|
|
100
|
+
## Endpoints overview
|
|
101
|
+
|
|
102
|
+
Every endpoint requires a valid `Authorization: Bearer <token>` header.
|
|
103
|
+
|
|
104
|
+
| Resource | Endpoints | Description |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| **Todos** | `GET` `POST` `PUT` `DELETE` `/todos` | Full CRUD for todos |
|
|
107
|
+
| **Projects** | `GET` `POST` `PUT` `DELETE` `/projects` | Full CRUD for projects |
|
|
108
|
+
| **Smart lists** | `GET` `/inbox` `/today` `/upcoming` `/anytime` `/someday` `/logbook` | Read-only access to Things smart lists |
|
|
109
|
+
| **Tags** | `GET` `/tags` | List tags and items by tag |
|
|
110
|
+
| **Areas** | `GET` `/areas` | List areas |
|
|
111
|
+
| **Search** | `GET` `/search` `/search/advanced` | Full-text and filtered search |
|
|
112
|
+
| **Health** | `GET` `/health` | Service status and database connectivity |
|
|
113
|
+
|
|
114
|
+
> **Note:** `DELETE` on todos and projects is **irreversible** — it completes or cancels the item. Things 3 does not support true deletion.
|
|
115
|
+
|
|
116
|
+
See [docs/api-reference.md](docs/api-reference.md) for full endpoint details, request/response schemas, and query parameters.
|
|
117
|
+
|
|
118
|
+
## Limitations
|
|
119
|
+
|
|
120
|
+
- **macOS only** — Things 3 is a Mac app. The API must run on the same machine.
|
|
121
|
+
- **GUI session required for writes** — Write operations invoke the Things URL scheme, which requires an active GUI session.
|
|
122
|
+
- **No true deletion** — `DELETE` endpoints complete or cancel items instead.
|
|
123
|
+
|
|
124
|
+
## Further documentation
|
|
125
|
+
|
|
126
|
+
- [Configuration reference](docs/configuration.md) — All environment variables and their defaults
|
|
127
|
+
- [API reference](docs/api-reference.md) — Full endpoint documentation with request/response details
|
|
128
|
+
- [Deployment guide](docs/deployment.md) — Running as a launchd service, n8n integration
|
|
129
|
+
- [Development guide](docs/development.md) — Setting up a dev environment, running tests
|
|
130
|
+
- [Changelog](CHANGELOG.md) — Version history
|
|
131
|
+
|
|
132
|
+
## Licence
|
|
133
|
+
|
|
134
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Things API
|
|
2
|
+
|
|
3
|
+
REST API for [Things 3](https://culturedcode.com/things/) — expose your tasks over HTTP.
|
|
4
|
+
|
|
5
|
+
Things API reads directly from the Things SQLite database via [things.py](https://github.com/thingsapi/things.py) and writes back through the [Things URL scheme](https://culturedcode.com/things/support/articles/2803573/). It runs as a lightweight FastAPI service on any Mac where Things is installed, giving you full programmatic access to your tasks from tools like [n8n](https://n8n.io/), [curl](https://curl.se/), or any HTTP client.
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
**Requirements:** macOS with [Things 3](https://culturedcode.com/things/) installed, Python 3.12+, and [uv](https://docs.astral.sh/uv/).
|
|
10
|
+
|
|
11
|
+
### 1. Clone and install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
git clone https://github.com/jaydenk/things-api.git
|
|
15
|
+
cd things-api
|
|
16
|
+
uv venv
|
|
17
|
+
uv pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Configure
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
cp env.example .env
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Open `.env` and set your API token — this is the bearer token that authenticates every request:
|
|
27
|
+
|
|
28
|
+
```dotenv
|
|
29
|
+
THINGS_API_TOKEN=choose-a-secure-random-string
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To enable write operations (creating, updating, completing todos), you also need a Things URL scheme auth token. To get one, open **Things > Settings > General > Enable Things URLs** and copy the token:
|
|
33
|
+
|
|
34
|
+
```dotenv
|
|
35
|
+
THINGS_AUTH_TOKEN=your-things-url-scheme-token
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Without `THINGS_AUTH_TOKEN`, the API runs in **read-only mode**.
|
|
39
|
+
|
|
40
|
+
See [docs/configuration.md](docs/configuration.md) for all configuration options.
|
|
41
|
+
|
|
42
|
+
### 3. Run
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
uv run things-api
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The server starts on `http://localhost:5225`.
|
|
49
|
+
|
|
50
|
+
### 4. Try it
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
# Health check
|
|
54
|
+
curl http://localhost:5225/health \
|
|
55
|
+
-H "Authorization: Bearer YOUR_TOKEN"
|
|
56
|
+
|
|
57
|
+
# List today's tasks
|
|
58
|
+
curl http://localhost:5225/today \
|
|
59
|
+
-H "Authorization: Bearer YOUR_TOKEN"
|
|
60
|
+
|
|
61
|
+
# Create a todo (requires THINGS_AUTH_TOKEN)
|
|
62
|
+
curl -X POST http://localhost:5225/todos \
|
|
63
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
64
|
+
-H "Content-Type: application/json" \
|
|
65
|
+
-d '{"title": "Buy milk", "when": "today"}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
To run the server persistently (auto-start at login, auto-restart on crash), see the [deployment guide](docs/deployment.md).
|
|
69
|
+
|
|
70
|
+
## Endpoints overview
|
|
71
|
+
|
|
72
|
+
Every endpoint requires a valid `Authorization: Bearer <token>` header.
|
|
73
|
+
|
|
74
|
+
| Resource | Endpoints | Description |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **Todos** | `GET` `POST` `PUT` `DELETE` `/todos` | Full CRUD for todos |
|
|
77
|
+
| **Projects** | `GET` `POST` `PUT` `DELETE` `/projects` | Full CRUD for projects |
|
|
78
|
+
| **Smart lists** | `GET` `/inbox` `/today` `/upcoming` `/anytime` `/someday` `/logbook` | Read-only access to Things smart lists |
|
|
79
|
+
| **Tags** | `GET` `/tags` | List tags and items by tag |
|
|
80
|
+
| **Areas** | `GET` `/areas` | List areas |
|
|
81
|
+
| **Search** | `GET` `/search` `/search/advanced` | Full-text and filtered search |
|
|
82
|
+
| **Health** | `GET` `/health` | Service status and database connectivity |
|
|
83
|
+
|
|
84
|
+
> **Note:** `DELETE` on todos and projects is **irreversible** — it completes or cancels the item. Things 3 does not support true deletion.
|
|
85
|
+
|
|
86
|
+
See [docs/api-reference.md](docs/api-reference.md) for full endpoint details, request/response schemas, and query parameters.
|
|
87
|
+
|
|
88
|
+
## Limitations
|
|
89
|
+
|
|
90
|
+
- **macOS only** — Things 3 is a Mac app. The API must run on the same machine.
|
|
91
|
+
- **GUI session required for writes** — Write operations invoke the Things URL scheme, which requires an active GUI session.
|
|
92
|
+
- **No true deletion** — `DELETE` endpoints complete or cancel items instead.
|
|
93
|
+
|
|
94
|
+
## Further documentation
|
|
95
|
+
|
|
96
|
+
- [Configuration reference](docs/configuration.md) — All environment variables and their defaults
|
|
97
|
+
- [API reference](docs/api-reference.md) — Full endpoint documentation with request/response details
|
|
98
|
+
- [Deployment guide](docs/deployment.md) — Running as a launchd service, n8n integration
|
|
99
|
+
- [Development guide](docs/development.md) — Setting up a dev environment, running tests
|
|
100
|
+
- [Changelog](CHANGELOG.md) — Version history
|
|
101
|
+
|
|
102
|
+
## Licence
|
|
103
|
+
|
|
104
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Things API launchd agent
|
|
3
|
+
|
|
4
|
+
Install:
|
|
5
|
+
1. Edit this file — set WorkingDirectory and ProgramArguments paths
|
|
6
|
+
2. Ensure your .env file exists in the working directory with THINGS_API_TOKEN set
|
|
7
|
+
3. cp com.things-api.server.plist ~/Library/LaunchAgents/
|
|
8
|
+
4. launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.things-api.server.plist
|
|
9
|
+
|
|
10
|
+
Uninstall:
|
|
11
|
+
launchctl bootout gui/$(id -u)/com.things-api.server
|
|
12
|
+
rm ~/Library/LaunchAgents/com.things-api.server.plist
|
|
13
|
+
-->
|
|
14
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
15
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
16
|
+
<plist version="1.0">
|
|
17
|
+
<dict>
|
|
18
|
+
<key>Label</key>
|
|
19
|
+
<string>com.things-api.server</string>
|
|
20
|
+
|
|
21
|
+
<!-- Working directory — the server reads .env from here -->
|
|
22
|
+
<key>WorkingDirectory</key>
|
|
23
|
+
<string>/path/to/things-api</string>
|
|
24
|
+
|
|
25
|
+
<!--
|
|
26
|
+
Option A: Run from local source (development / pre-PyPI)
|
|
27
|
+
Uses uv run to execute from the project directory.
|
|
28
|
+
Replace /path/to/uv with the output of: which uv
|
|
29
|
+
-->
|
|
30
|
+
<key>ProgramArguments</key>
|
|
31
|
+
<array>
|
|
32
|
+
<string>/path/to/uv</string>
|
|
33
|
+
<string>run</string>
|
|
34
|
+
<string>things-api</string>
|
|
35
|
+
</array>
|
|
36
|
+
|
|
37
|
+
<!--
|
|
38
|
+
Option B: Run from PyPI (after publishing)
|
|
39
|
+
Uncomment this block and comment out Option A above.
|
|
40
|
+
Replace /path/to/uvx with the output of: which uvx
|
|
41
|
+
|
|
42
|
+
<key>ProgramArguments</key>
|
|
43
|
+
<array>
|
|
44
|
+
<string>/path/to/uvx</string>
|
|
45
|
+
<string>things-api</string>
|
|
46
|
+
</array>
|
|
47
|
+
-->
|
|
48
|
+
|
|
49
|
+
<key>KeepAlive</key>
|
|
50
|
+
<true/>
|
|
51
|
+
<key>RunAtLoad</key>
|
|
52
|
+
<true/>
|
|
53
|
+
<key>StandardOutPath</key>
|
|
54
|
+
<string>/tmp/things-api.log</string>
|
|
55
|
+
<key>StandardErrorPath</key>
|
|
56
|
+
<string>/tmp/things-api.log</string>
|
|
57
|
+
</dict>
|
|
58
|
+
</plist>
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
Every endpoint requires a valid `Authorization: Bearer <token>` header.
|
|
4
|
+
|
|
5
|
+
## Health
|
|
6
|
+
|
|
7
|
+
| Method | Path | Description |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `GET` | `/health` | Returns service status, database readability, and whether write operations are available |
|
|
10
|
+
|
|
11
|
+
**Response:**
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"status": "healthy",
|
|
16
|
+
"read": true,
|
|
17
|
+
"write": true
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The `status` field is `"healthy"` when the database is readable, or `"degraded"` if it cannot be accessed.
|
|
22
|
+
|
|
23
|
+
## Todos
|
|
24
|
+
|
|
25
|
+
| Method | Path | Description |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `GET` | `/todos` | List all incomplete todos |
|
|
28
|
+
| `GET` | `/todos/{id}` | Get a specific todo by UUID |
|
|
29
|
+
| `POST` | `/todos` | Create a new todo |
|
|
30
|
+
| `PUT` | `/todos/{id}` | Update an existing todo |
|
|
31
|
+
| `DELETE` | `/todos/{id}` | Complete or cancel a todo (**irreversible**) |
|
|
32
|
+
|
|
33
|
+
### Query parameters for `GET /todos`
|
|
34
|
+
|
|
35
|
+
| Parameter | Type | Description |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `project_id` | string | Filter by project UUID |
|
|
38
|
+
| `area_id` | string | Filter by area UUID |
|
|
39
|
+
| `tag` | string | Filter by tag name |
|
|
40
|
+
| `include_checklist` | bool | Include checklist items in the response |
|
|
41
|
+
|
|
42
|
+
### `POST /todos` — Create
|
|
43
|
+
|
|
44
|
+
Returns `202 Accepted` with the submitted title. The Things URL scheme does not return the UUID of the created item.
|
|
45
|
+
|
|
46
|
+
**Request body:**
|
|
47
|
+
|
|
48
|
+
| Field | Required | Type | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `title` | **Yes** | string | Todo title |
|
|
51
|
+
| `notes` | No | string | Notes/description |
|
|
52
|
+
| `when` | No | string | Schedule: `"today"`, `"tomorrow"`, `"evening"`, `"someday"`, or a date string |
|
|
53
|
+
| `deadline` | No | string | Deadline date (YYYY-MM-DD) |
|
|
54
|
+
| `tags` | No | string[] | Tag names to apply |
|
|
55
|
+
| `checklist_items` | No | string[] | Checklist items to add |
|
|
56
|
+
| `list_id` | No | string | Project UUID to add the todo to |
|
|
57
|
+
| `list_title` | No | string | Project title to add the todo to |
|
|
58
|
+
| `heading` | No | string | Heading name within the project |
|
|
59
|
+
| `heading_id` | No | string | Heading UUID within the project |
|
|
60
|
+
|
|
61
|
+
### `PUT /todos/{id}` — Update
|
|
62
|
+
|
|
63
|
+
Returns the updated todo if read-back succeeds, or `202 Accepted` if verification times out.
|
|
64
|
+
|
|
65
|
+
**Request body** (all fields optional):
|
|
66
|
+
|
|
67
|
+
| Field | Type | Description |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `title` | string | New title |
|
|
70
|
+
| `notes` | string | Replace notes |
|
|
71
|
+
| `prepend_notes` | string | Prepend to existing notes |
|
|
72
|
+
| `append_notes` | string | Append to existing notes |
|
|
73
|
+
| `when` | string | Reschedule |
|
|
74
|
+
| `deadline` | string | New deadline (YYYY-MM-DD) |
|
|
75
|
+
| `tags` | string[] | Replace tags |
|
|
76
|
+
| `add_tags` | string[] | Add tags without removing existing ones |
|
|
77
|
+
| `checklist_items` | string[] | Replace checklist |
|
|
78
|
+
| `prepend_checklist_items` | string[] | Prepend to checklist |
|
|
79
|
+
| `append_checklist_items` | string[] | Append to checklist |
|
|
80
|
+
| `list_id` | string | Move to project by UUID |
|
|
81
|
+
| `list_title` | string | Move to project by title |
|
|
82
|
+
| `heading` | string | Move to heading by name |
|
|
83
|
+
| `heading_id` | string | Move to heading by UUID |
|
|
84
|
+
|
|
85
|
+
### `DELETE /todos/{id}` — Complete or cancel
|
|
86
|
+
|
|
87
|
+
**This action is irreversible.** Things 3 does not support true deletion.
|
|
88
|
+
|
|
89
|
+
**Request body** (optional):
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{"action": "complete"}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
or
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{"action": "cancel"}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Defaults to `"complete"` if no body is provided.
|
|
102
|
+
|
|
103
|
+
## Projects
|
|
104
|
+
|
|
105
|
+
| Method | Path | Description |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `GET` | `/projects` | List all projects |
|
|
108
|
+
| `GET` | `/projects/{id}` | Get a project by UUID |
|
|
109
|
+
| `POST` | `/projects` | Create a new project |
|
|
110
|
+
| `PUT` | `/projects/{id}` | Update an existing project |
|
|
111
|
+
| `DELETE` | `/projects/{id}` | Complete or cancel a project (**irreversible**) |
|
|
112
|
+
|
|
113
|
+
### `POST /projects` — Create
|
|
114
|
+
|
|
115
|
+
Returns `202 Accepted`. Request body:
|
|
116
|
+
|
|
117
|
+
| Field | Required | Type | Description |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `title` | **Yes** | string | Project title |
|
|
120
|
+
| `notes` | No | string | Notes/description |
|
|
121
|
+
| `when` | No | string | Schedule |
|
|
122
|
+
| `deadline` | No | string | Deadline date (YYYY-MM-DD) |
|
|
123
|
+
| `tags` | No | string[] | Tag names |
|
|
124
|
+
| `area_id` | No | string | Area UUID |
|
|
125
|
+
| `area_title` | No | string | Area title |
|
|
126
|
+
| `todos` | No | string[] | Initial todo titles to create in the project |
|
|
127
|
+
|
|
128
|
+
### `PUT /projects/{id}` — Update
|
|
129
|
+
|
|
130
|
+
All fields optional. Same pattern as todo updates (`notes`, `prepend_notes`, `append_notes`, `when`, `deadline`, `tags`, `add_tags`, `area_id`, `area_title`).
|
|
131
|
+
|
|
132
|
+
### `DELETE /projects/{id}` — Complete or cancel
|
|
133
|
+
|
|
134
|
+
Same semantics as todo DELETE. **Irreversible.**
|
|
135
|
+
|
|
136
|
+
## Smart lists
|
|
137
|
+
|
|
138
|
+
| Method | Path | Description |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `GET` | `/inbox` | Inbox todos |
|
|
141
|
+
| `GET` | `/today` | Today's todos |
|
|
142
|
+
| `GET` | `/upcoming` | Upcoming scheduled todos |
|
|
143
|
+
| `GET` | `/anytime` | Anytime todos |
|
|
144
|
+
| `GET` | `/someday` | Someday todos |
|
|
145
|
+
| `GET` | `/logbook` | Completed todos |
|
|
146
|
+
|
|
147
|
+
### Query parameters for `GET /logbook`
|
|
148
|
+
|
|
149
|
+
| Parameter | Type | Description |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `period` | string | Time period, e.g. `3d` (3 days), `1w` (1 week), `2m` (2 months) |
|
|
152
|
+
| `limit` | int | Maximum number of items to return |
|
|
153
|
+
|
|
154
|
+
## Areas
|
|
155
|
+
|
|
156
|
+
| Method | Path | Description |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `GET` | `/areas` | List all areas |
|
|
159
|
+
|
|
160
|
+
### Query parameters
|
|
161
|
+
|
|
162
|
+
| Parameter | Type | Description |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `include_items` | bool | Include todos and projects within each area |
|
|
165
|
+
|
|
166
|
+
## Tags
|
|
167
|
+
|
|
168
|
+
| Method | Path | Description |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `GET` | `/tags` | List all tags |
|
|
171
|
+
| `GET` | `/tags/{tag}/items` | Get all items with a specific tag |
|
|
172
|
+
|
|
173
|
+
### Query parameters for `GET /tags`
|
|
174
|
+
|
|
175
|
+
| Parameter | Type | Description |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `include_items` | bool | Include tagged items in the response |
|
|
178
|
+
|
|
179
|
+
## Search
|
|
180
|
+
|
|
181
|
+
| Method | Path | Description |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `GET` | `/search?q=` | Full-text search across todos |
|
|
184
|
+
| `GET` | `/search/advanced` | Filtered search with multiple criteria |
|
|
185
|
+
|
|
186
|
+
### Query parameters for `GET /search/advanced`
|
|
187
|
+
|
|
188
|
+
| Parameter | Type | Description |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `status` | enum | `incomplete`, `completed`, or `canceled` |
|
|
191
|
+
| `tag` | string | Filter by tag name |
|
|
192
|
+
| `area` | string | Filter by area UUID |
|
|
193
|
+
| `type` | enum | `to-do`, `project`, or `heading` |
|
|
194
|
+
| `start_date` | string | Start date (YYYY-MM-DD) |
|
|
195
|
+
| `deadline` | string | Deadline date (YYYY-MM-DD) |
|
|
196
|
+
| `last` | string | Time period, e.g. `7d`, `2w`, `1m` |
|
|
197
|
+
|
|
198
|
+
## Error responses
|
|
199
|
+
|
|
200
|
+
| Status | Meaning |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `401 Unauthorized` | Missing or invalid bearer token |
|
|
203
|
+
| `404 Not Found` | Todo or project not found by UUID |
|
|
204
|
+
| `422 Unprocessable Entity` | Invalid request body |
|
|
205
|
+
| `429 Too Many Requests` | Rate limit exceeded (too many failed auth attempts) |
|
|
206
|
+
| `500 Internal Server Error` | Write operation failed |
|
|
207
|
+
| `503 Service Unavailable` | Write endpoints called without `THINGS_AUTH_TOKEN`, or database not accessible |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
All settings are read from environment variables or a `.env` file in the working directory. Copy `env.example` to `.env` and adjust as needed.
|
|
4
|
+
|
|
5
|
+
## Environment variables
|
|
6
|
+
|
|
7
|
+
| Variable | Required | Default | Description |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `THINGS_API_HOST` | No | `0.0.0.0` | Address the server binds to |
|
|
10
|
+
| `THINGS_API_PORT` | No | `5225` | Port the server listens on |
|
|
11
|
+
| `THINGS_API_TOKEN` | **Yes** | — | Bearer token used to authenticate every request. The server refuses to start without it. |
|
|
12
|
+
| `THINGS_AUTH_TOKEN` | No | — | Things URL scheme auth token — enables write operations (create, update, complete, cancel) |
|
|
13
|
+
| `THINGS_DB_PATH` | No | Auto-detected | Override the path to the Things SQLite database. By default, `things.py` finds it automatically. |
|
|
14
|
+
| `THINGS_VERIFY_TIMEOUT` | No | `0.5` | Seconds to wait after a write before reading back the result for verification |
|
|
15
|
+
|
|
16
|
+
## Read-only vs read-write mode
|
|
17
|
+
|
|
18
|
+
Without `THINGS_AUTH_TOKEN`, the API runs in **read-only mode**. All `GET` endpoints work normally, but `POST`, `PUT`, and `DELETE` return `503 Service Unavailable`.
|
|
19
|
+
|
|
20
|
+
To enable write operations:
|
|
21
|
+
|
|
22
|
+
1. Open **Things 3**
|
|
23
|
+
2. Go to **Settings > General > Enable Things URLs**
|
|
24
|
+
3. Copy the auth token
|
|
25
|
+
4. Set `THINGS_AUTH_TOKEN` in your `.env` file
|
|
26
|
+
|
|
27
|
+
## Security notes
|
|
28
|
+
|
|
29
|
+
- `THINGS_API_TOKEN` is stored internally as a `SecretStr` — it will not appear in logs, stack traces, or error messages.
|
|
30
|
+
- All authentication uses timing-safe comparison to prevent timing attacks.
|
|
31
|
+
- Failed authentication attempts are rate-limited (10 per minute per IP).
|
|
32
|
+
- The Swagger/ReDoc documentation UI is disabled. The API schema is not exposed at `/docs` or `/redoc`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
These instructions assume you've already completed the [Getting Started](../README.md#getting-started) steps — the repo is cloned, dependencies are installed, and your `.env` file is configured.
|
|
4
|
+
|
|
5
|
+
## Running as a launchd service
|
|
6
|
+
|
|
7
|
+
A launchd agent keeps Things API running in the background. It starts automatically at login and restarts if it crashes.
|
|
8
|
+
|
|
9
|
+
### 1. Edit the plist template
|
|
10
|
+
|
|
11
|
+
Open `com.things-api.server.plist` in the project root and set two paths:
|
|
12
|
+
|
|
13
|
+
**`WorkingDirectory`** — the absolute path to your cloned `things-api` directory. This is where the server looks for your `.env` file.
|
|
14
|
+
|
|
15
|
+
```xml
|
|
16
|
+
<key>WorkingDirectory</key>
|
|
17
|
+
<string>/Users/yourname/things-api</string>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**`ProgramArguments`** — the absolute path to `uv`. Find it with `which uv`.
|
|
21
|
+
|
|
22
|
+
```xml
|
|
23
|
+
<key>ProgramArguments</key>
|
|
24
|
+
<array>
|
|
25
|
+
<string>/opt/homebrew/bin/uv</string>
|
|
26
|
+
<string>run</string>
|
|
27
|
+
<string>things-api</string>
|
|
28
|
+
</array>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Note:** launchd does not inherit your shell's `PATH`, so you must use the full path to `uv`.
|
|
32
|
+
|
|
33
|
+
### 2. Install and load
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
cp com.things-api.server.plist ~/Library/LaunchAgents/
|
|
37
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.things-api.server.plist
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Verify
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
curl -s http://localhost:5225/health -H "Authorization: Bearer YOUR_TOKEN"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You should see `{"status":"healthy","read":true,...}`.
|
|
47
|
+
|
|
48
|
+
### 4. Stop and unload
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
launchctl bootout gui/$(id -u)/com.things-api.server
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To reload after editing the plist, bootout then bootstrap again.
|
|
55
|
+
|
|
56
|
+
### Logs
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
tail -f /tmp/things-api.log
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### How it works
|
|
63
|
+
|
|
64
|
+
- **`WorkingDirectory`** — launchd starts the process in your project directory, so `pydantic-settings` automatically loads the `.env` file. No tokens need to be hardcoded in the plist.
|
|
65
|
+
- **`KeepAlive: true`** — restarts the process if it crashes.
|
|
66
|
+
- **`RunAtLoad: true`** — starts when you log in.
|
|
67
|
+
|
|
68
|
+
## n8n integration
|
|
69
|
+
|
|
70
|
+
Things API works well as a backend for [n8n](https://n8n.io/) workflows. Use an **HTTP Request** node:
|
|
71
|
+
|
|
72
|
+
| Setting | Value |
|
|
73
|
+
|---|---|
|
|
74
|
+
| Method | `GET` / `POST` / `PUT` / `DELETE` |
|
|
75
|
+
| URL | `http://<host>:5225/today` (or any endpoint) |
|
|
76
|
+
| Authentication | Header Auth |
|
|
77
|
+
| Header Name | `Authorization` |
|
|
78
|
+
| Header Value | `Bearer YOUR_TOKEN_HERE` |
|
|
79
|
+
| Response Format | JSON |
|
|
80
|
+
|
|
81
|
+
For write operations, set the body content type to JSON and pass the relevant fields (e.g. `{"title": "New task", "when": "today"}`).
|
|
82
|
+
|
|
83
|
+
## Network access
|
|
84
|
+
|
|
85
|
+
By default, the server binds to `0.0.0.0`, making it accessible on all network interfaces. To restrict to localhost only, set `THINGS_API_HOST=127.0.0.1` in your `.env`.
|
|
86
|
+
|
|
87
|
+
For remote access (e.g. from another machine on your network or via [Tailscale](https://tailscale.com/)), the default `0.0.0.0` binding works — just ensure the port (default `5225`) is accessible and use your machine's IP or Tailscale hostname in requests.
|