Cold Leads

Підписки, які ініціює агент і схвалює людина

Для кого
Розробники AI-агентів, чий власник ще не має ключа Cold Leads
Проблема
Агент, якому потрібна перевірка e-mail, ніколи не повинен мати платіжної картки чи оформлювати підписку сам. Якщо відправити людину реєструватися, обирати тариф і копіювати ключ, завдання переривається, а ключі, вставлені в чат, витікають.
Рішення
Агент викликає один ендпоінт без ключа й отримує посилання Stripe Checkout для свого власника та секретний claim-токен. Власник переглядає тариф і ухвалює рішення. Після оплати агент рівно один раз отримує API-ключ за claim-токеном — через опитування або після підписаного callback-запиту.
Що ви отримаєте
Робочий секретний API-ключ для нового акаунта Business власника, переданий агентові один раз, і лист власникові з посиланням, яке відкриває веб-застосунок Cold Leads.

Адреси, ідентифікатори та результати в прикладах ілюстративні. Домен example.com зарезервовано для документації, тож реальна перевірка цих адрес поверне invalid.

Приклади коду однакові для всіх мов, коментарі в них — англійською.

Процес коротко

Послідовність
Agent (no key)                 Cold Leads                         Human owner              Stripe
     |                              |                                   |                     |
     | POST /api/agent/provision    |                                   |                     |
     | owner_email, agent_id,       |                                   |                     |
     | callback_url (optional)      |                                   |                     |
     |----------------------------->| pending account, disabled key,    |                     |
     |                              | Checkout session (24 h) --------------------------------->|
     |<-----------------------------| checkout_url, session_id,         |                     |
     |                              | claim_token (shown once)          |                     |
     | shows checkout_url --------------------------------------------->|                     |
     |                              |                                   | reviews plan, pays->|
     |                              |<-------------------------------------- payment webhook --|
     |                              | activates account and key,        |                     |
     |                              | e-mails the owner a sign-in link ->|                     |
     |<-- signed callback (optional, no secrets) -----------------------|                     |
     | GET /api/agent/status        |                                   |                     |
     | ?session_id=...              |                                   |                     |
     | X-Claim-Token: clt_...       |                                   |                     |
     |----------------------------->|                                   |                     |
     |<-----------------------------| status active + api_key           |                     |
     |                              | (first call only)                 |                     |

Цей самий процес вбудовано в Node.js SDK (agent.provision, agent.waitForActivation, verifyCallbackSignature) і в локальний stdio MCP-сервер як інструменти provision_account_and_get_payment_link і check_provisioning_status. Хостованому MCP-ендпоінту ключ потрібен у кожному запиті, тож агент без ключа виконує цей крок через REST, SDK або stdio-сервер.

Крок 1: запит посилання на оплату (ключ не потрібен)

owner_email — людина, яка володітиме акаунтом і платитиме за нього; agent_id — назва вашого агента чи продукту, яку показують на сторінці оплати. callback_url необов’язковий і має бути https-адресою на публічному хості.

Запит
curl -s https://coldleads.app/api/agent/provision \
  -H "Content-Type: application/json" \
  -d '{
    "owner_email": "owner@example.com",
    "agent_id": "Acme Research Agent",
    "callback_url": "https://agent.example.com/hooks/coldleads"
  }'
Відповідь 200
{
  "status": "payment_required",
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_…",
  "session_id": "cs_live_…",
  "provision_id": "prov_…",
  "claim_token": "clt_…",
  "expires_at": "2026-10-01T09:30:00.000Z",
  "plan": { "id": "business", "name": "Business", "price": "$99", "amount": 99, "currency": "usd", "interval": "month", "api": true },
  "status_url": "https://coldleads.app/api/agent/status?session_id=cs_live_…",
  "instructions_for_agent": "Show the human owner (owner@example.com) this payment link and let them decide: … Do not open, pay or forward the link yourself. Keep claim_token secret (it is shown only now). …"
}
HTTPerrorКоли
400invalid_owner_email, agent_id_required, invalid_callback_url, bad_bodyТіло не є коректним JSON, e-mail власника не проходить перевірку синтаксису, agent_id порожній або callback_url не є https-адресою на публічному хості.
409account_existsДля цього e-mail уже існує робочий простір Cold Leads. Натомість попросіть у власника секретний ключ із Налаштування → API-ключі.
429rate_limitedПонад 10 запитів на годину з однієї IP-адреси або понад 3 запити для того самого e-mail власника на добу.
502gatewayStripe не зміг створити сесію оплати; спробуйте пізніше.

