# External API Agent Guide Use this guide to build complete, reliable API clients (SDKs, wrappers, or integrations) for Time Magic. It mirrors the external API surface documented at `/docs/api` and reflects the actual request/response payloads. ## Base URL - All endpoints are served under `/api`. - The host is environment-specific (e.g., `https://time-app.purple-magic.com`). - Full URL format: `https:///api/...`. ## Authentication - Every request must include the API token in the `Authorization` header. - Format: `Authorization: Bearer ` - Tokens are user-specific and should be stored securely. ## Required Headers - `Authorization: Bearer ` - `Accept: application/json` - `Content-Type: application/json` for requests with a body. ## Access Rules - **GET** requests require an active subscription. - Without a subscription, the API returns **402 Payment Required** with: - `{ "info": "Subscription required for GET requests." }` - **POST/PUT/PATCH/DELETE** requests do **not** require a subscription. ## Rate Limits - **5 requests per minute per user**. - If exceeded, the API returns **429 Too Many Requests** with: - `{ "info": "Rate limit exceeded. Try again later." }` ## Error Handling - **401 Unauthorized**: Missing or invalid token. - `{ "errors": "Unauthorized" }` - **404 Not Found**: Resource does not exist or does not belong to the user. - `{ "errors": "Not found" }` - **422 Unprocessable Entity**: Validation errors. - `{ "errors": { ... } }` - **402 Payment Required**: Subscription required for GET requests. - `{ "info": "Subscription required for GET requests." }` - **429 Too Many Requests**: Rate limit exceeded. - `{ "info": "Rate limit exceeded. Try again later." }` ## Response Conventions - Collection endpoints return a **JSON array** of resource objects (no wrapper key). - Single-resource endpoints return a **JSON object**. - Primary identifier is `uuid` (not `id`). All `:id` route params are UUIDs. - Timestamps are ISO-8601 strings. --- # Resources ## Projects ### Fields - `uuid` (string) - `name` (string, required) - `description` (string, nullable) - `project_type` (string, one of: `tech_startup`, `other`) - `rate_cents` (integer, nullable) - `rate_currency` (string, nullable) - `created_at` (string) - `updated_at` (string) ### Endpoints - **GET** `/api/projects` - Returns all projects for the authenticated user. - **GET** `/api/projects/:id` - Returns a single project by UUID. - **POST** `/api/projects` - Body: `{ "project": { "name": "...", "description": "...", "project_type": "other", "rate_cents": 5000, "rate_currency": "USD" } }` - **PUT** `/api/projects/:id` - Body: `{ "project": { "name": "...", "description": "..." } }` - **DELETE** `/api/projects/:id` - Returns **204 No Content**. --- ## Tasks Tasks belong to projects. ### Fields - `uuid` (string) - `name` (string, required) - `description` (string, nullable) - `status` (string, one of: `pending`, `in_progress`, `completed`) - `task_type` (string, one of: `default`, `permanent`) - `data` (object, nullable) - `project_uuid` (string) - `created_at` (string) - `updated_at` (string) ### Endpoints - **GET** `/api/projects/:project_id/tasks` - Returns tasks for the given project UUID. - **GET** `/api/tasks/:id` - Returns a single task by UUID. - **POST** `/api/projects/:project_id/tasks` - Body: `{ "task": { "name": "...", "description": "...", "task_type": "default" } }` - **PUT** `/api/tasks/:id` - Body: `{ "task": { "name": "...", "description": "...", "task_type": "default" } }` - Workflow events use the `event` query param (no body required): - `event=start_tracking` — start the task and create a running time entry. - `event=stop_tracking` — stop tracking and pause the task. - `event=start_without_tracking` — start the task without creating a time entry. - `event=finish` — complete the task (also closes any current time entry). - **DELETE** `/api/tasks/:id` - Returns **204 No Content**. --- ## Activities ### Fields - `uuid` (string) - `name` (string, required) - `activity_type` (string, one of: `productive`, `leisure`, `other`) - `aasm_state` (string, one of: `pending`, `in_progress`) - `created_at` (string) - `updated_at` (string) ### Endpoints - **GET** `/api/activities` - Returns all activities for the authenticated user. - **GET** `/api/activities/:id` - Returns a single activity by UUID. - **POST** `/api/activities` - Body: `{ "activity": { "name": "...", "activity_type": "productive" } }` - **PUT** `/api/activities/:id` - Body: `{ "activity": { "name": "...", "activity_type": "productive" } }` - Workflow events use the `event` query param (no body required): - `event=start_tracking` — move to `in_progress`. - `event=stop_tracking` — move back to `pending`. - **DELETE** `/api/activities/:id` - Returns **204 No Content**. --- ## Goals Goals can belong to the user or a project. ### Fields - `uuid` (string) - `text` (string, required) - `period` (string, one of: `month`, `week`) - `month` (string, ISO-8601 date; normalized to period start) - `goalable_type` (string, `User` or `Project`) - `goalable_uuid` (string, present when the goal is attached to a user or project) - `created_at` (string) - `updated_at` (string) ### Endpoints - **GET** `/api/goals` - Returns goals for the authenticated user. - Optional query: `project_id=` to filter goals for a project. - **GET** `/api/goals/:id` - Returns a single goal by UUID. - **POST** `/api/goals` - Create a user goal: - Body: `{ "goal": { "text": "...", "period": "month", "month": "2025-10-01" } }` - Create a project goal: - Body: `{ "goal": { "text": "...", "period": "month", "month": "2025-10-01", "project_id": "" } }` - **PUT** `/api/goals/:id` - Body: `{ "goal": { "text": "...", "period": "week", "month": "2025-10-06" } }` - **DELETE** `/api/goals/:id` - Returns **204 No Content**. --- ## Time Entries Time entries can be attached to a task or activity. ### Fields - `uuid` (string) - `begin_time` (string, ISO-8601, required) - `end_time` (string, ISO-8601, nullable) - `duration` (integer seconds, nullable; populated when `end_time` is set) - `comment` (string, nullable) - `resource_type` (string, `Activity` or `Task`) - `resource_uuid` (string, UUID of the activity or task) - `created_at` (string) - `updated_at` (string) ### Endpoints - **GET** `/api/time_entries` - Returns all time entries for the authenticated user. - **GET** `/api/time_entries/:id` - Returns a single time entry by UUID. - **POST** `/api/time_entries` - Body: `{ "time_entry": { "begin_time": "...", "end_time": "...", "duration": 3600, "comment": "...", "resource_type": "task", "resource_id": "" } }` - `resource_type` should be **lowercase** (`task` or `activity`). - `resource_id` is optional; when provided it must reference a resource owned by the user. - **PUT** `/api/time_entries/:id` - Body: `{ "time_entry": { "begin_time": "...", "end_time": "...", "comment": "..." } }` - **DELETE** `/api/time_entries/:id` - Returns **204 No Content**. --- # Client Guidance (for Wrappers) When generating an SDK or wrapper, ensure you: - Add `Authorization: Bearer ` to every request. - Treat all `:id` parameters as UUID strings. - Parse collection responses as arrays (not `{ resource: [...] }`). - Handle the subscription requirement for GET requests explicitly. - Use `event` on task/activity updates for workflow transitions. - Surface validation errors directly to callers from `errors` payloads.