# Native CRM: pipelines, automations, deals, teams and the CRM add-on

> **You want.** *"cómo funciona el CRM nativo de Ninjō: pipelines, etapas y automatizaciones"* ·
> *"crear una automatización de pipeline que agregue el contacto cuando cambia una propiedad"* ·
> *"CRM add-on precio billing"* · *"crm_not_enabled native CRM add-on enable"* ·
> *"native CRM deals opportunities quotes payment link"* · *"CRM property vs custom property
> scope CONTACT CONVERSATION PIPELINE_RECORD"*.
>
> **Short answer.** The native CRM is built into Ninjō; nothing is written to an outside CRM.
> Contacts, tags, properties and notes are on every plan. Pipelines, pipeline automations, teams,
> routing, deals and commerce are the **CRM add-on**, billed per workspace. Start every session
> with `get_crm_overview`.

This doc is the native CRM only. Pushing contacts into GoHighLevel, GHL booking, and owners and
routing basics are in `how-to/gohighlevel-and-crm`. If a contact did not land in a pipeline, go
to `runbooks/contact-not-in-pipeline`.

## Before any call

1. `get_crm_overview` (pass `influencer_id` if the connection covers several workspaces; a CRM
   **write** without it is refused on purpose). It returns your permissions and, per area
   (`pipelines`, `pipelineAutomations`, `assignments`, `routing`, `opportunities`, `commerce`,
   `sales`), whether read and write are on.
2. An area shows `false` → either the add-on is off (below) or your role lacks it. Do not keep
   calling that area's tools.
3. The CRM tools need an OAuth login as a real person. API keys and user-less credentials always
   get 403.

## The CRM add-on

- **What it gates:** pipelines and drafts, pipeline automations, teams, assignments, routing,
  human share, commerce, sales, opportunities. A call to any of those tools in a workspace
  without it answers **403 `crm_not_enabled`**: *"This workspace does not have the CRM add-on.
  An organization admin can turn it on from the organization billing page."*
- **What stays open without it:** contacts, tags, properties, notes, imports and exports.
- **Price:** one more line on the organization's Ninjō invoice, per workspace, same billing
  date, with a free trial (7 days by default). Prices and trials vary by organization, so send the operator to the billing
  page for the number instead of quoting one.
- **Turning it on:** an organization admin, in Studio, on the organization billing page or the
  "El CRM es un producto adicional" popup over the locked CRM ("Empezar los … días gratis"). No
  MCP tool does it. A non-admin sees "Pedile a un admin de la organización que lo active."
- **What happens while it is off:** property changes are still recorded, but the automation
  events they produce are **discarded, not queued**. Turning the add-on on later does not replay
  them. Contacts whose properties flipped while it was off never enter the pipeline on their own;
  add them by hand (see the runbook).

## The pieces

| Piece | What it is | Read | Write |
|---|---|---|---|
| **Pipeline + stages** | A board. A contact has at most one **active record** per pipeline; the record sits in one stage | `get_crm_pipelines` | `save_crm_pipeline_draft` → `get_crm_pipeline_draft` → `publish_crm_pipeline_draft` |
| **Pipeline record** | The contact's card in a pipeline | `get_crm_pipelines({pipelineId, recordId})` | `execute_crm_pipeline_command` (`add-contact`, `move-record`, `close-record`, `transfer-record`) |
| **Pipeline automation** | A rule that adds, moves or transfers a record | `get_crm_pipeline_automations` | `manage_crm_pipeline_automation` |
| **Tag** | A reusable contact label | `list_crm_tags`, `get_crm_contact_tags` | `manage_crm_tag`, `replace_crm_contact_tags`, trigger `add_tag_ids` |
| **Team + routing** | Who owns a conversation or record | `get_crm_teams`, `get_crm_routing`, `get_crm_assignment` | `manage_crm_team`, `manage_crm_routing`, `execute_crm_assignment` |
| **Deal (opportunity)** | One **quote** = one row. No quote, no opportunity | `list_crm_opportunities` | `manage_crm_opportunities` (owner, stage, lost, next action) |
| **Commerce** | Products → immutable prices → quote → payment link → sale/payment evidence | `get_crm_commerce`, `get_crm_contact_commerce` | `execute_crm_commerce_command`, `manage_crm_payment_link`, `execute_crm_sale_command` |

Native pipeline ids from `get_crm_pipelines` are not the ids `list_crm_pipelines` /
`list_crm_stages` return: those are mirrors of an **external** CRM (GHL). Mixing them is a
common first error.

## Pipeline automations

A rule has one trigger and one action:

