# API Reference

All spec endpoints are mounted under `/api` and require authentication.

## List Specs

```
GET /api/specs
```

Returns all spec artifacts with phase status summaries. Content is stripped from the response to keep the payload small.

**Query parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `attached_only` | boolean | No | `false` | When true, only return specs from attached packs or standalone artifacts |
| `space_id` | string | No | `null` | Filter by space attachment context |

Each spec in the response includes a `mode` field (`feature` or `bugfix`) indicating the spec template type.

**Response:** `200 OK`

```json
[
  {
    "id": "abc123",
    "fqn": "@myapp/spec/api-design",
    "type": "spec",
    "namespace": "myapp",
    "name": "api-design",
    "mode": "feature",
    "editable": true,
    "source_label": "project",
    "metadata": {
      "phases": {
        "requirements": {"status": "approved", "approved_at": "2026-03-18T09:00:00Z", "approved_by": null},
        "design": {"status": "draft", "approved_at": null, "approved_by": null},
        "tasks": {"status": "draft", "approved_at": null, "approved_by": null}
      }
    }
  }
]
```

---

## Generate Spec (LLM-Assisted)

```
POST /api/specs/generate
```

Generate a draft spec from a natural-language prompt or GitHub issue using the LLM. Returns YAML content for client-side review — the spec is **not** saved until the client calls `POST /api/specs`.

**Request body:**

```json
{
  "prompt": "Add retry logic to the API client with configurable backoff",
  "namespace": "myapp",
  "name": "retry-logic"
}
```

Or from a GitHub issue:

```json
{
  "issue_number": 42,
  "namespace": "myapp",
  "name": "issue-fix"
}
```

| Field | Type | Required | Constraints |
|---|---|---|---|
| `prompt` | string | No | Natural-language description. Provide either `prompt` or `issue_number` |
| `issue_number` | integer | No | GitHub issue number (must be >= 1). Fetched via `gh issue view` |
| `namespace` | string | Yes | 1-64 characters |
| `name` | string | Yes | 1-64 characters |

At least one of `prompt` or `issue_number` must be provided. If both are provided, `issue_number` takes precedence.

**Response:** `200 OK`

```json
{
  "content_yaml": "requirements: |\n  ...\ndesign: |\n  ...\ntasks:\n  - id: task-1\n    summary: ...\n",
  "creation_flow": "prompt"
}
```

The `creation_flow` field is `"prompt"` or `"from_issue"` depending on the input. Pass this value when creating the spec via `POST /api/specs` to record the creation method.

**Errors:**

| Code | Cause |
|---|---|
| `422 Unprocessable Entity` | Neither `prompt` nor `issue_number` provided |
| `422 Unprocessable Entity` | `issue_number` is less than 1, or GitHub issue fetch failed |
| `500 Internal Server Error` | LLM generation failed |

---

## Create Spec

```
POST /api/specs
```

Create a new spec artifact from YAML content.

**Request body:**

```json
{
  "namespace": "myapp",
  "name": "api-design",
  "content": "requirements: |\n  ...\ndesign: |\n  ...\ntasks:\n  - id: task-1\n    summary: First task\n"
}
```

| Field | Type | Required | Constraints |
|---|---|---|---|
| `namespace` | string | Yes | 1-64 characters |
| `name` | string | Yes | 1-64 characters |
| `content` | string | Yes | Valid spec YAML (see [Schema Reference](schema-reference.md)) |
| `creation_flow` | string | No | One of: `null`, `"prompt"`, `"from_issue"`, `"manual"`. Records how the spec was created |

**Response:** `201 Created`

Returns the created artifact dict with all fields including `fqn`, `metadata`, and `id`.

**Errors:**

| Code | Cause |
|---|---|
| `409 Conflict` | A spec with this FQN already exists |
| `422 Unprocessable Entity` | YAML validation failed (missing fields, invalid tasks, circular deps) |

To update spec content after creation, use `PATCH /api/specs/{fqn}` (see below).

---

## Get Spec

```
GET /api/specs/{fqn}
```

Returns a spec with parsed phase statuses, stale reasons, and version history.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN, e.g. `@myapp/spec/api-design` |

**Response:** `200 OK`

