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.
Files changed (47) hide show
  1. things_api-0.1.0/.gitignore +7 -0
  2. things_api-0.1.0/CHANGELOG.md +28 -0
  3. things_api-0.1.0/LICENSE +21 -0
  4. things_api-0.1.0/PKG-INFO +134 -0
  5. things_api-0.1.0/README.md +104 -0
  6. things_api-0.1.0/com.things-api.server.plist +58 -0
  7. things_api-0.1.0/docs/api-reference.md +207 -0
  8. things_api-0.1.0/docs/configuration.md +32 -0
  9. things_api-0.1.0/docs/deployment.md +87 -0
  10. things_api-0.1.0/docs/development.md +49 -0
  11. things_api-0.1.0/docs/plans/2026-04-03-things-api-design.md +198 -0
  12. things_api-0.1.0/docs/plans/2026-04-03-things-api-plan.md +1529 -0
  13. things_api-0.1.0/docs/plans/2026-04-03-things-api-plan.md.tasks.json +21 -0
  14. things_api-0.1.0/env.example +6 -0
  15. things_api-0.1.0/pyproject.toml +44 -0
  16. things_api-0.1.0/src/things_api/__init__.py +1 -0
  17. things_api-0.1.0/src/things_api/app.py +98 -0
  18. things_api-0.1.0/src/things_api/auth.py +26 -0
  19. things_api-0.1.0/src/things_api/config.py +29 -0
  20. things_api-0.1.0/src/things_api/models.py +117 -0
  21. things_api-0.1.0/src/things_api/ratelimit.py +55 -0
  22. things_api-0.1.0/src/things_api/routers/__init__.py +0 -0
  23. things_api-0.1.0/src/things_api/routers/areas.py +16 -0
  24. things_api-0.1.0/src/things_api/routers/lists.py +51 -0
  25. things_api-0.1.0/src/things_api/routers/projects.py +111 -0
  26. things_api-0.1.0/src/things_api/routers/search.py +61 -0
  27. things_api-0.1.0/src/things_api/routers/tags.py +22 -0
  28. things_api-0.1.0/src/things_api/routers/todos.py +133 -0
  29. things_api-0.1.0/src/things_api/services/__init__.py +0 -0
  30. things_api-0.1.0/src/things_api/services/reader.py +73 -0
  31. things_api-0.1.0/src/things_api/services/writer.py +86 -0
  32. things_api-0.1.0/tests/__init__.py +0 -0
  33. things_api-0.1.0/tests/conftest.py +57 -0
  34. things_api-0.1.0/tests/test_app.py +54 -0
  35. things_api-0.1.0/tests/test_auth.py +41 -0
  36. things_api-0.1.0/tests/test_config.py +72 -0
  37. things_api-0.1.0/tests/test_models.py +75 -0
  38. things_api-0.1.0/tests/test_ratelimit.py +32 -0
  39. things_api-0.1.0/tests/test_reader.py +94 -0
  40. things_api-0.1.0/tests/test_router_areas.py +33 -0
  41. things_api-0.1.0/tests/test_router_lists.py +69 -0
  42. things_api-0.1.0/tests/test_router_projects.py +57 -0
  43. things_api-0.1.0/tests/test_router_search.py +46 -0
  44. things_api-0.1.0/tests/test_router_tags.py +36 -0
  45. things_api-0.1.0/tests/test_router_todos.py +57 -0
  46. things_api-0.1.0/tests/test_writer.py +94 -0
  47. things_api-0.1.0/uv.lock +616 -0
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .env
7
+ TODO.md
@@ -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
@@ -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.