# DripPulse Agent Skills v1 (downloadable)

This file is meant to be **downloaded by an agent framework** (Claude/OpenClaw, MCP-style toolchains, generic HTTP agents).
It is designed so a framework can fetch one file, then work immediately after a human provides an API key.

## 1) Identity, scope, and safety

You are an operations agent that controls exactly **one DripPulse organization (tenant)** via API.

- **Tenant boundary**: determined by the Bearer token. Do not attempt to access data outside the token’s org.
- **Never reveal secrets**: never print API keys or Authorization headers.
- **Prefer safe edits**: avoid destructive bulk actions unless explicitly asked.
- **Validate before writing**: fetch IDs (templates/pipelines/stages/projects) instead of guessing.
- **Idempotency**: for agent execution endpoints, use `idempotency_key` to safely retry.

## 2) API base + authentication

- Base origin: `https://drippulse.io` (override with `DRIPPULSE_BASE_URL`)
- Base API prefix: `${DRIPPULSE_BASE_URL}/api/v1`

All requests:

```http
Authorization: Bearer ${DRIPPULSE_API_KEY}
Accept: application/json
Content-Type: application/json
```

### 2.1) Canonical documentation URLs (how external agents discover behavior)

Models do **not** implicitly know DripPulse. Your orchestration should **fetch or bundle** these artifacts and inject them into the agent context (system prompt, tool docs, or RAG).

| Artifact | URL (production) | Purpose |
|----------|-------------------|---------|
| **This skills file** | `https://drippulse.io/docs/skills/drippulse-agent-skills-v1.md` | Single-file integration contract (version in filename: `v1`). |
| **OpenAPI** | `https://drippulse.io/docs/skills/drippulse-openapi-v1.json` | Schema-first clients and codegen. |
| **Human overview** | `https://drippulse.io/docs/agent-skills.html` | Explains downloads + auth pattern. |
| **External agents guide** | `https://drippulse.io/docs/external-agents-guide.html` | Sessions vs enrollments, what to use when, GUI parity. |
| **API reference (enrollments)** | `https://drippulse.io/docs/api-reference.html#workflow-enrollments-visibility` | List/detail payloads, `step_funnel`, errors. |

**Bootstrap recommendation:** On agent startup, `GET` the Markdown skills URL with `Accept: text/markdown`, cache the body, and refresh on a schedule or when you intentionally upgrade to a new `vN` URL. **Verify** the response `Content-Type` (expect `text/markdown`, not HTML from a mis-routed SPA). Use `Accept: application/json` for the OpenAPI file. Paths are stable under `/docs/skills/…` (no redirect dependency for automation).

### 2.2) “Sessions” vs what you integrate (customer boundary)

| Term | Meaning for you |
|------|-----------------|
| **Dashboard / browser session** | Human login to the web app — **not** used for `/api/v1`; use an **organization API key**. |
| **OpenClaw gateway sessions** | Internal LLM tool API (`sessions_spawn`, `sessions_list`, `sessions_history`) used **inside** DripPulse. **Not** a tenant API on `drippulse.io` for customers. |
| **Workflow enrollment** | **Your** durable per-contact runner: create with `POST .../enrollments/bulk`, observe with `GET .../enrollments` and `GET .../enrollments/:id`. Treat `enrollment.id` as the stable handle for a person in a sequence. |
| **Agent executions** | When using `POST /agents/:id/execute`, inspect history via `GET /api/v1/agents/:id/executions` (and related status/logs endpoints). |

If your stack needs its own “conversation id,” store it in **your** database and map it to `lead_ref` or `enrollment_id` when calling DripPulse.

## 3) Minimal required tool

Your runtime must provide an HTTP tool equivalent to:

- `http.request(method, url, headers, body)` → returns `status`, `headers`, `text/json`

### Recommended retry behavior

- `429`: exponential backoff (1s, 2s, 4s, 8s, 16s) then stop and report rate limiting.
- `5xx`: retry with backoff up to 3 attempts if safe.
- `401/403`: stop and request valid credentials/permissions.
- `422`: do **not** blind retry; read `errors` and correct payload.

## 4) Out-of-the-box templates (the “catalog”)

DripPulse ships a catalog of deployable **agent templates**.

List templates (no auth required per controller config; still safe to send Bearer):

```http
GET /api/v1/agent_templates
```

Optionally filter by category:

```http
GET /api/v1/agent_templates?category=lead_qualification
```

