---
title: "Add a goal node"
method: POST
path: "/api/goal/nodes"
tags: ["Goal"]
---

# Add a goal node

`POST /api/goal/nodes`

## Request

- Base URL: `http://localhost:38200`
- URL: `http://localhost:38200/api/goal/nodes`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `token`

## Request body

- object
  - `project_id` integer, nullable
  - `parent_id` integer, nullable
  - `title` string, required
  - `kind` string: objective | milestone | goal | work_item | relay
  - `owner_role` string
  - `status` string
  - `origin` string
  - `size` string
  - `effort_weeks` number
  - `target_weeks` integer
  - `verify_cmd` string
  - `external_ref` object
  - `payload` object
  - `content` string
  - `order_index` integer
  - `do` string: One-sentence work order
  - `brief` object, nullable: Six-section cold-executor brief, stored as payload.brief and rendered into the linked contexts row. All six keys or none (T925).
    - `intent` string, required
    - `decisions` string, required
    - `pointers` string, required
    - `rejected` string, required
    - `doneWhen` string, required
    - `gotchas` string, required
  - `session_id` integer: Agent session recorded on the node's history row (T926).

## Response `201`

Created node. A work_item in a project whose board uses T<NN> ids gets the next one in externalRef.local_id (T926). A work_item created without a brief also carries `hint`, the brief template with one filled example (T925).

- GoalNode: The `goal_nodes` row (src/db/schema.ts), minus `embedding`. `kind='relay'` rows are also returned by the Relay endpoints below (same table) — see the `Relay` schema. `status`/`kind`/`origin`/`ownerRole` are free varchars, never enums, so a new value never needs a migration; the enum lists below are the values in current use, not a hard constraint.
  - `id` integer
  - `tenantId` integer
  - `projectId` integer, nullable
  - `parentId` integer, nullable
  - `kind` string: objective | milestone | goal | work_item | relay
  - `ownerRole` string, nullable
  - `status` string: draft | not_started | ready | in_progress | blocked | done | superseded
  - `origin` string, nullable: greenfield | leverage | migrate | unknown
  - `title` string
  - `contextId` integer, nullable
  - `verifyCmd` string, nullable
  - `evidence` string, nullable
  - `targetWeeks` integer, nullable
  - `startsAt` string, date-time, nullable
  - `endsAt` string, date-time, nullable
  - `size` string, nullable
  - `effortWeeks` number, nullable
  - `externalRef` object
  - `orderIndex` integer
  - `fromRole` string, nullable: Relay routing (kind='relay' only).
  - `toRole` string, nullable: Relay routing (kind='relay' only).
  - `claimedBySessionId` integer, nullable
  - `nudgeCount` integer
  - `lastNudgedAt` string, date-time, nullable
  - `nudgedBySessionId` integer, nullable
  - `payload` object
  - `doneAt` string, date-time, nullable
  - `archivedAt` string, date-time, nullable
  - `createdAt` string, date-time
  - `updatedAt` string, date-time
  - `syncOwned` boolean: Server-only marker set exclusively by POST /api/goal/sync.
  - `objectiveRunId` string, nullable

## Other responses

- `400`: Request body/query failed Zod validation.
- `401`: Missing or invalid credentials.

---

[API](https://skmtc.dev/contextq/apis/contextq-api.md) · [All operations](https://skmtc.dev/contextq/apis/contextq-api/llms.txt) · [OpenAPI document](https://skmtc.dev/contextq/apis/contextq-api/revisions/7afc44056a22?raw)
