# Zevari API Playbooks

URL: https://docs.zevari.ai/api/playbooks

> Ordered API playbooks for developers and AI agents building Zevari workflows over the public REST API.

## Where This Sits

API Playbooks is the call-order guide for developers, scheduled runners, CRM integrations, internal tools, and AI agents using the public REST API. Use the API Reference for endpoint schemas. Use MCP Reference for MCP tool semantics. Use Help Workflows for human-facing Zevari workflow concepts.

## Operating Principles

### Use the playbooks for order of operations

The API reference tells you the exact schema for one endpoint. API Playbooks tell you which endpoint to call first, which response fields to preserve, and what to do next.

### Treat the API key as the active context

A Zevari API key is bound to an organization and active LinkedIn sender. Do not invent workspace, account, or sender IDs that are not returned by Zevari.

### Stop at approval boundaries

LinkedIn sends, public posts, campaign activation, and other sensitive writes require confirmations.requestAction before the write endpoint executes.

### Read safety before scheduling writes

Call safety.getStatus before queued LinkedIn work. If safetyPaused is true, hold or route work according to the pause fields instead of firing into a blocked sender.

### Preserve request IDs and docs URLs

Every API error includes request_id plus error docs and suggested action fields. Preserve those values in logs and support handoffs.

## Confirmation Action Types

When staging a confirmation, use the action_type that matches the endpoint you will execute after approval.

### linkedin.sendConnectionRequest

Use confirmations.requestAction with action_type linkedin_send_connection_request before executing linkedin.sendConnectionRequest.

### linkedin.sendMessage

Use confirmations.requestAction with action_type linkedin_send_message before executing linkedin.sendMessage.

### linkedin.sendInMail

Use confirmations.requestAction with action_type linkedin_send_inmail before executing linkedin.sendInMail.

### linkedin.createPost

Use confirmations.requestAction with action_type linkedin_create_post before executing linkedin.createPost.

### linkedin.commentOnPost

Use confirmations.requestAction with action_type linkedin_comment_on_post before executing linkedin.commentOnPost.

### linkedin.reactToPost

Use confirmations.requestAction with action_type linkedin_react_to_post before executing linkedin.reactToPost.

### campaigns.updateStatus status=active

Use confirmations.requestAction with action_type campaign_activate before executing campaigns.updateStatus status=active.

## Bootstrap an API Runner

Discover the public API surface, verify the API key context, and check LinkedIn safety before planning work. Methods: GET /v1/methods, profile.get, safety.getStatus, libraryContext.get.

### 1. List the public methods

Fetch /v1/methods at startup or during tool discovery. Use the returned method names, paths, summaries, and docs URLs as the source of truth. Example: curl -sS https://api.zevari.ai/v1/methods \
  -H "Authorization: Bearer $ZEVARI_API_KEY"

### 2. Read the business profile

Call profile.get to understand the workspace voice, company context, ICP, and defaults before generating copy or scoring leads. Example: curl -sS -X POST https://api.zevari.ai/v1/profile/get \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

### 3. Check LinkedIn safety

Call safety.getStatus before queueing LinkedIn writes. If safetyPaused is true, do not call send endpoints until the status says active or the response provides an auto-resume timestamp you can schedule around. Example: curl -sS -X POST https://api.zevari.ai/v1/safety/getStatus \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

### 4. Load Library context only when generation needs it

Call libraryContext.get before generating outreach, campaign copy, or content that should reflect saved templates and brand context.

### Note 1

Use OpenAPI for exact schemas; use this playbook to decide call order.

### Note 2

API runners should cache method discovery briefly but refresh after deployment or schema errors.

## Run an Approval-Gated LinkedIn Write

Stage the exact write payload, wait for approval or Autopilot, then execute the write endpoint with the returned confirmation_id. Methods: confirmations.requestAction, linkedin.sendConnectionRequest, linkedin.sendMessage, linkedin.sendInMail, linkedin.createPost, linkedin.commentOnPost, linkedin.reactToPost.

### 1. Build the exact executable payload

Use the target write endpoint's exact field names. For example, connection requests use identifier and optional note; messages use chatId or recipientId plus message.

