--- name: vectal-api description: Manage tasks, projects, notes and habits in Vectal (vectal.ai) via the vectal CLI or REST API. Use when the user asks to create, list, update, complete, delete or search their Vectal tasks, projects, notes or habits, or to check their Vectal account. --- # Vectal API Skill Vectal is an AI-powered task management app. This skill lets you manage the user's Vectal account — tasks, projects, notes, habits and search — through two interchangeable interfaces: 1. **The `vectal` CLI** — preferred when Node.js is available. 2. **The REST API** — always available, works with plain `curl`. The latest version of this skill is always at https://www.vectal.ai/skill ## Setup & auth - Everything authenticates with a Vectal API key, format `vec-{uuid}`. The user creates one in the Vectal app under **Settings → API Keys**. - Read the key from the `VECTAL_API_KEY` environment variable. Never hardcode, print, log or commit it. - Verify the key before doing real work: `vectal whoami` (CLI) or `GET /api/v1/me` (API). - If no key is available (env var unset, no CLI config), stop and ask the user for one — don't guess. ## Option 1 — the `vectal` CLI (preferred) Install with `npm install -g vectal`, or run ad-hoc with `npx vectal`. Every command and subcommand supports `--help`. ```bash # Auth vectal login --key vec-... # save key to ~/.config/vectal/config.json (chmod 600) vectal whoami # verify key; prints account email + plan # Tasks vectal task list [--project ] [--status active,completed,archived] [--limit ] vectal task get vectal task add "" [--importance 1-100] [--due ] [--context ""] \ [--project ] [--recur "every monday"] [--timezone ] vectal task update [--name ...] [--importance ...] [--due ...] [--context ...] [--project ...] \ [--status ...] [--kanban to-do|in-progress|testing|completed] [--recur ...] [--timezone ...] vectal task done # complete; recurring tasks auto-schedule the next occurrence vectal task rm # archive (soft delete) vectal task batch changes.json # up to 500 creates/updates/completes/deletes in ONE call (or pipe JSON to stdin) # Projects vectal project list [--status active,archived] vectal project get vectal project add "" [--description ""] [--color "#2563EB"] [--context ""] vectal project update [--name ...] [--description ...] [--color ...] [--context ...] [--status active|archived] vectal project rm # archive # Notes vectal note list vectal note get vectal note add "" [--content "<markdown>"] [--importance 1-100] [--project <id>] [--pin] vectal note update <id> [--name ...] [--content ...] [--importance ...] [--project ...] [--pin | --no-pin] vectal note rm <id> # Habits vectal habit list [--days 1-90] [--timezone <IANA>] # includes streaks + per-day activity vectal habit add "<name>" --recur "daily" [--description "<text>"] vectal habit update <id> [--name ...] [--description ...] [--recur ...] vectal habit check <id> [--date YYYY-MM-DD] [--timezone <IANA>] # idempotent vectal habit uncheck <id> [--date YYYY-MM-DD] [--timezone <IANA>] # idempotent vectal habit rm <id> # archive # Search vectal search "<query>" # full-text across tasks, notes and projects ``` CLI behavior: - Add `--json` to any command for the raw API response — use it whenever you need to parse output. - Exit code 0 = success, 1 = error. Error messages go to stderr. - Network errors, `429` and `5xx` are retried automatically (honoring `Retry-After`), and every write carries an idempotency key, so a retry never applies a change twice. - Key resolution order: `VECTAL_API_KEY` env var, then `~/.config/vectal/config.json`. - `VECTAL_API_URL` overrides the API host (e.g. `http://localhost:8000` for local dev). ## Option 2 — REST API Base URL: `https://api.vectal.ai/api/v1` Every request needs `Authorization: Bearer $VECTAL_API_KEY`. Request bodies are JSON (`Content-Type: application/json`). ```bash # List active tasks curl -s -H "Authorization: Bearer $VECTAL_API_KEY" \ "https://api.vectal.ai/api/v1/tasks?status=active" # Create a task curl -s -X POST https://api.vectal.ai/api/v1/tasks \ -H "Authorization: Bearer $VECTAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Review PR #42", "importance": 75, "due_date": "2026-07-10T09:00:00Z"}' ``` A machine-readable OpenAPI schema is public (no auth): `GET https://api.vectal.ai/api/v1/openapi.json` ### Rate limits 100 requests per minute per account, shared across all endpoints. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the window resets). On `429`, wait the seconds given in `Retry-After` before retrying. The limit counts requests, not tasks: send bulk changes as one `POST /tasks/batch` call. ### Bulk changes and safe retries `POST /tasks/batch` takes up to 500 changes in one call and applies them all-or-nothing: if any item is invalid, nothing is written and the error names the item (e.g. `update[3]: ...`). Results come back in input order. ```json { "create": [{ "name": "Draft launch post", "importance": 80 }], "update": [{ "id": "<task-id>", "due_date": "2026-07-10T09:00:00Z" }], "complete": ["<task-id>"], "delete": ["<task-id>"], "timezone": "Europe/Prague" } ``` The response is `{ "data": { "created": [...], "updated": [...], "completed": [...], "deleted": [...] } }`. Send an `Idempotency-Key` header (any unique string, e.g. a UUID) on writes, and reuse it when you retry the same change. Within 24 hours a retry returns the first response (`Idempotent-Replayed: true`) instead of applying the change twice. ### Response envelope - Single item: `{ "data": { ... } }` - List: `{ "data": [ ... ], "pagination": { "page": 1, "per_page": 100, "total": 42 } }` - Error: ```json { "error": { "message": "Task not found: 6f1c...", "code": "not_found" }, "status": 404 } ``` Every error has this shape, and `message` says what to fix. Error codes: `missing_auth`, `invalid_key_format`, `invalid_key`, `user_not_found`, `auth_error`, `invalid_request`, `invalid_batch`, `invalid_recurrence`, `not_found`, `forbidden`, `rate_limited`, `idempotency_key_reused`, `idempotency_in_progress`, `create_failed`, `update_failed`, `delete_failed`, `internal_error`. HTTP statuses: `200` OK, `201` created, `400` bad request, `401` bad or missing key, `403` forbidden, `404` not found, `409` same write still running, `422` invalid body, `429` rate limited, `500` server error. ### Tasks | Method | Path | Description | |--------|------|-------------| | GET | /tasks | List tasks, sorted by importance. Query: `?project_id`, `?status` (comma-separated), `?limit` (1-1000, default 100) | | GET | /tasks/{id} | Get a single task | | POST | /tasks | Create a task (201) | | PATCH | /tasks/{id} | Update a task — send only the fields to change | | DELETE | /tasks/{id} | Archive (soft-delete) a task | | POST | /tasks/{id}/complete | Complete a task — recurring tasks auto-schedule the next occurrence | | POST | /tasks/batch | Up to 500 creates/updates/completes/deletes in one all-or-nothing call (see above) | Task fields (create requires only `name`; update accepts the same fields, all optional): | Field | Type | Notes | |-------|------|-------| | name | string | Task name (required on create) | | importance | int | 1-100, higher = more important; drives sort order in the app | | due_date | string | ISO 8601 datetime, e.g. `2026-07-10T09:00:00Z` | | context | string | Description / notes, markdown. Checklists live here as `- [ ]` checkboxes | | project_id | string | UUID of the project to assign | | status | string | `active`, `completed`, `archived` | | kanban_column | string | `to-do`, `in-progress`, `testing`, `completed` | | recurrence_text | string | `daily`, `weekly`, `monthly`, `every weekday`, `every monday and thursday`, `every 3 days`, `every 2 weeks`. `null` or `""` stops repeating; other phrases return a 400 | | timezone | string | IANA timezone (e.g. `Europe/Prague`) used to interpret dates/recurrence | ### Projects | Method | Path | Description | |--------|------|-------------| | GET | /projects | List projects. Query: `?status` (comma-separated, default `active`) | | GET | /projects/{id} | Get a single project | | POST | /projects | Create a project (201) | | PATCH | /projects/{id} | Update a project | | DELETE | /projects/{id} | Archive a project | Project fields (create requires only `name`): | Field | Type | Notes | |-------|------|-------| | name | string | Project name (required on create) | | description | string | Short description | | color | string | Hex color for the UI, e.g. `#2563EB` | | context | string | Project context / system prompt, max 16,000 chars | | status | string | `active`, `archived` (update only) | ### Notes | Method | Path | Description | |--------|------|-------------| | GET | /notes | List all active notes | | GET | /notes/{id} | Get a single note | | POST | /notes | Create a note (201) | | PATCH | /notes/{id} | Update a note | | DELETE | /notes/{id} | Soft-delete a note | Note fields (create requires only `name`): | Field | Type | Notes | |-------|------|-------| | name | string | Note title (required on create) | | context | string | Note content, markdown supported | | importance | int | 1-100, default 50 | | project_id | string | UUID of the project to assign | | is_pinned | bool | Pin the note to the top | ### Habits | Method | Path | Description | |--------|------|-------------| | GET | /habits | List habits with current streak + per-day activity. Query: `?days` (1-90, default 28), `?timezone` | | POST | /habits | Create a habit (201) — `name` and `recurrence_text` required | | PATCH | /habits/{id} | Update a habit (`name`, `description`, `recurrence_text`) | | DELETE | /habits/{id} | Archive a habit | | POST | /habits/{id}/complete | Mark completed for today. Idempotent. Query: `?date` (YYYY-MM-DD), `?timezone` | | DELETE | /habits/{id}/complete | Remove a completion. Idempotent. Same query params | Habit fields: `name` (1-200 chars, required), `recurrence_text` (required, natural language), `description` (max 500 chars). ### Search & identity | Method | Path | Description | |--------|------|-------------| | GET | /search?q= | Full-text search across tasks, notes and projects | | GET | /me | Verify the key; returns `user_id`, `email`, `plan` | Search results are `{ "id", "table_name", "name", "snippet", "due_date", "rank" }` where `table_name` is `tasks`, `notes` or `projects`. ## Tips for agents - **Verify first.** Run `vectal whoami` / `GET /me` once before a batch of operations. A 401 means a bad key — ask the user, don't retry. - **Resolve names to IDs with search.** All IDs are UUIDs. When the user says "the onboarding task", use `vectal search "onboarding"` (or list with filters) to find the ID — never guess. - **`context` updates replace the whole field.** To edit a checklist or append notes: GET the item, modify the full `context` string, PATCH it back. - **Checklists** are markdown checkboxes inside a task's `context`: `- [ ] item` / `- [x] done item`. - **Complete tasks via the complete endpoint** (`vectal task done` / `POST /tasks/{id}/complete`), not by patching `status` — it handles recurring tasks correctly. Never recreate a recurring task manually. - **Pass `timezone`** (IANA name) whenever you set due dates or recurrence from natural language, and when checking habits — day boundaries follow the user's local day, not UTC. - **Backfill habits** with `?date=YYYY-MM-DD` (or `--date`); check/uncheck are idempotent so retries are safe. - **Deletes are soft** — tasks, projects and habits are archived, notes are soft-deleted. Still, confirm with the user before bulk deletions. - **Importance drives priority.** 1-100, higher = more important. Task lists come back sorted by importance. - **Stay under the rate limit** (100 req/min): read with filtered list calls, and send more than a few changes as one `vectal task batch` / `POST /tasks/batch` call instead of looping single requests.