关于 CanYouGrab.it
CanYouGrab.it 是一个通过 DNS 检查域名可用性的 API,并返回可用、不可用或结果不确定的状态。它面向需要查询域名的 API 使用者,同时提供账户用量、配额和密钥管理。
主要功能
- 01单次批量检查最多 100 个域名
- 02基于 DNS 返回可用性结果
- 03跟踪每分钟和每月配额
- 04创建、轮换和撤销 API 密钥
- 05账户用量摘要
适用场景
- 01在一次请求中检查多个域名
- 02在应用中集成域名查询
- 03监控 API 配额与用量
- 04管理 API 使用者的凭据
MCP 相关
README 提到了 MCP 服务及其运行时,但未说明面向客户端提供的工具或功能。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.canyougrab.it/mcpclaude mcp add --transport http einiba-canyougrab https://api.canyougrab.it/mcp{"mcpServers":{"einiba-canyougrab":{"type":"http","url":"https://api.canyougrab.it/mcp"}}}在 Cursor 中打开{"servers":{"einiba-canyougrab":{"type":"http","url":"https://api.canyougrab.it/mcp"}}}在 VS Code 中打开{"mcpServers":{"einiba-canyougrab":{"type":"http","url":"https://api.canyougrab.it/mcp"}}}{"mcpServers":{"einiba-canyougrab":{"serverUrl":"https://api.canyougrab.it/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
3| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| domains | array | 是 |
详细介绍
CanYouGrab API
域名可用性查询 API,采用 FastAPI + DNS(Unbound resolver)构建,支持订阅计费。
在线服务:
- API:
https://api.canyougrab.it - 开发者门户:
https://portal.canyougrab.it - 认证:
https://auth.canyougrab.it(Auth0 自定义域名)
架构概览
┌──────────────────────────────────────────────────────────────────┐
│ Developer Portal │
│ (Zudoku/React on portal.canyougrab.it) │
│ Usage Dashboard · API Keys · Pricing · API Reference (OAS) │
└──────────────────┬───────────────────────────────────────────────┘
│ Auth0 JWT (portal) / Bearer API key (API)
▼
┌──────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (v5.0.0) │
│ api.canyougrab.it:8000 │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ app.py │ │ keys.py │ │billing.py│ │ auth.py │ │
│ │ /check/bulk │ │ /keys │ │/billing │ │ API key + │ │
│ │ /usage │ │ CRUD │ │/stripe │ │ JWT auth │ │
│ │ │ │ rotate │ │ webhook │ │ │ │
│ └──────┬──────┘ └──────────┘ └────┬─────┘ └─────────────┘ │
│ │ │ │
└─────────┼────────────────────────────┼───────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Valkey (Redis) │ │ Stripe API │
│ Job queue + │ │ Subscriptions │
│ Rate limiting │ │ Webhooks │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────────────────┐
│ RQ Worker │──DNS──▶ │ Unbound Resolver │
│ (worker.py) │ │ (dedicated droplet) │
│ ThreadPool(10) │ │ NS queries via VPC │
│ via RQ queue │ └──────────────────────────────┘
└─────────────────┘
┌──────────────────────────────┐
───────────────────▶│ PostgreSQL │
(auth, usage, │ API keys + Usage logs │
billing only) └──────────────────────────────┘
目录结构
zuplo/
├── backend/ # Python FastAPI backend
│ ├── app.py # Main API: /check/bulk, /usage, /health
│ ├── auth.py # API key auth (SHA-256) + Auth0 JWT auth (RS256)
│ ├── billing.py # Stripe checkout, portal, webhooks, card-on-file, usage details
│ ├── keys.py # API key CRUD: create, list, rotate, revoke (+ Turnstile)
│ ├── antifraud.py # Anti-fraud: Turnstile, device fingerprints, risk scoring
│ ├── email_utils.py # Email normalization + disposable email detection
│ ├── dns_client.py # DNS-based domain availability checking via Unbound
│ ├── queries.py # PostgreSQL queries: usage tracking, auth, billing
│ ├── valkey_client.py # Redis/Valkey job queue client (RQ-backed)
│ ├── rq_tasks.py # RQ task function: process_domain_job()
│ ├── worker.py # RQ worker process (ThreadPoolExecutor per job)
│ ├── migrations/ # SQL migrations
│ │ └── 001_free_tier_antifraud.sql
│ └── requirements.txt # Python dependencies
├── portal/ # Developer portal (Zuplo + Zudoku)
│ ├── config/
│ │ ├── routes.oas.json # OpenAPI 3.1 spec (public API documentation)
│ │ └── policies.json # Zuplo policies (empty — all routing is direct)
│ ├── docs/ # Zudoku documentation portal
│ │ ├── src/
│ │ │ ├── config.ts # API_BASE, Turnstile site key, Stripe PK
│ │ │ ├── UsageDashboard.tsx # Usage + billing dashboard component
│ │ │ ├── PricingPage.tsx # Plan selection + Stripe checkout
│ │ │ ├── PricingPlans.tsx # Pricing card grid component
│ │ │ └── CardSetupPage.tsx # Stripe Elements card-on-file for Free+
│ │ ├── public/ # Static assets (logos, banners, CSS overrides)
│ │ ├── zudoku.config.tsx # Portal config: theme, nav, Auth0, API key mgmt
│ │ └── package.json # Frontend dependencies (React 19, Zudoku)
│ ├── package.json # Workspace root (Zuplo v6, TypeScript v5)
│ └── README.md # Zuplo boilerplate (not project-specific)
├── mcp-server/ # MCP package for ChatGPT, Claude, and remote MCP clients
│ ├── pyproject.toml # MCP package metadata + version
│ ├── server.json # MCP registry/server metadata
│ ├── uv.lock # Locked MCP runtime dependencies
│ └── src/canyougrab_mcp/
│ ├── __init__.py
│ └── server.py # stdio + streamable-http MCP entrypoint
├── .github/workflows/
│ ├── deploy.yml # Production deploy (on tag push v*)
│ └── deploy-dev.yml # Dev deploy (on push to dev branch)
├── .claude/
│ └── launch.json # Local dev server config (port 9200)
└── package.json # Root workspace config
API 端点
公共 API(API 密钥认证:Authorization: Bearer cyg_...)
| 方法 | 路径 | 描述 |
|---|---|---|
POST | /api/check/bulk | 检查最多 100 个域名。长轮询直至结果就绪(最长 30 秒)。 |
GET | /api/account/usage | 已认证消费者的用量摘要。 |
GET | /api/account/quota-check | 轻量级月度 + 每分钟配额检查。 |
GET | /health | 健康检查(无需认证)。 |
门户 API(Auth0 JWT 认证)
| 方法 | 路径 | 描述 |
|---|---|---|
POST | /api/keys | 创建新的 API 密钥。 |
GET | /api/keys | 列出用户的 API 密钥。 |
POST | /api/keys/{id}/rotate | 轮换密钥(撤销旧密钥并创建新密钥)。 |
DELETE | /api/keys/{id} | 撤销(软删除)密钥。 |
POST | /api/billing/checkout | 创建 Stripe Checkout 会话。 |
POST | /api/billing/portal | 创建 Stripe Customer Portal 会话。 |
POST | /api/billing/setup-card | 为 Free+ 创建 SetupIntent,以便保存银行卡。 |
POST | /api/billing/confirm-free-plus | 验证银行卡指纹并升级到 Free+。 |
GET | /api/billing/card-status | 检查用户是否已保存银行卡。 |
GET | /api/billing/usage/detailed | 门户控制面板的逐密钥用量明细。 |
POST | /api/antifraud/turnstile/verify | 验证 Cloudflare Turnstile 令牌。 |
POST | /api/antifraud/device/register | 注册设备指纹(Fingerprint Pro)。 |
GET | /api/antifraud/risk | 获取已认证用户的风险评估。 |
POST | /api/antifraud/assess-signup | 在注册时运行多信号风险评估。 |
内部 / Webhook
| 方法 | 路径 | 描述 |
|---|---|---|
POST | /api/account/usage/detailed | 多消费者用量明细。 |
POST | /api/stripe/webhook | Stripe webhook 接收端(已验证签名)。 |
核心请求流程
域名可用性检查
Client FastAPI Valkey Worker Unbound
│ │ │ │ │
│ POST /api/check/bulk │ │ │ │
│ { domains: [...] } │ │ │ │
│─────────────────────────▶│ │ │ │
│ │── validate key ──────────│ │ │
│ │── check minute rate ────▶│ INCR ratelimit:id:min │ │
│ │── check monthly quota ──▶│ (PostgreSQL) │ │
│ │── record usage ─────────▶│ (PostgreSQL) │ │
│ │── create_job() ─────────▶│ HSET job:{uuid} │ │
│ │ │ RQ enqueue (with retry) │ │
│ │ │ │ │
│ │ │◀── RQ Worker dequeues ───│ │
│ │ │ │ │
│ │ │──── claim_job() ────────▶│ │
│ │ │ │── ThreadPool(10) │
│ │ │ │── check_domain_dns()
│ │ │ │── NS query ───────▶│
│ │ │ │◀── NOERROR/NXDOMAIN│
│ │ │◀── complete_job() ───────│ │
│ │ │ │ │
│ │◀─ poll get_job_status() ─│ │ │
│ │ (0.3s interval, 30s │ │ │
│ │ max timeout) │ │ │
│◀─────────────────────────│ │ │ │
│ { results: [...] } │ │ │ │
计费 / 订阅流程
User → Pricing Page → Select Plan → Auth0 login
→ POST /api/billing/checkout (JWT auth)
→ Find/create Stripe customer (linked by auth0_sub metadata)
→ Stripe Checkout Session created → redirect to Stripe
→ User pays → Stripe fires webhook
→ POST /api/stripe/webhook (HMAC-SHA256 verified)
→ checkout.session.completed → fetch subscription → get price ID
→ Map price to plan → UPDATE api_keys SET plan, lookups_limit
订阅方案
| 方案 | 每月查询次数 | 每分钟速率限制 | 每次请求的域名数 | 价格 |
|---|---|---|---|---|
| Free | 500 | 30/min | 30 | $0 |
| Free+ | 10,000 | 100/min | 100 | $0(已保存银行卡) |
| Basic | 20,000 | 300/min | 100 | $10/mo |
| Pro | 50,000 | 1,000/min | 100 | $20/mo |
| Business | 300,000 | 3,000/min | 100 | $30/mo |
认证
API 密钥认证(公共 API 消费者)
- 格式:
Authorization: Bearer cyg_<token> api_keys表中存储的是经过 SHA-256 哈希处理的密钥(从不存储明文)- 门户中会显示密钥前缀(
cyg_XXXXXXXX...) - 创建时仅返回一次完整密钥
- 撤销时采用软删除(记录
revoked_at时间戳,不会物理删除)
JWT 认证(门户 / 控制面板)
- Auth0 租户:
dev-mqe5tavp6dr62e7u - 自定义域名:
auth.canyougrab.it - 算法:RS256,使用 JWKS 验证(缓存 1 小时)
- 受众:
https://api.canyougrab.it - 社交登录:Google、Apple(使用 Apple 登录)
通过 DNS 检查域名是否可用
域名可用性通过查询运行在独立 DigitalOcean Droplet 上的专用 Unbound 递归 DNS 解析器进行检查。工作进程通过 VPC 私有网络发送 NS 记录查询,并解读响应:
| DNS 响应 | available | 含义 |
|---|---|---|
| NOERROR + NS 记录 | false | 域名已注册并已委派 |
| NXDOMAIN | true | 域名不在区域中——可能可用 |
| NoAnswer(NOERROR,无 NS) | false | 已注册,但处于停放状态/未委派 |
| SERVFAIL / Timeout | null | 情况不明——检查失败 |
Unbound 解析器会积极缓存(已注册域名缓存 7 天,NXDOMAIN 缓存 5 分钟),并直接查询 TLD 权威服务器,以避免公共解析器的速率限制。
数据库架构
使用 PostgreSQL 进行身份验证、用量跟踪和计费,而非域名查询。
PostgreSQL 表
| 表 | 用途 | 关键列 |
|---|---|---|
api_keys | 用户 API 密钥 | id、user_sub、email_normalized、key_hash、key_prefix、plan、lookups_limit、revoked_at |
usage_log_daily | 每日用量汇总 | consumer、lookups、recorded_at(DATE,每个 consumer+日期 唯一) |
usage_log_minute | 每分钟用量汇总 | consumer、lookups、minute_start(TIMESTAMP,每个 consumer+分钟 唯一) |
card_fingerprints | 每张卡只允许一个免费账户 | user_sub、stripe_fingerprint(唯一组合) |
device_fingerprints | 基于设备的多账户检测 | user_sub、visitor_id(Fingerprint Pro) |
account_risk | 综合风险评分 | user_sub(唯一)、risk_score、risk_signals(JSONB) |
Valkey(Redis)数据结构
| 键模式 | 类型 | 用途 | TTL |
|---|---|---|---|
job:{uuid} | Hash | 作业状态、域名、结果 | 1 小时 |
rq:queue:canyougrab-jobs | List | RQ 作业队列(由 RQ 管理) | — |
rq:job:{uuid} | Hash | RQ 作业元数据(重试次数、状态) | 可配置 |
ratelimit:{consumer}:{YYYYMMDDHHmm} | String | 每分钟速率限制计数器(INCR) | 60s |
基础设施
服务器
| 环境 | 域名 | 用途 |
|---|---|---|
| 生产环境 | api.canyougrab.it | FastAPI + Worker |
| 开发环境 | dev.canyougrab.it | FastAPI + Worker |
| 门户 | portal.canyougrab.it | Zudoku 静态站点 |
| 身份验证 | auth.canyougrab.it | Auth0 自定义域名 |
外部服务
| 服务 | 用途 |
|---|---|
| DigitalOcean | Droplet(API 服务器、Unbound 解析器)、托管 PostgreSQL、托管 Valkey |
| Cloudflare | canyougrab.it 区域的 DNS、CDN、SSL,Turnstile 机器人防护 |
| Auth0 | 用户身份验证、社交登录(Google + Apple)、签发 JWT |
| Stripe | 订阅计费、结账、客户门户、Webhook |
| GitHub Actions | CI/CD 部署流水线 |
部署流水线
生产环境部署
**触发条件:**推送匹配 v* 的 git 标签(例如 v1.0.5)
git tag v1.0.5 && git push origin v1.0.5
流水线(.github/workflows/deploy.yml):
- GitHub Actions 在推送
v*标签时触发 - 使用
DEPLOY_SSH_KEY通过 SSH 登录生产服务器(DEPLOY_HOST密钥) - 在服务器上检出目标引用(
git fetch+git checkout <tag>) - 运行仓库管理的部署脚本:
/opt/canyougrab-repo/scripts/deploy-host.sh
开发环境部署
**触发条件:**推送到 dev 分支
git push origin dev
流水线(.github/workflows/deploy-dev.yml):
- GitHub Actions 在推送到
dev时触发 - 使用相同的
DEPLOY_SSH_KEY通过 SSH 登录开发服务器(DEV_DEPLOY_HOST密钥) - 在服务器上检出
dev引用(git fetch+git checkout dev) - 运行仓库管理的部署脚本:
/opt/canyougrab-repo/scripts/deploy-host.sh
由仓库管理的主机部署脚本
两个流水线都会使用简短的内联 SSH 引导脚本,将 /opt/canyougrab-repo 更新到目标引用,然后调用该检出版本中的 scripts/deploy-host.sh。由仓库管理的部署脚本负责:
- 使用
pip install -r requirements.txt安装后端依赖 - 将后端、门户和 MCP 源代码目录
rsync到各自的运行时目录 - 如果主机同时运行
canyougrab-mcp.service,则重新安装或刷新mcp-server软件包/运行时 - 通过 systemd 重启 FastAPI(uvicorn)、工作进程和 MCP 服务
如果 /mcp 与 API 部署在同一台主机上,仅部署后端是不够的。MCP 服务必须在同一次部署中更新并重启,否则 OAuth 元数据和实际运行的 MCP 行为可能会不一致。
如果需要,仍可保留现有的 /opt/deploy.sh,作为手动兼容垫片;但自动化流水线应以仓库中的 scripts/deploy-host.sh 为唯一可信来源。
分支策略
| 分支 | 用途 | 部署到 |
|---|---|---|
main | 生产版本发布 | 打标签后 → api.canyougrab.it |
dev | 开发/预发布 | 自动 → dev.canyougrab.it |
| 功能分支 | 正在进行的工作 | 不自动部署 |
环境变量
后端(服务器上必需)
# DNS Resolver (Unbound on dedicated droplet)
DNS_RESOLVER_HOSTNAME=unbound.canyougrab.internal # VPC internal hostname (resolved via socket.gethostbyname)
DNS_RESOLVER_PORT=53
DNS_QUERY_TIMEOUT=5.0 # Per-query timeout in seconds
# PostgreSQL (DigitalOcean Managed Database — auth, usage, billing only)
POSTGRES_HOST= # DB cluster hostname
POSTGRES_PORT=5432
POSTGRES_DB=canyougrab
POSTGRES_USER=canyougrab
POSTGRES_PASSWORD= # DB password
POSTGRES_SSLMODE=require
# Valkey / Redis (DigitalOcean Managed Database)
VALKEY_HOST= # Valkey cluster hostname
VALKEY_PORT=25061
VALKEY_USERNAME=default
VALKEY_PASSWORD= # Valkey password
# Auth0
AUTH0_DOMAIN=dev-mqe5tavp6dr62e7u.us.auth0.com
AUTH0_AUDIENCE=https://api.canyougrab.it
# Stripe
STRIPE_SECRET_KEY= # sk_live_... (prod) or sk_test_... (dev)
STRIPE_WEBHOOK_SECRET= # whsec_... (per-environment)
STRIPE_PRICE_BASIC= # price_... (live price ID for Basic plan)
STRIPE_PRICE_PRO= # price_... (live price ID for Pro plan)
STRIPE_PRICE_BUSINESS= # price_... (live price ID for Business plan)
# Cloudflare Turnstile (bot prevention on key creation)
TURNSTILE_SECRET_KEY= # 0x4AAAA... (from Cloudflare dashboard)
# Portal
PORTAL_URL=https://portal.canyougrab.it
# Worker
BATCH_CONCURRENCY=10 # Thread pool size for domain checks
VALKEY_QUEUE_NAME=canyougrab-jobs # RQ queue name (default: canyougrab-jobs)
# Monitoring (optional — only if monitoring stack is installed)
SLACK_ALERTS_WEBHOOK_URL= # Slack incoming webhook for Alertmanager
RQ_METRICS_PORT=9122 # Prometheus metrics exporter port
# Auto-scaler (optional — only on API host with autoscaler enabled)
DO_API_TOKEN= # DigitalOcean API token
DO_WORKER_SNAPSHOT_ID= # Snapshot ID for new worker droplets
AUTOSCALER_MIN_WORKERS=1
AUTOSCALER_MAX_WORKERS=5
AUTOSCALER_SCALE_UP_THRESHOLD=50
AUTOSCALER_SCALE_DOWN_IDLE_MINUTES=10
GitHub Actions 密钥
| 密钥 | 用途 |
|---|---|
DEPLOY_HOST | 生产服务器 IP |
DEV_DEPLOY_HOST | 开发服务器 IP |
DEPLOY_SSH_KEY | 两台服务器共用的 SSH 私钥 |
本地开发
后端
cd backend
pip install -r requirements.txt
# Start API server
uvicorn app:app --reload --port 8000
# Start worker (separate terminal)
python worker.py
需要将 DNS_RESOLVER_HOST 指向一个 Unbound 实例(或使用 8.8.8.8 进行基本测试),还需要 PostgreSQL 和 Valkey/Redis(本地运行或通过隧道连接到托管实例)。
门户
cd portal/docs
npm install
npm run dev # Starts Zudoku dev server (via zuplo)
或者使用已配置的启动服务器:
# From repo root
npx zuplo dev # Starts on port 9200
门户开发服务器在 portal/docs/src/config.ts 中将 API_BASE 硬编码为 https://api.canyougrab.it。若要使用本地后端进行开发,请将其改为 http://localhost:8000。
SEO:.it 顶级域名的地理定位要求
由于 canyougrab.it 使用 .it 国家/地区代码顶级域名(意大利),所有面向用户的 HTML 页面(门户、文档)都必须包含表明内容面向英语用户的信号:
- 每个页面都必须包含
<html lang="en">。 - 在
<head>中添加 Hreflang 标签:<link rel="alternate" hreflang="en-US" href="{page_url}" /> <link rel="alternate" hreflang="en" href="{page_url}" /> <link rel="alternate" hreflang="x-default" href="{page_url}" /> - 规范链接标签 — 指向自身的
<link rel="canonical" href="{page_url}" />。 - 所有内容均使用英语 — 任何页面上都不得出现意大利语文本。
这适用于门户(portal.canyougrab.it)、API 文档以及任何其他公开呈现的 HTML 页面。
关键设计决策
- 实时 DNS 查询:通过向专用的 Unbound 递归解析器发送 NS 查询来检查域名是否可用。这样可以获取实时结果(没有 24 小时的区域文件延迟),自动适用于任何 TLD,并避免每日批量加载区域文件所带来的运维复杂性。Unbound 会积极缓存(已注册域名缓存 7 天),因此重复查询耗时约为 1 ms。
- 可空的可用性状态:DNS 的故障模式比数据库查询更多。当 Unbound 返回 SERVFAIL 或超时时,API 会返回
available: null,而不是可能造成危险的误报。使用方应将null视为“无法确定”。 - 批量检查使用作业队列:批量端点使用由 Valkey 提供支持的 RQ(Redis Queue)来分发作业,并支持自动重试(重试 2 次,退避间隔为 5 秒/30 秒)和失败作业跟踪。工作进程会通过线程池并行执行 DNS 查询。
- 长轮询:批量检查端点会保持 HTTP 连接打开(每 0.3 秒轮询一次 Valkey,最长 45 秒),而不是要求客户端自行实现轮询。作业结果存储在自定义的 Valkey 哈希中(而非 RQ 内置的结果存储),因此轮询逻辑独立于队列框架。
- 使用 SHA-256 哈希处理 API 密钥:密钥在存储前会先进行哈希处理(与密码类似),因此即使数据库泄露,原始密钥也不会暴露。
- 通过 Stripe 元数据建立关联:通过 Stripe 客户对象上的
auth0_sub元数据,将 Stripe 客户与 Auth0 用户关联起来,无需单独的映射表。 - 双重跟踪用量:同时记录每日(
usage_log_daily)和每分钟(usage_log_minute)用量,分别用于月度配额和每分钟速率限制。 - 不使用 Zuplo 网关策略:项目最初使用 Zuplo 作为 API 网关,后迁移至直接使用 FastAPI(提交
3abef70)。开发者门户仍使用 Zuplo 门户/文档框架。
服务上的进程
核心服务(每台主机)
- FastAPI(uvicorn):在端口 8000 上提供所有 HTTP 端点
- RQ Worker(worker.py):处理 RQ 队列中的域名检查作业
- MCP 服务(如果此服务器托管
/mcp):为远程 MCP 客户端提供服务
监控堆栈(API 主机,可选)
- Prometheus(localhost:9090):抓取指标并评估告警规则
- Alertmanager(localhost:9093):将告警路由到 Slack
- redis_exporter(localhost:9121):将 Valkey 指标导出到 Prometheus
- RQ 指标导出器(localhost:9122):导出队列深度、工作进程数量和失败作业
- Grafana(localhost:3000,由 nginx 代理):用于查看队列运行状况的仪表板
- 自动扩缩容器(可选):通过 DO API 扩缩 Worker Droplet
使用以下命令安装监控堆栈:sudo bash scripts/setup-monitoring.sh
主机上的所有活动服务都必须通过 systemd 管理,并在部署期间重启。
所有自动化部署都应通过 scripts/deploy-host.sh 执行;该脚本负责重启主机上已安装的上述服务。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。