```json
{
  "id": "abc123",
  "fqn": "@myapp/spec/api-design",
  "type": "spec",
  "namespace": "myapp",
  "name": "api-design",
  "content": "requirements: |\n  ...",
  "version": 2,
  "versions": [
    {"version": 2, "content": "...", "content_hash": "sha256:..."},
    {"version": 1, "content": "...", "content_hash": "sha256:..."}
  ],
  "phase_info": {
    "requirements": {"status": "approved", "reason": null},
    "design": {"status": "stale", "reason": "Phase 'design' is stale: requirements changed since approval at version 1"},
    "tasks": {"status": "draft", "reason": null}
  },
  "metadata": { ... }
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |

---

## Get Stale Status

```
GET /api/specs/{fqn}/stale
```

Returns the stale status of each phase with derived human-readable reasons.

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "phases": {
    "requirements": {"status": "approved", "reason": null},
    "design": {"status": "stale", "reason": "Phase 'design' is stale: requirements changed since approval at version 1"},
    "tasks": {"status": "draft", "reason": null}
  }
}
```

The `reason` field is `null` for non-stale phases.

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |

---

## Get Diff

```
GET /api/specs/{fqn}/diff?v1=N&v2=M
```

Returns a per-phase content diff between two spec versions.

**Query parameters:**

| Parameter | Type | Required | Default |
|---|---|---|---|
| `v1` | integer | No | Previous version |
| `v2` | integer | No | Current (latest) version |