Крок 2: власник ухвалює рішення в Stripe

  • Покажіть checkout_url людині й дайте їй вирішити. Не відкривайте, не оплачуйте й не пересилайте посилання самостійно.
  • Сторінка Stripe називає продукт Cold Leads Business (API access) і вказує ім’я агента, ціну $99 на місяць, щомісячне поновлення до скасування та посилання на умови. Пробного періоду на цьому шляху немає, а під час оплати може додатися податок.
  • Посилання дійсне 24 години. Після цього статус змінюється на expired, і агент може запросити нове посилання.
  • Щойно оплата пройде, Cold Leads активує акаунт і ключ та надсилає власникові листом одноразове посилання, яке відкриває веб-застосунок, де можна керувати підпискою або перевипустити ключ агента.

Крок 3: одноразове отримання ключа

Опитуйте GET /api/agent/status?session_id=… із заголовком X-Claim-Token кожні 15–30 секунд, поки статус — pending_payment. Перша відповідь після оплати на запит із дійсним claim-токеном містить api_key; жодна наступна відповідь його не містить.

statusПоля, крім session_id і plan
pending_paymentexpires_at; checkout_url, якщо claim-токен дійсний
active (перший виклик із claim-токеном)activated_at, api_key, api_key_prefix, mcp_url, api_base, message
active (будь-який наступний виклик)activated_at, api_key_prefix, mcp_url, api_base, key_delivered_at, message
active (без дійсного claim-токена)лише activated_at
expiredmessage: запросіть нове посилання через POST /api/agent/provision
suspendedнемає
Перша відповідь після оплати
{
  "session_id": "cs_live_…",
  "plan": "business",
  "status": "active",
  "activated_at": "2026-09-30T10:02:11.000Z",
  "api_key_prefix": "sk_…9f3c",
  "mcp_url": "https://coldleads.app/api/mcp",
  "api_base": "https://coldleads.app/api/v1",
  "api_key": "sk_…",
  "message": "This is the only time the key is shown. Store it as COLDLEADS_API_KEY; send it as Authorization: Bearer <key>."
}
poll_status.py
# pip install httpx
import os
import time

import httpx

BASE = "https://coldleads.app"
SESSION_ID = os.environ["COLDLEADS_SESSION_ID"]    # session_id from the provision response
CLAIM_TOKEN = os.environ["COLDLEADS_CLAIM_TOKEN"]  # claim_token (clt_...), shown only once


def save_key(key: str) -> None:
    path = os.path.expanduser("~/.coldleads_api_key")  # or your secret manager
    fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w") as f:
        f.write(key)


while True:
    r = httpx.get(f"{BASE}/api/agent/status", params={"session_id": SESSION_ID},
                  headers={"X-Claim-Token": CLAIM_TOKEN}, timeout=30)
    if r.status_code == 429:  # 120 status requests per minute per IP
        time.sleep(60)
        continue
    r.raise_for_status()
    s = r.json()
    if s["status"] == "active":
        if "api_key" not in s:
            raise SystemExit("The key was already collected; the owner can rotate it in Settings -> API keys.")
        save_key(s["api_key"])
        print("API key saved; prefix", s["api_key_prefix"])
        break
    if s["status"] in ("expired", "suspended"):
        raise SystemExit(s.get("message", s["status"]))
    time.sleep(20)  # pending_payment: poll every 15-30 seconds

Необов’язково: підписаний callback замість опитування

  • Коли власник оплатив, Cold Leads надсилає один POST на callback_url із тайм-аутом 5 секунд. Він не переходить за редиректами й не повторює спроб, тож залиште опитування як запасний варіант.
  • JSON-тіло містить event (coldleads.account.activated), provision_id, session_id, status (active) і status_url. Ключа в ньому ніколи немає: отримайте його запитом статусу з кроку 3.
  • Заголовки: X-Coldleads-Event з назвою події та X-Coldleads-Signature зі значенням sha256=, за яким іде hex-дайджест: HMAC-SHA256 від сирого тіла запиту з ключем, яким є SHA-256 hex-дайджест вашого claim-токена.
Node.js (Express)
// npm install express
import express from "express";
import { createHash, createHmac, timingSafeEqual } from "node:crypto";

const CLAIM_TOKEN = process.env.COLDLEADS_CLAIM_TOKEN; // clt_... from the provision response