Get one template (includes resolved execution policy):

```http
GET /api/v1/agent_templates/:id
```

## 5) Deploy an agent (from template or workflow)

### 5.1 Spawn/deploy from a template (recommended default)

```http
POST /api/v1/agents/spawn

{
  "name": "Inbound Lead Qualification",
  "type": "template",
  "template_id": 1,
  "configuration": { "min_score_threshold": 0.72 }
}
```

Notes:
- `name` is **idempotent** for spawn: if an agent already exists with the same name in the org, you’ll receive `202 Accepted` with its `id`.
- Deployment uses the OpenClaw registration path internally when enabled; you still manage it through DripPulse API.

### 5.2 Spawn a workflow-backed agent

```http
POST /api/v1/agents/spawn

{
  "name": "Workflow Executor",
  "type": "workflow",
  "workflow_id": 123,
  "configuration": {}
}
```

### 5.3 Observe deployment / status / executions

```http
GET /api/v1/agents
GET /api/v1/agents/:id
GET /api/v1/agents/:id/status
GET /api/v1/agents/:id/executions
GET /api/v1/agents/:id/logs
GET /api/v1/agents/:id/costs
GET /api/v1/agents/:id/metrics
```

## 6) Run an agent (execute / inference)

### 6.1 Execute (preferred generic run)

Use `idempotency_key` so retries are safe.

```http
POST /api/v1/agents/:id/execute

{
  "input": { "company": "Acme", "industry": "SaaS" },
  "idempotency_key": "partner-evt-20260408-xyz"
}
```

### 6.2 Inference (structured JSON / LLM task)

```http
POST /api/v1/agents/:id/inference

{
  "input": { "lead": { "email": "new@acme.com", "company": "Acme" } },
  "idempotency_key": "lead-42"
}
```

## 7) Create a custom agent template (advanced)

Create (requires auth; may require elevated permissions depending on deployment rules):

```http
POST /api/v1/agent_templates

{
  "name": "My Custom Template",
  "prompt": "You are a CRM operations assistant. Output JSON with ...",
  "category": "crm_ops"
}
```

Update template name/prompt:

```http
PATCH /api/v1/agent_templates/:id
{
  "name": "My Custom Template v2",
  "prompt": "Updated prompt..."
}
```

Update execution policy (admin-only):

```http
PATCH /api/v1/agent_templates/:id/execution_policy
{
  "execution_policy": { "max_steps": 20, "allow_tools": ["http"] }
}
```

## 8) Projects (organize work)

Projects are org-scoped containers (often used to group agents/workflows).

```http
GET /api/v1/projects
POST /api/v1/projects
GET /api/v1/projects/:id
PATCH /api/v1/projects/:id
DELETE /api/v1/projects/:id
```

Create:

```http
POST /api/v1/projects
{
  "project": {
    "name": "RevOps Q2",
    "description": "Lead routing + pipeline hygiene",
    "status": "active"
  }
}
```

## 9) Webhooks (deliver events out of DripPulse)

Webhooks let you subscribe to DripPulse events and deliver signed payloads to your HTTPS endpoint.

Event catalog (types + payload schema):

```http
GET /api/v1/webhooks/catalog
```

Create webhook subscription:

```http
POST /api/v1/webhooks
{
  "url": "https://hooks.example.com/drippulse",
  "event_type": "workflow.failed",
  "active": true
}
```

Notes:
- Response includes a `secret` (store safely; used to verify signature).
- Deliveries include headers like `X-DripPulse-Signature` (HMAC-SHA256 over raw JSON).

Send a test delivery:

```http
POST /api/v1/webhooks/:id/test
```

List deliveries:

```http
GET /api/v1/webhooks/:id/deliveries?limit=20
```

## 10) API keys (provision credentials for agents)

List keys (secrets redacted):

```http
GET /api/v1/api_keys
```

Create (full key returned once):

```http
POST /api/v1/api_keys
{
  "name": "Production automation",
  "expires_in": 365
}
```

Revoke:

```http
DELETE /api/v1/api_keys/:id/revoke
```

Usage stats:

```http
GET /api/v1/api_keys/usage
```

## 11) Workflows (automation)

Base path: `/api/v1/workflows`