### 2. Apply sender-tier note limits before staging

For linkedin_send_connection_request, cap note to the active sender's LinkedIn tier before calling confirmations.requestAction: free senders support 200 characters; premium and sales_navigator senders support 300. If you exceed the active sender's limit, staging or execution returns HTTP 400 with ok=false and data.error='connection_note_too_long'. Treat that as a payload error, shorten the note, and stage a new confirmation.

### 3. Stage confirmation

Call confirmations.requestAction with the action_type that matches the write endpoint and the exact payload you intend to execute. Example: curl -sS -X POST https://api.zevari.ai/v1/confirmations/requestAction \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action_type": "linkedin_send_connection_request",
    "payload": {
      "identifier": "ACoAAAexample",
      "note": "Saw your post on AI workflow ops. Open to connecting?"
    }
  }'

### 4. Branch on Autopilot

If data.autopilot.approved is true, immediately call the write endpoint with data.confirmation_id. If not, store data.approval_url, show it to the user, and wait until they approve in Zevari.

### 5. Execute with confirmation_id

Retry the original write endpoint with the approved confirmation_id. Do not edit the payload between confirmation and execution. Example: curl -sS -X POST https://api.zevari.ai/v1/linkedin/sendConnectionRequest \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "ACoAAAexample",
    "note": "Saw your post on AI workflow ops. Open to connecting?",
    "confirmation_id": "pac_..."
  }'

### 6. Handle drift or expiry

If approval expires or the payload drifts, stage a new confirmation with the current exact payload. An explicitly rejected personalized enrollment is terminal for that idempotency key: stop, and use a new key only when the user makes a new request.

### Note 1

REST clients should not use MCP-only chat approval tools.

### Note 2

Do not reuse a confirmation_id after changing an over-limit connection note; stage a fresh confirmation with the shortened note.

### Note 3

For two or more outbound LinkedIn writes in one planned batch, use the bulk-confirmation pattern when available instead of staging separate approval URLs.

## Run a Campaign V2 sequence

Create a lead-owned Campaign V2 campaign on /v2, enroll people, approve if needed, then poll progress. Do not use frozen /v1/campaigns.* for this product. Methods: campaignsV2.create, campaignsV2.addLeads, campaignsV2.schedule, campaignsV2.getProgress, campaignsV2.getPending, campaignsV2.pause.

### 1. Create the Campaign V2 template

Call POST /v2/campaignsV2/create with a name and sequence. This does not send. Executable steps include connect, message, email_send, view_profile, like_post, comment_on_post, react_post_comment, and inmail. Pass LinkedIn post_id and comment_id as JSON strings. Example: curl -sS -X POST https://api.zevari.ai/v2/campaignsV2/create \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Founder outreach",
    "sequence": [
      { "action_type": "view_profile", "delay_hours": 0, "config": {} },
      { "action_type": "connect", "delay_hours": 24, "config": { "message": "Saw your work on revenue operations." } }
    ]
  }'

### 2. Enroll leads

Call POST /v2/campaignsV2/addLeads with one identity per person (URL, slug, provider id, gl_ lead id, or email). Read added, duplicates, unresolved, suppressed, blocked, warnings, and approval. Do not retry duplicates or suppressed as if they failed. Example: curl -sS -X POST https://api.zevari.ai/v2/campaignsV2/addLeads \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "cv2_...",
    "leads": [{ "identity": "https://www.linkedin.com/in/jane-founder" }]
  }'

### 3. Approve if Manual, then schedule

If approval.mode is autopilot, first steps are already scheduled. If manual, show approval.approval_url, wait for browser approval, then POST /v2/campaignsV2/schedule with confirmation_id. REST clients do not have MCP chat-approval tools. Example: curl -sS -X POST https://api.zevari.ai/v2/campaignsV2/schedule \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "cv2_...",
    "confirmation_id": "pac_..."
  }'

### 4. Poll progress. scheduled is not sent

