# Ticker API

Ticker is a habit tracking and todo management app. This API lets AI agents and integrations manage habits, todos, and categories on behalf of a user.

There are two transports — pick whichever fits your client:

- **REST** at `https://ticker.tazzen.com/api/v1` — for traditional HTTP clients (curl, n8n, Zapier, custom scripts).
- **MCP** at `https://ticker.tazzen.com/mcp` — for AI assistants that speak the Model Context Protocol (Claude Desktop, ChatGPT, Cursor, etc.). The MCP server exposes the same domain capabilities as a small set of tools and inherits the same auth + scope model as REST.

## Authentication

All requests on both transports require a Bearer token:

```
Authorization: Bearer <your-token>
```

Tokens are personal access tokens issued by Tazzen Identity at `https://account.tazzen.com/profile/api-tokens`. You pick the scopes when you create one and you can revoke any token in one click. There is **no anonymous access** to write actions — every mutation is bound to a user-issued token.

## Base URLs

```
https://ticker.tazzen.com/api/v1   (REST)
https://ticker.tazzen.com/mcp      (MCP / JSON-RPC over HTTPS POST)
```

## MCP Tools

When an MCP client connects, it sees these tools. Each tool checks its own required scope on the presented token and rejects the call with an MCP error if the scope is missing.

| Tool | Scope | Read-only? | Destructive? | Purpose |
|---|---|---|---|---|
| `get-today-habits` | `habits:read` | yes | no | List the user's habits due today with streak counts |
| `get-pending-todos` | `todos:read` | yes | no | List incomplete todos (optionally overdue only) |
| `create-habit` | `habits:write` | no | no | Create a new daily habit |
| `create-todo` | `todos:write` | no | no | Create a one-shot or recurring todo |
| `toggle-todo` | `todos:write` | no | **yes** | Flip a todo between complete and incomplete |
| `update-todo` | `todos:write` | no | **yes** | Change title / description / due_date on an existing todo |
| `delete-todo` | `todos:write` | no | **yes** | Permanently delete a todo (cannot be undone) |
| `complete-habit` | `habits:write` | no | **yes** | Mark a habit done for today and bump the streak |

Destructive tools advertise the standard MCP `destructiveHint` annotation; most MCP clients render an explicit user confirmation step before calling them.

Every MCP tool invocation is recorded in a per-token action log so the token's owner can see at a glance what each agent did. The log is for human review only — it is not exposed back via the API.

### Connecting Claude Desktop

In your Claude Desktop config (`~/.config/Claude/claude_desktop_config.json` or equivalent), add:

```json
{
  "mcpServers": {
    "ticker": {
      "url": "https://ticker.tazzen.com/mcp",
      "headers": {
        "Authorization": "Bearer <paste-your-token-here>"
      }
    }
  }
}
```

Restart Claude Desktop and the eight tools above will be available in the conversation.

### OAuth (Claude Connectors, ChatGPT)

For AI clients that prefer OAuth over a pasted token (Claude's web Connectors UI, ChatGPT custom GPTs, Cursor's remote MCP), paste `https://ticker.tazzen.com/mcp` as the connector URL. The client will:

1. Fetch `https://ticker.tazzen.com/.well-known/oauth-protected-resource` to discover the authorization server (account.tazzen.com).
2. Walk the standard OAuth 2.1 + PKCE flow at account.tazzen.com, where you'll see the consent screen and pick which scopes the connector gets.
3. Receive a token bound to the scopes you approved — same auth model as a PAT, same tools, same per-token action log.

No client-side config beyond the URL. The 401 from `/mcp` advertises the discovery endpoint via `WWW-Authenticate: Bearer realm="ticker", resource_metadata="..."` per RFC 9728, so any spec-compliant MCP client auto-discovers.

## REST endpoints

### Dashboard

```
GET /api/v1/dashboard
```

Returns today's habits, pending todos, activity history, and stats.

**Scopes required:** `habits:read`, `todos:read`

**Response:**
```json
{
  "habits": [...],
  "todos": [...],
  "activity_strip": {"2026-03-24": 3, "2026-03-23": 5},
  "stats": {
    "total_habits": 5,
    "completed_today": 2,
    "total_streak": 14,
    "pending_todos": 3
  }
}
```

### Habits