```http
GET /api/v1/workflows
POST /api/v1/workflows
GET /api/v1/workflows/:id
PATCH /api/v1/workflows/:id
DELETE /api/v1/workflows/:id
POST /api/v1/workflows/:id/test
GET /api/v1/workflows/step_types
GET /api/v1/workflows/:workflow_id/enrollments
GET /api/v1/workflows/:workflow_id/enrollments/:id
POST /api/v1/workflows/:workflow_id/enrollments/bulk
DELETE /api/v1/workflows/:workflow_id/enrollments/:id
```

**GET `/api/v1/workflows/:workflow_id/enrollments`** — Response includes:

- `enrollments` — up to **500** rows (newest first); each row extends core enrollment fields with `contact_email`, `contact_name`, `steps_total`, `current_step_name`, `current_step_type`, `progress_label`, `waiting_until` (when scheduled wait), `last_error` (when present).
- `total` — count of all enrollments for the workflow (may exceed 500).
- `step_funnel` — aggregate counts per workflow step index (`step_index` is **0-based**, aligned with the workflow `steps` array). Each step bucket includes `active_leads`, `waiting_leads` (`next_run_at` in the future), `running_leads` (= active − waiting), plus `name` / `type`. `totals` includes `active`, `completed`, `failed`, `cancelled`, `total_enrollments`.

**GET `/api/v1/workflows/:workflow_id/enrollments/:id`** — Returns `enrollment` (summary as above), `action_timeline`, `deliveries`, `nurture_events`, `runtime_state`.

**Dashboard:** Saved workflow → **Enrollments** tab; **Add enrollments** supports single contact, bulk JSON, CRM contacts/leads, timezone, optional `agent_id` — same semantics as `POST .../enrollments/bulk`.

**POST `/api/v1/workflows/:id/test`** — Dry run; response includes `test_fixture_profile` and final `sample_data`. **Merge semantics (read before asserting on `sample_data`):**
1. Server starts from a **fixture profile**: default **minimal** keys (`first_name`, `last_name`, `email`, `score`). With `fixture_profile: demo` (or `full` / `rich` / `legacy`), many extra keys appear (`company_name`, `industry`, …) — **dashboard Test uses `demo`**; match that in API calls if you want UI parity.
2. Your `sample_data` / `input` / `runtime_payload` is **shallow-merged on top**: for each **top-level** key, **your value wins**; keys you omit **remain from the fixture** (so `demo` still injects fields you did not send).
3. After each step, outputs are **shallow-merged** into the bag; **nested objects are not deep-merged** (e.g. a new `payload` replaces the prior `payload` object).

**Email / integrations — test vs live:**
- **`/test`**: **Stubbed** — `[test]` logs, no live ESP sends; safe preview.
- **`enrollments/bulk`**: **Live** — `config.integration_id` picks the adapter; real delivery needs org **`integration_connections`** (provider + optional `connection_key`, default `default`).
- **`POST /agents/:id/execute`** (workflow agent): **production-capable** unless your setup is explicitly non-live; respect connections the same as enrollments when the run executes real steps.

Full tables: `https://drippulse.io/docs/api-reference.html#workflow-test-merge` and `#workflow-email-stub-vs-live`.

**Inbound events (no hosted “workflow POST URL”):** Your server receives webhooks or form POSTs, then calls `enrollments/bulk` or `agents/:id/execute`. See `/docs/event-bridge-workflows.html` (includes a **deduped form → server → bulk** example) and repo `CHANGELOG.md` (2026-04-10 enrollments/bulk fix).

**POST `/api/v1/workflows/:workflow_id/enrollments/bulk`** — Real per-contact runs (`mode: live`). Requires JSON body:

```http
POST /api/v1/workflows/WORKFLOW_ID/enrollments/bulk
Content-Type: application/json
Authorization: Bearer YOUR_ORG_API_KEY

{
  "contacts": [
    {
      "email": "lead@company.com",
      "first_name": "Alex",
      "lead_ref": "uuid-from-crm-lead-create"
    }
  ],
  "timezone": "UTC"
}
```

`lead_ref` can be omitted if you pass `id`, `uuid`, `public_id`, `crm_id`, or `lead_id` (first present wins). **422** when nothing is created (not `201` + zero rows). Typical `error` codes: `workflow_archived`, `contacts_required`, `contacts_empty`, `no_enrollments_created`. **404** if `agent_id` is set but agent missing. **201** may include `skipped_invalid_rows` if some contact objects were skipped but at least one row was created. Wrap as `{ "enrollment": { "contacts": [ ... ] } }` if convenient. Each successful bulk creates **new** rows — dedupe on your side or delete mistakes with `DELETE .../enrollments/:id`. Workflow agents support `idempotency_key` on `execute` instead. Details: `https://drippulse.io/docs/api-reference.html#enrollment-bulk-errors`.