GET /v2/campaignsV2/getProgress. Read send_window before assuming the worker is late. Skipped steps carry reason_code and reason_detail (failure_reason is the receipt status). A replied lead with leftover skipped already_reached steps is success — override sequences_forced_due:0 is not a window miss. GET /v2/campaignsV2/getPending for approvals and failed steps. Pause with POST /v2/campaignsV2/pause if outreach must stop. Example: curl -sS -G https://api.zevari.ai/v2/campaignsV2/getProgress \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "campaign_id=cv2_..."

### Note 1

Do not use /v1/campaigns.* for Campaign V2. Those methods are the classic V1 engine.

### Note 2

Do not use /v2/campaigns/submit. That path is an unused iss-131 stub, not Campaign V2.

### Note 3

An explicit post_id never falls back to the lead's latest post. Unavailable explicit posts skip after retrieve (ISS-127): reason_detail=post_unavailable, public reason_code=channel_unavailable. Comment replies need both post_id and comment_id as JSON strings.

## Create and Launch a Campaign

Create a fixed or continuous draft campaign, follow its returned next action, then activate it with approval. Methods: campaigns.create, campaigns.validate, confirmations.requestAction, campaigns.updateStatus, campaigns.addTargets, campaigns.getProgress.

### 1. Create the campaign as a draft

Call campaigns.create with targets, step configuration, generated content, and enrollment_mode. Enrollment defaults to fixed; use continuous whenever the campaign should keep accepting new leads after launch. Continuous campaigns are not limited to generic or template-resolvable copy — personalization_mode is independent of enrollment_mode, so a continuous campaign can carry full per-lead personalization via personalized_steps and/or sequence_override on individual targets, same as fixed. Send enrollment_mode as a top-level field, never nested under settings; a settings-only enrollment_mode now returns a loud warning telling you to delete the draft and recreate it with the field top-level, rather than silently defaulting. Creation persists a draft and returns status, effective enrollment_mode, targets_editable, add_targets_method, and next_action; it does not launch sending.

### 2. Validate before launch

Call campaigns.validate or inspect the creation response for missing targets, unsafe step configuration, unsupported action types, or sender readiness issues.

### 3. Stage activation approval

Campaign activation is approval-gated. Stage a confirmation with action_type campaign_activate and payload containing the campaign_id. Example: curl -sS -X POST https://api.zevari.ai/v1/confirmations/requestAction \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action_type": "campaign_activate",
    "payload": { "campaign_id": "campaign_..." }
  }'

### 4. Activate after approval

When Autopilot approves or the user approves in the browser, call campaigns.updateStatus with status active and the same confirmation_id. Example: curl -sS -X PATCH https://api.zevari.ai/v1/campaigns/updateStatus \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "campaign_...",
    "status": "active",
    "confirmation_id": "pac_..."
  }'

### 5. Append leads with shared campaign copy

For an active continuous campaign, call campaigns.addTargets once with a stable idempotency key and omit personalized_steps; accepted targets use the executable shared campaign copy and are scheduled atomically. Draft/paused additions remain unscheduled; terminal campaigns require a new campaign. Example: curl -sS -X POST https://api.zevari.ai/v1/campaigns/addTargets \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-sync-2026-07-19-batch-0042" \
  -d '{
    "campaign_id": "campaign_...",
    "idempotency_key": "crm-sync-2026-07-19-batch-0042",
    "targets": [{
      "lead_id": "lead_...",
      "linkedin_provider_id": "ACoAAAexample",
      "name": "Jane Founder"
    }]
  }'

### 6. Append per-lead copy and/or a per-lead sequence shape without pausing