Habits are recurring habits (daily, weekly, or custom). They track streaks with forgiving grace periods (a missed day or two doesn't break the streak).

| Method | Path | Scope | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/habits` | `habits:read` | List habits (paginated) |
| GET | `/api/v1/habits/{id}` | `habits:read` | Get a habit |
| POST | `/api/v1/habits` | `habits:write` | Create a habit |
| PUT | `/api/v1/habits/{id}` | `habits:write` | Update a habit |
| DELETE | `/api/v1/habits/{id}` | `habits:write` | Delete a habit |
| POST | `/api/v1/habits/{id}/complete` | `habits:write` | Mark completed today |
| DELETE | `/api/v1/habits/{id}/complete` | `habits:write` | Undo today's completion |

**Filters:** `?frequency=daily|weekly|custom` `?time_of_day=morning|afternoon|evening` `?completed_today=true|false`

**Create/update body:**
```json
{
  "name": "Morning meditation",
  "type": "good",
  "frequency": "daily",
  "time_of_day": "morning",
  "category_id": 1,
  "description": "10 minutes of mindfulness",
  "grace_days": 1
}
```

Required: `name`, `type` (good/bad), `frequency` (daily/weekly/custom).

**Habit response:**
```json
{
  "id": 1,
  "name": "Morning meditation",
  "type": "good",
  "frequency": "daily",
  "time_of_day": "morning",
  "streak_count": 14,
  "grace_days": 1,
  "is_completed_today": true,
  "completions_count": 42,
  "category": {"id": 1, "name": "Health", "color": "#FF5733"},
  "created_at": "2026-01-15T08:00:00.000000Z"
}
```

### Todos

Todos are one-time tasks with optional due dates, time estimates, and contexts.

| Method | Path | Scope | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/todos` | `todos:read` | List todos (paginated, default: incomplete) |
| GET | `/api/v1/todos/{id}` | `todos:read` | Get a todo |
| POST | `/api/v1/todos` | `todos:write` | Create a todo |
| PUT | `/api/v1/todos/{id}` | `todos:write` | Update a todo |
| DELETE | `/api/v1/todos/{id}` | `todos:write` | Delete a todo |
| POST | `/api/v1/todos/{id}/toggle` | `todos:write` | Toggle completion |
| PUT | `/api/v1/todos/{id}/reminder` | `todos:write` | Set or update the calling user's per-todo reminder time |
| DELETE | `/api/v1/todos/{id}/reminder` | `todos:write` | Clear the calling user's per-todo reminder |

**Filters:** `?status=incomplete|completed|all` `?due=today|overdue|upcoming` `?context=...` `?time_of_day=morning|afternoon|evening`

**Create/update body:**
```json
{
  "title": "Call dentist",
  "due_date": "2026-03-25",
  "time_of_day": "morning",
  "estimated_minutes": 15,
  "context": "phone",
  "category_id": 1
}
```

Required: `title`.

**Todo response:**
```json
{
  "id": 30,
  "title": "Call dentist",
  "due_date": "2026-03-25",
  "time_of_day": "morning",
  "estimated_minutes": 15,
  "context": "phone",
  "is_completed": false,
  "is_overdue": false,
  "category": {"id": 1, "name": "Health", "color": "#FF5733"},
  "created_at": "2026-03-24T14:00:00.000000Z"
}
```

### Categories

Categories group habits and todos by theme.

| Method | Path | Scope | Description |
|--------|------|-------|-------------|
| GET | `/api/v1/categories` | `categories:read` | List categories |
| GET | `/api/v1/categories/{id}` | `categories:read` | Get a category |
| POST | `/api/v1/categories` | `categories:write` | Create a category |
| PUT | `/api/v1/categories/{id}` | `categories:write` | Update a category |
| DELETE | `/api/v1/categories/{id}` | `categories:write` | Delete a category |

**Create/update body:**
```json
{
  "name": "Health",
  "color": "#FF5733",
  "icon": "heart"
}
```

Required: `name`.

## Pagination

List endpoints return paginated results (15 per page). Use `?page=N` to navigate.

```json
{
  "data": [...],
  "links": {"first": "...", "last": "...", "prev": null, "next": "..."},
  "meta": {"current_page": 1, "last_page": 3, "per_page": 15, "total": 42}
}
```

## Errors

| Status | Meaning |
|--------|---------|
| 401 | Missing, invalid, or expired token |
| 403 | Insufficient scope |
| 404 | Resource not found |
| 409 | Conflict (e.g. habit already completed today) |
| 422 | Validation error |
| 429 | Rate limited (60 requests/minute) |

**Validation error format:**
```json
{
  "message": "The title field is required.",
  "errors": {"title": ["The title field is required."]}
}
```

## Scopes

| Scope | Grants |
|-------|--------|
| `habits:read` | List and view habits |
| `habits:write` | Create, update, delete, complete habits |
| `todos:read` | List and view todos |
| `todos:write` | Create, update, delete, toggle todos |
| `categories:read` | List and view categories |
| `categories:write` | Create, update, delete categories |