// X-Coldleads-Signature: sha256=<hex HMAC-SHA256 of the raw body>, key = SHA-256 hex digest of the claim token
function validSignature(rawBody, header, claimToken) {
  if (!Buffer.isBuffer(rawBody) || typeof header !== "string" || !header.startsWith("sha256=")) return false;
  const key = createHash("sha256").update(claimToken).digest("hex");
  const expected = createHmac("sha256", key).update(rawBody).digest();
  const given = Buffer.from(header.slice("sha256=".length), "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

const app = express();
app.post("/hooks/coldleads", express.raw({ type: "application/json" }), (req, res) => {
  if (!validSignature(req.body, req.get("x-coldleads-signature"), CLAIM_TOKEN)) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(204); // answer fast: Cold Leads waits 5 seconds and does not retry
  if (event.event === "coldleads.account.activated") {
    // the callback carries no key: collect it now with GET /api/agent/status + X-Claim-Token
    console.log("owner paid, session", event.session_id);
  }
});
app.listen(3000);
Python (Flask)
# pip install flask
import hashlib
import hmac
import json
import os

from flask import Flask, request

CLAIM_TOKEN = os.environ["COLDLEADS_CLAIM_TOKEN"]  # clt_... from the provision response
app = Flask(__name__)


def valid_signature(raw_body: bytes, header, claim_token: str) -> bool:
    if not header or not header.startswith("sha256="):
        return False
    key = hashlib.sha256(claim_token.encode()).hexdigest().encode()
    expected = hmac.new(key, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header[len("sha256="):])


@app.post("/hooks/coldleads")
def coldleads_callback():
    raw = request.get_data()  # the raw bytes, before any JSON parsing
    if not valid_signature(raw, request.headers.get("X-Coldleads-Signature"), CLAIM_TOKEN):
        return "", 401
    event = json.loads(raw)
    if event.get("event") == "coldleads.account.activated":
        # the callback carries no key: collect it with GET /api/agent/status + X-Claim-Token
        print("owner paid, session", event["session_id"])
    return "", 204

Безпека

  • Поводьтеся з claim_token як із паролем. Він повертається лише один раз, зберігається лише його хеш, і це єдиний спосіб отримати ключ: без нього жодна відповідь статусу не містить ключа, хоч за session_id, хоч за owner_email.
  • API-ключ видається рівно один раз. Якщо його втрачено, власник входить в акаунт і перевипускає ключ у Налаштування → API-ключі.
  • Оплата не доводить, що платник контролює скриньку власника. Вебдоступ до акаунта надається лише owner_email: через одноразове посилання, надіслане на цю адресу, або через вхід у Cold Leads із цією адресою, щойно її підтверджено.
  • Перевіряйте підпис callback на сирому тілі, перш ніж щось робити, хоча callback не містить секретів.
  • Неоплачені запити не висять довго: посилання спливає через 24 години, а неоплачені записи про підключення видаляються через 7 днів разом із невикористаними акаунтом і ключем.

Ліміти та вартість

  • Тариф: Business із доступом до API, $99 на місяць у доларах США, щомісячне поновлення до скасування; 10 000 кредитів на перевірку та API на місяць; додаткові пакети по 1 000 кредитів коштують $5. Може додаватися податок.
  • Підключення: 10 запитів на годину з IP-адреси та 3 запити на e-mail власника на добу. Запити статусу: 120 на хвилину з IP-адреси.
  • Після активації ключ має звичайний ліміт API — 120 запитів на хвилину.

Питання

Чи може агент оплатити або вибрати дешевший тариф?

Ні. Посилання веде на тариф Business, єдиний тариф з API, і оплатити його може лише людина на сторінці Stripe. Завдання агента — показати посилання й чекати.

А якщо власник так і не оплатить?

Посилання на оплату спливає через 24 години, і статус стає expired. Агент може запросити нове посилання (до 3 запитів на e-mail власника на добу). Неоплачені записи видаляються через 7 днів.

А якщо власник уже користується Cold Leads?

Запит завершується помилкою 409 account_exists. Власник створює секретний ключ у Налаштування → API-ключі (тариф Business) і передає його агентові як COLDLEADS_API_KEY.

Чи працює це через MCP?

Так, із локальним stdio-сервером (npx -y --allow-git=root github:anttka4cz/mcp-server-coldleads). Він запускається без ключа, пропонує provision_account_and_get_payment_link і check_provisioning_status і переходить на новий ключ, щойно його отримано. Хостований ендпоінт coldleads.app/api/mcp вимагає ключ у кожному запиті.