Add recipients with campaigns.addTargets. Fixed and continuous campaigns accept two independent personalization mechanisms per target, and a single target may use one or both together: personalized_steps overrides that lead's message copy (a full text override per step, validated and snapshotted); sequence_override overrides that lead's step shape entirely (its own ordered list of step types, e.g. view_profile -> comment -> connection_request-with-note -> follow-ups, instead of the campaign's default steps). Complete personalized additions apply to already-active fixed or continuous campaigns without pausing them and without resetting other targets' timers. Use a stable idempotency_key. Eligible Autopilot applies; otherwise show approval link, approve confirmation_id, and retry identically. Same payload replays; changed payload conflicts; rejection terminates the key. A new key requires a new user request. Existing recipients and shared campaign steps remain unchanged. Approved content is immutable; active targets follow campaign timing. Missing copy: PERSONALIZED_SEQUENCE_INCOMPLETE. Step drift before activation: CAMPAIGN_PERSONALIZED_SNAPSHOT_INVALID. Example: curl -sS -X POST https://api.zevari.ai/v1/campaigns/addTargets \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-personalized-jane-mia-2026-07-22" \
  -d '{
    "campaign_id": "campaign_...",
    "idempotency_key": "crm-personalized-jane-mia-2026-07-22",
    "targets": [
      {
        "lead_id": "lead_jane",
        "linkedin_provider_id": "ACoAAAexample1",
        "name": "Jane Founder",
        "personalized_steps": [
          {
            "step_order": 1,
            "message": "Jane, your approach to revenue operations stood out. Open to connecting?"
          },
          {
            "step_order": 2,
            "body": "Thanks for connecting, Jane. Your founder-led sales system mirrors what we see with AI teams."
          }
        ]
      },
      {
        "lead_id": "lead_mia",
        "linkedin_provider_id": "ACoAAAexample2",
        "name": "Mia Operator",
        "sequence_override": [
          { "step_order": 1, "type": "view_profile" },
          { "step_order": 2, "type": "comment" },
          { "step_order": 3, "type": "connection_request", "note": "Enjoyed your take on ops tooling." },
          { "step_order": 4, "type": "follow_up_message", "message": "Following up on the connection, Mia." }
        ]
      }
    ]
  }'

# If approval is pending, repeat the exact request after approval and add:
# "confirmation_id": "pac_..."

### 7. Monitor progress

Call campaigns.getProgress for counts, target states, blocked conditions, and recent execution outcomes. If safety pauses the sender, hold new activation and scheduling work until safety clears.

## Search LinkedIn and Save Targets

Page through LinkedIn search results, normalize prospects, save targets, and optionally attach them to a campaign. Methods: linkedin.searchProfiles, targets.save, targets.createList, campaigns.addTargets.

### 1. Search with a stable query

Call linkedin.searchProfiles with the query, filters, and limit. Keep the same request body while paging. Example: curl -sS -X POST https://api.zevari.ai/v1/linkedin/searchProfiles \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "founder AI agency",
    "job_titles": ["Founder"],
    "locations": ["United States"],
    "limit": 50
  }'

### 2. Use next_cursor for pagination

If data.has_more is true and data.next_cursor is present, repeat the same search body with cursor set to that value. Stop when has_more is false.

### 3. Save usable targets

Call targets.save or targets.createList with the profiles you actually want to pursue. Keep provider IDs and LinkedIn URLs from the search result; do not synthesize them.

### 4. Add targets to a campaign when ready

Call campaigns.addTargets after the campaign exists and targets are reviewed. Send the same stable value as body idempotency_key and Idempotency-Key header. Omit personalized_steps to use shared copy in an active continuous campaign. For a new recipient's exact sequence, supply complete personalized_steps; active fixed and continuous campaigns stage approval and enroll without pausing. If manual approval is required, approve and repeat the identical request with confirmation_id. Draft/paused additions stay unscheduled; terminal campaigns require a new campaign. Follow content_mode, snapshot provenance, dispositions, error_code, and next_action.

### Note 1

Search endpoints are provider-backed and safety-scoped. Respect rate-limit and safety responses instead of looping aggressively.

### Note 2

Cursors are opaque. Do not parse or alter them.

### Note 3

For large Sales Navigator result sets (hundreds to thousands), prefer the scheduled saved-search playbook instead of paging interactively in one session.

## Schedule a Sales Navigator Saved Search

Create a paced Sales Navigator people (Leads) search that runs daily or weekly, resumes from a durable cursor, lands net-new leads on a list, and stops when exhausted. Requires a Sales Navigator-tier LinkedIn sender. Methods: savedSearches.create, savedSearches.list, savedSearches.runNow, savedSearches.update, savedSearches.delete.

