---
title: "Create an action anchored to a project. Set setAsNext to make it the project's next action pointer. Returns the full RPC result including the action id. When the created action has no dueAt the response carries warnings[] with code no_due_date — nothing is rejected, but an action with no clock is invisible to the attention layer."
method: POST
path: "/projects/{id}/actions"
---

# Create an action anchored to a project. Set setAsNext to make it the project's next action pointer. Returns the full RPC result including the action id. When the created action has no dueAt the response carries warnings\[\] with code no_due_date — nothing is rejected, but an action with no clock is invisible to the attention layer.

`POST /projects/{id}/actions`

## Request

- Base URL: `https://forbiz.io/api/public/agent`
- URL: `https://forbiz.io/api/public/agent/projects/{id}/actions`
- Auth: HTTP bearer

## Path parameters

- `id` string, required

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `verb` string
  - `description` string
  - `expectedSignal` string
  - `expectedSignalSource` union: BORN WITH A SIGNAL SOURCE (1.43.0). WHERE the expected signal will appear, as { kind, detail }: kind is one of email_thread, chat, phone_call, site_order, bank, calendar, in_person, system, other, and detail is free text (max 200 chars) identifying the place to look. OPTIONAL: when absent the 201 carries missing: ["expected_signal_source"] with one sentence saying that without it Forbiz cannot tell "nobody looked" from "looked and found nothing". NEVER inferred from the description. The legacy strings "human" and "external" are still accepted and keep their exact attention/Telegram semantics. An invalid value is refused 400 invalid_expected_signal_source carrying allowed_kinds, the full kind enum (1.60.0).
    - 'human' | 'external'
    - object
      - `kind` 'email_thread' | 'chat' | 'phone_call' | 'site_order' | 'bank' | 'calendar' | 'in_person' | 'system' | 'other', required
      - `detail` string
  - `actionType` string
  - `humanInterventionKind` 'decide' | 'execute', nullable: Same semantics as POST /actions. 'decide' + actionType='strategy_checkpoint' proposes a strategy checkpoint; approval is human-only.
  - `expectedSignalAt` string, date-time: When the expected signal is due. Persisted on the action (1.29.0); alias: expected_signal_at.
  - `earliestActionableAt` string, date-time
  - `dueAt` string, date-time
  - `effortMinutes` integer
  - `urgency` number
  - `lossRisk` number
  - `distractionRisk` number
  - `roi` object, nullable: BORN WITH A VALUE (1.43.0). What this commitment is worth, each dimension an integer 0-5: cash, access, proof, narrative, relationship, learning, compliance. OPTIONAL and never inferred. When absent the response carries missing: ["roi"] and priority: { value, defaulted: true } — nothing pretends to be ranked when its inputs were never given. The existing write-time priority trigger is unchanged; ORDERING happens at READ time in GET /next and GET /state with three named terms (value, days_late, blocking) and their weights published in rank_reason on every ranked item.
    - `cash` integer
    - `access` integer
    - `proof` integer
    - `narrative` integer
    - `relationship` integer
    - `learning` integer
    - `compliance` integer
  - `whyThisMove` string
  - `setAsNext` boolean
  - `commitmentKey` string, nullable: RECOMMENDED. Same three-layer rule as POST /actions: a declared key refuses a second OPEN action for the same objective with 409 COMMITMENT_ALREADY_OPEN; with no key the server derives auto:<kind>:<id>:<action_type> from an object DECLARED IN THE BODY (the project in the path is this route's container, not an objective, so it never derives a key); otherwise a look-alike OPEN action is refused 409 POSSIBLE_DUPLICATE, overridable with confirm_new: true. Alias: commitment_key.
  - `confirm_new` boolean: Send true to override a POSSIBLE_DUPLICATE refusal. Never merges, edits or closes anything; does not override COMMITMENT_ALREADY_OPEN.
  - `purpose` string, nullable: ACCEPTED AND IGNORED (1.56.0). `purpose` is a PROJECT field, not an action field: it is not stored on the action and it hides nothing. The action is created exactly as if the field had not been sent and it appears in the normal queues (/next, /state, /prompts, /decisions/recommendation, nudges, digest). When present the response carries purpose_ignored: true and purpose_ignored_note { code: "purpose_ignored", message }. To practise, POST /projects with purpose 'test' and create the actions inside that project.

## Response `200`

Created

- object

## Other responses

- `208`: Idempotent replay of a previous request with the same Idempotency-Key. Nothing was applied again.
- `400`: Validation error
- `404`: Project not found in this workspace
- `409`: Refused, nothing created. COMMITMENT_ALREADY_OPEN carries the open action plus its executable commands; POSSIBLE_DUPLICATE carries up to 3 candidates and is overridable with confirm_new: true.

---

[API](https://skmtc.dev/forbiz/apis/forbiz-external-agent-api.md) · [All operations](https://skmtc.dev/forbiz/apis/forbiz-external-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/forbiz/apis/forbiz-external-agent-api/revisions/e5b10d59dc5f?raw)
