# Errors Source: https://docs.callprep.app/api-reference/errors Complete reference of all error codes returned by the CallPrep API. All errors return a JSON body with an `error` field containing a machine-readable code. ```json theme={null} { "error": "credits_exhausted", "used": 50, "limit": 50 } ``` ## HTTP status codes | Status | Meaning | | ------ | ------------------------------------- | | `202` | Request accepted (POST /research) | | `200` | Success (GET /research-status) | | `400` | Bad request — invalid parameters | | `401` | Unauthorized — missing or invalid key | | `404` | Not found — research ID doesn't exist | | `429` | Too many requests — credits exhausted | | `500` | Internal server error | ## Error codes ### Authentication errors (401) | Code | Description | | ----------------- | ----------------------------------------------------- | | `missing_api_key` | No `Authorization` header provided | | `invalid_key` | Key not found, revoked, or belongs to another account | ### Request errors (400) | Code | Description | | ------------------------ | ---------------------------------------------- | | `missing_email` | `email` field is required | | `invalid_email_format` | Email address is not valid | | `missing_key_id` | API key ID not found in request | | `api_key_has_no_product` | The key has no product context — regenerate it | ### Credit errors (429) | Code | Description | | --------------------- | -------------------------------- | | `credits_exhausted` | Monthly credits are fully used | | `trial_limit_reached` | Free plan 3-credit limit reached | Response body includes: ```json theme={null} { "error": "credits_exhausted", "used": 50, "limit": 50 } ``` ### Not found (404) | Code | Description | | ----------- | ------------------------------------------------------------------ | | `not_found` | The `research_id` does not exist or belongs to a different account | ### Research job errors These appear in the `error` field when `status` is `failed`: | Code | Description | | ---------------- | --------------------------------------------------------- | | `internal_error` | An unexpected error occurred in the pipeline | | `timeout` | The job exceeded maximum processing time (auto-retryable) | ## Handling errors ```javascript theme={null} const res = await fetch(`${BASE_URL}/research`, { ... }); const data = await res.json(); if (!res.ok) { switch (data.error) { case 'credits_exhausted': console.error(`Out of credits: ${data.used}/${data.limit}`); // Notify user to upgrade break; case 'invalid_key': console.error('Invalid API key — check your credentials'); break; case 'invalid_email_format': console.error('Invalid email address provided'); break; default: console.error('API error:', data.error); } return; } ``` ## Retrying failed jobs If a research job returns `status: failed`, you can resubmit the same request. A new credit will be consumed. If failures persist for the same email, please contact [hello@callprep.app](mailto:hello@callprep.app). Jobs that fail due to a timeout (infrastructure issue) are eligible for a credit refund. Contact support with the `research_id` to request a refund. # GET /research-status/{research_id} Source: https://docs.callprep.app/api-reference/get-research-status Poll for research results using the research_id returned by POST /research. ## Overview Poll this endpoint until `status` is `completed` or `failed`. Recommended polling interval: every **5 seconds**. This endpoint does **not** consume credits — poll as many times as needed. **Base URL:** `https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1` *** ## Request ```http theme={null} GET /research-status/{research_id} Authorization: Bearer cp_live_... ``` ### Path parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------------------- | | `research_id` | string | ✅ | The `research_id` returned by `POST /research` | ### Example ```bash theme={null} curl \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research-status/res_1a7b6e9c35a9b848 \ -H "Authorization: Bearer cp_live_..." ``` *** ## Response — processing **HTTP 200** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "processing", "estimated_seconds": 15 } ``` Continue polling until `status` changes to `completed` or `failed`. *** ## Response — completed **HTTP 200** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "completed", "completed_at": "2026-05-03T08:27:08.004+00:00", "data": { "prospect": { "id": 42, "name": "Sarah Mitchell", "title": "VP of Sales", "company": "Acme Corp", "email": "sarah.mitchell@acmecorp.com", "linkedin_url": "https://www.linkedin.com/in/sarah-mitchell/", "photo": "https://media.licdn.com/dms/image/...", "last_role_start": "2024-03-01", "summary": "Sarah Mitchell is VP of Sales at Acme Corp, leading a 40-person enterprise sales team. Her LinkedIn activity shows a focus on outbound pipeline efficiency and CRM adoption, suggesting an interest in tools that reduce manual research overhead for her reps.", "opening_talk": [ "Your post about reducing sales cycle length through better pre-call prep resonated — that's exactly the problem CallPrep solves for teams like yours.", "You mentioned your reps spend 45 minutes on average researching before each call — we can cut that to under 60 seconds.", "Your comment on LinkedIn ROI for outbound caught my attention — CallPrep pulls live LinkedIn activity into every research brief automatically." ], "posts": [ { "text": "We cut our average sales cycle by 18% last quarter. The change? Requiring reps to complete a pre-call brief before every discovery call...", "posted": "2026-04-21 09:15:00", "post_url": "https://www.linkedin.com/feed/update/urn:li:activity:...", "reshared": false, "summary": "Reduced sales cycle 18% by mandating pre-call research briefs before every discovery call." } ] }, "company": { "id": 42, "name": "Acme Corp", "description": "Acme Corp is a B2B SaaS platform for enterprise sales teams, offering CRM integrations, pipeline analytics, and AI-assisted outreach across 30+ countries.", "linkedin_url": "https://www.linkedin.com/company/acmecorp/", "website": "https://acmecorp.com", "industry": "software", "hq": "San Francisco, US", "revenue": "24M", "employees": "180", "technologies": ["Salesforce", "HubSpot", "AWS", "Stripe", "Segment"], "problems": [ "Enterprise sales teams waste hours on manual prospect research before each call", "Reps lack competitive intelligence when entering discovery calls with informed buyers", "Sales managers struggle to ensure consistent pre-call preparation across distributed teams", "Outbound sequences lack personalization due to limited time for prospect research" ], "services": [ "AI-powered pre-call research briefs generated from LinkedIn, company news, and CRM data", "Competitive intelligence alerts when prospects engage with competitor content", "Sales playbook automation that tailors discovery questions to prospect personas", "CRM-native workflow that surfaces research directly inside Salesforce and HubSpot" ], "icp": [ "Enterprise SaaS companies with 50+ person sales teams running high-volume outbound", "RevOps leaders standardizing pre-call processes across distributed sales orgs", "Sales managers at B2B companies with ACV over $20k requiring consultative selling", "SDR teams doing account-based outreach where personalization directly impacts reply rates" ], "synergy_points": [ "CallPrep directly eliminates Acme Corp's core pain point — reps spending 45+ minutes on pre-call research — by automating enrichment in under 60 seconds.", "Acme Corp's Salesforce and HubSpot integrations align with CallPrep's CRM-native positioning, enabling seamless adoption without workflow disruption.", "Acme Corp sells to enterprise sales teams — the exact buyers who understand and budget for sales productivity tools, reducing education overhead in the sales cycle.", "Acme Corp's focus on outbound personalization makes CallPrep's LinkedIn post analysis and opening talk generation directly relevant to their reps' daily workflow." ], "discovery_questions": [ "You've invested in Salesforce and HubSpot — how are your reps currently pulling pre-call intelligence from those systems before discovery calls, and where does the process break down?", "With 180 people and a distributed team, how do you ensure consistent pre-call preparation standards across your SDR and AE org — is that a process or a tooling challenge?", "Your LinkedIn post mentioned cutting sales cycles by 18% through pre-call briefs — what's the biggest bottleneck preventing you from scaling that behavior across the full team?" ], "clients": [{ "count": "1,200+" }], "news": [ { "summary": "Acme Corp raised a $12M Series A to expand enterprise integrations and grow its European GTM team.", "url": "https://techcrunch.com/2026/03/acme-corp-series-a", "date": "2026-03" } ], "insights": [ "Competitors like Gong and Chorus position on call recording and post-call analytics — Acme Corp's gap is pre-call intelligence, which CallPrep fills directly.", "Market gap: most sales intelligence tools provide contact data but not contextual, AI-generated talking points — CallPrep's differentiation is actionable output, not raw data.", "CallPrep gives Acme Corp reps a measurable edge: personalized opening lines grounded in prospect LinkedIn activity have 3x higher engagement in discovery calls.", "Key concern: Acme Corp may already evaluate competing tools for pre-call features — reps need to position CallPrep as complementary enrichment, not a competing workflow tool." ] }, "decision_makers": [ { "id": 1, "full_name": "Sarah Mitchell", "position_title": "VP of Sales", "linkedin_url": "https://www.linkedin.com/in/sarah-mitchell/", "picture_url": "https://media.licdn.com/dms/image/...", "location_text": "San Francisco, CA", "company_name": "Acme Corp" }, { "id": 2, "full_name": "James Okafor", "position_title": "Chief Revenue Officer", "linkedin_url": "https://www.linkedin.com/in/james-okafor/", "picture_url": "https://media.licdn.com/dms/image/...", "location_text": "San Francisco, CA", "company_name": "Acme Corp" } ] } } ``` *** ## Response — failed **HTTP 200** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "failed", "error": "internal_error" } ``` You can resubmit the same request — a new credit will be consumed. If the failure was caused by an infrastructure error, the credit is refunded automatically. *** ## Status values | Status | Meaning | Action | | ------------ | ----------------------------- | ---------------- | | `queued` | Job waiting to be picked up | Continue polling | | `processing` | Enrichment in progress | Continue polling | | `completed` | All enrichment steps finished | Use the data | | `failed` | Unrecoverable error occurred | Resubmit request | *** ## Error responses **401 — Missing or invalid API key** ```json theme={null} { "error": "invalid_key" } ``` **404 — Research ID not found** ```json theme={null} { "error": "not_found" } ``` *** For a production-ready polling implementation with timeouts and error handling, see the [Handling errors](/guides/handling-errors) guide. For a full description of all fields in the `data` object, see the [Response fields](/api-reference/response-fields) reference. # POST /research Source: https://docs.callprep.app/api-reference/post-research Submit a prospect email to start an enrichment job. ## Overview Submit a prospect's email address to start an enrichment job. Returns immediately with a `research_id` — enrichment runs asynchronously in the background. Poll [`GET /research-status/{research_id}`](/api-reference/get-research-status) until `status` is `completed`. **Base URL:** `https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1` *** ## Request ```http theme={null} POST /research Authorization: Bearer cp_live_... Content-Type: application/json ``` ### Body parameters | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------- | | `email` | string | ✅ | Prospect's work email address | | `prospect_name` | string | ❌ | Full name — improves match accuracy | | `company_name` | string | ❌ | Company name — improves match accuracy | | `linkedin_url` | string | ❌ | Prospect's LinkedIn profile URL | | `company_linkedin_url` | string | ❌ | Company LinkedIn URL — overrides auto-detected | ### Minimal request ```bash theme={null} curl -X POST \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research \ -H "Authorization: Bearer cp_live_..." \ -H "Content-Type: application/json" \ -d '{ "email": "sarah.mitchell@acmecorp.com" }' ``` ### Full request ```bash theme={null} curl -X POST \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research \ -H "Authorization: Bearer cp_live_..." \ -H "Content-Type: application/json" \ -d '{ "email": "sarah.mitchell@acmecorp.com", "prospect_name": "Sarah Mitchell", "company_name": "Acme Corp", "linkedin_url": "https://www.linkedin.com/in/sarah-mitchell/", "company_linkedin_url": "https://www.linkedin.com/company/acmecorp/" }' ``` *** ## Response **HTTP 202 — Accepted** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "processing", "estimated_seconds": 30 } ``` | Field | Description | | ------------------- | --------------------------------------------------- | | `research_id` | Unique ID for this job — use it to poll for results | | `status` | Always `processing` on initial response | | `estimated_seconds` | Estimated completion time in seconds | *** ## Error responses **401 — Missing or invalid API key** ```json theme={null} { "error": "missing_api_key" } { "error": "invalid_key" } ``` **400 — Invalid request** ```json theme={null} { "error": "missing_email" } { "error": "invalid_email_format" } ``` **429 — Credits exhausted** ```json theme={null} { "error": "credits_exhausted", "used": 50, "limit": 50 } ``` See the [Errors reference](/api-reference/errors) for the full list of error codes. *** ## Notes Providing `prospect_name` and `company_name` improves match accuracy and is recommended whenever you have that information available. After submitting, poll [`GET /research-status`](/api-reference/get-research-status) every **5 seconds** until `status` is `completed`. See the [Handling errors](/guides/handling-errors) guide for a production-ready polling implementation. # Response fields Source: https://docs.callprep.app/api-reference/response-fields Complete reference of all fields returned by GET /research-status. ## Completed response example ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "completed", "completed_at": "2026-05-03T08:27:08.004+00:00", "data": { "prospect": { "id": 42, "name": "Sarah Mitchell", "title": "VP of Sales", "company": "Acme Corp", "email": "sarah.mitchell@acmecorp.com", "linkedin_url": "https://www.linkedin.com/in/sarah-mitchell/", "photo": "https://media.licdn.com/dms/image/...", "last_role_start": "2024-03-01", "summary": "Sarah Mitchell is VP of Sales at Acme Corp, leading a 40-person enterprise sales team. Her LinkedIn activity shows a focus on outbound pipeline efficiency and CRM adoption, suggesting an interest in tools that reduce manual research overhead for her reps.", "opening_talk": [ "Your post about reducing sales cycle length through better pre-call prep resonated — that's exactly the problem CallPrep solves for teams like yours.", "You mentioned your reps spend 45 minutes on average researching before each call — we can cut that to under 60 seconds.", "Your comment on LinkedIn ROI for outbound caught my attention — CallPrep pulls live LinkedIn activity into every research brief automatically." ], "posts": [ { "text": "We cut our average sales cycle by 18% last quarter. The change? Requiring reps to complete a pre-call brief before every discovery call...", "posted": "2026-04-21 09:15:00", "post_url": "https://www.linkedin.com/feed/update/urn:li:activity:...", "reshared": false, "summary": "Reduced sales cycle 18% by mandating pre-call research briefs before every discovery call." } ] }, "company": { "id": 42, "name": "Acme Corp", "description": "Acme Corp is a B2B SaaS platform for enterprise sales teams, offering CRM integrations, pipeline analytics, and AI-assisted outreach across 30+ countries.", "linkedin_url": "https://www.linkedin.com/company/acmecorp/", "website": "https://acmecorp.com", "industry": "software", "hq": "San Francisco, US", "revenue": "24M", "employees": "180", "technologies": ["Salesforce", "HubSpot", "AWS", "Stripe", "Segment"], "problems": [ "Enterprise sales teams waste hours on manual prospect research before each call", "Reps lack competitive intelligence when entering discovery calls with informed buyers", "Sales managers struggle to ensure consistent pre-call preparation across distributed teams", "Outbound sequences lack personalization due to limited time for prospect research" ], "services": [ "AI-powered pre-call research briefs generated from LinkedIn, company news, and CRM data", "Competitive intelligence alerts when prospects engage with competitor content", "Sales playbook automation that tailors discovery questions to prospect personas", "CRM-native workflow that surfaces research directly inside Salesforce and HubSpot" ], "icp": [ "Enterprise SaaS companies with 50+ person sales teams running high-volume outbound", "RevOps leaders standardizing pre-call processes across distributed sales orgs", "Sales managers at B2B companies with ACV over $20k requiring consultative selling", "SDR teams doing account-based outreach where personalization directly impacts reply rates" ], "synergy_points": [ "CallPrep directly eliminates Acme Corp's core pain point — reps spending 45+ minutes on pre-call research — by automating enrichment in under 60 seconds.", "Acme Corp's Salesforce and HubSpot integrations align with CallPrep's CRM-native positioning, enabling seamless adoption without workflow disruption.", "Acme Corp sells to enterprise sales teams — the exact buyers who understand and budget for sales productivity tools, reducing education overhead in the sales cycle.", "Acme Corp's focus on outbound personalization makes CallPrep's LinkedIn post analysis and opening talk generation directly relevant to their reps' daily workflow." ], "discovery_questions": [ "You've invested in Salesforce and HubSpot — how are your reps currently pulling pre-call intelligence from those systems before discovery calls, and where does the process break down?", "With 180 people and a distributed team, how do you ensure consistent pre-call preparation standards across your SDR and AE org — is that a process or a tooling challenge?", "Your LinkedIn post mentioned cutting sales cycles by 18% through pre-call briefs — what's the biggest bottleneck preventing you from scaling that behavior across the full team?" ], "clients": [{ "count": "1,200+" }], "news": [ { "summary": "Acme Corp raised a $12M Series A to expand enterprise integrations and grow its European GTM team.", "url": "https://techcrunch.com/2026/03/acme-corp-series-a", "date": "2026-03" } ], "insights": [ "Competitors like Gong and Chorus position on call recording and post-call analytics — Acme Corp's gap is pre-call intelligence, which CallPrep fills directly.", "Market gap: most sales intelligence tools provide contact data but not contextual, AI-generated talking points — CallPrep's differentiation is actionable output, not raw data.", "CallPrep gives Acme Corp reps a measurable edge: personalized opening lines grounded in prospect LinkedIn activity have 3x higher engagement in discovery calls.", "Key concern: Acme Corp may already evaluate competing tools for pre-call features — reps need to position CallPrep as complementary enrichment, not a competing workflow tool." ] }, "decision_makers": [ { "id": 1, "full_name": "Sarah Mitchell", "position_title": "VP of Sales", "linkedin_url": "https://www.linkedin.com/in/sarah-mitchell/", "picture_url": "https://media.licdn.com/dms/image/...", "location_text": "San Francisco, CA", "company_name": "Acme Corp" }, { "id": 2, "full_name": "James Okafor", "position_title": "Chief Revenue Officer", "linkedin_url": "https://www.linkedin.com/in/james-okafor/", "picture_url": "https://media.licdn.com/dms/image/...", "location_text": "San Francisco, CA", "company_name": "Acme Corp" } ] } } ``` *** ## prospect fields | Field | Description | | ----------------- | ------------------------------------------------------------------------ | | `name` | Full name | | `title` | Job title, translated to English | | `company` | Current company name | | `email` | Work email address | | `linkedin_url` | LinkedIn profile URL | | `photo` | Profile photo URL (nullable) | | `last_role_start` | Start date of current role — `YYYY-MM-DD` (nullable) | | `summary` | AI-generated 2-3 sentence professional summary | | `opening_talk` | 3 conversation starters personalised to the prospect's LinkedIn activity | | `posts` | Up to 3 recent LinkedIn posts with AI-generated summaries | *** ## company fields | Field | Description | | --------------------- | --------------------------------------------------------- | | `name` | Company name | | `description` | Company description, translated to English | | `linkedin_url` | LinkedIn company page URL | | `website` | Company website | | `industry` | Industry (e.g. `software`, `marketing & advertising`) | | `hq` | Headquarters location | | `revenue` | Estimated annual revenue (nullable) | | `employees` | Estimated employee count | | `technologies` | Tech stack detected for the company | | `problems` | 4 problems the company solves for its customers | | `services` | 4 main services or products the company offers | | `icp` | 4 Ideal Customer Profiles of the company | | `synergy_points` | 4 synergies between this company and **your product** | | `discovery_questions` | 3 discovery questions tailored for this specific prospect | | `clients` | Estimated client count (nullable) | | `news` | Up to 3 recent company news items (max 12 months old) | | `insights` | 4 competitive insights about the company | | `posts` | Up to 3 recent LinkedIn company posts with AI summaries | *** ## decision\_makers fields Decision makers are only available on the **BDR plan and above**. On other plans, `decision_makers` will be an empty array. | Field | Description | | ---------------- | ---------------------------- | | `full_name` | Full name | | `position_title` | Job title | | `linkedin_url` | LinkedIn profile URL | | `picture_url` | Profile photo URL (nullable) | | `location_text` | Location (nullable) | | `company_name` | Company they work at | *** ## Nullable fields Fields may be `null` when data is not available for a given prospect or company. Always handle `null` values in your code: ```javascript theme={null} const summary = data.prospect?.summary ?? 'No summary available'; const dm = data.decision_makers ?? []; ``` # API Keys Source: https://docs.callprep.app/authentication/api-keys How to generate, manage, and secure your CallPrep API keys. ## Generating an API key 1. Go to [API Keys](https://call-prep-api.vercel.app/api-keys) in the dashboard 2. Click **Generate new key** 3. Enter your product URL — we'll automatically extract your product name and description 4. Review the generated product context (name, description, key features, target customers) 5. Click **Create key** Your key will be shown **once**. Copy it immediately and store it securely. If you lose your API key, you'll need to generate a new one. There is no way to retrieve an existing key after it's been shown. ## Product context Every research is generated against **your product** — its name, description, key features and target customers — so the insights are about what you actually sell. Your account has **one product**, shared by every key and by the Chrome extension and the Autopilot. Edit it under **Settings → Product** in the dashboard. Older accounts may still have more than one product from before this changed. Only the current one is used for new research. ## Key format ``` cp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Keys always start with `cp_live_` followed by 42 random characters. ## Using your key Pass your key as a Bearer token in the `Authorization` header: ```bash theme={null} curl -H "Authorization: Bearer cp_live_..." \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research-status/res_xxx ``` ## Key limits by plan | Plan | API keys | | ---------- | -------- | | Free | 1 | | Individual | 1 | | BDR | 3 | | BDR+ | 10 | | Enterprise | Custom | ## Security best practices API keys should only be used server-side. Never include them in browser JavaScript, mobile apps, or public repositories. Use environment variables instead. ```bash theme={null} # .env CALLPREP_API_KEY=cp_live_... ``` Generate a new key periodically and update your integrations. Delete old keys from the dashboard once they're no longer in use. Use different keys for development, staging, and production. This way you can revoke a compromised dev key without affecting production. Immediately delete the key from the dashboard and generate a new one. Check your research logs in the dashboard to identify any unauthorised usage. ## Revoking a key Go to [API Keys](https://call-prep-api.vercel.app/api-keys) in the dashboard, find the key and click **Revoke**. Revocation is immediate — any requests using that key will return `401 invalid_key`. # Rate limits Source: https://docs.callprep.app/authentication/rate-limits Understanding credit consumption and request limits. ## Credits Each call to `POST /research` consumes **1 credit** from your monthly plan. Credits reset at the start of your billing cycle, and are shared with the Chrome extension and the Autopilot — one pool for the whole account. | Plan | Credits/month | | ---------- | ------------- | | Free | 10 | | Individual | 125 | | BDR | 300 | | BDR+ | 800 | | Enterprise | Custom | `GET /research-status` does **not** consume credits — poll as many times as needed. ## Cache hits If a prospect or company was recently enriched (within cache TTL), the request returns cached data immediately. A credit is still consumed, but the response is near-instant. ## Credit alerts You can enable email notifications when you reach 75% and 95% of your monthly credits in [Settings → Notifications](https://call-prep-api.vercel.app/settings). ## What happens when credits run out `POST /research` will return `429 credits_exhausted`. Your existing data and API keys remain active — you just can't start new research jobs until the next billing cycle or until you upgrade your plan. ```json theme={null} { "error": "credits_exhausted", "used": 300, "limit": 300 } ``` ## Concurrent requests There is no hard limit on concurrent requests, but the research worker processes one job per invocation. For high-volume batch enrichment, consider spacing requests out or contact us about custom plans. ## API key limits See the [API Keys](/authentication/api-keys) page for key limits per plan. # Activity and troubleshooting Source: https://docs.callprep.app/autopilot/activity Where to look when a message did not go out, where a lead you expected went, and how to end one lead's sequence by hand. Four places answer almost every question about an Autopilot. In order: 1. **The row** on the Autopilot list — counters and the delivery chip 2. **Activity** — one lead's timeline, with the reason for every skip 3. **The queue** — leads that have not matched anything yet 4. **Upcoming sends** and the capacity bars — what is scheduled and what is behind *** ## The row Each Autopilot shows **leads · active · responded · closed** and a **responded rate**. The **delivery chip** appears only when leads are sitting **uncontacted** — it stays quiet when everything is moving, so when it does speak it is worth reading. Expanding it shows how many leads are waiting for a first message, and how many of this Autopilot's leads are reachable per channel. Low reach on a channel is a configuration answer, not a fault: prospected lists rarely have phone numbers or Telegram handles, inbound sign-ups usually do. *** ## Activity — one lead at a time **Activity** on an Autopilot opens the lead list. Filter by outcome — *active*, *responded*, *closed*, *failed* — or search by name, email or company. Expanding a lead shows its timeline: every touch, in order, with the channel, the step, when it was scheduled, when it was sent, **the message that actually went out**, and — where something did not happen — the reason. ### Why a message was skipped Nothing is skipped silently. These are the reasons you will actually see: | Reason on the timeline | What it means | What to do | | -------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | **No phone number on the contact** | No number in any of the properties you listed | Add the number in HubSpot, or accept that this lead is email/LinkedIn only | | **No Telegram @username on the contact** | Telegram cannot open a chat from a number | See [Telegram](/integrations/telegram) | | **No LinkedIn profile on this lead** | Enrichment found no profile, or the URL was unusable | Nothing — the other channels carry on | | **Already a connection — no invite needed** | You are already connected | Nothing. Message steps behind it still run | | **LinkedIn didn't return that profile** | The profile could not be reached at send time | Usually transient; check the profile still exists | | **LinkedIn invite was never accepted** | A message step waited a week past its date | Nothing — the sequence moves on | | **Lead never warmed up** | A WhatsApp or Telegram step waited three weeks for a reply or an accepted invite | Expected on warm-only channels. Pair them with email or LinkedIn | | **Couldn't write a credible note / message** | The research produced nothing true to build on for that channel | Nothing to fix per lead. A generic message is the thing we refuse to send | | **Nothing to follow up with** | A follow-up email with no angle to build on — the first email still went | As above | | **The address stopped accepting mail** | A hard bounce ended the email steps for this lead | Other channels continue | ### What is *not* a skip A message held back by the daily limit, the sending window, or a weekend is **rescheduled**, not skipped — it moves to the next available slot and keeps its place. If your queue is deep, "not sent yet" is far more common than "not sending". ### Why a sequence ended The lead row carries the reason: *Lead replied*, *Connected on a call*, *Meeting booked*, *LinkedIn invite accepted*, *CRM property changed*, *Lead unsubscribed*, *Closed manually*, *No reachable channel*, *Fell too far behind its schedule*, *Domain is on the excluded list*, *Its Autopilot was archived*. The first four are wins — see [What stops a sequence](/autopilot/stop-conditions). *** ## The queue — leads that matched nothing yet At the bottom of the Autopilot list: **Queue — N waiting**. * **Waiting** — the lead was created but matched no filter yet, and is re-checked as its CRM data arrives. Each row shows roughly how long it has left. * **Abandoned** — the wait time ran out and nothing matched. **No research was run and no credit was spent.** Abandoned is the normal end for leads you never intended to contact. If leads you *did* intend to contact land there, the filter is the thing to look at — compare it against the property values those contacts actually have. Depending on your account, abandoned leads can be **reprocessed** (run through the filters again as they are now) or **sent to a specific Autopilot** by hand. Both research the lead and spend a credit at that point. *** ## Capacity and upcoming sends The capacity bars show what today's limits allow across your LinkedIn seats and mailboxes, and how much is already used. Under them, the queue depth: how much is waiting behind the cap, and roughly how long it will take to clear at the current limits. **Upcoming sends** lists everything scheduled, nearest first, grouped by day and filterable by channel — in the order the sender will actually work through it: first messages before follow-ups. Dates in that list are **intentions, not promises**. Anything that does not fit a day's limit moves forward. If the queue is weeks deep, the fix is a higher daily limit, another mailbox or seat, or fewer leads — not waiting. *** ## Finding one lead The search box above the Autopilot list answers two questions at once: it filters Autopilots by name, and it finds a **lead** by email, first or last name, or company — telling you **which Autopilot** they are in, including archived ones. Clicking a result opens that Autopilot's Activity with the lead's address already filled in. *** ## Closing a lead by hand Under a lead's timeline in Activity: **close** ends that lead's sequence on every channel and cancels everything still queued for them. * It cannot be undone — the lead comes back the way it arrived, through your CRM. * It does **not** retract a win: a lead who already replied stays counted as responded. * It changes nothing else about the Autopilot; every other lead carries on. *** ## Nothing is sending at all Work down this list: 1. **Is the Autopilot switched on?** The list shows it plainly. 2. **Is a channel connected**, verified, and not paused? A paused mailbox or seat sends nothing. 3. **Email only:** is the sender identity complete? Incomplete identity holds every email step and shows an "Emails are on hold" banner. 4. **Are leads arriving at all?** If the queue is empty and no leads are enrolled, the problem is upstream — see [HubSpot troubleshooting](/integrations/hubspot#troubleshooting). 5. **Are leads enrolled but nothing scheduled?** Check the delivery chip on the row for the reachability of each channel. 6. **Is everything simply queued?** Open Upcoming sends and the capacity bars. *** ## Frequently asked questions Either its next touch is genuinely days away, or its messages are behind the daily limit. Expand the lead: a scheduled date in the future is the first case, a date in the past that keeps moving is the second. Check the delivery chip on the row: if very few of its leads have a phone number in your CRM, that channel cannot work for this audience, and the fix is in HubSpot rather than in the Autopilot. Yes — expand the lead in Activity. Each sent touch carries the real body, not a reconstruction. If it was shortened to fit a channel limit, the timeline says so. The lead matched the Autopilot but has no address on any channel it uses — for example a LinkedIn-only Autopilot and a lead with no LinkedIn profile. The sequence is closed immediately rather than left pending forever. A message drifted further past its planned date than the limit you set under Autopilot settings, so it was dropped instead of arriving badly out of context. That limit is off by default — see [Settings](/autopilot/settings). # Autopilot analytics Source: https://docs.callprep.app/autopilot/analytics Outcomes, responded rate and the weekly trend — what each number counts, and the one setting that changes what "responded" means. **Dashboard → Autopilot → 📈 Analytics**, or read the counters on each Autopilot row for the same figures per Autopilot. *** ## The four outcomes Every lead that has ever entered an Autopilot is in exactly one of these: | Outcome | Meaning | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | **Active** | The sequence is still running | | **Responded** | A win — see below | | **Closed** | It ended without a win: the sequence finished, they unsubscribed, a CRM property changed, you closed it by hand, there was no reachable channel | | **Failed** | Something went wrong that was not the lead's doing. In practice this is empty | **Responded rate** = responded ÷ all leads enrolled. It is the number worth watching over time, and the one that makes two Autopilots comparable. An outcome is not the same as the operational status. A lead who replied on day two counts as **responded** immediately, even while the last messages are being cancelled. *** ## What counts as responded Four things, broken out separately in the mix: | Win | Comes from | | ------------------- | -------------------------------------------------------------------------------------------------------------- | | **Email reply** | They answered your email | | **Call connected** | A call logged on the HubSpot contact with a *Connected* outcome | | **Meeting booked** | A meeting logged on the contact after the lead was enrolled | | **LinkedIn accept** | The connection request was accepted — **only counted on Autopilots where you switched that stop condition on** | **That last row is the one to understand.** If *LinkedIn invite accepted* is not one of your stop conditions, an acceptance is recorded as a **connection**, not as a response — it appears in its own `Connected` count and stays out of your responded rate. This is deliberate. Leaving that condition off is a statement that connecting is not the goal, and a responded rate that counted it anyway would flatter the Autopilot for something you said did not count. Autoresponders and out-of-office replies never count. *** ## The weekly trend Leads enrolled per week as bars, responded rate as a line over them. Read them together: a good-looking rate on a week with four leads is noise, and a dip on a week you imported a list is arithmetic, not a regression. *** ## Reading the numbers honestly * **A responded rate below your expectations, with a healthy first-message count**, is a message problem — read what actually went out in [Activity](/autopilot/activity) and fix the angles. * **A responded rate that cannot move because almost nothing was sent** is a delivery problem. Check the delivery chip and the capacity bars first. * **A high closed count with no wins** on a young Autopilot usually means leads ran out of sequence rather than out of interest — the sequence may be too short, or too much of it landed on channels those leads are not reachable on. * **Wins that arrive after the sequence ended** still count. A reply weeks later is recorded against the lead. *** ## Frequently asked questions Because accepted LinkedIn invites are being recorded as connections. If connecting is what you wanted from that sequence, switch on **LinkedIn invite accepted** in its stop conditions and future acceptances count as wins. No. Wins are recorded as they are observed, with the rules in force at that moment. Last quarter's chart is not rewritten by a checkbox today. Not here. CallPrep records what happened to the outreach — replies, calls, meetings. What those turn into is your CRM's question, which is why every touch is written back onto the HubSpot contact. Yes. Archiving takes an Autopilot out of routing; it does not rewrite what it did. Its leads, messages and wins stay in the record. # Build your first Autopilot Source: https://docs.callprep.app/autopilot/build Step by step through the builder — who it contacts, where it reaches out, how it sounds, what it sends, and when it stops. Go to **Dashboard → Autopilot** and either **Start from a template** (see [Templates](/autopilot/templates)) or **Build from scratch**. This page walks through the second one, because that is the version where you see every decision. *** ## 1. Name Name it after **who it targets**, not what it sends — `Agencies — Poland`, not `Email sequence 2`. In three months this list will have eight rows and you will be reading it in a hurry. *** ## 2. Who should it contact? The filter reads your **HubSpot contact properties**: pick a property, an operator, and a value. | Operator | Use it for | | ---------------------------------------- | ----------------------------------------------- | | **is** / **is not** | Exact match. Several values means *any of them* | | **contains** / **doesn't contain** | Free-text properties | | **>**, **≥**, **\<**, **≤**, **between** | Numbers — a score, a headcount, a value | | **is known** / **is unknown** | Whether the property is filled in at all | Add up to three rules and choose whether **all** must be true (AND) or **any** (OR). **An Autopilot with no rules matches every lead.** The list marks it *Catch-all* and greys out everything below it — nothing further down will ever see a lead. Deliberately, that is a useful bottom-of-the-list Autopilot. By accident, it takes over your whole account. Properties that get filled in during onboarding are often empty at the moment the contact is created. That is expected and handled: the lead waits in the queue and is re-checked as its data arrives. See [Settings](/autopilot/settings#queue-wait-time). *** ## 3. Where can it reach out? Toggle the channels this Autopilot may use. Only channels you have actually connected can send. Under the channels is a line worth reading before you commit: **how many of this Autopilot's leads are reachable that way**. A LinkedIn step is useless to a lead with no LinkedIn profile, and a WhatsApp step is useless without a phone number in your CRM. This is where you find that out, rather than two weeks later. WhatsApp and Telegram only message leads who already engaged, unless you switch that off per channel. A sequence made **only** of those steps will wait indefinitely for a warm signal it cannot itself produce — pair them with email or LinkedIn. *** ## 4. How should it sound, and what is it for? **Tone** changes the voice and the greeting. It never changes what a message asks for. | Tone | Reads like | | ------------ | ---------------------------------------------------------------- | | **Friendly** | Warm and casual, like writing to someone you know a little | | **Direct** | The shortest version, no softeners | | **Formal** | Polite and professional, no contractions — for enterprise buyers | | **Founder** | Personal and honest, typed quickly by a busy founder | **Goal** is what every message works towards, and some goals need something from you before the Autopilot can be switched on: | Goal | Needs | | --------------------- | ------------------------------------------------------------------------------------- | | **Engage with leads** | Nothing — messages simply invite a reply | | **Meetings** | Your calendar link (Calendly, Cal.com, anything) | | **Self serve** | Your pricing page | | **Qualify** | One to three qualifying questions, in the order you want them asked — one per message | A goal without its input cannot be enabled. This is deliberate: a "book a meeting" sequence with no link is a series of messages asking for something the reader cannot do. **How did these leads come to you?** — one sentence, in your words (*"they registered for a trial"*, *"they downloaded the pricing guide"*). It becomes the **opening line of the first message**, which is most of the difference between a cold email and an obvious one. *** ## 5. The sequence Each touch is a **day**, a **channel**, and an **angle**. * **Day** — 1 is the day the lead is enrolled. Later touches are business days from there. * **Channel and kind** — email; LinkedIn *connection request* or *message*; WhatsApp; Telegram. * **Angle** — your instruction for what this message should be about. Not the final text: the message is written per lead from the angle plus that lead's research. * **Subject angle** (email) — only on the first email when threading is on, since the rest reply inside the same thread. * **Uses research** — which signals to build on: their post, their title, the company, the fit with your product. Leave it alone for a sensible default. ### Show example **Show example** renders a real message for a real lead of yours, through the same code path that sends, with your signature underneath. If you would not send that one, change the angle and look again. ### What the angle can and cannot do Your angle **outranks** the house writing rules — it is your campaign. Four things it cannot override, because they are not style preferences: * the length a channel physically allows * **inventing facts** — including a performance number that is not in your product description or in the research * the rule that a reshared post is not the lead's own words * the sender-identity block on commercial email Quote a sentence in the angle and it will be reproduced nearly word for word in **every** message from that step. That is a good way to pin an opening you like — and a good way to send four hundred people the same sentence. The builder warns you when a step does this. ### A note on LinkedIn steps LinkedIn works in two moves: a **connection request**, then a **message** once it is accepted. A message step waits for the acceptance on its own and gives up after a week rather than hanging forever. A second connection request in the same sequence can never do anything — LinkedIn allows one — and the builder says so. ### ✨ Build with AI **Build with AI** drafts the whole sequence from your product, channels and filter. Treat it as a first draft: read every angle, run **Show example** on at least the first two touches, and cut anything you would not send yourself. *** ## 6. When should it stop? | Condition | What it means | | ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | | **They reply — on any channel** | Email, LinkedIn, WhatsApp or Telegram. Leave this on. | | **You log a connected call, or they book a meeting (in HubSpot)** | Someone on your side already picked this lead up. | | **LinkedIn invite accepted (you're connected)** | A choice, not a default — see below. | | **A CRM property changes** | Nominate a property and a value. Leave the value empty to stop on any change. | **An accepted connection request is a connection, not an answer.** Turn this on if connecting *is* the goal of the sequence — the acceptance is then counted as a win. Leave it off if you plan to say something afterwards, or the sequence will end exactly when it becomes useful. Full detail, including what deliberately does **not** stop a sequence, is on [What stops a sequence](/autopilot/stop-conditions). *** ## 7. Save and switch on Save from the bar at the bottom, then switch the Autopilot on in the list. From that moment, a **new** contact matching the filter is researched and enrolled within about a minute. Existing contacts are not swept up — the Autopilot acts on contacts created from now on. After a day or two, check: * the **counters on the row** — leads, active, responded * the **delivery chip**, which appears only when leads are sitting uncontacted * **[Activity](/autopilot/activity)** for individual leads and skip reasons *** ## Frequently asked questions Three or four over two weeks is a sequence. Twelve is a campaign nobody finished reading. Note that the last email is written to ask for nothing — it exists to close politely, so cutting it makes the sequence end mid-pitch. Yes, and the changes apply to messages that have not been sent yet. Leads already enrolled keep their place in the sequence. Deleting a step from the middle can leave already-scheduled messages without a matching angle — they fall back to a generic one rather than sending something written for a different step. It stops routing new leads and leads in flight are stopped, but everything it did stays: the leads, the messages, the analytics. Nothing is deleted, and a lead that already replied keeps its win. LinkedIn accepts one connection request per person. A second one in the same sequence can only ever be skipped, so the builder defaults a new LinkedIn touch to *message* once a request already exists. Yes. Channels belong to your account, not to one Autopilot, and the daily limits are per mailbox or per seat — shared across everything you run. # Autopilot overview Source: https://docs.callprep.app/autopilot/overview How the Autopilot turns a new CRM contact into researched, personalised outreach across LinkedIn, email, WhatsApp and Telegram — and stops the moment someone answers. ## What an Autopilot is An Autopilot is **three decisions**: 1. **Who** gets contacted — a filter over your HubSpot contact properties 2. **What** is said to them — a sequence of touches, each written per lead from that lead's research 3. **When it stops** — a reply, a booked meeting, a CRM change Everything between those decisions happens on its own: the research, the writing, the sending, the pacing, the daily limits and the record in your CRM. ``` New contact in HubSpot ↓ Does it match one of your Autopilots? ── no ──→ waits in the queue, then is ↓ yes abandoned (no research, no credit) Research the person and their company (1 credit) ↓ Enrol into that Autopilot's sequence ↓ Touch by touch, from your own LinkedIn account / mailbox / number ↓ They answer → the sequence stops, on every channel ``` You can run several Autopilots side by side — one for inbound signups, one for a prospected list, one as a catch-all. A lead only ever belongs to **one** of them at a time. *** ## What you need before it can run | Requirement | Where | Why | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | A product | Settings → Product | Every message is written from your product description. Without it there is nothing to say. | | HubSpot connected | [Integrations → HubSpot](/integrations/hubspot) | This is what tells CallPrep a lead exists. | | At least one channel | [LinkedIn](/integrations/linkedin), [Email](/integrations/email), [WhatsApp](/integrations/whatsapp), [Telegram](/integrations/telegram) | A channel is where messages send from. An Autopilot with no connected channel has nowhere to go. | | Sender identity (email only) | Settings → Outreach → Email signature | Commercial email must identify the sender. Email steps are held until it is complete. | *** ## Which Autopilot gets a lead Autopilots are checked **top to bottom**, and the **first matching filter wins**. Nothing further down ever sees that lead. Use the arrows on the list to reorder — specific Autopilots above, broad ones below. * An Autopilot with **no filter** matches every lead. That is occasionally what you want, as a catch-all at the bottom of the list, and never what you want by accident. The list marks it **Catch-all** and greys out everything beneath it as *Never reached*. * A **fallback Autopilot** is checked differently: it is skipped during the normal pass and gets the **last look** at a lead just before it would be abandoned. It keeps its own filter — a fallback is not the same thing as a catch-all. * A lead already in an active sequence is not enrolled a second time. *** ## What it costs **One credit per researched lead** — the same pool as the API and the Chrome extension. Sending, retrying, skipping and rescheduling are all free. A lead costs **nothing** when it is: * abandoned after matching no filter * skipped by *Addresses to skip* or *Excluded domains* * already researched for this product (with **Research each contact only once** on, the default) See [Credits](/plans/credits). *** ## Where everything lives Name, filter, channels, voice, the sequence, stop conditions. What ends it, what deliberately does not, and the one CRM gap to know about. Why a message was not sent, where a missing lead went, closing a lead by hand. Start from a ready-made Autopilot instead of a blank one. Outcomes, responded rate, and what counts as a win. Queue wait time, addresses to skip, dropping messages that fall behind. *** ## Frequently asked questions Nothing is wrong yet — an Autopilot only acts on contacts **created after** it was switched on. Existing contacts are not swept up. If a new contact was created and still nothing happened, work through [Activity and troubleshooting](/autopilot/activity). Only if your filter lets it. Two guards help: **Excluded domains** (Settings → Outreach) skips whole companies for good, and a stop condition on a CRM property ends a sequence the moment someone's lifecycle stage moves. Both are worth setting before the first Autopilot goes live. No. The first matching filter wins, and a lead in an active sequence is not enrolled again. It can enter a different Autopilot later, once the first sequence has ended and the contact comes back through your CRM. No. You write the **angle** for each touch — the instruction — and the message itself is written per lead from that lead's research. **Show example** in the builder renders a real one before you commit. The first email still goes out, because a first message can honestly ask what someone is trying to achieve. Later messages and the other channels need something real to say, and skip rather than send filler — with the reason recorded on the lead's timeline. Switch the Autopilots off, or pause the channel: a paused LinkedIn seat or mailbox stops sending while keeping every lead enrolled. Leads resume where they left off. # Autopilot settings Source: https://docs.callprep.app/autopilot/settings The three account-wide settings behind every Autopilot — how long a lead waits for a match, when a late message is dropped, and which addresses are skipped outright. **Dashboard → Autopilot → the gear icon.** These apply to every Autopilot on the account, not to one of them. *** ## Queue wait time **2, 6, 12, 24 or 48 hours. Default: 6.** Contacts are often created *before* the data that qualifies them — someone signs up, the contact appears, and the onboarding answers your filter needs arrive minutes or hours later. So a contact that matches nothing yet waits in the queue and is re-checked as its CRM data arrives: every minute for the first quarter of an hour, then less often, until this window runs out. Then it is **abandoned** — never researched, no credit spent. | Set it shorter if | Set it longer if | | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Your filter uses properties that are set at creation time, and waiting only delays the obvious | Your filter uses onboarding answers, enrichment fields, or anything a human fills in later | If you have a **fallback Autopilot**, it gets the last look at the lead at the end of this window, and takes it if its own filter matches. *** ## Drop messages that fall too far behind **Off, 7, 14, 30 or 60 days. Default: off.** When there is more to send than the daily limits allow, messages move forward rather than being dropped. Usually that is right. But a follow-up written for "three days after the intro" landing five weeks later is worse than not landing: it reads as though nobody was paying attention. This is **lateness, not age** — how far past its planned date a message is. Fourteen days behind plan means the same thing in a two-week sequence and a two-month one. Leads dropped this way end with *Fell too far behind its schedule* in [Activity](/autopilot/activity). Turning this on is a symptom fix. If messages routinely fall weeks behind, the real answer is a higher daily limit, another mailbox or seat, or fewer leads entering at once — check the capacity bars. *** ## Addresses to skip **Both off by default.** Each one narrows who your Autopilots contact, and narrowing is a choice — so neither is on unless you say so. | Setting | Skips | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **Skip role-based addresses** | Mailboxes named after a function, not a person: `info@`, `contact@`, `kontakt@`, `office@`, `sales@`, `support@` and similar | | **Skip free mailbox providers** | Personal mailbox domains: Gmail, Outlook, Yahoo, iCloud, Proton, wp.pl and similar | Both are checked **before anything else**, so a skipped lead costs no research and no credit, and appears with its reason in the HubSpot intake log. Free-provider skipping is blunter than it looks: a founder on Gmail is still a founder. Only switch it on if your product genuinely does not sell to people at a personal address. Turning either setting on does not touch leads already enrolled, and turning one off does not go back for leads it skipped earlier — those come back the way any lead does, through your CRM. *** ## Related settings elsewhere | Setting | Where | What it does | | ---------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------- | | **Excluded domains** | Settings → Outreach | Companies never researched or contacted at all — your own domain, partners, customers | | **Research each contact only once** | Settings → Outreach | Stops a re-created or re-fired contact from spending a second credit. On by default | | **Content language** | Settings → Language | The language every generated message is written in | | **Email signature and sender identity** | Settings → Outreach | Required before any email step can send | | **Daily limits, sending hours, warm-up** | Each channel's integration page | Per seat and per mailbox — see [LinkedIn](/integrations/linkedin) and [Email](/integrations/email) | *** ## Frequently asked questions No. A lead in the queue has not been researched — that is the whole point of the wait. Credits are spent at the moment a filter matches. Only leads that match nothing. A lead matching a filter is researched and enrolled within about a minute, whatever the wait time is set to. No — all three are account-wide. Per-Autopilot decisions (filter, channels, voice, stop conditions) live in the builder. # What stops a sequence Source: https://docs.callprep.app/autopilot/stop-conditions Every signal that ends an Autopilot sequence, the ones that deliberately do not, and the two gaps worth knowing about before they surprise you. A sequence should end the moment a human takes over. This page is the complete list of what ends it — and, just as importantly, what does not. Every condition below except unsubscribe, bounce and manual close is a **toggle** on the Autopilot, under **When should it stop?**. What is checked is what you switched on. *** ## What stops it | Signal | Detail | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | **They reply, on any channel** | An answer on email, LinkedIn, WhatsApp or Telegram stops **every** channel, not just the one they answered on. Counted as a win. | | **You log a connected call** | A call on the HubSpot contact with a *Connected* outcome, logged after the lead entered the sequence. | | **They book a meeting** | A meeting logged on the HubSpot contact after the lead entered the sequence. | | **A CRM property changes** | The property and value you nominated — for example *Lifecycle stage is opportunity*. Empty value means any change. | | **A LinkedIn invite is accepted** | Only if you switched this on. It then counts as a win. | | **They unsubscribe** | Always, on every channel, permanently. Not a toggle. | | **You close the lead by hand** | From the lead's timeline in [Activity](/autopilot/activity#closing-a-lead-by-hand). | | **A hard bounce** | Stops the **email** steps for that lead only — the address failed, not the person. Other channels continue. | ### When the check happens Twice, on purpose: * **Right before every message goes out** — so an answer that arrived overnight is seen before the next send, not after it * **Between messages**, on a regular sweep — so the panel reflects reality even while nothing is being sent *** ## What does not stop it This is the part people do not expect. ### Out-of-office and automatic replies An auto-reply is not an answer, and treating it as one would silently end sequences every time someone goes on holiday. Out-of-office messages, delivery receipts and system acknowledgements are recognised and ignored — the sequence carries on. ### A call or meeting from before the lead entered the sequence Only activity logged **after** enrolment counts. A lead who spoke to you six months ago and has now come back through your CRM gets a fresh sequence — that is the intent, not an oversight. ### Activity logged only on a deal If you log a call or a meeting on a **deal** and it is not associated with the contact — HubSpot shows *0 contacts* on the activity — **CallPrep cannot see it and the sequence keeps running.** **What to do:** log activity on the contact, or associate the contact with the activity on the deal. It takes one click in HubSpot and is worth making a team habit. This is a known limitation, not a bug being fixed in the background. Without this paragraph the symptom is "the Autopilot wrote to someone I had already spoken to" with no visible cause. ### A reply to a message someone sent outside the sequence If a colleague emails a lead from their own mailbox and the lead replies to **that** message, the answer may be invisible to CallPrep — it never saw the original thread. **What to do:** reply inside the sequence's thread where you can, or close the lead by hand in Activity. If the email is logged on the HubSpot contact, that also stops it. ### An email reply to a custom-domain sender A mailbox connected as a **custom domain** is send-only: replies land in your inbox and CallPrep has no access to it, so *stop on reply* cannot fire from email for those leads. Gmail and Outlook mailboxes do not have this problem — see [Email](/integrations/email#replies-and-bounces). For a custom domain, log the reply on the HubSpot contact or close the lead by hand. *** ## What counts as a win Four outcomes are wins, and they are what the responded rate in [Analytics](/autopilot/analytics) is built from: * **Lead replied** — on any channel * **Connected on a call** * **Meeting booked** * **LinkedIn invite accepted** — *only* on Autopilots where you switched that condition on Everything else that ends a sequence — completed, unsubscribed, CRM property, closed by hand, no reachable channel — is closed, not won. A lead that replied **keeps** its win even if the sequence is later closed by hand or the Autopilot is archived. A win, once recorded, is never taken back. *** ## Frequently asked questions Almost always one of three things: the reply came to a mailbox CallPrep cannot read (custom domain), the reply was to a message sent outside the sequence, or the activity was logged only on a deal. All three are above, with what to do about each. No. It neither stops the sequence nor counts as a response, so your responded rate is not inflated by holiday responders. Switch on **LinkedIn invite accepted (you're connected)**. Bear in mind that any LinkedIn messages you scheduled after the request will then never send — which is correct if connecting was the point, and a mistake if you meant to follow up. Not from the panel. A lead comes back the way it arrived: through your CRM. That is deliberate — an "undo" would re-send a schedule you already decided against. Yes. An unsubscribe stops every active sequence for that person, on every channel, and is never cleared — not by a re-import, not by a new Autopilot. # Autopilot templates Source: https://docs.callprep.app/autopilot/templates Start from a ready-made Autopilot instead of a blank one — what a copy brings with it, what it deliberately leaves behind, and why it always arrives switched off. ## The library **Dashboard → Autopilot → 📚 Templates.** Each template expands to show everything it would give you: who it targets, the whole sequence day by day with each touch's angle and the research it uses, what it asks for, and what stops it. A timeline across the top shows the shape of the sequence from the lead's side — how many messages, how close together, on which channels. **Use this template** copies it into your account. *** ## What a copy is A **snapshot**, not a link. Once copied, the Autopilot is yours: editing it changes nothing anywhere else, and if the template is later changed or removed, your copy carries on exactly as it is. The copy arrives: * **Switched off**, always — see below * **At the bottom of your priority list**, so a new, often broad Autopilot cannot quietly take leads from the specific ones you already run * **Renamed** if you already have one with that name *** ## Read the warnings before switching it on A template was written against someone else's CRM and someone else's connected channels, so a copy can arrive with gaps. They are listed right after copying: | Warning | What it means | What to do | | ----------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Filter properties this portal does not have** | The template filters on a property that does not exist in your HubSpot | Rebuild those rules with your own properties. The rules are kept, not silently deleted, so you can see what was intended | | **Channels you have not connected** | It has steps on a channel you do not have | Connect it, or delete those steps | | **Matches every lead** | Its filter carries no usable rule in your portal | Add a filter before enabling, unless you deliberately want a catch-all | | **The goal needs your link** | A meetings or self-serve template cannot carry someone else's calendar or pricing URL | Paste yours in the builder | **This is why a copy is never enabled automatically.** An Autopilot whose filter did not survive the copy matches *everyone* — and an enabled one would start contacting your whole CRM before you had read a single line of it. *** ## What a template never carries Two things are deliberately left behind, because they belong to one account and would be wrong — not merely empty — in another: * **Your calendar or pricing link.** Someone else's booking link is worse than no link: the sequence looks configured and sends leads to the wrong place. * **How the leads came to you.** That sentence becomes the opening line of the first message. A template claiming *"I saw you just registered"* to a prospected list is a lie in the first sentence. Tone, goal and qualifying questions **do** come across. *** ## Before you enable a copy 1. Fix anything the warnings named 2. Fill in **How did these leads come to you?** in your own words 3. Run **Show example** on the first two touches — this is the moment to find out whether the template's voice fits yours 4. Check the stop conditions, especially *LinkedIn invite accepted* 5. Check where it sits in the priority list *** ## Frequently asked questions No. A copy is a snapshot taken at the moment you clicked. Nothing about it changes unless you change it. Not yourself — the library is curated. If you have built one that works well and would be useful to others, tell us at [hello@callprep.app](mailto:hello@callprep.app). They answer different questions. A template is a proven shape you adapt; **Build with AI** drafts a sequence from your own product and audience. Both need the same review before switching on. Only if it is more specific than what sits above it. Order decides who takes a lead first, and the more specific Autopilot should always be the one that wins. # Calendar and battlecards Source: https://docs.callprep.app/extension/calendar Connect Google Calendar so new meetings are researched before you get to them, and a briefing email lands in your inbox with everything worth knowing. ## What this does With Google Calendar connected, the extension lists your upcoming meetings and can research the person you are about to meet **before the meeting exists in your head** — then email you a battlecard about them. ``` A new meeting appears in your calendar ↓ It has an external attendee who has not been researched yet ↓ Research runs on its own (1 credit) ↓ A battlecard email arrives · the meeting card in the panel fills in ``` Calendar access is **read-only**. CallPrep never creates, edits, moves or cancels anything in your calendar. *** ## Connecting Google Calendar is connected as part of signing in to the extension with Google — approve the calendar permission when Chrome asks for it. Your meetings then appear in the side panel. If the connection later expires, the panel says so and offers **Reconnect with Google**. Nothing is lost while it is disconnected, except the automatic research that would have run. *** ## Automatic research **Settings → auto research** in the extension. Off, meetings are listed and you research whoever you like by hand. On, a new meeting is researched on its own. ### Exactly what gets researched * **One person per new meeting** — the first external attendee who has not been researched yet. Not every attendee, and not the whole room * **You are never researched**, and neither is anyone on an **[excluded domain](/autopilot/settings)** * A meeting whose attendees you have already researched is skipped * **One credit per automatic research**, from the same monthly pool as everything else A busy calendar spends credits without being asked. If you sit in a lot of internal or recurring meetings with outside guests, add those domains to **Excluded domains** first, or leave automatic research off and research the two meetings a week that matter. When your credits run out, automatic research stops until they reset. Nothing breaks; the meetings are simply listed unresearched. *** ## The battlecard email When a meeting-driven research finishes, CallPrep emails you a briefing. It is worth having in your inbox: it arrives while you are somewhere else, and it is readable on a phone two minutes before the call. It comes in three shapes, depending on what was actually found: | Shape | When | What you get | | -------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | **Full battlecard** | The person and their company were both found | Who they are, what the company does, synergy points with your product, opening lines from their recent activity, discovery questions | | **Short battlecard** | The person was found but there is nothing solid about the company | The person, the opening lines and the questions. The company sections are left out rather than printed empty | | **A short note** | Nothing solid was found | One paragraph saying so, and why — either there was no public trace of this person, or a data source did not answer | That third one is deliberate. Silence would leave you wondering whether the briefing was still coming; a note tells you there is nothing to wait for, while there is still time to look them up yourself. To stop these emails: **Settings → Notifications → Meeting battlecard** in the dashboard. *** ## What is stored, and for how long | Data | Kept | | --------------------------------------------------------- | -------------------------------------------------------------- | | Meetings read from your calendar (title, time, attendees) | **30 days**, then deleted automatically | | The research itself (prospect, company, insights) | Under the normal [data retention](/plans/data-retention) rules | Disconnecting the calendar (**Settings → auto research → Disconnect Google Calendar**) deletes the meetings CallPrep stored and stops any further reading of your calendar. **Research you already ran stays** — it is yours, and it is not calendar data. *** ## Frequently asked questions The primary calendar of the Google account you signed in with, read-only. Meetings without an external attendee are ignored. No — one person per meeting, the first external attendee who has not been researched yet. Research the others by hand from the meeting card if you need them. It runs when the meeting **appears in your calendar**, not on a timer before it. A meeting booked a week out is researched a week out; one booked an hour before is researched then. That is also when the battlecard email is sent. No. What reaches the AI is the prospect's public professional data — name, title, company, LinkedIn activity — plus your product description. Meeting titles, descriptions and attendee lists are not sent to it. Not today — the calendar connection lives in the extension. Any research started from a meeting produces the email; researching by email address in the dashboard or the API does not. Yes, the stored meetings are deleted on disconnect, and they would have been deleted after 30 days anyway. Your research history is untouched. # Chrome extension Source: https://docs.callprep.app/extension/overview Research a prospect from a side panel in your browser, see your upcoming meetings with the attendees already researched, and connect LinkedIn in one click. ## What it is **AI Call Prep App** is a Chrome side panel: your upcoming meetings, the people in them already researched, and a box to research anyone else by email. Same account, same credits and same research as the dashboard and the API — a different way in. Add it to Chrome, then pin it to your toolbar. Automatic research for new meetings, and the briefing email that follows. *** ## Setup ### 1. Install and open Install from the Chrome Web Store, pin the icon to your toolbar, and click it to open the side panel. It opens beside whatever page you are on and stays there while you work. ### 2. Sign in with Google **Continue with Google.** The extension signs in with Google only — if your CallPrep account uses the same Google address, it is the same account, with the same plan and the same credits. ### 3. Tell it about your product On first run it asks for three things, and they are worth two minutes: | Question | Why it matters | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | **Your product** — paste your website and it drafts the description, features and target customers | Every insight is written against this. A vague product description produces vague synergy points | | **Sales methodology** — Consultative, Challenger, Solution, Sandler, Value or Inbound Selling | Shapes the discovery questions you get | | **Excluded domains** | Companies never researched or contacted — your own domain, partners, customers | All three stay editable later, in **Settings** inside the extension and in the dashboard under **Settings → Product** and **Settings → Outreach**. They are the same settings, not a second copy. *** ## What you can do in the panel ### Research someone by email Type an email address and the research starts. It takes roughly 20 to 60 seconds, or a couple of seconds if that person was researched recently. **One credit per research.** You can have a couple of researches running at once; the panel says so when you reach the limit. ### See your meetings With Google Calendar connected, the panel lists your upcoming meetings, and each external attendee can be researched from the meeting card — or researched automatically before you get there. See [Calendar and battlecards](/extension/calendar). ### Read the research Each result opens in tabs: * **Insights** — a summary of the person, opening lines drawn from their recent LinkedIn activity, synergy points with your product, competitive notes * **Company** — description, industry, HQ, size, tech stack, what they sell, who they sell to, discovery questions to ask * **Decision makers** — other senior people at that company, with titles and LinkedIn profiles *(BDR plan and above)* * **Social** — recent posts from the prospect and the company, and company news ### Find an earlier research The research list holds everything your account has researched, searchable and sortable, with an **Extension only** filter when you want just the ones from here. ### Connect LinkedIn in one click **Settings → LinkedIn integration** connects the LinkedIn account your Autopilot outreach sends from, using the session already open in this browser — no password, and it works if you sign in to LinkedIn with Google. This is the same seat you would otherwise connect in the dashboard; see [LinkedIn](/integrations/linkedin). *** ## Credits and plans The extension draws on the **same monthly credits** as everything else: one credit per research, whether it was started by you or automatically from a meeting. Cached results still cost a credit — see [Credits](/plans/credits). Decision makers require the **BDR plan or above**; on other plans that tab stays empty. *** ## Frequently asked questions No. Sign in with the Google address on your CallPrep account and it is the same account — same plan, same credits, same research history, same product. No. It reads nothing from the pages you browse. It talks to CallPrep, to Google for sign-in and calendar, and — only when you connect LinkedIn from Settings — to your LinkedIn session in this browser, once, to establish that connection. That means the data sources had little to say about that address. It happens with personal mailboxes and very small companies. The company block is still built from the email domain where possible, and you keep the discovery questions. Yes, and they share everything: one product, one credit pool, one research history. A prospect researched in the extension returns instantly through the API, and the other way round. Chrome, and Chromium browsers that install from the Chrome Web Store and support side panels (Edge, Brave, Arc). Firefox and Safari are not supported. # Handling errors Source: https://docs.callprep.app/guides/handling-errors Best practices for handling errors and retries in production. ## Error categories **Client errors (4xx)** — caused by invalid requests. Fix the request before retrying. **Server errors (5xx)** — caused by infrastructure issues. Safe to retry with backoff. **Job failures** — `status: failed` in research results. Usually safe to resubmit. ## Production-ready error handler ```javascript theme={null} class CallPrepClient { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1'; } async research(email, options = {}) { const res = await fetch(`${this.baseUrl}/research`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email, ...options }), }); const data = await res.json(); if (!res.ok) { throw new CallPrepError(data.error, res.status, data); } return data; } async pollStatus(researchId, { intervalMs = 5000, timeoutMs = 180000 } = {}) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { await sleep(intervalMs); const res = await fetch(`${this.baseUrl}/research-status/${researchId}`, { headers: { 'Authorization': `Bearer ${this.apiKey}` }, }); const data = await res.json(); if (data.status === 'completed') return data; if (data.status === 'failed') { throw new CallPrepError('job_failed', 200, data); } } throw new CallPrepError('polling_timeout', 0, { researchId }); } } class CallPrepError extends Error { constructor(code, status, body) { super(`CallPrep error: ${code}`); this.code = code; this.status = status; this.body = body; } } const sleep = ms => new Promise(r => setTimeout(r, ms)); ``` ## Handling specific error codes ```javascript theme={null} try { const { research_id } = await client.research(email); const result = await client.pollStatus(research_id); return result.data; } catch (err) { if (!(err instanceof CallPrepError)) throw err; switch (err.code) { case 'credits_exhausted': case 'trial_limit_reached': // Notify user to upgrade — don't retry notifyUser('Credit limit reached. Please upgrade your plan.'); break; case 'invalid_key': // Configuration issue — alert the developer alertDev('Invalid CallPrep API key'); break; case 'invalid_email_format': // Validate email before calling API console.warn('Invalid email:', email); break; case 'job_failed': // Pipeline error — safe to retry once console.error('Research job failed:', err.body); break; case 'polling_timeout': // Job took too long — check research_id manually console.error('Polling timed out for:', err.body.researchId); break; default: // Unexpected error — log and alert console.error('Unexpected CallPrep error:', err); } } ``` ## Retry strategy | Error | Retry? | Strategy | | ---------------------- | ------ | ---------------------------------- | | `credits_exhausted` | ❌ | Wait for next billing cycle | | `invalid_key` | ❌ | Fix configuration | | `invalid_email_format` | ❌ | Fix input data | | `job_failed` | ✅ | Retry once after 5s | | `5xx` errors | ✅ | Exponential backoff, max 3 retries | | `polling_timeout` | ✅ | Resubmit with fresh `research_id` | ## Logging Always log the `research_id` — it's essential for debugging with support: ```javascript theme={null} const { research_id } = await client.research(email); logger.info('Research started', { research_id, email }); const result = await client.pollStatus(research_id); logger.info('Research completed', { research_id, duration_ms: result._meta?.duration_ms }); ``` # Polling best practices Source: https://docs.callprep.app/guides/polling-best-practices How to efficiently poll for research results without wasting requests. ## Recommended polling pattern Poll every **5 seconds** with a maximum timeout of **3 minutes**. Most requests complete in 20–60 seconds. ```javascript theme={null} async function pollResearch(researchId, apiKey, options = {}) { const { intervalMs = 5000, // 5 seconds between polls timeoutMs = 180000, // 3 minute max onProgress = null, } = options; const BASE_URL = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1'; const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { await new Promise(r => setTimeout(r, intervalMs)); const res = await fetch(`${BASE_URL}/research-status/${researchId}`, { headers: { 'Authorization': `Bearer ${apiKey}` }, }); const data = await res.json(); onProgress?.(data); if (data.status === 'completed') return data; if (data.status === 'failed') throw new Error(`Research failed: ${data.error}`); } throw new Error('Research timed out after 3 minutes'); } ``` ## Status values | Status | Meaning | Action | | ------------ | ------------------------------------------- | ------------------- | | `queued` | Job is waiting to be picked up by a worker | Continue polling | | `processing` | Pipeline is running | Continue polling | | `completed` | All enrichment steps finished successfully | Use the data | | `failed` | Pipeline encountered an unrecoverable error | Check `error` field | ## Polling does not consume credits `GET /research-status` is **free** — you can poll as many times as needed. Credits are only consumed when you call `POST /research`. ## Cache hits return immediately If data for the same email was recently enriched (within cache TTL), `status` will be `completed` on the **first poll** — sometimes even before your first poll. Build your code to handle an immediate `completed` response. ```javascript theme={null} // Always check status immediately after submitting const initial = await fetch(`${BASE_URL}/research-status/${researchId}`, ...); const data = await initial.json(); if (data.status === 'completed') { // Cache hit — no need to poll return data; } // Otherwise, start polling ``` ## Exponential backoff (optional) For high-volume usage, consider exponential backoff to reduce load: ```javascript theme={null} let delay = 2000; // start at 2s while (/* not done */) { await new Promise(r => setTimeout(r, delay)); delay = Math.min(delay * 1.5, 10000); // cap at 10s // ... check status } ``` # Email Integration Source: https://docs.callprep.app/integrations/email Send outreach from your own mailbox — Gmail, Outlook or your own domain — with per-mailbox limits, warm-up, threading, and reply and bounce detection. ## Overview Connect the mailbox your outreach is sent from. Emails go out **through your real mailbox**, so they look and thread like anything else you send, and replies come back to you. What is sent — who gets contacted, what each email says, how many days apart — is set up in **Dashboard → Autopilot**. A connected mailbox on its own never sends anything. ``` Autopilot decides who and what ↓ One mailbox is assigned to the lead for the whole sequence ↓ First email · follow-ups reply inside the same thread ↓ A reply (or a hard bounce) stops the emails ↓ Everything logged on the HubSpot contact ``` *** ## Setup Go to **Integrations → Email** and click **Add another mailbox** (or connect your first one). The page shows how many mailboxes your plan allows — *N of M mailboxes*. ### Option A — Gmail or Google Workspace (recommended) Click **Connect Gmail** and approve access. Sending happens through your real Gmail account: no DNS to set up, best deliverability, native threading. ### Option B — Outlook or Microsoft 365 (recommended) Click **Connect Outlook** and approve access. Same as above, through Microsoft. ### Option C — Your own domain For a sending address on a domain you own that is not on Google or Microsoft. Click **Set up a custom-domain address**, enter the from name and address, then add the DNS records shown to your domain and click **Check verification**. DNS changes usually take a few minutes. Personal mailbox domains (gmail.com, outlook.com, yahoo.com…) are rejected on this path — use the Gmail or Outlook buttons instead. With a custom-domain address, replies land in your mailbox and CallPrep cannot see them, so sequences are **not** stopped automatically by an email reply. See *Replies and bounces* below. ### Finish: your sender identity Commercial email has to say who is sending it. Under **Settings → Outreach → Email signature** set your name, your company and a postal address, and optionally your photo or logo, phone and website. Until the identity is complete, email steps are **held** — the dashboard shows an "Emails are on hold" banner with a link to the right card. LinkedIn, WhatsApp and Telegram are unaffected. Every outreach email also carries a standard **unsubscribe header**, which mail clients turn into their own one-click unsubscribe button. You can add a visible opt-out as well: a P.S. in your own words ("just tell me and I'll stop") or a plain unsubscribe link. An unsubscribe stops every channel for that person, not just email. *** ## Mailbox settings Open a mailbox row to see its settings. | Setting | What it does | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Timezone, Send from / Send until, Send on weekends** | The window this mailbox may send in | | **Daily limit** | Emails per day from this address (up to 500). Pick what a person could plausibly send | | **Of which imported** | A sub-limit for imported leads, so a bulk list cannot eat the day's capacity that fresh leads need. Defaults to 70% of the daily limit | | **This mailbox sends** | *Everything*, *Organic leads only*, or *Imported leads only* — useful for keeping bulk-list risk off the address you actually talk to people from | | **Collect replies here** | Marks this mailbox as the reply address for the account. Only a verified mailbox can be chosen | | **Pause this mailbox** | Sequences skip it and send from the others. Leads already assigned to it wait | ### Warm-up A newly connected mailbox starts at **5 emails on its first day and climbs by 5 a day** until it reaches the daily limit you set. A brand-new address has no reputation to spend, and providers score the pattern: fifty cold emails on day one reads as a list. The row shows `5/day · warming up` while this is happening, and how many days are left until the full limit. You can switch warm-up off if the address genuinely is not new to sending — an established domain, or a mailbox reconnected after being removed. CallPrep will warn you first and record the decision, because the cost of getting it wrong is the reputation of the address itself, not just one campaign. *** ## How sending works * **One mailbox per lead.** A lead is assigned a mailbox when it enters a sequence and keeps it, so the whole conversation comes from one address. * **Follow-ups thread.** Later emails reply inside the first email's thread, with a `Re:` subject, so the lead sees one conversation rather than five cold emails. This is a per-Autopilot setting. * **First emails come first.** When there is more to send than the daily limit allows, leads who have never been contacted are served before follow-ups — the capacity bar on the Autopilot page shows the split, and **Upcoming sends** shows the queue behind it. * **One email per lead per day**, regardless of how many steps come due. * **Several mailboxes share the load.** New leads are assigned to whichever mailbox has the most room, counting warm-up, pause state and role. *** ## Replies and bounces **Replies.** For Gmail and Outlook mailboxes, CallPrep notices a reply and stops the sequence (if the Autopilot has *stop on reply* on, which is the default). Out-of-office and auto-replies do not count as an answer. A reply logged on the HubSpot contact also stops the sequence, which is the safety net for custom-domain mailboxes. **Bounces.** A permanent bounce stops the **email** steps for that lead — other channels continue, since it is the address that failed, not the person. If more than **5% of a mailbox's emails bounce permanently** over a week (measured once it has sent at least 20), the mailbox is **paused automatically** and you get an email explaining why. Temporary delays do not count. *** ## What CallPrep can read | Provider | Access requested | What that means | | ----------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- | | Gmail / Workspace | Send email · read message **metadata** | Headers only — sender, recipient, subject line, dates. Message bodies are not accessible | | Outlook / Microsoft 365 | Send email · read basic mail | Everything except message bodies, attachments and previews | | Own domain | none | Sending only; CallPrep has no access to your mailbox | The reading is used for one purpose: noticing that a lead answered, and that a message bounced. Only addresses that belong to a lead in a live sequence are matched — anything else is passed over and nothing about it is stored. *** ## When a mailbox stops working OAuth access can end without warning — a password change, a revoked grant, an admin policy. When that happens the mailbox is marked **Disconnected**, the dashboard shows a red banner, and you get an email asking you to reconnect. The other mailboxes keep sending in the meantime, at reduced capacity. A send that failed because the authorization was dead is **retried after you reconnect** — those leads are not skipped. If a send fails for another reason, the mailbox row explains it in plain language (a Google account without Gmail enabled, a domain not verified yet, rate limiting), with the provider's own wording under **Technical details**. *** ## Credits Researching a lead costs **one credit**. Sending emails is free, and so is a skipped or rescheduled email. See [Credits](/plans/credits). *** ## Frequently asked questions On Gmail it can read **headers only** — who a message is from, to, its subject and date — never the body. On Outlook it can read everything except the body, attachments and previews. With a custom-domain sender it has no read access at all. It uses this to spot replies and bounces for leads in a live sequence. It depends on your plan — the top of the Email page shows *N of M mailboxes* and hides the add button once you are at the limit. Reconnecting an address you already have never counts as a new mailbox, so you can always repair what you have. See the [Billing page](https://call-prep-api.vercel.app/billing). That is the warm-up ramp: 5 on day one, +5 each day until it reaches your daily limit. The row shows how many days are left. It can be switched off, but only do that if the address has an established sending history. Your sender identity is incomplete. Commercial email must identify the sender, so email steps wait until your name, company and postal address are filled in under **Settings → Outreach → Email signature**. Other channels keep running. No. Follow-ups are sent as replies in the first email's thread, keeping one subject and one conversation. You can turn threading off per Autopilot. Not on Gmail or Outlook: the reply is detected and the sequence stops, and the check is repeated right before each send. With a custom-domain mailbox CallPrep cannot see the reply — log the email on the HubSpot contact, or close the lead from the Autopilot's Activity view. More than 5% of its emails bounced permanently over the last week (counted once it has sent at least 20). That is a deliverability alarm, not a quota — check where the addresses came from before resuming. You will have had an email explaining it. The mailbox marked **Collect replies here**. If a lead's sequence is sent from a different mailbox, that address is used as the reply-to, so the conversation lands in one place. Yes — that is what **This mailbox sends** is for. Set one to *Imported leads only* and another to *Organic leads only*, and bulk-list risk stays off the address you actually converse from. Connect the mailbox you can sign in to. Aliases and shared mailboxes work only when the provider lets that account send as the address — if it does not, the send is rejected and the row will say so. The person is added to your suppression list and every active sequence for them stops — on all channels, not just email. Suppression is permanent and is never cleared by a re-import. *** ## Need help? Contact us at [hello@callprep.app](mailto:hello@callprep.app) and we'll help you get your mailbox connected. # HubSpot Integration Source: https://docs.callprep.app/integrations/hubspot Connect the CRM that feeds your Autopilots — new contacts start the research and outreach, and every message is written back onto the contact timeline. ## Overview HubSpot is where your leads come from. Once connected, CallPrep watches for **newly created contacts** and, for each one: 1. Checks it against your Autopilot filters — does anyone want this lead? 2. Researches the person and their company, but **only if a filter matches** 3. Enrols the lead into that Autopilot's sequence 4. Writes every message that goes out back onto the HubSpot contact timeline 5. Watches the contact for a reason to stop — a reply, a logged call, a booked meeting, or a property you nominate There is nothing to schedule and no sync to run. A contact created in HubSpot reaches your Autopilots within about a minute. **Changed in 2026.** The HubSpot integration used to pull contacts on a schedule, filter them by lead score, and write results into custom `cp_` contact properties. That is gone. Leads now arrive as they are created, who gets contacted is decided by each Autopilot's own filter, and results are written to the contact timeline instead of custom fields. Properties created by the old version are left untouched in your portal — nothing fills them any more, and you can delete them in HubSpot if you want to. *** ## Before you start * **A product.** Everything runs on your product description — it is what the AI researches against and writes from. Add it under **Settings → Product**. * **A HubSpot account** where you can approve app installs. If you cannot install apps yourself, your HubSpot admin has to run the connect step. *** ## Setup ### Step 1 — Connect HubSpot In the dashboard, go to **Integrations → HubSpot**, pick the product you want to integrate, and click **Connect HubSpot**. Approve the request in HubSpot and you are returned to CallPrep with the connection showing as *Connected*. During the install HubSpot shows a yellow banner saying the app has not been reviewed or approved by HubSpot, and asks you to confirm before finishing. That is what HubSpot shows for every app not yet listed in its Marketplace — our listing is in progress. Click through to continue. **One product per connection.** The connection is tied to the product you picked. To point it at a different product, disconnect and connect again. ### Step 2 — Decide who gets contacted The connection itself contacts nobody. That decision lives in **Dashboard → Autopilot**, where each Autopilot has its own lead filter built from your HubSpot contact properties — for example *Lifecycle stage is customer*, or *Country is Poland or Germany*. Two account-wide filters sit above that: | Setting | Where | What it does | | --------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **Addresses to skip** | Dashboard → Autopilot → Settings | Skips role mailboxes (`info@`, `contact@`, `sales@`…) and free mailbox providers (Gmail, Outlook, Yahoo…). Both **off** by default. | | **Excluded domains** | Settings → Outreach | Companies you never want researched or contacted — your own domain, partners, existing customers. | Both are checked before anything else, so a skipped contact costs no research and no credit. ### Step 3 — Connect a channel and turn an Autopilot on Messages need somewhere to send from: [LinkedIn](/integrations/linkedin), [Email](/integrations/email), [WhatsApp](/integrations/whatsapp) or [Telegram](/integrations/telegram). *** ## Permissions CallPrep asks for | Permission | Why it is needed | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------ | | Read contacts and their properties | Match leads against your filters, read the phone number or handle to message, and watch for a reason to stop | | Write to contacts | Log outreach on the contact timeline — a sent email becomes an Email activity, everything else a Note | | Read contact property settings | List your portal's properties in the pickers, so you can build filters without typing internal names | | Read owners | On a portal shared by several reps, route each lead to the CallPrep account of the HubSpot owner | **CallPrep never edits your contacts.** The write permission is used for timeline activity and nothing else — no property is changed, and no deal, pipeline, workflow or list is touched. If you connected HubSpot a long time ago and leads are not arriving on a portal shared with colleagues, click **Reconnect**. A permission added after your connection was made is not granted retroactively, and without owner access every lead on a shared portal is skipped. *** ## What lands in HubSpot For every message that actually goes out, CallPrep logs it on the contact: * **Emails** appear as an *Email* activity on the timeline, with the subject and body that were sent * **LinkedIn invites and messages, WhatsApp and Telegram messages** appear as a *Note* Notes say whether the message was written and sent by the Autopilot or typed by hand, so a colleague reading the record knows what they are looking at. *** ## When the outreach stops A sequence should end the moment a human takes over. CallPrep reads these signals from HubSpot, and each one can be switched on per Autopilot: | Signal | What it means | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Reply** | The lead answered — on email, LinkedIn, WhatsApp or Telegram. Counted as a win. | | **Call connected** | A call logged on the contact with a *Connected* outcome. | | **Meeting booked** | A meeting logged on the contact after the lead entered the sequence. | | **Property changed** | A contact property you nominate reaches a value you nominate — for example *Lifecycle stage is opportunity*. Leave the value empty to stop on any change. | | **Email logged in HubSpot** | An incoming email recorded on the contact, even if it landed in a mailbox CallPrep cannot read. | Stop conditions are **read** from HubSpot with your own connection, right before each message goes out and again between messages — so an answer that arrives overnight is seen before the next message is sent. *** ## The waiting queue Contacts are often created *before* the data that qualifies them: someone signs up, the contact is created, and the onboarding answers your filter needs arrive three minutes later. So a contact that matches nothing yet is **not** thrown away and **not** researched. It waits in the queue and is re-checked as its CRM data arrives. If nothing matches within the wait time, it is **abandoned**: never researched, no credit spent. * The wait time is set in **Dashboard → Autopilot → Settings** (2 to 48 hours, 6 hours by default) * The queue is visible at the bottom of the Autopilot list — **Queue — N waiting** * If you have a **fallback Autopilot**, it gets the final check at the end of the wait and takes any lead its own filter matches *** ## Several reps on one HubSpot portal Each rep connects HubSpot from **their own** CallPrep account and connects their own channels. A new contact is routed to the CallPrep account of the person who **owns it in HubSpot**, and the outreach goes out from that person's LinkedIn seat or mailbox. Nobody sends on anyone else's behalf. *** ## Credits One credit per **researched** contact — the same pool as the API and the Chrome extension. * A contact that matches no Autopilot and is abandoned costs **nothing** * A contact skipped by *Addresses to skip* or *Excluded domains* costs **nothing** * With **Research each contact only once** on (Settings → Outreach, on by default), a contact re-created or re-fired by HubSpot is not researched twice See [Credits](/plans/credits) for monthly limits and reset dates. *** ## Disconnecting **Integrations → HubSpot → Disconnect** removes CallPrep's access. New contacts stop reaching your Autopilots immediately; leads already enrolled keep running on what CallPrep already knows about them, minus any stop signal it can no longer read. Everything already written to your HubSpot timeline stays. *** ## Troubleshooting | Symptom | What to check | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Nothing happens when a contact is created | Is at least one Autopilot **enabled**? Does its filter match the contact's current property values? Is the address caught by *Addresses to skip* or *Excluded domains*? | | Leads land in the queue and are abandoned | The filter never matched. Open **Queue → Abandoned**, then compare the filter against the values those contacts actually have. | | Works for you, not for a colleague's contacts | The colleague needs their own CallPrep account with HubSpot connected. If they have one, click **Reconnect** on both accounts so owner access is granted. | | Everything is enabled but nothing sends | The Autopilot needs a channel that is actually connected — and the lead needs an address on it (a LinkedIn profile, a phone number, a handle). | *** ## Frequently asked questions No. Lead score filtering belonged to the old scheduled sync and is gone. Each Autopilot now has its own filter over any contact properties you like — you can still filter on a score property if you have one, using **greater than** or **between**. Usually inside a minute. If it does not match a filter yet it goes into the queue and is re-checked every minute for the first quarter of an hour, then every few minutes, until your wait time runs out. No. It reads contacts and writes activity onto the contact timeline. It never edits contact properties, deals, pipelines, workflows or lists. They stay in your portal exactly as they are. Nothing has filled them since the scheduled sync was retired, so they are safe to delete in HubSpot if you want the space back. Results now live on the contact timeline instead. Not from one CallPrep account — the connection carries one product, and switching products means disconnecting and connecting again. Separate CallPrep accounts can each connect the same portal with their own product. Because CallPrep is not yet listed in the HubSpot App Marketplace — the banner and the extra confirmation dialog appear for every unlisted app, whoever built it. Our domain is verified with HubSpot and the listing is in progress. The install is a standard OAuth grant and you can revoke it in HubSpot at any time. Open **Dashboard → Autopilot → Queue**. *Waiting* means the wait time has not run out and the lead is still being re-checked; *Abandoned* means it never matched a filter and was dropped without spending a credit. From the abandoned list you can still send a lead to an Autopilot by hand. Yes — every contact created in the portal is treated the same way, including contacts created by an import. Before loading an old list, check your Autopilot filters, and use *Excluded domains* for anything you do not want touched. Yes. Open the Autopilot's **Activity**, find the lead and close it — the remaining messages are cancelled and nothing else about the Autopilot changes. No. Connecting the app is the whole setup. Do not build a workflow to push contacts to CallPrep — new contacts already arrive on their own, and a workflow that re-creates or re-fires contacts can only cause duplicate work. *** ## Need help? Contact us at [hello@callprep.app](mailto:hello@callprep.app) and we'll get back to you within one business day. # LinkedIn Integration Source: https://docs.callprep.app/integrations/linkedin Connect the LinkedIn account your outreach sends from — personalised connection requests and follow-up messages, within limits that keep your account safe. ## Overview This page is about **one thing**: connecting the LinkedIn account (a *seat*) that your outreach is sent from, and setting the limits it sends within. What is actually sent — who gets contacted, what each message says, how many days apart — is set up in **Dashboard → Autopilot**. A connected seat on its own never sends anything. ``` Autopilot decides who and what ↓ Connection request with a note written for that person ↓ They accept ↓ Follow-up message on LinkedIn · reply stops the sequence ↓ Everything logged on the HubSpot contact ``` Invites always go out from **your own** LinkedIn account. CallPrep never uses a shared or synthetic profile. *** ## Setup Go to **Integrations → LinkedIn**. There are three ways to connect — all three end with the same result, so pick whichever suits how you sign in to LinkedIn. ### Option A — Chrome extension (one click) Install the CallPrep Chrome extension, open it and go to **Settings → LinkedIn integration**. It reuses the session you are already signed into, so it works with Google / SSO logins and needs no password. ### Option B — Email and password Click **Connect account** and sign in to LinkedIn in the window that opens. Two-factor authentication is fully supported — authenticator app, SMS or one-time code. ### Option C — Session cookie (for Google / SSO sign-in) If you sign in to LinkedIn with Google and have no LinkedIn password, click **Sign in to LinkedIn with Google? Connect without a password** and follow the steps: in a browser where you are logged into LinkedIn, open DevTools (F12) → **Application → Cookies → [https://www.linkedin.com](https://www.linkedin.com)**, copy the value of the `li_at` cookie and paste it in. The cookie is used once to establish the connection and is not stored by CallPrep. *** ## Sending limits Open a connected seat to set its schedule. These are the settings that protect the account: | Setting | Default | Notes | | -------------------------- | ----------------------- | ----------------------------------------------------------- | | **Timezone** | Your browser's timezone | Everything below is in this timezone | | **Send from / Send until** | 06:00 – 22:00 | Outside 06:00–22:00 you must confirm the risk before saving | | **Send on weekends** | Off | Turning it on also needs a confirmation | | **Daily limit** | Up to **20** invites | A hard ceiling — it cannot be raised, only lowered | **20 invites a day per account is the maximum.** LinkedIn restricts accounts that behave unlike a person — sending at night, at weekends, or at volume. To send more, connect an additional LinkedIn account: each one has its own allowance. On top of the daily limit, CallPrep spaces sends out on its own: * **5–15 minutes** between two invites from the same account, randomised * Up to **90 minutes** of random delay when a batch of leads arrives at once * Anything that does not fit today's limit or window is moved to the next available slot — it is never dropped for being late *** ## What gets sent **The connection note** is written per lead from the research and from the angle you gave the step in your Autopilot. LinkedIn allows 200 characters, so notes are short by necessity. If a note comes back longer, it is trimmed back to a complete sentence — never cut mid-word — and the Activity timeline marks it as shortened. **If there is nothing credible to say, nothing is sent.** When the research turned up no usable angle, or the lead's name is not a real name (a role mailbox like *Info* or *Admin*), that invite is skipped with a reason on the timeline rather than sent as a generic note. **Follow-up messages** are a separate step type and only work on people you are connected to. A message step waits for the invite to be accepted and, if that has not happened within a week of its scheduled date, it is dropped with the reason *never connected*. Message steps allow more room than the note, but stay short on purpose — a long DM reads as a pitch. **People you are already connected to are never invited.** The invite step is skipped as *already connected*, and any message steps behind it run normally. *** ## Multiple accounts Connect more than one LinkedIn account to increase throughput — each has its own 20-a-day allowance, its own hours and its own timezone. On a shared HubSpot portal, a lead is routed to the rep who owns the contact, and goes out from that rep's own seat. *** ## Pausing, reconnecting and disconnecting * **Pause** stops that seat sending while keeping the connection. Leads stay enrolled and resume when you unpause. * **Reconnect** is what a *checkpoint* or *disconnected* status needs — LinkedIn asked for verification again, or the session ended. * **Disconnect** removes the account. LinkedIn steps have nowhere to send from until another account is connected. | Status | What it means | | ----------------------------- | ------------------------------------------------------------------------------------ | | **Connected** | Ready to send | | **Pending** | The connection was started but not finished — complete the sign-in | | **Checkpoint** | LinkedIn asked for extra verification. Reconnect, or use the email + password method | | **Disconnected** | The session ended. Reconnect | | **Error · already connected** | This LinkedIn account is already on your CallPrep account — remove the duplicate row | *** ## Credits Researching a lead costs **one credit**. Sending invites and messages is free, and so is a skipped or rescheduled touch. See [Credits](/plans/credits). *** ## Frequently asked questions You do not have to. The Autopilot writes each note and message per lead from the research and from the angle you set on the step. What you control is the angle, the tone and the goal — in **Dashboard → Autopilot**. No. It is enforced in the product, not just in the form, so a higher number would be a setting that silently does nothing. Connect another LinkedIn account to add another 20 a day. The limits exist for exactly this reason: no more than 20 invites a day, a randomised gap between them, working hours only, weekends off by default, and no invite to someone you are already connected to. You can go outside the safe hours or send at weekends, but only deliberately — CallPrep asks you to confirm and records that you did. Yes — use the Chrome extension (one click, no password), or the session-cookie option on the LinkedIn integration page. No. A lead is enrolled in one Autopilot at a time, an invite is sent once per sequence, and anyone you are already connected to is skipped instead of invited. Whatever your Autopilot says. If you switched on **LinkedIn invite accepted** as a stop condition, the sequence ends there and the acceptance is counted as a win. If you did not, the follow-up messages you designed keep going. Open the Autopilot's **Activity** and expand the lead — every skip carries its reason: no LinkedIn profile found, already connected, no credible angle to write from, or the seat's daily limit and window. Nothing is skipped silently. There is no per-invite approval queue any more. Use **Show example** on a step in the Autopilot builder to see exactly what that step produces for a real lead of yours, and adjust the angle until you like it. Yes — HubSpot is what tells CallPrep a lead exists. See [HubSpot Integration](/integrations/hubspot). No, and they should not: LinkedIn ties the account to a person and to their usual devices and locations. Each rep connects their own account from their own CallPrep account. *** ## Need help? Contact us at [hello@callprep.app](mailto:hello@callprep.app) and we'll help you get running. # n8n Source: https://docs.callprep.app/integrations/n8n Automate prospect research with n8n workflows. ## Overview [n8n](https://n8n.io) is a workflow automation platform that lets you connect CallPrep to your CRM, email tools, Slack, and more — without writing code. The official CallPrep n8n template is coming soon. In the meantime, use the HTTP Request node as described below. ## Setting up credentials 1. In n8n, go to **Credentials** → **New** → **Header Auth** 2. Set **Name** to `Authorization` 3. Set **Value** to `Bearer cp_live_your_api_key_here` 4. Save as **CallPrep API** ## Workflow: Research a prospect from a form submission This workflow triggers when a new prospect is added (e.g. from a Typeform or HubSpot form) and enriches them using CallPrep. ### Step 1 — Trigger Use any trigger node: Webhook, HubSpot, Salesforce, Google Sheets, etc. ### Step 2 — POST /research Add an **HTTP Request** node: | Field | Value | | ----------------- | ---------------------------------------------------------------- | | Method | POST | | URL | `https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research` | | Authentication | Header Auth → CallPrep API | | Body Content Type | JSON | **Body:** ```json theme={null} { "email": "={{ $json.email }}", "prospect_name": "={{ $json.name }}", "company_name": "={{ $json.company }}" } ``` ### Step 3 — Wait 30 seconds Add a **Wait** node set to 30 seconds. ### Step 4 — GET /research-status Add another **HTTP Request** node: | Field | Value | | -------------- | ------------------------------------------------------------------------------------------------ | | Method | GET | | URL | `https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research-status/={{ $json.research_id }}` | | Authentication | Header Auth → CallPrep API | ### Step 5 — IF: Check status Add an **IF** node: * Condition: `{{ $json.status }}` equals `completed` * True → continue to next step * False → loop back to Wait node (use a **Loop** node) ### Step 6 — Use the data Send the enriched data to Slack, update your CRM, create a brief document — whatever fits your workflow. ## Example use cases Trigger on new HubSpot contact → CallPrep research → Update HubSpot contact properties with summary, company insights, and opening talk tracks. Trigger 30 minutes before a Google Calendar meeting → Research the attendee's email → Post a brief to your Slack channel. Webhook from LinkedIn → CallPrep → Add to CRM with full enrichment. ## Coming soon The official CallPrep n8n node will support: * Native credential management * Automatic polling (no Wait node needed) * Structured output mapping # Integrations overview Source: https://docs.callprep.app/integrations/overview How HubSpot, LinkedIn, Email, WhatsApp and Telegram fit together — one CRM as the source of leads, four channels the outreach sends from. ## The short version CallPrep has three moving parts, and every integration belongs to one of them. | Part | What it is | Where you set it up | | ---------------- | ----------------------------------------------------------------------------------- | -------------------------- | | **The source** | HubSpot. A new contact in your CRM is what starts everything. | Integrations → HubSpot | | **The decision** | Autopilot. Who gets contacted, what is said, on which channels, and when it stops. | Dashboard → Autopilot | | **The channels** | LinkedIn, Email, WhatsApp, Telegram — the accounts messages are actually sent from. | Integrations → the channel | ``` New HubSpot contact ↓ Does it match one of your Autopilots? ──── no ──→ waits in the queue, then is abandoned ↓ yes (never researched, no credit spent) Research the person and their company (1 credit) ↓ Enrol into that Autopilot's sequence ↓ Messages sent from your own LinkedIn / mailbox / WhatsApp / Telegram ↓ Every message logged back on the HubSpot contact · a reply stops the sequence ``` Connecting a channel does not send anything on its own. A channel only sends when an Autopilot has a step on it — see **Dashboard → Autopilot**. *** ## The channels at a glance | Channel | What you connect | Hard daily limit | Cold outreach | Typical plan | | ---------------------------------- | -------------------------------------------------- | ---------------------------------- | ----------------------------------- | ----------------- | | [LinkedIn](/integrations/linkedin) | Your own LinkedIn account (a "seat") | 20 invites per account | Yes | Individual and up | | [Email](/integrations/email) | Gmail, Outlook / Microsoft 365, or your own domain | Up to 500 per mailbox, your choice | Yes | BDR and up | | [WhatsApp](/integrations/whatsapp) | Your WhatsApp number (QR scan) | 20 messages | Only after you switch off warm-only | BDR+ | | [Telegram](/integrations/telegram) | Your Telegram account (QR scan) | 20 messages | Only after you switch off warm-only | BDR+ | Every channel sends from **your** account, never from a shared or synthetic one. See the [Billing page](https://call-prep-api.vercel.app/billing) for what your plan includes. *** ## What each integration is for The source of leads and the record of what happened. New contacts start the flow; every message sent is written back onto the contact timeline. Connection requests with a personalised note, and follow-up messages once the request is accepted. Outreach from your real mailbox, threaded into one conversation, with reply and bounce detection. Short follow-ups to leads who already engaged. Cold messaging is off by default on both. *** ## Setup order 1. **Add your product** — Settings → Product. Everything else runs on it. 2. **Connect HubSpot** — this is what feeds leads in. 3. **Connect at least one channel** — LinkedIn is the quickest to start with. 4. **Create an Autopilot** — Dashboard → Autopilot, from scratch or from a template. Step 4 is the one that decides whether anything is sent. An account with HubSpot and a LinkedIn seat connected but no Autopilot enabled will sit quietly and never contact anyone. *** ## Need help? Stuck on any of this? Write to [hello@callprep.app](mailto:hello@callprep.app) and we'll get back to you within one business day. # Telegram Integration Source: https://docs.callprep.app/integrations/telegram Connect your Telegram account and follow up with leads who already engaged — warm-only by default, 20 messages a day, addressed by @username. ## Overview Connect the Telegram account your outreach is sent from. Messages go out from **your own account**, exactly as if you typed them. Like WhatsApp, Telegram is a follow-up channel: out of the box it messages **only leads who already engaged** — someone who accepted your LinkedIn invite or replied to your email. Add a **Telegram step** to a sequence in **Dashboard → Autopilot** — a connected account on its own never sends anything. Telegram is available on the BDR+ plan; see the [Billing page](https://call-prep-api.vercel.app/billing). **Telegram needs a @username, not a phone number.** Telegram cannot open a chat from a number, so a lead is reachable only if their handle is stored in HubSpot. There is no standard HubSpot field for it — most portals need a custom property. *** ## Setup ### Step 1 — Connect your account Go to **Integrations → Telegram** and click **Connect Telegram**. A page opens with a QR code: scan it from your phone (**Telegram → Settings → Devices → Link Desktop Device**). You are returned to CallPrep and the account shows as *connected*. ### Step 2 — Create a place to store handles, and point CallPrep at it 1. In HubSpot, create a contact property for the handle if you do not have one — for example *Telegram username*, a single-line text field. 2. In CallPrep, under **Telegram handle source**, list the properties that hold it, in the order they should be tried. Handles can be stored as `@name`, `name` or a `t.me/name` link — all three work. A number will not. A lead with no usable handle is skipped with *No Telegram handle on the contact* on the timeline, and the handle is looked up again in HubSpot right before each send, so one added later still gets used. ### Step 3 — Set the schedule | Setting | Default | Notes | | -------------------------- | ----------------------- | ----------------------------------------------------------------- | | **Timezone** | Your browser's timezone | Everything below is in this timezone | | **Send from / Send until** | 06:00 – 22:00 | A message at 3 a.m. is worse than no message | | **Send on weekends** | Allowed | Telegram is not a professional network — weekends are normal here | | **Daily limit** | Up to **20** messages | A hard ceiling. It cannot be raised, only lowered | *** ## Who can be messaged **Warm leads only (recommended, on by default).** A Telegram step is sent only after the lead has shown a warm signal — they accepted your LinkedIn invite or replied to your email. Until then the message waits and is re-checked; if the lead never warms up within three weeks it is dropped with *never warm* on the timeline, and the rest of the sequence carries on. **Cold outreach (off by default).** Turning warm-only off lets this account message people who never responded. CallPrep asks you to confirm and records the decision: an unsolicited message from an unknown account is what gets reported on Telegram, and reported accounts are banned outright. *** ## What gets sent Messages are written per lead from the research and from the angle you gave the step. They are deliberately **very short** — a couple of lines, no links, no emoji, and they say who you are. If the research found nothing credible to build on, the message is skipped with a reason rather than sent as filler. Every message sent is logged as a note on the HubSpot contact, and a reply stops the sequence on all channels. *** ## Pausing and disconnecting * **Pause** stops this account sending while keeping the connection. * **Disconnect** removes it. Telegram steps have nowhere to send from until an account is connected again. You can also end the session from **Telegram → Settings → Devices**. *** ## Frequently asked questions No. Telegram only opens a chat from a @username, so the handle has to be in HubSpot. This is a Telegram rule, not a CallPrep one. Ask for it where you already ask for a phone number — a sign-up form, an onboarding question, a booking form — and map that field to a HubSpot contact property. Leads without a handle simply skip the Telegram step; the rest of the sequence is unaffected. Telegram could not resolve it: the username was changed, deleted, or was never a username (a phone number, an email, a first name). The reason is on the step in the Autopilot's **Activity** view. No. It is your own Telegram account, linked as a device — so the lead sees a message from you, not from a bot account, and can reply in the normal way. Only if you switch **Warm leads only** off, confirm the warning and accept the risk. Unsolicited messages are the fastest way to get a Telegram account reported and banned. No. The limit is enforced in the product, so a higher number would be a setting that silently does nothing. It protects the account. The sequence stops on every channel, and the reply counts as a win in the Autopilot's analytics. Answer them yourself in Telegram as usual. *** ## Need help? Contact us at [hello@callprep.app](mailto:hello@callprep.app) and we'll help you connect your account. # WhatsApp Integration Source: https://docs.callprep.app/integrations/whatsapp Connect your WhatsApp number and let the Autopilot follow up with leads who already engaged — warm-only by default, 20 messages a day, from your own number. ## Overview Connect the WhatsApp number your outreach is sent from. Messages go out from **your own number**, exactly as if you typed them. WhatsApp is a follow-up channel by design. Out of the box it messages **only leads who already engaged** — someone who accepted your LinkedIn invite or replied to your email. Cold messaging is possible, but you have to switch it on deliberately. Add a **WhatsApp step** to a sequence in **Dashboard → Autopilot** — a connected number on its own never sends anything. WhatsApp is available on the BDR+ plan; see the [Billing page](https://call-prep-api.vercel.app/billing). *** ## Setup ### Step 1 — Connect your number Go to **Integrations → WhatsApp** and click **Connect WhatsApp**. A page opens with a QR code: scan it from your phone the same way you would sign in to WhatsApp Web (**WhatsApp → Settings → Linked devices → Link a device**). You are returned to CallPrep and the number shows as *connected*. Keep the phone online and the linked device in place. If you unlink CallPrep from your phone, or the session expires, the number goes to *disconnected* and WhatsApp steps stop until you reconnect. ### Step 2 — Tell CallPrep where the phone number lives Under **Phone number source**, list the HubSpot contact properties that hold the lead's number, in the order they should be tried. The default is *Mobile phone number*, then *Phone number*. Numbers should be in international format (for example `+48 600 123 456`). A lead with no number on any of the listed properties is skipped, with *No phone number on the contact* on the timeline — and the number is looked up again in HubSpot right before each send, so a number added later still gets used. ### Step 3 — Set the schedule | Setting | Default | Notes | | -------------------------- | ----------------------- | ----------------------------------------------------------------- | | **Timezone** | Your browser's timezone | Everything below is in this timezone | | **Send from / Send until** | 06:00 – 22:00 | A WhatsApp message at 3 a.m. is worse than no message | | **Send on weekends** | Allowed | WhatsApp is not a professional network — weekends are normal here | | **Daily limit** | Up to **20** messages | A hard ceiling. It cannot be raised, only lowered | *** ## Who can be messaged This is the most important setting on the page. **Warm leads only (recommended, on by default).** A WhatsApp step is sent only after the lead has shown a warm signal — they accepted your LinkedIn invite or replied to your email. Until then the message waits and is re-checked; if the lead never warms up within three weeks it is dropped with *never warm* on the timeline, and the rest of the sequence carries on. **Cold outreach (off by default).** Turning warm-only off lets this number message people who never responded. CallPrep asks you to confirm and records the decision, because messaging strangers' phone numbers is the exact pattern WhatsApp bans for — and WhatsApp bans outright, with no restricted middle state like LinkedIn's. *** ## What gets sent Messages are written per lead from the research and from the angle you gave the step. They are deliberately **very short** — a couple of lines, no links, no emoji, and they say who you are, because an unexplained message from an unknown number gets reported. If the research found nothing credible to build on, the message is skipped with a reason rather than sent as filler. Every message sent is logged as a note on the HubSpot contact, and a reply stops the sequence on all channels. *** ## Pausing and disconnecting * **Pause** stops this number sending while keeping the connection. * **Disconnect** removes it. WhatsApp steps have nowhere to send from until a number is connected again. You can also remove CallPrep from your phone under **WhatsApp → Linked devices**. *** ## Frequently asked questions No. It links to your existing WhatsApp account as a device, the same mechanism as WhatsApp Web — so messages come from your normal number, with no templates to get approved and no per-message fee. Only if you switch **Warm leads only** off, confirm the warning and accept the risk. We keep it on by default because unsolicited WhatsApp messages are what gets numbers banned. No. The limit is enforced in the product, so a higher number would be a setting that silently does nothing. It protects the number. Almost always because the lead has no phone number in HubSpot. Open the Autopilot's **Activity** and expand a lead to see the reason on each step, and check the delivery chip on the Autopilot row for how many of its leads have a number at all. Inbound leads who filled in a form usually have one; prospected lists usually do not. From HubSpot, using the properties you listed under **Phone number source**, in order. CallPrep does not buy or guess phone numbers. Yes. The link works like WhatsApp Web: your phone has to be reachable, and if the device is unlinked the connection ends and CallPrep tells you it is disconnected. There is no marker on the message — it comes from your number and reads like something you wrote. Which is also why the messages are short, mention who you are, and never carry a link. The sequence stops on every channel, and the reply counts as a win in the Autopilot's analytics. Answer them yourself in WhatsApp as usual. *** ## Need help? Contact us at [hello@callprep.app](mailto:hello@callprep.app) and we'll help you connect your number. # How it works Source: https://docs.callprep.app/introduction/how-it-works Understanding how CallPrep enriches your prospects. ## Async pipeline CallPrep uses an **asynchronous pipeline** — when you submit a research request, we immediately return a `research_id` and run enrichment in the background. Poll `/research-status/{research_id}` every 5 seconds until `status` is `completed`. ``` POST /research → research_id (instant response) ↓ [background enrichment] ↓ GET /research-status → completed + data ``` ## What we enrich For every prospect email you submit, CallPrep returns: **Prospect data** * Job title (translated to English), company, LinkedIn URL, photo * AI-generated professional summary * 3 personalised conversation starters based on LinkedIn activity * Up to 3 recent LinkedIn posts with AI summaries **Company data** * Industry, HQ, revenue, employee count, tech stack * 4 problems the company solves for customers * 4 synergy points with **your product** (personalised per API key) * 3 tailored discovery questions to ask in the call * Competitive insights and recent company news **Decision makers** *(BDR plan and above)* * Key people at the company — Directors, VPs, C-level * Name, title, LinkedIn profile, location ## Product personalisation Every research is generated against **your product** — its name, description, key features and target customers. That context is what makes synergy points, discovery questions and opening lines specific to what you sell rather than generic sales advice. Your account has **one product**, shared by everything: the API, the Chrome extension and the Autopilot. Edit it under **Settings → Product** in the dashboard — paste your website and it drafts the description for you, or write it by hand. Changing the product affects everything generated **from now on**. Research already cached is not rewritten. ## Typical completion time Most research requests complete in **20–60 seconds**. If the prospect was recently researched, results may return much faster. # Overview Source: https://docs.callprep.app/introduction/overview Research every prospect before you talk to them — and let the Autopilot do the outreach that gets you the conversation. # What is CallPrep? CallPrep does two things, and you can use either one on its own: * **Research** — give it a prospect's email address and it returns who they are, what their company does, where your product fits, and what to open with. In the dashboard, in a Chrome side panel, or through the API. * **Outreach** — the **Autopilot** takes new contacts from your CRM, researches them, and runs a personalised sequence across LinkedIn, email, WhatsApp and Telegram from your own accounts, stopping the moment someone answers. New CRM contact in, researched outreach out. Your meetings, researched before you get to them. Your first research request in under 5 minutes. HubSpot, LinkedIn, email, WhatsApp, Telegram. ## What a research gives you **About the person** * Job title, company, LinkedIn profile, photo * An AI summary of who they are professionally * Three conversation openers drawn from their recent LinkedIn activity * Their recent posts, summarised — and marked when a post is a reshare rather than their own words **About the company** * Industry, HQ, size, revenue signals, tech stack * What they sell and who they sell to * Synergy points with **your** product, not generic advice * Discovery questions tailored to the call * Competitive notes and recent company news *(BDR+)* **Decision makers** *(BDR and above)* * Other senior people at that company — name, title, LinkedIn, location ## Why it is different * **Everything is written against your product.** Your product description, key features and target customers are the context for every insight — so "synergy points" means points between *their* company and *your* product. * **It does not guess.** Where the research turns up nothing credible, the Autopilot skips that message rather than sending a generic one, and tells you why on the lead's timeline. * **The outreach sends from your accounts** — your LinkedIn profile, your mailbox, your number — inside limits that protect them. * **Results are cached**, so researching the same person again is instant. ## Where to start | You want to | Start here | | --------------------------------------------- | ----------------------------------------- | | Automate outreach from your CRM | [Autopilot overview](/autopilot/overview) | | Prepare for meetings already in your calendar | [Chrome extension](/extension/overview) | | Enrich prospects from your own code | [Quickstart](/introduction/quickstart) | ## Base URL ``` https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1 ``` # Quickstart Source: https://docs.callprep.app/introduction/quickstart Make your first research request in under 5 minutes. ## 1. Generate an API key Go to the [CallPrep dashboard](https://call-prep-api.vercel.app/api-keys) and create an API key. If you have not set up your product yet, do that first under **Settings → Product** — paste your website and it drafts the description for you. Every insight is written against that product, so it is the difference between generic output and useful output. Keep your API key secret. Never expose it in client-side code or public repositories. ## 2. Submit a research request ```bash theme={null} curl -X POST \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research \ -H "Authorization: Bearer cp_live_..." \ -H "Content-Type: application/json" \ -d '{ "email": "john.doe@acmecorp.com" }' ``` **Response:** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "processing", "estimated_seconds": 30 } ``` ## 3. Poll for results Use the `research_id` to check the status every 5 seconds until `status` is `completed`. ```bash theme={null} curl \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research-status/res_1a7b6e9c35a9b848 \ -H "Authorization: Bearer cp_live_..." ``` **Response when completed:** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "completed", "completed_at": "2026-05-05T10:27:08.004Z", "data": { "prospect": { "name": "John Doe", "title": "VP of Sales", "summary": "John is VP of Sales at Acme Corp...", "opening_talk": [ "Your post about reducing sales cycles resonated...", ... ] }, "company": { ... }, "decision_makers": [ ... ] } } ``` ## Complete Node.js example ```javascript theme={null} const API_KEY = 'cp_live_...'; const BASE_URL = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1'; async function researchProspect(email) { // 1. Submit const res = await fetch(`${BASE_URL}/research`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ email }), }); const { research_id } = await res.json(); console.log('Research started:', research_id); // 2. Poll until completed while (true) { await new Promise(r => setTimeout(r, 5000)); const statusRes = await fetch(`${BASE_URL}/research-status/${research_id}`, { headers: { 'Authorization': `Bearer ${API_KEY}` }, }); const data = await statusRes.json(); if (data.status === 'completed') return data; if (data.status === 'failed') throw new Error(data.error); console.log('Still processing...'); } } // Usage const result = await researchProspect('john.doe@acmecorp.com'); console.log(result.data.prospect.opening_talk); ``` Typical completion time is **20–60 seconds** depending on data availability. If cached data exists, results return in under 2 seconds. ## Next steps Learn about API key best practices Full reference of all returned fields How to handle errors gracefully Automate with n8n templates # Credits Source: https://docs.callprep.app/plans/credits How credits work and when they reset. ## What is a credit? One credit = **one researched prospect**. It costs the same wherever the research was started from, and it is one pool for the whole account: | Started by | Costs | | --------------------------------------------- | ------------------------------------------------------- | | `POST /research` (API) | 1 credit | | A research in the Chrome extension | 1 credit | | An automatic research from a calendar meeting | 1 credit per meeting — one attendee, not the whole room | | A lead matching an Autopilot filter | 1 credit | A credit is consumed whether the result comes from cache or from live lookups. ## What is free * A lead that matched no Autopilot and was **abandoned** — no research, no credit * A lead skipped by *Addresses to skip* or *Excluded domains* * A contact re-created or re-fired by HubSpot, while **Research each contact only once** is on (the default) * Sending, retrying, skipping or rescheduling any outreach message * Polling `GET /research-status` ## Credit reset Credits reset at the start of your **billing cycle** — the same day each month as when you first subscribed. You'll see the exact reset date on your [Analytics](https://call-prep-api.vercel.app/analytics) page. Unused credits **do not roll over** to the next month. ## Monitoring usage You can track your credit usage in real time: * **Sidebar** — remaining credits shown in the dashboard sidebar * **Analytics page** — daily usage chart and credit donut ## Credit alerts Enable email notifications at 75% and 95% usage in [Settings → Notifications](https://call-prep-api.vercel.app/settings). ## Cache hits still consume credits When a research request returns cached data (response in under 2 seconds), 1 credit is still consumed. This is intentional — you're using the service and the cached data was paid for on a previous request. ## Credit refunds Credits are refunded automatically if a research job fails due to an infrastructure error (timeout, pipeline crash). Credits are **not** refunded when: * The prospect wasn't found in any data source * The AI step failed but other data was returned * You submitted an invalid email If you believe a credit was incorrectly consumed, contact [hello@callprep.app](mailto:hello@callprep.app) with the `research_id`. # Data retention Source: https://docs.callprep.app/plans/data-retention What CallPrep stores about the people you research, how long it keeps it, what happens on a deletion request, and how GDPR applies. ## The short version * Enriched data about a person is **deleted or stripped of personal data after 12 months**, automatically, every night. * Working records that carry an address in passing are cleared after **90 days**. * Meetings read from your calendar are deleted after **30 days**. * An **unsubscribe is kept for good** — deleting it on a timer would mean contacting someone who asked you not to. * Deleting your account removes your data **immediately**, not on a timer. *** ## What is stored, and for how long | What | Kept for | Then | | ---------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------ | | Prospect records — name, title, LinkedIn profile, photo, AI summary | **365 days** since last update | Deleted | | Company records | **365 days** since last update | Deleted | | Decision-maker records scraped from company pages | **180 days** | Deleted | | Research jobs and their results | **365 days** | Personal data removed; the record of the run — date, status, cost — is kept | | Outreach sequences and the messages sent in them | **365 days** | Names, addresses and message content removed; the record of what was sent and when is kept | | Working records that carry an address in passing (enrolment decisions, bounce records, queued leads) | **90 days** | Deleted | | Meetings read from Google Calendar | **30 days** | Deleted | | Unsubscribes and suppression entries | **Kept** | Never deleted | | Consent and audit records | **Kept** | Never deleted | **Why some records are stripped rather than deleted.** A sent message is two things at once: a record about a person, and a record of what your account did. Deleting it would quietly rewrite your own history — fewer leads last quarter, different response rates, costs that no longer add up. So the personal data comes out and the shape of the record stays. After that it is no longer personal data, and your analytics still say what they said yesterday. The purge runs **daily**. Nothing has to be requested for it to happen. *** ## Refresh windows — different thing, same page Retention is when data is *deleted*. A refresh window is how long enriched data is considered **fresh enough to reuse** instead of paying to look it up again. | Data | Refresh window | | --------------- | -------------- | | Prospect | 30 days | | Company | 90 days | | Decision makers | 180 days | Inside the window, a repeat research returns what is already there — see [Caching behaviour](/guides/caching-behavior). Outside it, the data is looked up again and the record is overwritten. *** ## Deleting one person's data on request If someone asks to be deleted — a prospect, a lead, anyone in your CRM — send the request to [hello@callprep.app](mailto:hello@callprep.app) with the email address. We run a targeted erasure: * Their **prospect record is deleted** * **Research jobs, sequences and sent messages** covering them keep their shape with the personal data removed * **Any running sequence for them is stopped** * Their address is **added to the suppression list by default**, so a later import cannot start contacting them again. Say so if you do not want that * The erasure itself is **recorded permanently** — that record is the proof the request was honoured, and when The **company record is not deleted**: it describes an employer, is shared by everyone at that domain, and is not personal data about the person asking. We can scope the erasure to your account alone, or run it across every account where that address appears — say which when you write. *** ## Deleting your account Deleting your CallPrep account removes your data **immediately and in full**: research, prospects, companies, sequences, messages, mailboxes, seats, integration connections, API keys, calendar meetings. Two things survive, deliberately and without identifying you: consent and audit records, kept as evidence that permissions were granted and warnings were shown, and operational error logs used to keep the service running. *** ## GDPR CallPrep processes personal data (names, business email addresses, LinkedIn profiles, job titles) **on your behalf, as a processor**. You are the controller, and you are responsible for having a lawful basis for contacting the people you put into it. * **Data is stored in the EU** — Supabase, `eu-west-1` (Ireland) * **Your data is scoped to your account.** Other customers never see it * **We never sell your data**, and we never make it available to another customer * Enrichment necessarily involves **sub-processors** — data providers, an AI model provider, email delivery and messaging infrastructure. They are named in the DPA * **Deletion requests** from the people you research are handled as described above For a **Data Processing Agreement**, write to [hello@callprep.app](mailto:hello@callprep.app). *** ## Frequently asked questions It runs from when the record was **last updated**. Researching the same person again refreshes them, and the clock restarts from that point. No. That is the reason records are stripped rather than deleted: counts, dates, outcomes and costs stay exactly as they were. It is a record that says "do not contact this address" — deleting it on a schedule would produce exactly the harm the request was meant to prevent. It is kept, it is never used for anything else, and it stops every channel. Yes. Write to [hello@callprep.app](mailto:hello@callprep.app) and we will export the research and outreach records on your account. No. Your product description and prospect data are sent to the model to generate the output you asked for, under terms that do not permit training on it. Retention is unchanged by the plan — the same windows apply. Cancelling stops new research; deleting your account removes the data. # Plans overview Source: https://docs.callprep.app/plans/overview What each CallPrep plan includes — credits, outreach channels, mailboxes and API keys. ## Plans **\$0** · 10 credits to try everything with no commitment **\$25/mo** · 125 credits · LinkedIn outreach **\$59/mo** · 300 credits · LinkedIn + email · decision makers **\$199/mo** · 800 credits · every channel · sales pitch and company news **Enterprise** — custom credits and seats, every channel, SSO, priority support and a dedicated Slack channel. [Talk to us](mailto:hello@callprep.app). Annual billing is cheaper per month — $22 on Individual, $49 on BDR, \$149 on BDR+. Write to us to switch. *** ## What each plan includes | | Free | Individual | BDR | BDR+ | Enterprise | | ----------------------------------------- | ----- | ---------- | ---------- | -------- | --------------- | | Credits per month | 10 | 125 | 300 | 800 | Custom | | Prospect and company enrichment | ✅ | ✅ | ✅ | ✅ | ✅ | | AI insights and discovery questions | ✅ | ✅ | ✅ | ✅ | ✅ | | LinkedIn posts analysis | ✅ | ✅ | ✅ | ✅ | ✅ | | Chrome extension and calendar battlecards | ✅ | ✅ | ✅ | ✅ | ✅ | | Decision makers | ❌ | ❌ | ✅ | ✅ | ✅ | | Sales pitch and company news | ❌ | ❌ | ❌ | ✅ | ✅ | | **Autopilot — LinkedIn** | ❌ | ✅ | ✅ | ✅ | ✅ | | **Autopilot — email** | ❌ | ❌ | ✅ | ✅ | ✅ | | **Autopilot — WhatsApp and Telegram** | ❌ | ❌ | ❌ | ✅ | ✅ | | Sending mailboxes | 1 | 2 | 3 | 5 | Custom | | API keys | 1 | 1 | 3 | 10 | Custom | | Support | Email | Email | Email, 24h | Priority | Dedicated Slack | **Credits are one pool.** A research costs one credit whether it came from the API, the Chrome extension, a calendar meeting or an Autopilot. See [Credits](/plans/credits). *** ## Add-ons Need more of one thing without moving up a plan — extra credits every month, an extra mailbox, extra API keys? Write to [hello@callprep.app](mailto:hello@callprep.app) and we will add it to your subscription. Add-on credits stack on top of your plan's monthly allowance and renew with it. *** ## Changing plan Upgrades, downgrades and annual billing all go through [hello@callprep.app](mailto:hello@callprep.app) — there is no self-serve checkout yet. Your current plan and usage are on the [Billing page](https://call-prep-api.vercel.app/billing) in the dashboard. Downgrading lowers your monthly credits and can take away a channel your Autopilots are using. Check what is running before you drop a tier. # cURL Source: https://docs.callprep.app/sdks/curl Using the CallPrep API with cURL. ## Submit a research request ```bash theme={null} curl -X POST \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research \ -H "Authorization: Bearer cp_live_..." \ -H "Content-Type: application/json" \ -d '{"email": "john.doe@acmecorp.com"}' ``` ## With all optional fields ```bash theme={null} curl -X POST \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research \ -H "Authorization: Bearer cp_live_..." \ -H "Content-Type: application/json" \ -d '{ "email": "john.doe@acmecorp.com", "prospect_name": "John Doe", "company_name": "Acme Corp", "linkedin_url": "https://www.linkedin.com/in/johndoe", "company_linkedin_url": "https://www.linkedin.com/company/acme" }' ``` **Response:** ```json theme={null} { "research_id": "res_1a7b6e9c35a9b848", "status": "processing", "estimated_seconds": 30 } ``` ## Poll for results ```bash theme={null} curl \ https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1/research-status/res_1a7b6e9c35a9b848 \ -H "Authorization: Bearer cp_live_..." ``` ## Shell script — submit and poll ```bash theme={null} #!/bin/bash API_KEY="cp_live_..." BASE="https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1" EMAIL="${1:-john.doe@acmecorp.com}" # Submit echo "Submitting research for: $EMAIL" RESPONSE=$(curl -s -X POST "$BASE/research" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d "{\"email\": \"$EMAIL\"}") RESEARCH_ID=$(echo "$RESPONSE" | grep -o '"research_id":"[^"]*"' | cut -d'"' -f4) echo "Research ID: $RESEARCH_ID" # Poll while true; do sleep 5 STATUS_RESPONSE=$(curl -s "$BASE/research-status/$RESEARCH_ID" \ -H "Authorization: Bearer $API_KEY") STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"status":"[^"]*"' | cut -d'"' -f4) echo "Status: $STATUS" if [ "$STATUS" = "completed" ]; then echo "Done!" echo "$STATUS_RESPONSE" | python3 -m json.tool break elif [ "$STATUS" = "failed" ]; then echo "Failed!" echo "$STATUS_RESPONSE" exit 1 fi done ``` **Usage:** ```bash theme={null} chmod +x research.sh ./research.sh john.doe@acmecorp.com ``` # Node.js Source: https://docs.callprep.app/sdks/nodejs Using the CallPrep API with Node.js. ## Installation No SDK required — use the native `fetch` API (Node.js 18+) or any HTTP client. ```bash theme={null} # Optional — for older Node.js versions npm install node-fetch ``` ## Minimal example ```javascript theme={null} const API_KEY = process.env.CALLPREP_API_KEY; const BASE = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1'; async function research(email) { // 1. Submit const res = await fetch(`${BASE}/research`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); const { research_id } = await res.json(); // 2. Poll while (true) { await new Promise(r => setTimeout(r, 5000)); const s = await fetch(`${BASE}/research-status/${research_id}`, { headers: { 'Authorization': `Bearer ${API_KEY}` }, }); const data = await s.json(); if (data.status === 'completed') return data; if (data.status === 'failed') throw new Error(data.error); } } const result = await research('john.doe@acmecorp.com'); console.log(result.data.prospect.opening_talk); ``` ## With TypeScript ```typescript theme={null} interface ResearchResult { research_id: string; status: 'completed' | 'failed' | 'processing' | 'queued'; completed_at: string | null; data?: { prospect: { name: string; title: string; company: string; summary: string; opening_talk: string[]; }; company: { name: string; synergy_points: string[]; discovery_questions:string[]; }; decision_makers: Array<{ full_name: string; position_title: string; linkedin_url: string; }>; }; } async function research(email: string): Promise { const BASE = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1'; const headers = { 'Authorization': `Bearer ${process.env.CALLPREP_API_KEY}`, 'Content-Type': 'application/json', }; const init = await fetch(`${BASE}/research`, { method: 'POST', headers, body: JSON.stringify({ email }), }); const { research_id } = await init.json(); while (true) { await new Promise(r => setTimeout(r, 5000)); const res = await fetch(`${BASE}/research-status/${research_id}`, { headers }); const data = await res.json() as ResearchResult; if (data.status === 'completed') return data; if (data.status === 'failed') throw new Error(data.error as string); } } ``` ## With Express.js ```javascript theme={null} import express from 'express'; const app = express(); app.use(express.json()); app.post('/enrich', async (req, res) => { const { email } = req.body; if (!email) return res.status(400).json({ error: 'email required' }); try { const data = await research(email); res.json(data); } catch (err) { res.status(500).json({ error: err.message }); } }); app.listen(3000); ``` # Python Source: https://docs.callprep.app/sdks/python Using the CallPrep API with Python. ## Installation ```bash theme={null} pip install requests python-dotenv ``` ## Minimal example ```python theme={null} import os, time, requests from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv('CALLPREP_API_KEY') BASE = 'https://rpiqzfzokrwxavztrpmp.supabase.co/functions/v1' HEADERS = { 'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/json', } def research(email: str, **kwargs) -> dict: # 1. Submit res = requests.post( f'{BASE}/research', headers=HEADERS, json={'email': email, **kwargs}, ) res.raise_for_status() research_id = res.json()['research_id'] # 2. Poll while True: time.sleep(5) status_res = requests.get( f'{BASE}/research-status/{research_id}', headers=HEADERS, ) data = status_res.json() if data['status'] == 'completed': return data if data['status'] == 'failed': raise RuntimeError(f"Research failed: {data.get('error')}") # Usage result = research('john.doe@acmecorp.com', prospect_name='John Doe') prospect = result['data']['prospect'] print(prospect['summary']) print('\n'.join(prospect['opening_talk'])) ``` ## With error handling ```python theme={null} from requests.exceptions import HTTPError def safe_research(email: str) -> dict | None: try: return research(email) except HTTPError as e: code = e.response.json().get('error') if code in ('credits_exhausted', 'trial_limit_reached'): print('Credit limit reached — upgrade your plan') elif code == 'invalid_key': print('Invalid API key') elif code == 'invalid_email_format': print(f'Invalid email: {email}') else: print(f'API error: {code}') return None except RuntimeError as e: print(f'Job failed: {e}') return None ``` ## Batch enrichment ```python theme={null} import concurrent.futures def research_batch(emails: list[str], max_workers: int = 5) -> list[dict]: results = [] with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(safe_research, email): email for email in emails} for future in concurrent.futures.as_completed(futures): email = futures[future] result = future.result() if result: results.append({'email': email, 'data': result['data']}) return results # Enrich 10 prospects in parallel emails = ['a@company.com', 'b@company.com', ...] results = research_batch(emails, max_workers=3) ``` When running batch enrichment, space out requests to avoid hitting concurrent limits. A `max_workers` of 3-5 is recommended for most use cases. # Contact & support Source: https://docs.callprep.app/support/contact How to get help with the CallPrep API. ## Support channels | Plan | Channel | Response time | | ---------- | ----------------------- | ------------- | | Free | Email | 48h | | Individual | Email | 48h | | BDR | Email | 24h | | BDR+ | Email, priority | Priority | | Enterprise | Dedicated Slack channel | Priority | ## Email support [hello@callprep.app](mailto:hello@callprep.app) When reporting an issue, please include: * Your `research_id` (if applicable) * The email address you were researching * The error code or unexpected behaviour you observed * Timestamp of the issue ## Reporting bugs Found a bug? Email [hello@callprep.app](mailto:hello@callprep.app) with: * Steps to reproduce * Expected vs actual behaviour * `research_id` if relevant ## Feature requests We love hearing from users. Send feature requests to [hello@callprep.app](mailto:hello@callprep.app) with subject line `Feature request: ...` ## Something looks broken Before writing in, two places answer most of it: * **Autopilot** — the row's delivery chip and a lead's timeline in [Activity](/autopilot/activity) say why a message was not sent * **API** — a job that ends `failed` carries an `error`; see [Handling errors](/guides/handling-errors) If it is us, tell us at [hello@callprep.app](mailto:hello@callprep.app) — include the `research_id` or the lead's email address and roughly when it happened. # FAQ Source: https://docs.callprep.app/support/faq Frequently asked questions about the CallPrep API. Typically **20–60 seconds**. If the prospect was recently researched, results may return much faster — sometimes in under 2 seconds. If no data is found for the provided email, the pipeline still attempts to enrich the company via the email domain and generate AI insights from available data. The `status` will still be `completed`, but some fields like `summary` or `opening_talk` may be `null`. No. Only `POST /research` consumes 1 credit. You can poll `GET /research-status` as many times as needed without any credit impact. Yes. CallPrep caches enriched data per `(email, product)` combination. Subsequent requests for the same email return data much faster. A credit is still consumed per request — but the response may be near-instant. All AI-generated fields (`summary`, `opening_talk`, `synergy_points`, etc.) are generated in the language set in your account preferences. Supported languages: English, Polish, German, French, Spanish, Italian, Portuguese, Dutch. Source data is always translated to your preferred language. No. Decision makers are a **plan-gated feature** available on the **BDR plan and above**. If your plan doesn't include this feature, `decision_makers` will be an empty array. You can check your plan on the [Billing](https://call-prep-api.vercel.app/billing) page. By default, CallPrep auto-detects the company LinkedIn URL from the enrichment data. Providing `company_linkedin_url` in your request overrides auto-detection — useful when the prospect's profile doesn't link to the correct company page. No. All enriched data is scoped to your account and product. Different users never see each other's data, even for the same email address. A job stuck in `processing` is failed automatically after 10 minutes. Most research finishes in well under a minute, so anything past a couple of minutes is already unusual. You can then resubmit the same request. If this happens repeatedly, please contact [hello@callprep.app](mailto:hello@callprep.app) with the `research_id`. Yes — credits are refunded automatically when a job fails due to an infrastructure error. For other cases, contact [hello@callprep.app](mailto:hello@callprep.app) with the `research_id`. `synergy_points` is an AI-generated list of 4 connections between the prospect's company and **your product** — based on the product context tied to your API key. This field is unique to CallPrep and personalised per API key, not generic advice. Go to **Settings → Product** in the dashboard. Your account has one product, used by the API, the Chrome extension and the Autopilot alike. Changes apply to everything generated from then on; research already cached is not rewritten.