### 1. Confirm Sales Navigator and destination list

The API key's bound LinkedIn sender must be sales_navigator. Decide the destination lead list (existing target list id, or a name the create call can reuse/create). People/Leads search only — do not use company-search URLs.

### 2. Create with URL xor filters

Call savedSearches.create with cadence daily or weekly, max_leads_per_run (default 500, max 1500), and exactly one of search_url (copied from Sales Navigator Leads results) or filters. Provide exactly one of target_list_id or target_list_name. Identical definitions are idempotent and return the existing saved search. Example: curl -sS -X POST https://api.zevari.ai/v2/savedSearches/create \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "US VP Sales B2B SaaS",
    "cadence": "daily",
    "max_leads_per_run": 500,
    "target_list_name": "SN VP Sales — US",
    "filters": {
      "job_titles": ["VP of Sales", "Head of Sales"],
      "seniority_levels": ["VP", "Director"],
      "locations": ["United States"],
      "company_sizes": ["51-200", "201-500"],
      "keywords": ["B2B", "SaaS"]
    }
  }'

### 3. Queue an immediate pass when needed

Call savedSearches.runNow with the returned saved_search_id (sss_…). The worker picks it up shortly. On an exhausted search, runNow reports exhausted and does not rescan — change the definition via savedSearches.update to restart.

### 4. Monitor list growth and exhaustion

Call savedSearches.list for status, last_run stats, scrape_list_exhausted, and paging_total_count. Review the destination list in Targets. New SN leads are ICP-autoscored in the background — start working high-fit leads before the full list finishes.

### 5. Pause, retune, or soft-delete

savedSearches.update with enabled false pauses without wiping the cursor. Changing URL or filters resets cursor and exhaustion. savedSearches.delete soft-deletes the schedule; leads already on the list remain.

### Note 1

Use /v2 savedSearches.* for this feature. Do not confuse with one-off linkedin.searchProfiles, which has no durable schedule or exhaustion latch.

### Note 2

Default daily 500/run is about ~3,500 leads per week when runs fill — pace large lists so LinkedIn is not flooded in one session.

### Note 3

Enable the Saved search exhausted notification route if you want an email when the result set is fully collected.

## Triage Inbox and Send a Reply

Read inbox context, classify or research the sender, generate a reply, stage approval, then send the message. Methods: linkedin.getInbox, linkedin.getMessages, agents.researchPerson, agents.generateMessages, confirmations.requestAction, linkedin.sendMessage.

### 1. List inbox threads

Call linkedin.getInbox to find the active thread or lead conversation. Store the chatId returned by Zevari.

### 2. Read message history

Call linkedin.getMessages with the chatId before drafting a reply. Do not reply from stale or partial context.

### 3. Research and draft

Use agents.researchPerson, agents.behavioralProfile, or agents.generateMessages when the reply needs personalization. The output is a draft, not permission to send.

### 4. Stage and execute

Call confirmations.requestAction with action_type linkedin_send_message and payload containing chatId and message. After approval, call linkedin.sendMessage with the same chatId, message, and confirmation_id.

### 5. Protect against stale replies

If you track expected_last_inbound_at, include it so Zevari can block replies when the lead has responded since the draft was prepared.

## Save, Review, and Publish Content

Create content drafts through the API, keep publishing approval-gated, and use LinkedIn content endpoints only after confirmation. Methods: content.save, content.list, content.update, confirmations.requestAction, linkedin.createPost, linkedin.reactToPost, linkedin.commentOnPost.

### 1. Save draft content

Call content.save for drafts and content.update for revisions. Saving internal content is not the same as publishing to LinkedIn.

### 2. Stage LinkedIn publishing separately

When a public LinkedIn post is ready, stage a confirmation with action_type linkedin_create_post and payload containing the exact post content.

### 3. Publish with confirmation_id

Call linkedin.createPost only after approval. Preserve confirmation_id and the exact approved content.

### 4. Engage with existing posts carefully

For comments and reactions, stage linkedin_comment_on_post or linkedin_react_to_post first. Use provider post IDs returned by Zevari or LinkedIn search/read endpoints.