**Webhooks:** The builder “Webhook” input step is metadata-only at runtime; use **Outbound Webhook** (`webhook_callback`) to POST out. Start runs via enrollments bulk or `agents/execute`.

Golden path:
1) Create workflow with minimal valid payload
2) Fetch it back and confirm step graph
3) Run `/test` with sample input (add `fixture_profile` if you need demo fields)
4) Create enrollments (bulk) for live nurture, or use `POST /agents/spawn` + `execute` with `type=workflow`
5) CRM: `POST /crm/leads` then bulk with the same identifiers in `contacts[]`

**When to use what (assistants — do not treat enrollments as universal):**

| Goal | Prefer |
|------|--------|
| Multi-step sequence **per lead** over **time** (waits, timed emails, branches), with **per-run** ops visibility | **`POST .../enrollments/bulk`** — one `contacts[]` object per person entering the sequence |
| **Dry-run** / validate steps / no live mail or external calls | **`POST .../workflows/:id/test`** |
| **One-shot** or tool-driven run, **idempotent** retries, workflow bound to an **agent** | **`POST /agents/:id/execute`** (workflow-type agent; use `idempotency_key`) |
| **Record keeping only** (create/update lead, opportunity, activity) **without** a timed workflow | **CRM APIs** (`/crm/...`) — no enrollment required |
| Inbound form/webhook | **Your server** validates → then **`enrollments/bulk`** and/or **agent execute** (see event bridge doc) |

If the user’s scenario is batch analytics, org-wide cron, or a single immediate API response with no per-row schedule, **do not default to enrollments** — confirm intent or suggest CRM + reports/agents instead.

## 12) CRM starter layer (org-scoped)

All paths below are under `/api/v1/crm/...`.

### 12.1 Opportunity pipelines & stages (Kanban)

Fetch pipelines and stages (always do this before choosing stage IDs):

```http
GET /api/v1/crm/opportunity_pipelines
```

Choose default pipeline and stage:
- Pick pipeline with `id == default_pipeline_id` (fallback: first pipeline).
- Pick an `open` stage: prefer “Qualification” / “Prospect”, else first open stage.

### 12.2 Create an opportunity in a stage

```http
POST /api/v1/crm/opportunities
{
  "opportunity": {
    "name": "ACME expansion Q2",
    "pipeline_stage_id": "<stage_uuid>",
    "amount": "120000",
    "currency": "USD"
  }
}
```

### 12.3 Move an opportunity between stages

```http
PATCH /api/v1/crm/opportunities/opp_abc123
{
  "opportunity": {
    "pipeline_stage_id": "<target_stage_uuid>"
  }
}
```

### 12.4 Reorder opportunities within a stage (card order)

```http
POST /api/v1/crm/opportunities/reorder
{
  "pipeline_stage_id": "<stage_uuid>",
  "ordered_ids": ["opp_a1", "opp_b2", "opp_c3"]
}
```

### 12.5 Accounts, contacts, leads, activities (CRUD)

```http
POST /api/v1/crm/accounts
{ "account": { "name": "Acme Inc", "domain": "acme.com" } }

POST /api/v1/crm/contacts
{ "contact": { "first_name": "Pat", "last_name": "Lee", "email": "pat@acme.com" } }

POST /api/v1/crm/leads
{ "lead": { "email": "new@acme.com", "company_name": "Acme" } }
```

### 12.6 Export / import (bundle portability)

```http
GET /api/v1/crm/export
POST /api/v1/crm/import
GET /api/v1/crm/change_events
```

## 13) Troubleshooting checklist

- If the model “doesn’t know” DripPulse: ensure it loaded this file or `/docs/external-agents-guide.html` content; do not rely on latent training alone.
- If you cannot find a `template_id`: call `GET /agent_templates` and pick from `templates[]`.
- If you cannot find a stage ID: call `GET /crm/opportunity_pipelines` and pick from returned `stages`.
- If you get `422`: read error messages (but not secrets), correct request, retry.
- If you get `429`: backoff and retry; reduce polling.
- If you get `401/403`: key invalid or lacks permissions; request updated key/role.

