924b66bc64
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
922 lines
20 KiB
Markdown
922 lines
20 KiB
Markdown
# Opal-Task API Reference
|
|
|
|
REST API for the opal task manager. Built with Go and [chi](https://github.com/go-chi/chi) router, backed by SQLite.
|
|
|
|
**Base URL:** `http://localhost:8080` (default) or behind a reverse proxy at `/api`
|
|
|
|
## Table of Contents
|
|
|
|
- [Authentication](#authentication)
|
|
- [Response Format](#response-format)
|
|
- [Endpoints](#endpoints)
|
|
- [Health](#health)
|
|
- [OAuth](#oauth)
|
|
- [Tasks](#tasks)
|
|
- [Tags](#tags)
|
|
- [Projects](#projects)
|
|
- [Sync](#sync)
|
|
- [API Keys](#api-keys)
|
|
- [Data Models](#data-models)
|
|
- [Reports](#reports)
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
The API supports two authentication methods:
|
|
|
|
### API Key
|
|
|
|
Generate a key with the CLI, then pass it as a Bearer token:
|
|
|
|
```bash
|
|
opal server keygen --name "My Phone"
|
|
# Output: oak_aBcDeFgH... (shown once, save it)
|
|
```
|
|
|
|
```
|
|
Authorization: Bearer oak_aBcDeFgH...
|
|
```
|
|
|
|
Keys are bcrypt-hashed at rest. The `oak_` prefix identifies opal API keys.
|
|
|
|
### OAuth / JWT
|
|
|
|
When OAuth is enabled, authenticate through the [login flow](#get-authlogin) to receive a JWT:
|
|
|
|
```
|
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
|
```
|
|
|
|
JWTs are HS256-signed, issued by `opal-task`, and expire after 1 hour by default (configurable via `JWT_EXPIRY`).
|
|
|
|
### Public Endpoints
|
|
|
|
These endpoints require no authentication:
|
|
|
|
| Endpoint | Description |
|
|
|---|---|
|
|
| `GET /health` | Health check |
|
|
| `GET /auth/login` | Get OAuth login URL |
|
|
| `POST /auth/callback` | OAuth code exchange |
|
|
| `POST /auth/refresh` | Refresh access token |
|
|
| `POST /auth/logout` | Revoke refresh token |
|
|
|
|
All other endpoints require a valid `Authorization: Bearer <token>` header.
|
|
|
|
---
|
|
|
|
## Response Format
|
|
|
|
Every response follows this envelope:
|
|
|
|
### Success
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": { ... }
|
|
}
|
|
```
|
|
|
|
### Error
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "description of what went wrong"
|
|
}
|
|
```
|
|
|
|
### Conventions
|
|
|
|
- **JSON keys** are `snake_case` throughout.
|
|
- **Timestamps** are Unix seconds (integers), not ISO 8601 strings. Nullable timestamps are `null`.
|
|
- **Durations** (e.g., `recurrence_duration`) are in seconds. A 1-week recurrence is `604800`.
|
|
- **Status** is a single-character string: `"P"`, `"C"`, `"D"`, or `"R"`.
|
|
|
|
### Status Codes
|
|
|
|
| Code | Meaning |
|
|
|---|---|
|
|
| `200` | Success |
|
|
| `201` | Resource created |
|
|
| `400` | Invalid input (bad JSON, missing required fields, invalid UUID) |
|
|
| `401` | Missing or invalid authentication |
|
|
| `404` | Resource not found |
|
|
| `500` | Server error |
|
|
| `501` | Feature disabled (e.g., OAuth not configured) |
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### Health
|
|
|
|
#### `GET /health`
|
|
|
|
Returns server status. No authentication required.
|
|
|
|
```bash
|
|
curl http://localhost:8080/health
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"status": "ok"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### OAuth
|
|
|
|
#### `GET /auth/login`
|
|
|
|
Returns the OAuth authorization URL for redirecting the user to the identity provider.
|
|
|
|
```bash
|
|
curl http://localhost:8080/auth/login
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"url": "https://auth.example.com/application/o/authorize/?client_id=...&state=abc123",
|
|
"state": "abc123"
|
|
}
|
|
}
|
|
```
|
|
|
|
Returns `501` if OAuth is not enabled.
|
|
|
|
---
|
|
|
|
#### `POST /auth/callback`
|
|
|
|
Exchanges an OAuth authorization code for access and refresh tokens. The `code` parameter comes from the OAuth provider's redirect.
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `code` | string | yes | Authorization code from OAuth provider |
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:8080/auth/callback?code=AUTH_CODE_HERE"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"refresh_token": "dGhpcyBpcyBhIHJlZnJl...",
|
|
"expires_at": 1739700000,
|
|
"token_type": "Bearer",
|
|
"user": {
|
|
"id": 1,
|
|
"username": "alice",
|
|
"email": "alice@example.com"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /auth/refresh`
|
|
|
|
Exchanges a valid refresh token for a new access token.
|
|
|
|
**Request body:**
|
|
|
|
```json
|
|
{
|
|
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
|
|
}
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"expires_at": 1739703600,
|
|
"token_type": "Bearer"
|
|
}
|
|
}
|
|
```
|
|
|
|
Returns `401` if the refresh token is invalid or expired.
|
|
|
|
---
|
|
|
|
#### `POST /auth/logout`
|
|
|
|
Revokes a refresh token, preventing further use.
|
|
|
|
**Request body:**
|
|
|
|
```json
|
|
{
|
|
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
|
|
}
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"message": "logged out"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Tasks
|
|
|
|
#### `GET /tasks`
|
|
|
|
Lists tasks, either by named report or by filter parameters.
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `report` | string | no | Named report (see [Reports](#reports)). Overrides filter params. |
|
|
| `status` | string | no | Filter by status: `pending`, `completed`, `deleted`, `recurring` |
|
|
| `project` | string | no | Filter by project name |
|
|
| `priority` | string | no | Filter by priority: `L`, `D`, `M`, `H` |
|
|
| `tag` | string[] | no | Filter by tags (repeat for multiple: `?tag=home&tag=urgent`) |
|
|
|
|
**With report:**
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" \
|
|
"http://localhost:8080/tasks?report=overdue"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"report": "overdue",
|
|
"tasks": [ ... ],
|
|
"count": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
**With filters:**
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" \
|
|
"http://localhost:8080/tasks?status=pending&tag=home&priority=H"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"id": 42,
|
|
"status": "P",
|
|
"description": "Fix leaking faucet",
|
|
"project": "house",
|
|
"priority": 3,
|
|
"created": 1739174400,
|
|
"modified": 1739545800,
|
|
"start": null,
|
|
"end": null,
|
|
"due": 1740009600,
|
|
"scheduled": null,
|
|
"wait": null,
|
|
"until": null,
|
|
"recurrence_duration": null,
|
|
"parent_uuid": null,
|
|
"tags": ["home", "urgent"],
|
|
"urgency": 12.4
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /tasks`
|
|
|
|
Creates a new task using structured JSON fields.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `description` | string | yes | Task description |
|
|
| `tags` | string[] | no | Tags to attach |
|
|
| `project` | string | no | Project name |
|
|
| `priority` | string | no | `L` (low), `D` (default), `M` (medium), `H` (high) |
|
|
| `due` | int64 | no | Due date as Unix timestamp (seconds) |
|
|
| `scheduled` | int64 | no | Scheduled date as Unix timestamp |
|
|
| `wait` | int64 | no | Wait-until date as Unix timestamp |
|
|
| `until` | int64 | no | Expiration date as Unix timestamp |
|
|
| `recurrence` | string | no | Recurrence interval (e.g., `1d`, `2w`, `3m`, `1y`) |
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/tasks \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"description": "Buy groceries",
|
|
"tags": ["personal", "errands"],
|
|
"project": "household",
|
|
"priority": "M",
|
|
"due": 1739836800
|
|
}'
|
|
```
|
|
|
|
**Response** (`201 Created`):
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
|
"id": 43,
|
|
"status": "P",
|
|
"description": "Buy groceries",
|
|
"project": "household",
|
|
"priority": 2,
|
|
"created": 1739750400,
|
|
"modified": 1739750400,
|
|
"due": 1739836800,
|
|
"tags": ["personal", "errands"],
|
|
"urgency": 6.1
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /tasks/parse`
|
|
|
|
Creates a task from a CLI-style input string. Supports the same syntax as the `opal add` command: words without special prefixes become the description, `+tag` adds tags, `-tag` removes tags, and `key:value` pairs set attributes.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `input` | string | yes | CLI-style task input |
|
|
|
|
**Modifier syntax:**
|
|
|
|
| Syntax | Meaning | Example |
|
|
|---|---|---|
|
|
| `+tag` | Add tag | `+home` |
|
|
| `-tag` | Remove tag | `-garden` |
|
|
| `project:name` | Set project | `project:household` |
|
|
| `priority:X` | Set priority | `priority:H` |
|
|
| `due:value` | Set due date | `due:tomorrow`, `due:monday`, `due:2026-03-01` |
|
|
| `scheduled:value` | Set scheduled date | `scheduled:nextweek` |
|
|
| `wait:value` | Set wait date | `wait:friday` |
|
|
| `until:value` | Set expiration | `until:eom` |
|
|
| `recur:interval` | Set recurrence | `recur:1w`, `recur:2d` |
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/tasks/parse \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"input": "Change bed sheets +home project:household due:sunday recur:1w"}'
|
|
```
|
|
|
|
**Response** (`201 Created`):
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"task": {
|
|
"uuid": "c9bf9e57-1685-4c89-bafb-ff5af830be8a",
|
|
"id": 44,
|
|
"status": "P",
|
|
"description": "Change bed sheets",
|
|
"project": "household",
|
|
"priority": 1,
|
|
"created": 1739750400,
|
|
"modified": 1739750400,
|
|
"due": 1739836800,
|
|
"recurrence_duration": 604800,
|
|
"parent_uuid": "d4e5f6a7-b8c9-0123-4567-890abcdef012",
|
|
"tags": ["home"],
|
|
"urgency": 5.3
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
For recurring tasks (those with `recur:`), the API creates a template task (status `"R"`) and returns the first instance.
|
|
|
|
---
|
|
|
|
#### `GET /tasks/{uuid}`
|
|
|
|
Returns a single task by its UUID.
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"id": 42,
|
|
"status": "P",
|
|
"description": "Fix leaking faucet",
|
|
"project": "house",
|
|
"priority": 3,
|
|
"created": 1739174400,
|
|
"modified": 1739545800,
|
|
"due": 1740009600,
|
|
"tags": ["home", "urgent"],
|
|
"urgency": 12.4
|
|
}
|
|
}
|
|
```
|
|
|
|
Returns `404` if the UUID does not match any task.
|
|
|
|
---
|
|
|
|
#### `PUT /tasks/{uuid}`
|
|
|
|
Updates one or more fields on an existing task. Only include the fields you want to change.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Type | Description |
|
|
|---|---|---|
|
|
| `description` | string | New description |
|
|
| `status` | string | New status: `pending`, `completed`, `deleted` |
|
|
| `priority` | string | `L`, `D`, `M`, `H` |
|
|
| `project` | string | Project name |
|
|
| `due` | int64 | Unix timestamp (seconds) |
|
|
| `scheduled` | int64 | Unix timestamp |
|
|
| `wait` | int64 | Unix timestamp |
|
|
| `until` | int64 | Unix timestamp |
|
|
| `start` | int64 | Unix timestamp |
|
|
| `recurrence` | string | Recurrence interval |
|
|
|
|
All fields are optional.
|
|
|
|
```bash
|
|
curl -X PUT http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"priority": "H", "due": 1739923200}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"id": 42,
|
|
"status": "P",
|
|
"description": "Fix leaking faucet",
|
|
"project": "house",
|
|
"priority": 3,
|
|
"created": 1739174400,
|
|
"modified": 1739750400,
|
|
"due": 1739923200,
|
|
"tags": ["home", "urgent"],
|
|
"urgency": 14.7
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `DELETE /tasks/{uuid}`
|
|
|
|
Deletes a task. By default, sets the task status to `"D"` (soft delete). Pass `permanent=true` to remove it from the database entirely.
|
|
|
|
**Query parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|---|---|---|---|
|
|
| `permanent` | string | `false` | Set to `true` for permanent deletion |
|
|
|
|
```bash
|
|
# Soft delete
|
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
|
|
# Permanent delete
|
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
|
|
"http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890?permanent=true"
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"message": "task deleted"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /tasks/{uuid}/complete`
|
|
|
|
Marks a task as completed. Sets the status to `"C"` and records the completion time. For recurring task instances, this may trigger creation of the next instance.
|
|
|
|
```bash
|
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890/complete
|
|
```
|
|
|
|
**Response:** The updated task object with `"status": "C"`.
|
|
|
|
---
|
|
|
|
#### `POST /tasks/{uuid}/start`
|
|
|
|
Marks a task as actively being worked on by setting its `start` timestamp to now.
|
|
|
|
```bash
|
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890/start
|
|
```
|
|
|
|
**Response:** The updated task object with `start` set to the current unix timestamp.
|
|
|
|
---
|
|
|
|
#### `POST /tasks/{uuid}/stop`
|
|
|
|
Clears the `start` timestamp, marking the task as no longer actively being worked on.
|
|
|
|
```bash
|
|
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890/stop
|
|
```
|
|
|
|
**Response:** The updated task object with `start` cleared to `null`.
|
|
|
|
---
|
|
|
|
### Task Tags
|
|
|
|
#### `GET /tasks/{uuid}/tags`
|
|
|
|
Returns the tag list for a specific task.
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890/tags
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": ["home", "urgent"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /tasks/{uuid}/tags`
|
|
|
|
Adds a tag to a task.
|
|
|
|
**Request body:**
|
|
|
|
```json
|
|
{
|
|
"tag": "important"
|
|
}
|
|
```
|
|
|
|
**Response:** The updated task object.
|
|
|
|
---
|
|
|
|
#### `DELETE /tasks/{uuid}/tags/{tag}`
|
|
|
|
Removes a tag from a task.
|
|
|
|
```bash
|
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/tasks/a1b2c3d4-e5f6-7890-abcd-ef1234567890/tags/urgent
|
|
```
|
|
|
|
**Response:** The updated task object.
|
|
|
|
---
|
|
|
|
### Tags
|
|
|
|
#### `GET /tags`
|
|
|
|
Returns all tags used across all tasks.
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/tags
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": ["errands", "home", "important", "personal", "urgent", "work"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Projects
|
|
|
|
#### `GET /projects`
|
|
|
|
Returns all project names used across all tasks.
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/projects
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": ["household", "work", "garden"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Sync
|
|
|
|
These endpoints power the multi-device sync protocol. The CLI client uses them via `opal sync` commands.
|
|
|
|
#### `POST /sync/changes`
|
|
|
|
Returns all changes recorded since a given timestamp. Used by clients to pull updates from the server.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `since` | int64 | yes | Unix timestamp (seconds). Return changes after this time. Use `0` for initial sync. |
|
|
| `client_id` | string | yes | Unique identifier for the syncing device |
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/sync/changes \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"since": 1739600000, "client_id": "phone-abc123"}'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": 101,
|
|
"task_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"change_type": "create",
|
|
"changed_at": 1739650000,
|
|
"data": "description:Buy groceries\nstatus:80\npriority:2"
|
|
},
|
|
{
|
|
"id": 102,
|
|
"task_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"change_type": "update",
|
|
"changed_at": 1739660000,
|
|
"data": "status:67"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
The `change_type` is one of `create`, `update`, or `delete`. The `data` field uses a `key:value` format with newline separators, recorded by database triggers on every task mutation. Note that change log data uses raw database values (integer status codes), not the API's serialized format.
|
|
|
|
---
|
|
|
|
#### `POST /sync/push`
|
|
|
|
Pushes local task changes to the server. Conflicts are resolved with **last-write-wins** based on the `modified` timestamp.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `tasks` | Task[] | yes | Array of full task objects to push |
|
|
| `client_id` | string | yes | Unique identifier for the syncing device |
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/sync/push \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"client_id": "phone-abc123",
|
|
"tasks": [
|
|
{
|
|
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
"status": "P",
|
|
"description": "Buy groceries",
|
|
"priority": 2,
|
|
"created": 1739600000,
|
|
"modified": 1739650000,
|
|
"tags": ["personal"]
|
|
}
|
|
]
|
|
}'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"processed": 1,
|
|
"conflicts": 0
|
|
}
|
|
}
|
|
```
|
|
|
|
- **processed** — number of tasks successfully applied
|
|
- **conflicts** — number of tasks where the server had a newer version (still applied via last-write-wins)
|
|
|
|
---
|
|
|
|
### API Keys
|
|
|
|
#### `GET /auth/keys`
|
|
|
|
Lists all API keys for the current user. The key value itself is not returned (it is only shown once at creation time).
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/auth/keys
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"id": 1,
|
|
"name": "My Phone",
|
|
"user_id": 1,
|
|
"created_at": 1736935200,
|
|
"last_used": 1739558400,
|
|
"revoked": false
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `DELETE /auth/keys/{id}`
|
|
|
|
Revokes an API key. The key becomes immediately unusable.
|
|
|
|
**URL parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
|---|---|---|
|
|
| `id` | int | API key ID |
|
|
|
|
```bash
|
|
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
|
|
http://localhost:8080/auth/keys/1
|
|
```
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"message": "API key revoked"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### Task
|
|
|
|
| Field | Type | Description |
|
|
|---|---|---|
|
|
| `uuid` | string | Unique identifier (UUID v4) |
|
|
| `id` | int | Auto-increment database ID |
|
|
| `status` | string | `"P"` (pending), `"C"` (completed), `"D"` (deleted), `"R"` (recurring) |
|
|
| `description` | string | Task description |
|
|
| `project` | string \| null | Project name |
|
|
| `priority` | int | `0` = Low, `1` = Default, `2` = Medium, `3` = High |
|
|
| `created` | int | Unix timestamp (seconds) |
|
|
| `modified` | int | Unix timestamp (seconds) |
|
|
| `start` | int \| null | When the task was started (actively being worked on) |
|
|
| `end` | int \| null | When the task was completed or deleted |
|
|
| `due` | int \| null | Deadline |
|
|
| `scheduled` | int \| null | Earliest date the task is actionable |
|
|
| `wait` | int \| null | Task is hidden until this date |
|
|
| `until` | int \| null | Task auto-deletes after this date |
|
|
| `recurrence_duration` | int \| null | Recurrence interval in seconds (e.g., `604800` = 1 week) |
|
|
| `parent_uuid` | string \| null | UUID of the recurring template task |
|
|
| `tags` | string[] | Attached tags |
|
|
| `urgency` | float | Computed urgency score (higher = more urgent) |
|
|
|
|
#### Status Values
|
|
|
|
| Value | Meaning |
|
|
|---|---|
|
|
| `"P"` | Pending — active, not yet completed |
|
|
| `"C"` | Completed |
|
|
| `"D"` | Deleted (soft delete) |
|
|
| `"R"` | Recurring template |
|
|
|
|
#### Priority Values
|
|
|
|
| API Input | Numeric Value | Meaning |
|
|
|---|---|---|
|
|
| `L` | `0` | Low |
|
|
| `D` | `1` | Default |
|
|
| `M` | `2` | Medium |
|
|
| `H` | `3` | High |
|
|
|
|
Use the letter codes (`L`, `D`, `M`, `H`) when creating or updating tasks. Responses return the numeric value.
|
|
|
|
---
|
|
|
|
## Reports
|
|
|
|
Named reports return pre-filtered, pre-sorted task lists. Pass the report name as `?report=<name>` on `GET /tasks`.
|
|
|
|
| Report | Description |
|
|
|---|---|
|
|
| `active` | Tasks that have been started (pending with a `start` time) |
|
|
| `all` | All tasks including recurring templates |
|
|
| `completed` | Completed tasks |
|
|
| `list` | Pending tasks (default view) |
|
|
| `minimal` | Minimal output view |
|
|
| `newest` | Pending tasks sorted newest first |
|
|
| `next` | Next task due |
|
|
| `oldest` | Pending tasks sorted oldest first |
|
|
| `overdue` | Tasks past their due date |
|
|
| `ready` | Tasks ready to work on (past scheduled date, not waiting) |
|
|
| `recurring` | Recurring template tasks |
|
|
| `template` | Alias for `recurring` |
|
|
| `waiting` | Tasks with a future `wait` date |
|
|
|
|
---
|
|
|
|
## CORS
|
|
|
|
The API allows cross-origin requests:
|
|
|
|
- **Origins:** `*`
|
|
- **Methods:** `GET`, `POST`, `PUT`, `DELETE`, `OPTIONS`
|
|
- **Headers:** `Content-Type`, `Authorization`
|
|
|
|
---
|
|
|
|
## Running the Server
|
|
|
|
```bash
|
|
# Build
|
|
go build -o opal main.go
|
|
|
|
# Generate an API key
|
|
./opal server keygen --name "My Device" --db /path/to/opal.db
|
|
|
|
# Start the server
|
|
./opal server start --addr :8080 --db /path/to/opal.db
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `OAUTH_ENABLED` | `false` | Enable OAuth authentication |
|
|
| `OAUTH_CLIENT_ID` | — | OAuth client ID |
|
|
| `OAUTH_CLIENT_SECRET` | — | OAuth client secret |
|
|
| `OAUTH_ISSUER` | — | OAuth issuer URL |
|
|
| `OAUTH_REDIRECT_URI` | — | OAuth redirect URI |
|
|
| `JWT_SECRET` | — | Secret for signing JWTs |
|
|
| `JWT_EXPIRY` | `3600` | JWT lifetime in seconds |
|
|
| `REFRESH_TOKEN_EXPIRY` | `604800` | Refresh token lifetime in seconds (default 7 days) |
|
|
| `OPAL_DB_PATH` | XDG data dir | Override database file path |
|
|
| `OPAL_CONFIG_DIR` | `~/.config/opal` | Config directory |
|
|
| `OPAL_DATA_DIR` | `~/.local/share/opal` | Data directory |
|