## Handle Safety Pauses and Circuit Breakers

Use safety.getStatus to decide whether to wait, retry, contact support, or remove a sender from rotation. Methods: safety.getStatus, linkedin.getSafetyStatus.

### 1. Read status before queued sends

Call safety.getStatus for the active API key context before releasing queued LinkedIn outreach.

### 2. Pause on currentStatus

If currentStatus is paused_circuit_breaker or safetyPaused is true, stop LinkedIn writes for that sender. Do not keep retrying failed sends.

### 3. Schedule from absolute time

If autoResumes is true and resumesAt or cooldownEndsAt is present, hold queued work until that ISO timestamp, then refresh status before releasing the queue.

### 4. Use diagnostic fields for support

Capture pauseReasonDetail, triggeringActionType, failureCount, failureThreshold, upstreamErrorCodes, and requestIds. Include those in support handoffs.

### 5. Do not automate manual resume

If autoResumes is false or resumesAt is null, follow resolutionHint. Public REST does not expose linkedin.resumeAll because it mutates account-wide safety state.

## Handle API Errors

Preserve Zevari's structured error envelope and recover by reading docs_url and suggested_action before retrying. Methods: All methods.

### 1. Log the whole envelope

Store request_id, error.code, error.message, error.docs_url, error.suggested_action, method, and any validation details.

### 2. Use HTTP status for transport, error.code for behavior

A 401 means API key/auth context. A 400 may be invalid arguments, missing confirmation, payload drift, or safety denial. A 429 means rate limiting. The error.code and suggested_action tell you what to do next.

### 3. Retry only after correcting the cause

Do not blindly retry confirmation errors, safety pauses, invalid arguments, unsupported methods, or stale approvals. Read docs_url and rebuild the request if necessary.

### 4. Escalate with request_id

When contacting Zevari support, include request_id, endpoint path, method, timestamp, active workspace/sender if known, and the exact error fields.

## Check which leads replied to a campaign email

Poll campaign progress for reply counts, list the leads that replied with their reply bodies, read the full email thread for one target, then route warm replies into pipeline. Methods: campaigns.getProgress, campaigns.getReplies, leads.getEmailThread.

### 1. Poll progress for reply counts

Call campaigns.getProgress for the campaign to see aggregate counts including how many targets have replied. Use this to decide whether there are new responders worth pulling before listing every reply. Example: curl -sS -G https://api.zevari.ai/v1/campaigns/getProgress \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "campaign_id=cmp_..."

### 2. List who replied and what they said

Call campaigns.getReplies to list inbound email replies across the campaign. Each entry returns reply_id, campaign_target_id, lead_id, lead_name, lead_email, from_email, subject, text_preview (first 1000 chars), text_truncated, occurred_at, matched_by, and forwarded. Bodies carry an untrusted-input warning. Paginate older pages by passing the previous response's next_before and next_before_id as before and before_id; both are only set when count equals limit. Example: curl -sS -G https://api.zevari.ai/v1/campaigns/getReplies \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "campaign_id=cmp_..." \
  --data-urlencode "limit=50"

### 3. Read the full back-and-forth for a target

Call leads.getEmailThread with the campaign_target_id (or lead_id) from a reply to get the full conversation: inbound replies interleaved with outbound campaign emails, merged and sorted ascending by timestamp. Use this for the complete text rather than relying on the truncated preview in the list. Inbound bodies carry an untrusted-input warning. Example: curl -sS -G https://api.zevari.ai/v1/leads/getEmailThread \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "campaign_target_id=ct_..." \
  --data-urlencode "limit=100"

### 4. Route warm replies into pipeline

When a reply is a genuine response worth pursuing, stage the next action (pipeline opportunity, meeting, or a LinkedIn/email follow-up) through the normal approval-gated write path. Preserve campaign_target_id and lead_id so the follow-up attaches to the right record.

### Note 1

Email replies surface alongside LinkedIn replies; campaigns.getReplies covers the email channel specifically.

### Note 2

Reply bodies may be large. campaigns.getReplies returns a preview per reply, while leads.getEmailThread returns the full text for one target.

### Note 3