If omitted, defaults to diffing the two most recent versions.

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "version_from": 1,
  "version_to": 2,
  "phase_changes": [
    {"phase": "requirements", "content_changed": true, "content_diff": "--- \n+++ \n@@ ... @@\n ..."},
    {"phase": "design", "content_changed": false, "content_diff": null},
    {"phase": "tasks", "content_changed": true, "content_diff": "--- \n+++ \n@@ ... @@\n ..."}
  ],
  "task_changes": [
    {"task_id": "new-task", "change_type": "added", "summary_before": null, "summary_after": "New task summary"},
    {"task_id": "old-task", "change_type": "removed", "summary_before": "Old task summary", "summary_after": null},
    {"task_id": "modified-task", "change_type": "modified", "summary_before": "Before", "summary_after": "After"}
  ]
}
```

Task change types: `added`, `removed`, `modified`.

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found, or requested version not found |
| `422 Unprocessable Entity` | Fewer than 2 versions exist |

---

## Approve Phase

```
POST /api/specs/{fqn}/phases/{phase}/approve
```

Approve a spec phase. Records the current timestamp and content version number.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |
| `phase` | One of: `requirements`, `design`, `tasks` |

**Response:** `200 OK`

```json
{
  "status": "approved",
  "fqn": "@myapp/spec/api-design",
  "phase": "requirements"
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format or invalid phase name |
| `404 Not Found` | Spec not found |

---

## Unapprove Phase

```
POST /api/specs/{fqn}/phases/{phase}/unapprove
```

Reset a phase to `draft` status, clearing all approval metadata.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |
| `phase` | One of: `requirements`, `design`, `tasks` |

**Response:** `200 OK`

```json
{
  "status": "draft",
  "fqn": "@myapp/spec/api-design",
  "phase": "design"
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format or invalid phase name |
| `404 Not Found` | Spec not found |

---

## Launch Workflow from Task

```
POST /api/specs/{fqn}/tasks/{task_id}/launch
```

Launch a workflow run targeting a specific task within a spec.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |
| `task_id` | Task ID from the spec's tasks list |

**Request body:**

```json
{
  "workflow_id": "my-workflow",
  "extra_inputs": {"key": "value"}
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `workflow_id` | string | Yes | Workflow definition ID to run |
| `extra_inputs` | object | No | Additional inputs merged into the workflow run |

**Response:** `201 Created`

```json
{
  "run_id": "abc123def456",
  "spec_fqn": "@myapp/spec/api-design",
  "task_id": "implement-crud"
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Workflow not found |
| `422 Unprocessable Entity` | Task not found in spec, or invalid workflow definition |

---

## Get Traceability

```
GET /api/specs/{fqn}/traceability
```

Returns a task-to-workflow-run mapping showing which tasks have been launched and their run status.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "tasks": [
    {
      "task_id": "implement-crud",
      "runs": [
        {"run_id": "abc123", "status": "completed", "started_at": "2026-03-18T10:00:00Z"}
      ]
    }
  ]
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |

---

## Update Spec Content

```
PATCH /api/specs/{fqn}
```

Update spec content. Creates a new version if the content hash changes, triggering invalidation rules on downstream phases.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN, e.g. `@myapp/spec/api-design` |

**Request body:**

```json
{
  "content_yaml": "requirements: |\n  Updated requirements...\ndesign: |\n  ...\ntasks:\n  - id: task-1\n    summary: First task\n"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `content_yaml` | string | Yes | Valid spec YAML content |

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "updated": true,
  "version": 3
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |
| `422 Unprocessable Entity` | YAML validation failed |

---

## `GET /api/specs/dashboard`

Portfolio dashboard with aggregated lifecycle state across all specs.

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `status` | string | — | Filter by overall status |
| `namespace` | string | — | Filter by namespace |
| `has_runs` | boolean | — | Filter by presence of workflow runs |
| `space_id` | string | — | Scope to a specific space |
| `phase` | string | — | Filter by specific phase name (use with `phase_status`) |
| `phase_status` | string | — | Filter by phase status value (use with `phase`) |

### Response

```json
{
  "specs": [
    {
      "fqn": "@ns/spec/name",
      "mode": "feature",
      "source": "local",
      "phases": {"requirements": "approved", "design": "draft", "tasks": "draft"},
      "overall_status": "in_progress",
      "stale_reasons": {},
      "run_summary": {"total": 3, "completed": 2, "failed": 0, "running": 1, "blocked": 0},
      "updated_at": "2026-03-22T12:00:00"
    }
  ],
  "totals": {
    "total_specs": 1,
    "all_approved": 0,
    "in_progress": 1,
    "stale": 0,
    "blocked": 0,
    "draft": 0
  },
  "filters_applied": {}
}
```

### Status Derivation

Priority order: `blocked` > `stale` > `draft` > `in_progress` > `all_approved`.

---

## Review Queue

```
GET /api/specs/queue
```

Returns the review inbox: specs needing review, stale specs, and blocked workflow runs.

**Query parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `space_id` | string | No | `null` | Filter by space attachment context |

**Response:** `200 OK`

```json
{
  "needs_review": [
    {"fqn": "@myapp/spec/api-design", "phase": "design", "status": "draft"}
  ],
  "stale": [
    {"fqn": "@myapp/spec/auth-flow", "phase": "requirements", "status": "stale", "reason": "requirements changed since approval at version 1"}
  ],
  "blocked": [
    {"run_id": "abc123", "spec_fqn": "@myapp/spec/api-design", "gate": "spec_design_approved"}
  ],
  "total": 3
}
```

---

## Conformance Check

```
POST /api/specs/{fqn}/conformance
```

Run deterministic conformance checks: task coverage, run health, phase status, and temporal validity.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |

**Request body:** None.

**Response:** `200 OK`

```json
{
  "spec_fqn": "@myapp/spec/api-design",
  "spec_version": 3,
  "checked_at": "2026-03-18T12:00:00Z",
  "conformant": false,
  "findings": [
    {"category": "phase_status", "message": "tasks phase is stale"},
    {"category": "run_health", "message": "task implement-crud has a failed run"}
  ]
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |

---

## List Git Links

```
GET /api/specs/{fqn}/links
```

Returns all git links (branches and PRs) associated with a spec.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "links": [
    {"id": "abc123", "type": "branch", "ref": "issue-42-api-design", "url": null, "created_at": "2026-03-18T10:00:00Z"},
    {"id": "def456", "type": "pr", "ref": "42", "url": "https://github.com/org/repo/pull/42", "created_at": "2026-03-18T11:00:00Z"}
  ]
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec not found |

---

## Create Git Link

```
POST /api/specs/{fqn}/links
```

Link a spec to a git branch or pull request.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |

**Request body:**

```json
{
  "type": "branch",
  "ref": "issue-42-api-design",
  "url": null
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `type` | string | Yes | One of: `branch`, `pr` |
| `ref` | string | Yes | Branch name or PR number |
| `url` | string | No | Optional URL (typically for PR links) |

**Response:** `201 Created`

```json
{
  "fqn": "@myapp/spec/api-design",
  "linked": true
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format or invalid link type |
| `404 Not Found` | Spec not found |

---

## Delete Git Link

```
DELETE /api/specs/{fqn}/links/{link_id}
```

Remove a git link from a spec.

**Path parameters:**

| Parameter | Description |
|---|---|
| `fqn` | Full artifact FQN |
| `link_id` | Link ID to delete |

**Response:** `200 OK`

```json
{
  "fqn": "@myapp/spec/api-design",
  "unlinked": true
}
```

**Errors:**

| Code | Cause |
|---|---|
| `400 Bad Request` | Invalid FQN format |
| `404 Not Found` | Spec or link not found |