- **Trigger `FACT_CHANGED`:** a contact property (or the contact's tags) changes to a value that
  matches `factOperator` + `factValue`. A `MOVE` needs `sourcePipelineId`; an `ADD` takes none;
  `sourceStageId` needs `sourcePipelineId` and limits the rule to records in that stage.
- **Trigger `PIPELINE_RECORD_ENTERED_STAGE`:** a record enters `sourceStageId`; across pipelines
  the action is `ADD_CONTACT_TO_PIPELINE` or `TRANSFER_PIPELINE_RECORD`.
- **Actions:** `ADD_CONTACT_TO_PIPELINE` (opens a record in `destinationStageId`),
  `MOVE_PIPELINE_RECORD` (moves the existing record), `TRANSFER_PIPELINE_RECORD` (closes it here,
  opens it in another pipeline).

**"Fact"** is the CRM's word for a property value with history. Every write to a contact
property, from the evaluator, an operator or an import, becomes a fact change, and the
automation reads that change. So the chain from the conversation is:

```text
lead says it → evaluator writes the custom property → fact change → automation adds/moves the record
```

The agent never writes the property; the evaluator does (`custom-properties`). Rules of the
engine that decide whether a rule runs, all from the platform code:

1. **It runs on a change, not on a state.** Writing the same value again produces no event. A
   rule created after the property was already true does nothing for that contact.
2. **Rules are read as they were when the property changed.** Enabling or fixing a rule later
   does not reach changes that already happened.
3. **`ADD` only adds.** If the contact already has an active record in that pipeline, the rule
   is refused (`ACTIVE_RECORD_EXISTS`) and the record stays where it is. To move a contact that
   is already in, you need a `MOVE` rule.
4. **One `MOVE` or `TRANSFER` per change.** All matching `ADD` rules run first, then the first
   `MOVE` or `TRANSFER` rule (by `priority`) whose source stage matches where the record is now.
   A rule with a source stage does nothing when the record is in any other stage, and leaves no
   trace. A `MOVE` with no source stage matches from any stage, and with the lowest priority it
   wins over the staged ones.
5. **Contact-scoped only.** Fact rules accept CONTACT properties and tags. The evaluator writes
   only CONTACT facts, so a property created with another scope never feeds an automation.
6. **Limits:** 100 rules per workspace ("This Account has reached the Pipeline automation rule
   limit"); a chain of rules triggering each other stops at depth 8.

**The pattern that works**, per destination stage, from a workspace where it runs cleanly:

- one `ADD_CONTACT_TO_PIPELINE` rule (for contacts not in the pipeline yet), and
- one `MOVE_PIPELINE_RECORD` rule **per stage the contact can come from**, each with
  `sourcePipelineId` and that `sourceStageId`. If the contact should move from any stage, one
  `MOVE` with only `sourcePipelineId` does the same.

The `ADD` will show a refusal for every contact already in the pipeline. That is expected, not a
failure.

To create one: `get_crm_pipeline_automations` (it lists the stage ids and the contact property
ids and option ids you can use) → `manage_crm_pipeline_automation({idempotencyKey,
command:{intent:"create", name, enabled, priority, destinationStageId,
trigger:{triggerKind:"FACT_CHANGED", factDefinitionId, factKey, factOperator:"EQUALS",
factValue:["true"], factActionKind:"ADD_CONTACT_TO_PIPELINE"}}})`. Values are strings; an
option property compares by **option id**, not label. Editing (`save`) and `archive` need the
rule's current `expectedRevision`.

## CRM property vs custom property

Same table, two doors. `list_custom_properties` / `upsert_custom_property` manage the
evaluator-filled properties on the **contact**. `list_crm_properties` / `create_crm_property`
also manage properties scoped to a **conversation** or a **pipeline record**
(`factSubjectType`). Only contact-scoped ones are written by the evaluator and accepted by
automations and routing criteria. To set one value by hand: `set_crm_contact_property`
(booleans are `"true"`/`"false"`, options are option ids, `""` clears). That write is a fact
change too, so it can fire automations, routing and property actions.

## Tags

Tags are a contact property with options. `replace_crm_contact_tags` replaces the **whole** set
(read first, send everything you want to keep). A trigger can add tags each time it fires
(`create_trigger`/`set_trigger_tags` with `add_tag_ids`); the evaluator never writes tags
(`custom-properties`, "Two kinds of property the evaluator will never write"). A tag change can
trigger a pipeline automation (operators on tag ids). `CRM_CONTACT_TAG` is a different thing: a
GoHighLevel property action (`how-to/gohighlevel-and-crm`).

## How it goes wrong

Errors seen on these tools, 2026-10-10 to 2026-11-09:

- **403 `crm_not_enabled`**: the add-on is off. The commonest failure of `get_crm_assignment`,
  `get_crm_pipelines` and `list_crm_opportunities`. Retrying or changing arguments
  changes nothing; an org admin turns it on.
- **403 "CRM account access or permission denied"** and **404** on an area: access or role, or
  the area is switched off. Meanings in `how-to/gohighlevel-and-crm`, "How it goes wrong". The
  automations read also needs `read:contacts` and access to every pipeline record.
- **"influencer_id is required: specify which workspace…"**: a multi-workspace connection. Pass
  the workspace the user named; the error lists the ones you may use.
- **409 `REVISION_CONFLICT`**: someone moved the record since you read it. Re-read, use the new
  `expectedRevision`, retry once.
- **409 `ACTIVE_RECORD_EXISTS`**: `add-contact` on a contact already in that pipeline. Use
  `move-record`, or `close-record` first if a new cycle is what you want.
- **400 "after requires the pipelineId and stageId it pages"**: `get_crm_pipelines` paging needs
  both.
- **404 "Assignment subject not found"**: wrong subject id for `get_crm_assignment`; it takes a
  conversation id or a pipeline record id, never a contact id.
- **`unrecognized_keys`**: these tools reject unknown arguments, `reason` at top level included.
- **"CRM upstream is unavailable" / "did not answer within 30 seconds"**: the write may have
  happened. Read the state, then retry with the **same** `idempotencyKey` and payload.
- **`list_crm_opportunities` empty with the add-on on**: there are no quotes. Pipeline records
  are not opportunities; read them with `get_crm_pipelines`.

## Invariant

`get_crm_overview` shows the area on; `get_crm_pipeline_automations` lists the rule
`enabled: true` with the property id you expect; a test contact whose property flips shows a
record in the destination stage seconds later (`get_crm_pipelines` with its `recordId`, or
`get_crm_contact`). No MCP tool lists rule executions, so the record itself is the proof.

See also: `runbooks/contact-not-in-pipeline`, `how-to/gohighlevel-and-crm` (GHL, owners and
routing basics), `custom-properties` (how the evaluator fills properties), `property-actions`
(properties that push to an outside CRM or webhook), `workflows` (follow-ups and notifications
gated on the same properties).
