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

# AI Agent Playbook

> How to operate Puffle from an autonomous AI agent using the public Lead Finder, Feed, Outbound, Puffle Agent, Unibox, and Senders API contract.

## Read this first

This guide is written for autonomous AI agents operating Puffle on behalf of a human customer. Use only the public endpoints documented under **API Reference**. Dashboard-only, admin-only, inbound webhook, OAuth callback, and private UI routes are intentionally outside this contract.

Public product language uses **Lead Finder**, **Feed**, **Outbound**, **Puffle Agent**, **Unibox**, and **Senders**. API paths retain implementation names, so keep documented paths and request fields exact; there is no public `/api/signals/planner/plan` endpoint.

If something appears missing from the docs, it is either intentionally out of scope or a gap to report back to the human. Do not guess endpoints.

***

## Authentication

Every public API call must include a Puffle API key as a Bearer token:

```http theme={null}
Authorization: Bearer pk_live_...
```

* Keys are created by the human customer in the Puffle dashboard.
* Keys are scoped to a single workspace.
* Never log or echo the key back to the customer.
* On `401 unauthorized`, stop and ask the human for a valid key.

See [API Overview authentication](/api-reference/overview#authentication) and [API key management](/api-reference/api-keys/overview).

MCP clients use a different integration: a `pmcp_live_` key (or ChatGPT OAuth link) against Puffle MCP, not a `pk_live_` API key. See [Connect Puffle MCP](/guides/connect-puffle-mcp).

***

## Boot sequence

Before issuing workflow calls, read the workspace profile and context.

```http theme={null}
GET /api/account
GET /api/context
```

Use this to understand the company, ICP, target market, differentiators, and current setup before planning or changing anything. If context is missing or clearly wrong, ask the human to update the Company settings before running Feed or Lead Finder searches or preparing Outbound.

***

## Async and background work

Feed and Lead Finder runs and Outbound launches can dispatch background work. Follow the endpoint response and poll only a documented status endpoint with backoff.

Recommended polling shape:

1. Start at 2 seconds.
2. Double up to 30 seconds.
3. Stop when the documented status becomes terminal, the operation returns a non-retryable error, or the user-defined time budget expires.
4. Surface the `correlationId`, `runId`, or `triggerRunId` when present so a human can investigate.

Do not busy-loop. Do not poll more than once per second.

***

## Error handling

The [API Overview](/api-reference/overview#errors) documents the current error shapes. Prefer structured errors when present:

```json theme={null}
{ "error": { "code": "invalid_request", "message": "..." } }
```

Some route-specific errors still return a flat shape such as `{ "error": "..." }`. Use each endpoint page's response examples as the source of truth.

| Status                     | Agent response                                                           |
| -------------------------- | ------------------------------------------------------------------------ |
| `400 invalid_request`      | Fix the payload from the message content. Do not retry the same body.    |
| `401 unauthorized`         | Stop. Ask the human for a valid API key.                                 |
| `403 forbidden`            | Stop. This workspace lacks access to this action; inform the human.      |
| `404 not_found`            | Resource does not exist or belongs to another workspace. Do not retry.   |
| `409 conflict`             | Fetch current state and decide whether the requested transition is done. |
| `422 unprocessable_entity` | Semantic error. Reread the endpoint doc before retrying.                 |
| `429 rate_limited`         | Back off. Respect `Retry-After` if present.                              |
| `500 internal_error`       | Retry once after 5 seconds unless the endpoint says the action happened. |

***

## Core user journeys

### Journey 1 - Create and run a Lead Finder search

```http theme={null}
GET  /api/lead-finder/sources
POST /api/lead-finder
GET  /api/lead-finder
POST /api/lead-finder/{id}/run
GET  /api/lead-finder/{id}
```

Use this when the user asks for a new Lead Finder search or wants to refresh an existing one. Discover the executable source catalog first, create the search with an explicit source configuration, and then run it. Updating a search changes future runs; it does not rewrite prior results. Use the matching `/api/feed` routes for Feed work.

`POST /api/lead-finder` already reserves the first Run. Poll `GET /api/lead-finder/{id}?limit=100&offset=0` until `latestRun.status` is `completed`, `partial`, or `failed`; `queued` and `running` are not terminal. Page through `results` using `pagination.hasMore`, `limit`, and `offset`. A `partial` Run can still contain usable results, but surface `latestRun.errors` before proposing outreach. A `failed` Run should not be retried unchanged.

Related docs: [List Lead Finder Sources](/api-reference/lead-finder/lead-finder.sources) · [Create Lead Finder Search](/api-reference/lead-finder/lead-finder.create) · [Run Lead Finder Searches](/api-reference/lead-finder/lead-finder.run-one) · [Get Lead Finder Search](/api-reference/lead-finder/lead-finder.get).

### Journey 2 - Prepare Outbound from a Lead Finder search

```http theme={null}
GET  /api/lead-finder/{id}?limit=100&offset=0
POST /api/campaigns
GET  /api/campaigns/{id}
POST /api/campaigns/{id}/leads
GET  /api/campaigns/{id}/leads?status=pending
PATCH /api/campaigns/{id}
POST /api/campaigns/{id}/generate-messages
POST /api/campaigns/{id}/launch
```

Use this when Lead Finder results are strong enough for outreach. The API path uses `/api/campaigns` because Outbound drafts are campaign-backed.

1. Finish paging through Lead Finder `results`. Select only results the human has approved for outreach and whose `lead` data contains a usable email address for email Outbound or LinkedIn profile URL for LinkedIn Outbound. Map that identity to the exact campaign recipient field: `email` or `linkedin_url`.
2. Create the draft with `POST /api/campaigns`. Creation does not add recipients.
3. Copy the reviewed contact fields into `POST /api/campaigns/{id}/leads` as `prospects`. This is the supported public preparation path; do not call the UI-only search-to-Outbound helper or assume results are linked automatically.
4. Check `imported` and `duplicates`, then fetch every page of `GET /api/campaigns/{id}/leads?status=pending`. Stop if `total` is zero or the roster differs from what the human approved.
5. Populate and review the sequence. Call `generate-messages` with the documented `resolve` action only after recipients and ordinary sequence content exist. Do not leave unresolved `$ai {instruction}$` slots; completing those slots requires a separate supported operation or human review in the dashboard.
6. Re-fetch the Outbound and sender state. Confirm `status: "draft"`, matching ready senders, at least one pending recipient, and non-empty content for every sendable node.
7. Present the human with the channel, sender identity, pending recipient count, and final sequence. Launch only after an explicit confirmation for that exact state.
8. After `200`, poll `GET /api/campaigns/{id}` every 30 seconds until `status` becomes `active`. Do not re-launch while it is `launching`. If launch returns `400`, surface and fix the blocker; on `409`, fetch current state before deciding what happened.

Related docs: [Get Lead Finder Search](/api-reference/lead-finder/lead-finder.get) · [Create Outbound Draft](/api-reference/campaigns/create-draft-campaign) · [Add Recipients](/api-reference/campaigns/add-leads-to-campaign) · [Review Recipients](/api-reference/campaigns/get-leads-in-campaign) · [Generate Outbound Messages](/api-reference/campaigns/generate-campaign-messages) · [Launch Outbound](/api-reference/campaigns/launch-campaign).

### Journey 3 - Manage replies in Unibox

```http theme={null}
GET   /api/threads
GET   /api/threads/{id}/messages
POST  /api/threads/{id}/messages
PATCH /api/threads/{id}
```

Use Unibox after replies arrive or when the human asks you to inspect ongoing conversations. If a thread requires a sender account that is not connected, direct the human to complete that setup in the dashboard.

Related docs: [Unibox](/core-concepts/unibox) · [List Threads](/api-reference/unibox/list-threads) · [Send Thread Message](/api-reference/unibox/send-thread-message).

### Journey 4 - Check sender readiness

```http theme={null}
GET /api/senders
GET /api/email/accounts
GET /api/email/domains
```

Use this before preparing or launching Outbound. Sender setup can require human-owned inbox/domain steps that agents should not fake or bypass.

Related docs: [List Senders](/api-reference/senders/list-senders) · [Email Accounts](/api-reference/senders/list-email-accounts) · [Email Domains](/api-reference/senders/list-email-domains).

### Journey 5 - Review Posts

```http theme={null}
GET  /api/calendar/posts
GET  /api/calendar/posts/{postId}
GET  /api/calendar/accounts
```

Use this when the human asks to inspect LinkedIn or X posts. The human chooses the account, media, schedule, and publish action in Posts.

Related docs: [List Posts](/api-reference/socials/list-social-posts) · [Get Post](/api-reference/socials/get-social-post) · [List Social Accounts](/api-reference/socials/list-social-accounts).

***

## What agents MUST NOT do

* **Do not** call endpoints under `/api/admin/*`.
* **Do not** call inbound webhook receivers or internal integration callbacks.
* **Do not** call dashboard-only routes that are absent from the API Reference.
* **Do not** attempt OAuth or sender connection flows autonomously. They require a human-operated browser session.
* **Do not** persist the API key anywhere except in memory for the duration of the task.
* **Do not** ignore endpoint-specific rate limits or poll faster than endpoint guidance.
* **Do not** launch Outbound without explicit user approval.

***

## Escalation

Stop and ask the human when:

* The requested action requires OAuth, login, sender/domain setup, or another browser interaction.
* A route returns `403`, `404`, or an unexpected `409`.
* A `500 internal_error` persists after one retry.
* The requested outcome has no corresponding endpoint documented here.