Respect pagination via the keyset cursor: pass next_before and next_before_id as before and before_id to fetch the next older page, and stop when next_before is null.

## Read Back the Audit Trail After a Write

Confirm exactly what happened to a write with actions.getReceipt, or page a lead's full action history with actions.listForLead, instead of re-querying from scratch. Methods: actions.getReceipt, actions.listForLead.

### 1. Look up one receipt

Every priority LinkedIn write (send_message, send_inmail, send_connection_request, create_post, targets_save, leads_lookup_by_url, and others) returns action.receipt_id on the write response. Call actions.getReceipt with that receipt_id to pull the receipt plus its linked confirmation, request, queue, and audit evidence, LinkedIn result IDs, safety snapshot, and hydrated lead context in one call. Example: curl -sS -G https://api.zevari.ai/v2/actions/getReceipt \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "receipt_id=rcpt_..."

### 2. Page through one lead's full history

Call actions.listForLead with lead_id to page cursor-based action history for that lead. Optional cursor, limit (default 50, max 100), from, and to narrow the page and window. Use this to reconstruct everything Zevari has done for a lead without replaying every write endpoint's response. Example: curl -sS -G https://api.zevari.ai/v2/actions/listForLead \
  -H "Authorization: Bearer $ZEVARI_API_KEY" \
  --data-urlencode "lead_id=lead_..." \
  --data-urlencode "limit=50"

### 3. Read receipt state, don't assume success

Receipt state is one of queued, executing, succeeded, failed, deferred, rejected, cancelled, or outcome_unknown. outcome_unknown means the LinkedIn response was lost or unclear; Zevari does not auto-retry it, so treat it as needing manual follow-up rather than a completed send.

### Note 1

Receipts and lead history are scoped to the authenticated user who owns that lead; there is no cross-teammate or cross-organization visibility into another user's receipts or leads, even within the same org.

### Note 2

Prefer this over re-listing inbox, campaign progress, or search results just to confirm a prior write — the receipt and per-lead history are the durable, queryable record.

### Note 3

These reads are /v2-only. /v1 stays frozen and does not expose receipts or lead history.

## Docs Links

- [Help Center](https://docs.zevari.ai/help): Human-facing guide to Zevari skills, workflows, agents, videos, safety, and support.
- [Workflow Guide](https://docs.zevari.ai/help/workflows): LinkedIn outreach, content, prospect research, audience analysis, campaign, inbox, and GTM workflows for Claude.
- [Prompting Guide](https://docs.zevari.ai/help/prompting-guide): Copy-ready prompts that require AI assistants to read the MCP reference before acting.
- [Workspaces and Sender Seats](https://docs.zevari.ai/help/workspaces-and-seats): Human-facing setup guide for free workspaces, payer-account billing, members, funded LinkedIn seats, and sender associations.
- [Safety Center](https://docs.zevari.ai/help/safetycenter): Human-facing guide to Zevari safety guardrails, warm-up, pauses, blocked actions, and recovery state.
- [Warm-Up](https://docs.zevari.ai/help/warm-up): Human-facing guide to LinkedIn sender warm-up and gradual activity ramping.
- [API Playbooks](https://docs.zevari.ai/api/playbooks): Ordered REST call flows for developers and AI agents using the Zevari API.
- [MoltSets + LinkedIn Workflows](https://docs.zevari.ai/api/moltsets-linkedin-workflows): Use MoltSets verified contact data with Zevari's LinkedIn enrichment, campaign, and approval-gated execution layer.
- [MCP Reference](https://docs.zevari.ai/mcp/reference): Agent-facing tool schemas, capability contracts, examples, gotchas, and recovery guidance.
- [API Reference](https://docs.zevari.ai/api/reference): Public REST API reference powered by the Zevari OpenAPI document.
- [MCP Reference JSON](https://api.zevari.ai/v1/mcp-reference.json): Machine-readable MCP tool reference.
- [LLMs Full](https://api.zevari.ai/llms-full.txt): AI-readable Zevari documentation bundle.
- [Support](mailto:issues@zevari.ai): Send bug reports, support handoffs, and feature requests.
