> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enginy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Playbook

> Read and update the workspace AI Playbook through the Enginy OpenAPI, with additive write semantics that never remove anything you did not ask to remove

## Overview

The AI Playbook is the company context Enginy uses to personalize messaging: company information,
products and services, ideal customer profiles (ICPs), competitors, testimonials, and FAQs.

Two endpoints cover it:

* Read the playbook: `GET /v1/ai-playbook` — scope `WORKSPACE_READ`
* Update the playbook: `PUT /v1/ai-playbook` — scope `WORKSPACE_WRITE`

As MCP tools these are `get_ai_playbook` and `update_ai_playbook`.

## Write semantics

`PUT /v1/ai-playbook` is additive on purpose. It is designed to be safe for an AI assistant that only
knows about the part of the playbook it is currently working on.

<Warning>
  The request body is **not** a replacement document. Sending a shorter list never deletes the rest.
</Warning>

| Situation                                       | What happens                                                         |
| ----------------------------------------------- | -------------------------------------------------------------------- |
| A section key is missing from the body          | That whole section is untouched                                      |
| A section is present but an item is not listed  | That item is untouched                                               |
| An item's key matches an existing item          | The existing item is updated with the fields you sent                |
| An item's key does not match anything           | A new item is created                                                |
| An item's key matches something removed earlier | That item is restored                                                |
| An id is listed in a section's `deleteIds`      | That item is removed (reversibly — sending the key back restores it) |

Removal is only ever possible through `deleteIds`. There is no way to make this endpoint clear a
section.

### How items are matched

Items are matched by their natural key, not by id:

| Section                 | Match key                      |
| ----------------------- | ------------------------------ |
| `productsServices`      | `name`                         |
| `idealCustomerProfiles` | `name`                         |
| `competitors`           | `name`                         |
| `testimonials`          | `companyName` + `promoterName` |
| `faqs`                  | `question`                     |

Matching is exact after trimming whitespace. Call `GET /v1/ai-playbook` first if you need the current
values and ids.

### Fields required to create

When a key does not exist yet, the item is created and a few fields become mandatory. When the key
already exists, every field is optional and only what you send is written.

| Section                 | Required to create                                |
| ----------------------- | ------------------------------------------------- |
| `productsServices`      | `description`                                     |
| `idealCustomerProfiles` | `companyIndustry`, `companySize`, `keyPainPoints` |
| `competitors`           | `productDifferentiator`                           |
| `testimonials`          | `jobPosition`, `mainBenefit`                      |
| `faqs`                  | `answer`                                          |

### What cannot be written

* `idealCompanies` — generated by Enginy from your playbook.
* `linkedInFilters` on an ideal customer profile — regenerated automatically whenever the profile
  changes.

Company-level fields are written only when you provide a non-empty value, so an empty string never
clears an existing value.

### The main product

At most one product or service can be the main one. Setting `isMainProduct: true` on a product
promotes it and unsets the previous main product. If the workspace ends up with products but no main
one, the first product in your payload is promoted automatically.

### Recommendations refresh

Enginy derives list and search recommendations from your playbook. When an update changes something
those recommendations depend on — the company overview, or any product, ICP, competitor, or
testimonial — a refresh runs in the background once the write has committed. FAQ-only updates, and
updates that touch only other company fields, do not trigger one.

The refresh is asynchronous and best-effort: it never delays or fails your request, and repeated
updates collapse onto a single in-flight refresh per workspace.

## Examples

Add one FAQ and leave the rest of the playbook alone:

```json theme={null}
{
  "faqs": {
    "upsert": [{ "question": "Do you offer a free trial?", "answer": "Yes, 14 days." }]
  }
}
```

Update a competitor and remove a different one in the same call:

```json theme={null}
{
  "competitors": {
    "upsert": [{ "name": "Rival Inc", "productDifferentiator": "We ship faster onboarding." }],
    "deleteIds": [42]
  }
}
```

Update company context and create an ICP:

```json theme={null}
{
  "company": {
    "companyIndustry": "B2B SaaS",
    "companyOverview": "We help outbound teams find and reach their ICP."
  },
  "idealCustomerProfiles": {
    "upsert": [
      {
        "name": "Mid-market RevOps",
        "companyIndustry": "SaaS",
        "companySize": "51-200",
        "keyPainPoints": "Manual list building and stale CRM data",
        "jobTitles": ["Head of RevOps", "Revenue Operations Manager"]
      }
    ]
  }
}
```

## Response

The response returns the full playbook after the write, exactly as `GET /v1/ai-playbook` would, plus a
`changes` object so you can verify what the call actually did:

```json theme={null}
{
  "status": "success",
  "message": "AI playbook updated successfully",
  "data": {
    "company": { "...": "..." },
    "allCompanyProductsServices": [],
    "idealCustomerProfiles": [],
    "idealCompanies": [],
    "competitors": [],
    "testimonials": [],
    "faqs": [],
    "changes": {
      "companyFieldsUpdated": 2,
      "productsServices": { "created": 0, "updated": 0, "restored": 0, "deleted": 0 },
      "idealCustomerProfiles": { "created": 1, "updated": 0, "restored": 0, "deleted": 0 },
      "competitors": { "created": 0, "updated": 0, "restored": 0, "deleted": 0 },
      "testimonials": { "created": 0, "updated": 0, "restored": 0, "deleted": 0 },
      "faqs": { "created": 0, "updated": 0, "restored": 0, "deleted": 0 }
    }
  }
}
```

## Errors

The endpoint validates the whole request before writing anything, so a rejected request changes
nothing at all.

| Status | When                                                                                                                                      |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | A `deleteIds` entry is not an existing item of this workspace — the ids come back in `details.invalidDeleteIds`                           |
| `400`  | An item is missing its match key, or a new item is missing a field required to create it — the items come back in `details.rejectedItems` |
| `404`  | The workspace was not found                                                                                                               |

## Limits

* Up to 50 items per section in `upsert`, and up to 50 ids per section in `deleteIds`.
* Unknown keys are rejected, so a typo fails loudly instead of being silently dropped.
