# @coloop-ai/respondent-sdk 中文文档

规范地址：https://npmcn.com/package/%40coloop-ai/respondent-sdk/

版本：0\.1\.0
npm 元数据更新时间：2026-09-22T13:49:43.722Z
中文译文更新时间：2026-11-10T11:17:50.008Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40coloop-ai/respondent-sdk
原文对照：https://npmcn.com/package/%40coloop-ai/respondent-sdk/?lang=original
项目主页：https://github.com/Genei-Ltd/respondent-sdk#readme
源码仓库：https://github.com/Genei-Ltd/respondent-sdk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：5.4 MB，24 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=18\.0\.0
- 运行依赖：1 个
- 首次发布：2026-09-22T13:49:43.046Z
- 最新版本发布：2026-09-22T13:49:43.420Z
- 近 30 天下载量：19453（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：1（近 12 个月 1 个）

## 中文简介

Type-security SDK for the President.io Partner API,由提供者的OpenAPI spec生成.

## 安装

```sh
npm install @coloop-ai/respondent-sdk
```

也可以使用：`pnpm add @coloop-ai/respondent-sdk`、`yarn add @coloop-ai/respondent-sdk`、`bun add @coloop-ai/respondent-sdk`

## README

# @ coloop-ai/ 答复者- sdk

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Genei-Ltd/respondent-sdk/blob/main/LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue?logo=typescript)](https://www.typescriptlang.org/)

类型安全客户端绑定 [答复者.io](https://www.respondent.io/) 合伙人API. 操作来自提供者的 OpenAPI 文档和 包裹在一个基因组SDK.

> 项目由CoLoop维护,不附属于或核可 由答复者.io——在使用前审查其API术语.

## 安装

```bash
pnpm add @coloop-ai/respondent-sdk
```

`zod` 是一个运行时间依赖( e)`^3.25.0 || ^4.0.0`(笑声) (掌声) 生成 Zod 计划导入自 `zod/v4`。 。 。 .

## 快开始

```ts
import { RespondentSdk } from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!, // sent as x-api-key
  apiSecret: process.env.RESPONDENT_API_SECRET!, // sent as x-api-secret
  timeoutMs: 10_000, // abort requests that hang for 10s
})

// Lookup IDs differ between staging and production, so resolve them at
// runtime rather than hardcoding them.
const topics = await respondent.lookups.topics({ pageSize: 1 })
const topicId = topics.results[0]?.id
if (!topicId) throw new Error('No project topics on this environment')

const project = await respondent.projects.create({
  publicTitle: 'Coffee habits study',
  publicInternalName: 'coffee-habits-q1',
  targetMarketType: 'b2c',
  typeOfResearch: 'remote',
  targetResearchMethodology: 'oneOnOne',
  participantTimeRequiredMinutes: 30,
  incentiveAmount: 50,
  targetNumberOfParticipants: 5,
  externalResearcher: {
    researcherId: 'researcher-1',
    researcherName: 'Ada Lovelace',
    bookingUrl: 'https://example.com/book',
  },
  targetProjectTopics: [topicId],
})

await respondent.screenerQuestions.create(project.id, {
  text: 'How often do you drink coffee?',
  questionType: 'radio',
  answers: [
    { text: 'Daily', answerValue: 1 }, // 1 qualifies
    { text: 'Never', answerValue: 2 }, // 2 disqualifies
  ],
})

// `publicDescription` is required at publish time, so set it first.
await respondent.projects.update(project.id, {
  publicDescription: 'A 30 minute chat about how you drink coffee.',
})

await respondent.projects.publish(project.id)
```

然后让每个申请人走进招募周期:

```ts
const responses = await respondent.screenerResponses.list(project.id, {
  status: 'PENDING',
})

for (const response of responses.results ?? []) {
  await respondent.screenerResponses.qualify(project.id, response.id, {
    qualifyStatus: true,
    qualifiedOverriden: false,
    disqualifyReasons: [],
    message: '',
  })

  await respondent.screenerResponses.invite(project.id, response.id, {
    meetingLink: 'https://example.com/session',
    bookingLink: 'https://example.com/book',
  })
}

// Then, in a later request — only once the session has actually happened,
// because this is what starts the incentive payment:
//
//   await respondent.screenerResponses.markAttended(projectId, responseId)
```

一个可运行的版本生活在 [`examples/recruit-participants-demo.ts`](https://npmcn.com/package/%40coloop-ai/respondent-sdk/examples/recruit-participants-demo.ts)。 。 。 .

## 环境

| 环境 | 基础 URL |
| --- | --- |
| 生产 | `https://api.respondent.io` |
| 粘贴 | `https://api-staging.respondent.io` |

生产是默认的。 目标通过导出常数中转 :

```ts
import {
  RESPONDENT_STAGING_BASE_URL,
  RespondentSdk,
} from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_STAGING_API_KEY!,
  apiSecret: process.env.RESPONDENT_STAGING_API_SECRET!,
  baseUrl: RESPONDENT_STAGING_BASE_URL,
})
```

粘接和生产是独立的世界: 单独的证书,单独的 网络用户,以及 **不同的查找标识** 工业、技能、专题和工作 标题。 运行时解析搜索ID `respondent.lookups.*` 改为 硬编码.

提供者的 OpenAPI 文档只声明中转服务器 。 生产 URL来自 [他们的文档](https://developers.respondent.io/reference/introduction-1),则 生成客户端的默认值被绑定为生产 `openapi-ts.config.ts`。 。 。 .

## 认证

每个请求都带两个信头, `x-api-key` (身份证)和 `x-api-secret` (密语). 全权证书由答辩方伙伴小组签发 生产准入需要经过审查的中试演示和 签署了《生活津贴》。 没有OAuth流量,也没有文件记载的范围模型.

证书和配置客户端保存于 `#private` 字段,没有 所以记录或连载 `RespondentSdk` 实例 无法打印,错误包含一个编辑的请求摘要而不是 `Request` 对象。 软件包没有导出 到达配置客户端: 生成的操作为平面 函数将客户端作为参数,所以没有类持有 建立客户登记册.

服务器控制的文本也被擦除。 答复头被减为a 名称的简短允许列表 — 一个标题 NAME 也由服务器控制, 所以一个 清单中未包括其价值—— 以及尚存价值, 和 `statusText` 和有效载荷, 发生任何密钥或 密钥替换为 `[redacted]`。将信息、代码和细节读回 所以它们也被覆盖了 一个反映 `x-api-key` 回来 `X-Request-Id`,或引用到错误消息中,或回答 带有一个以它命名的标题,但不能将其设置在错误上. `cause` 这是 基本错误,附则不变.

SDK也设定了 `redirect: 'error'`。节点将自定义信头转发到 源码变化方向调整——不同于 `Authorization` - 所以在重新定向后 将手 `x-api-key` 财务报告和审定财务报表 `x-api-secret` 指向任何主机重定向名称。 A 类 重定向请求以 a 失败 `RespondentSdkTransportError` 换句话说.

## 处理错误

每一个 SDK 产生的故障都会产生一个子类: `RespondentSdkError`,所以请 失败模式可以区分 :

| 错误 | 升起时 | `cause` |
| --- | --- | --- |
| `RespondentSdkApiError` | aPI以非-2xx状态(`status`, (中文(简体) ), `statusText`, (中文(简体) ), `payload`, (中文(简体) ), `responseHeaders`, (中文(简体) ), `code`, (中文(简体) ), `detail`(中文(简体) ) | 无——已读 `payload` |
| `RespondentSdkTransportError` | 没有到达任何回复——DNS、连接重设或拒绝重定向 | 隐藏的获取错误 |
| `RespondentSdkResponseError` | 其身体无法解码的成功状态(`status`, (中文(简体) ), `statusText`, (中文(简体) ), `responseHeaders`(中文(简体) ) | 页:1 `SyntaxError` |
| `RespondentSdkTimeoutError` | 配置内容 `timeoutMs` 逾期(`timeoutMs`(中文(简体) ) | 无——已读 `timeoutMs` |

一个失败是故意的不是 SDK 错误: 当你通过您的 拥有 `AbortSignal`它拒绝你 `signal.reason` 完全如您所愿,你已经准备好了.

每个 SDK 错误都带有编辑请求摘要 `request` (单位:千美元)`url`, (中文(简体) ), `method`, (中文(简体) ), 标题 `x-api-key` 编号 : `x-api-secret` 改为 `[redacted]`),以及 携带 `cause` 仅当存在潜在错误时。 没有带错误 `Response`数字 : `RespondentSdkApiError` 财务报告和审定财务报表 `RespondentSdkResponseError` 曝光 `status`, (中文(简体) ), `statusText` (单位:千美元) `responseHeaders` 地图代替。 那张地图只能保存 允许的列表标题 `retry-after`,利率限制头, `content-type`, (中文(简体) ), `date` 和请求编号头——其数值被擦除; 每个其它标题都被删除,名字也包括在内. `isRespondentSdkError` 与其中任何一个匹配; `isRespondentSdkApiError`, (中文(简体) ), `isRespondentSdkTransportError`, (中文(简体) ), `isRespondentSdkResponseError` 财务报告和审定财务报表 `isRespondentSdkTimeoutError` 狭.

```ts
import {
  RespondentSdk,
  isRespondentSdkApiError,
} from '@coloop-ai/respondent-sdk'

try {
  await respondent.projects.close(projectId, {})
} catch (error) {
  if (!isRespondentSdkApiError(error)) throw error

  if (error.status === 429) {
    console.warn('Respondent throttled request', error.payload)
    return
  }

  throw new Error(
    `Failed to close project (${String(error.status)}): ${error.message}`,
  )
}
```

提供者不记录一个错误响应计划, 及其 OpenAPI 文档未声明出错机构. `RespondentSdkApiError` 因此,探测 `error`, (中文(简体) ), `message` 财务报告和审定财务报表 `detail` 字段,用于可读信息并坠落 回HTTP状态线. `error.payload` 总是持有生体.

费率限额记录为250项请求/秒(500起,100 000 000起/天) 和50项请求/第二次(100起,200 000起/日)进行中。 429号 反应机构和任何 `Retry-After` 头为无相.

## 请求超时

设定 `timeoutMs` 以中止超过期限的请求。 最后期限涵盖 读取响应正文,而不仅仅是接收响应头,所以是一个服务器 答案,然后摊子仍然游走它。 SDK拒绝 `RespondentSdkTimeoutError`,可以视之为可重试.

最多只能是正数 `2_147_483_647` (约24.8天)和 建筑师会投出相反的. `setTimeout` 将更长的延迟回合包成 1ms,所以很长的最后期限 几乎会立即中止所有请求.

```ts
import {
  RespondentSdk,
  RespondentSdkTimeoutError,
} from '@coloop-ai/respondent-sdk'

const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!,
  apiSecret: process.env.RESPONDENT_API_SECRET!,
  timeoutMs: 5_000,
})

try {
  await respondent.projects.list()
} catch (error) {
  if (error instanceof RespondentSdkTimeoutError) {
    console.warn('Respondent request timed out, retrying shortly')
  }
}
```

## 取消请求

每个操作都使用带有 `AbortSignal`数字 :

```ts
const controller = new AbortController()
setTimeout(() => controller.abort(new Error('user navigated away')), 1_000)

await respondent.projects.list(
  { status: 'DRAFT' },
  {
    signal: controller.signal,
  },
)
```

电话用信号拒绝 `reason` 完全如您所愿,你已经准备好了 一个配置 `timeoutMs` 仍然应用在您的信号上; 无论哪个火先获胜.

## 观察请求

过 `onRequestSettled` 以便他听到每一个请求,一旦它解决, 无论它成功还是失败。 该事件带有已编辑的请求摘要 (单位:千美元)`url`, (中文(简体) ), `method`,并替换了带有证书的页眉 `[redacted]`, (中文(简体) ), 回应 `status` 当一个人到达时, `durationMs`,以及 `error` 当呼叫时 拒绝:非-2xx状态的 SDK 错误、运输故障或超时, 或你自己的流产原因。 它用于伐木和测量,不能 掷出.

```ts
const respondent = new RespondentSdk({
  apiKey: process.env.RESPONDENT_API_KEY!,
  apiSecret: process.env.RESPONDENT_API_SECRET!,
  onRequestSettled: (event) => {
    const { method, url } = event.request
    if (event.error === undefined) {
      console.info(`respondent ${method} ${url} ${String(event.status)}`)
    } else {
      console.error(`respondent ${method} ${url} failed`, event.error)
    }
  },
})
```

一个边缘:2xx的身体无法被解码,报告时有其状态,没有 `error`因为反应本身还不错 电话仍然拒绝 `RespondentSdkResponseError`。 。 。 .

## 倾斜

列表终点 `page` (默认) `1`, (中文(简体) ), `pageSize` (默认) `50`和 `includeCount` (默认) `false`返回时 `{ totalResults?, page, pageSize, results }`。 。 。 . `totalResults` 只有在您通过时才出现 `includeCount: true`。 。 。 .

## 高级别方法

`RespondentSdk` 将操作组合成模块:

-   `respondent.projects` —— 说 `list`, (中文(简体) ), `create`, (中文(简体) ), `retrieve`, (中文(简体) ), `update`, (中文(简体) ), `delete`, (中文(简体) ), `copy`, (中文(简体) ), `publish`, (中文(简体) ), `pause`, (中文(简体) ), `close`, (中文(简体) ), `audienceSizeEstimate`, (中文(简体) ), `replaceExternalScreenerQuestions`, (中文(简体) ), `uploadFile`, (中文(简体) ), `pitchSuggestions`。 。 。 .
-   `respondent.screenerQuestions` —— 说 `list`, (中文(简体) ), `create`, (中文(简体) ), `replaceAll`, (中文(简体) ), `retrieve`, (中文(简体) ), `update`, (中文(简体) ), `delete`, (中文(简体) ), `reorder`。 。 。 .
-   `respondent.screenerResponses` —— 说 `list`, (中文(简体) ), `retrieve`, (中文(简体) ), `payoutSummary`, (中文(简体) ), `qualify`, (中文(简体) ), `invite`, (中文(简体) ), `schedule`, (中文(简体) ), `markAttended`, (中文(简体) ), `markNoShow`, (中文(简体) ), `reject`, (中文(简体) ), `report`, (中文(简体) ), `favorite`, (中文(简体) ), `hide`, (中文(简体) ), `cancelInvite`, (中文(简体) ), `cancelBooking`, (中文(简体) ), `cancelBookingAndReinvite`, (中文(简体) ), `participantCancelBooking`, (中文(简体) ), `ingestExternalScreenerAnswers`, (中文(简体) ), `payout`。 。 。 .
-   `respondent.quota` —— 说 `create`, (中文(简体) ), `retrieve`, (中文(简体) ), `update`, (中文(简体) ), `delete`。 。 。 .
-   `respondent.webhooks` —— 说 `list`, (中文(简体) ), `create`, (中文(简体) ), `retrieve`, (中文(简体) ), `deactivate`, (中文(简体) ), `listEventTypes`, (中文(简体) ), `simulate`。 。 。 .
-   `respondent.pricing` —— 说 `balanceSummary`。 。 。 .
-   `respondent.profiles` —— 说 `retrieve`, (中文(简体) ), `createTestParticipant` (只结.
-   `respondent.teamRespondents` —— 说 `list`, (中文(简体) ), `retrieve`, (中文(简体) ), `batchInvite`。 。 。 .
-   `respondent.messaging` —— 说 `conversations` (单位:千美元)`list`, (中文(简体) ), `create`, (中文(简体) ), `retrieve`, (中文(简体) ), `update`, (中文(简体) ), `markAsRead`, (中文(简体) ), `addParticipant`, (中文(简体) ), `removeParticipant`和 `messages` (单位:千美元)`list`, (中文(简体) ), `retrieve`, (中文(简体) ), `inbox`, (中文(简体) ), `create`) (中文(简体) ). 常务代表 (单位:千美元)`listConversations`, (中文(简体) ), `createConversation`, (中文(简体) ), `listMessages`, (中文(简体) ), `createMessage`, (中文(简体) ), `inbox`坐在舱内本身.
-   `respondent.lookups` —— 说 `values`, (中文(简体) ), `industries`, (中文(简体) ), `jobTitles`, (中文(简体) ), `skills`, (中文(简体) ), `topics`。 。 。 .

终点 提供者的标记已贬值(`GET /v1/teams/{teamId}/projects`, (中文(简体) ), `POST /v1/messaging/messages`, 团队范围收件箱, 和腐烂的 添加- participant 路由) 没有被包. 它们仍然可以通过 生成客户端 .

## 网络呼号

Webhook 有效载荷和签名计划不在提供者的 OpenAPI 中 文档,所以 `@coloop-ai/respondent-sdk/webhooks` 和Zod一起做手模,还有.. 添加签名验证 .

命令事项: 校验字节, 存储事件并声明其 id 在一个 原子步骤,回答2xx,只有这样,工作才能完成——从中读回 存储记录.

使这种变化持久化的活性:一行被储存在工作之前一直等待 成功, 唯一开始工作的事情 是投票 等待的行, 因此,在索赔交易之外,没有什么东西可以拖取交货.

```ts
import type { UnknownWebhookEvent } from '@coloop-ai/respondent-sdk/webhooks'
import {
  WebhookEvent,
  getWebhookSignatureHeader,
  verifyAndDedupeWebhook,
} from '@coloop-ai/respondent-sdk/webhooks'

/**
 * Write the whole event down and claim its `uuid`, in ONE atomic step — a
 * unique index on `uuid` plus an "insert, ignore conflict" statement that
 * stores the payload alongside it.
 *
 * Inserting the `uuid` on its own is NOT "processed". It records only that the
 * event is safely on disk. Two concurrent copies of a replay both look new if
 * the check and the claim are separate steps.
 */
async function claimAndStore(
  event: UnknownWebhookEvent,
): Promise<'stored' | 'duplicate'> {
  const inserted = await db
    .insertInto('respondent_webhook_inbox')
    .values({ uuid: event.uuid, payload: JSON.stringify(event) })
    .onConflict((c) => c.column('uuid').doNothing())
    .executeTakeFirst()
  return inserted.numInsertedOrUpdatedRows === 1n ? 'stored' : 'duplicate'
}

export async function handleWebhook(request: Request) {
  // Read the RAW BYTES, before any JSON parsing. Not `request.text()`: that
  // decodes leniently, so it has already replaced invalid UTF-8 with U+FFFD
  // before the SDK can reject the body.
  const rawBody = new Uint8Array(await request.arrayBuffer())

  const outcome = await verifyAndDedupeWebhook({
    rawBody,
    signatureHeader: getWebhookSignatureHeader(request.headers),
    privateKey: process.env.RESPONDENT_WEBHOOK_PRIVATE_KEY!,
    claimAndStore,
  })

  if (outcome.status === 'invalid_signature') {
    return new Response('Forbidden', { status: 403 })
  }
  if (outcome.status === 'stored') {
    // A nudge, not the trigger. The row is already pending on disk and the
    // poll below picks it up regardless, so a failed wake-up strands nothing —
    // which is why its failure is swallowed rather than answered non-2xx.
    await wakeWebhookWorker().catch(() => {})
  }
  // A duplicate or an unparseable body is 2xx too: the provider retries
  // anything else, and a replay must not do the work twice.
  return new Response('OK')
}
```

工作开始于对尚未完成的行的民意调查,所以从不 取决于上面的警钟是否成功:

```ts
// Run this on a schedule, and on each wake-up.
async function drainPendingDeliveries() {
  const pending = await db
    .selectFrom('respondent_webhook_inbox')
    .select(['uuid'])
    .where('processedAt', 'is', null)
    .orderBy('receivedAt')
    .limit(100)
    .execute()

  for (const row of pending) {
    await processStoredDelivery(row.uuid)
  }
}

async function processStoredDelivery(uuid: string) {
  const row = await db
    .selectFrom('respondent_webhook_inbox')
    .select(['payload'])
    .where('uuid', '=', uuid)
    .where('processedAt', 'is', null)
    .executeTakeFirst()
  if (!row) return // already processed, or never stored

  const parsed = WebhookEvent.safeParse(JSON.parse(row.payload))
  if (parsed.success) {
    switch (parsed.data.event) {
      case 'SCREENER_RESPONSES.CREATED': {
        const { id, projectId } = parsed.data.payload.resource
        // Payloads carry IDs only — fetch the response to read it.
        const response = await respondent.screenerResponses.retrieve(
          projectId,
          id,
        )
        break
      }
      case 'PROJECTS.UPDATED': {
        console.log(parsed.data.payload.resource.updatedFields)
        break
      }
    }
  } else {
    console.warn('Unhandled webhook payload', parsed.error)
  }

  // Only now is the delivery processed.
  await db
    .updateTable('respondent_webhook_inbox')
    .set({ processedAt: new Date() })
    .where('uuid', '=', uuid)
    .execute()
}
```

在请求处理人内部进行业务工作,在索赔之后,如何 交货丢失: `uuid` 已经声称,所以当工作抛出 提供者的重试作为复制品回来,工作永远不会发生. 储存 事件首先使重试没有必要——记录在磁盘上,a 抛出它等待下一次尝试.

紧接后 `claimAndStore` 返回失败,当队列是 唯一触发器: 行会犯, 排队抛出, 提供者的重试 读作重复, 从来没有运行过作品。 要么写工作 在与收件箱行相同的交易中,或如上所述,对待决交易进行投票 排行并视入等为最优化.

A 类 `Uint8Array` 一旦签名, 机体就用致命的 UTF-8 验证解码 已经核实了,所以一个不合法的UTF-8的尸体 回来了作为 `malformed_body` 而不是被修复。 宽放的解码器取代 U+FFFD 用于 无效序列, `JSON.parse` 接受已修复的文本, 以及您的事件 然后商店就是供货商从未送来的 检查只用字节 : 通过 `new Uint8Array(await request.arrayBuffer())`,因为 `await request.text()` 已经为您做了宽大解码 `string` `rawBody` 当它到达时被接受.

如果您不想重新播放检查, 请用手进行验证 : `verifyWebhookSignatureFromRawBody` 抓起线条字节 `verifyWebhookSignatureFromParsedBody` 散开 `JSON.stringify(parsedBody)`读取 下一节,然后选择.

这五个事件类型是 `PROJECTS.UPDATED`, (中文(简体) ), `SCREENER_RESPONSES.CREATED`, (中文(简体) ), `SCREENER_RESPONSES.UPDATED`, (中文(简体) ), `MESSAGES.CREATED` 财务报告和审定财务报表 `CONVERSATIONS.CREATED`。 。 。 . `WebhookEvent` 双倍的受歧视的工会类型 (单位:千美元)`import type { WebhookEvent }`)和工会验证人 (单位:千美元)`WebhookEvent.safeParse`) (中文(简体) ). 每个事件计划都是个松散的物体,所以 提供商后来补充说,保留而不是剥去. `UnknownWebhookEvent` 接受 SDK 尚未建模的事件类型 .

### 提供方的文档和没有的

这个 `privateKey` 用于签名的返回方式 `POST /v1/webhooks`,还有 以 `GET /v1/webhooks` 财务报告和审定财务报表 `GET /v1/webhooks/{webhookId}` 所有三个答案都与 同样的 `WebhookDto` ——所以一个丢失的密钥可以被读回而不是被再创建. 供养者 [只签名样本](https://developers.respondent.io/docs/Webhooks/webhooks) 显示一个HMAC在车体上方, 底部64- 编码, 在 `Respondent-Webhook-Signature` 页眉为 `<algorithm>=<signature>`。 。 。 .

三个空白问题:

1.  **散列算法没有文献记录 .** 他们的样本解析了算法 退出头部值本身,并发布无示例头。 因为 信头是攻击者控制的, 校验只接受一个 允许列表( e)`sha256`, (中文(简体) ), `sha512` (默认情况下). 把它加宽 `allowedAlgorithms` 一旦提供者确认他们发送了哪一个.
2.  **他们签的字节在任何地方都没有说明.** 他们的花草样本 `JSON.stringify(request.body)` ——被解析并被重序的对象——即 无法保证复制到线上的字节。 这个SDK 船舶的读取和主张都没有证明: `verifyWebhookSignatureFromRawBody` (默认和什么) `verifyAndDedupeWebhook` (b) 将交付的字节拖走 `verifyWebhookSignatureFromParsedBody` 散开 `JSON.stringify(parsedBody)`。 。 。 . **只有真正的送货才能解决问题.** 将网点指向终点 记录原始正文和签名头,并发送一个与 `respondent.webhooks.simulate(webhookId, { event })`,看看谁是援助者 返回时 `true`直到你这样做, 将选择视为未经证实.
3.  **没有时间戳头和回放保护.** HMAC 证明 真实性,而非新鲜度,供应商对最多5个交货进行重试 时间。 关于事件的重复 `uuid` —— 说 `verifyAndDedupeWebhook` 这样做是为了 你,一个原子 `claimAndStore` 将事件记录为 它声称身份证明.

两个较小的硬化笔记: 载有多个签名头的快递 以未署名方式处理, 一份文摘只有在有条形加法的情况下才解码 基础64. `Buffer.from(value, 'base64')` 自己默默接受跟踪 垃圾,并接受多个拼写 相同的文摘.

其他行动事实:每个小队都有1个正在运行的网页,事件起火 只针对通过 API 创建的项目, 您必须在 3 秒内返回 2xx, 并且 故障每隔10分钟重审5次。 网络呼号 配置在装配中不结转到生产中.

## 生成 Zod 计划

每个 OpenAPI 组件计划也作为 Zod 计划在下面排放 `@coloop-ai/respondent-sdk/zod`名称 `z<SchemaName>`。用它们来作曲 供货商在自己计划中的语句:

```ts
import * as z from 'zod/v4'
import { zGender, zScreenerResponseStatus } from '@coloop-ai/respondent-sdk/zod'

const Participant = z.object({
  gender: zGender,
  status: zScreenerResponseStatus,
})
```

生成的文件导入自 `zod/v4`,这决定了两者 `zod@3.25+` 财务报告和审定财务报表 `zod@4`。 。 。 .

## 生成客户端访问

如果您需要完全控制请求选项, 请呼叫生成的操作 直接说 每个函数都是简单的函数,它占用了您配置的客户端; 没有生成的类,因此没有保存配置的注册 客户:

```ts
import { getV1Projects, sdk } from '@coloop-ai/respondent-sdk'

const client = sdk.createClient({
  baseUrl: 'https://api.respondent.io',
  headers: {
    'x-api-key': process.env.RESPONDENT_API_KEY!,
    'x-api-secret': process.env.RESPONDENT_API_SECRET!,
  },
  responseStyle: 'data',
})

const response = await getV1Projects({
  client,
  headers: {
    'x-api-key': process.env.RESPONDENT_API_KEY!,
    'x-api-secret': process.env.RESPONDENT_API_SECRET!,
  },
  query: { status: 'DRAFT' },
})
```

提供者声明 `x-api-key` 财务报告和审定财务报表 `x-api-secret` 作为标题参数比较 而不是作为 OpenAPI 安全方案,所以生成的类型需要它们每次调用 即使客户已经设定了 三项行动—— `GET /v1/team-respondents`, (中文(简体) ), `GET /v1/team-respondents/profiles/{profileId}` 财务报告和审定财务报表 `PUT /v1/team-respondents/batch-invite` ——声明完全没有标题参数, 这几乎肯定是一种疏忽 而不是未经证实的 路由; SDK 向每个请求发送证书.

`responseStyle` 接受 `'data'` (只有反应机构)或 `'fields'` (单位:千美元)`{ data, request, response }`) (中文(简体) ). `RespondentSdk` 用途 `'fields'` 内部和 返回尸体.

所有请求/答复类型均导出自 `@coloop-ai/respondent-sdk`。 。 。 .

## 在提供者的类别中已知的怪事

在此录制, 这样它们不会被误认为 SDK 错误 :

-   `CreateB2cProjectDto.targetGenders` 编号 : `CreateB2bProjectDto.targetGenders` 是一个 单人 `Gender`,而所有其他人口目标 (单位:千美元)`targetEthnicities`, (中文(简体) ), `targetAgeGroups`, (中文(简体) ), `targetEducation`, (中文(简体) ), `targetHouseholdIncome`)是数组。 复数名有惑.
-   `QualifyScreenerResponseDto` 标记所有需要的四个字段,包括 拼写错误 `qualifiedOverriden` 编辑 `disqualifyReasons` 编号 : `message` 字段 只有在取消资格时才适用.
-   `ProfileLocation.zipcode` 财务报告和审定财务报表 `ScreenerResponseInvitation.message` 声明 `"type": "string"` 与 `"default": null`销售商剥离这些缺省 -- 而Zod生成器的代码是不编译的.
-   `ScreenerResponsesPaginatedDto` 不标记 `results` 需要时 其他发泡DTO是,所以 `response.results` 仅此而已.
-   `PUT /v1/projects/{projectId}/files/form-data` 声明 `Content-Type` 财务报告和审定财务报表 `Content-Length` 标题参数 `uploadFile` 身体财产可选择。 销售者删除两个标题参数并制作 需要文件 : 获取内容设置类型—— 带有多段边框, 任何人事先都不能知道--长度和上传-- 没有文件的电话从来就没有用过.
-   三次小组答复行动宣布不 `x-api-key` 编号 : `x-api-secret` 标题参数, 而其他操作都会这样做 .
-   文件宣布没有安全方案,没有错误计划,没有429 答复,以及 `servers` 区块列表只运行.

`scripts/normalize-openapi.ts` 保持所有三个正常化, 每个报告 当计谋被更新的时候 `pnpm run schema:validate` 如果失败 卖出的文件会从正常形式中消失.

## 脚本

-   `pnpm run schema:update` - 唯一触及网络的脚本:获取 提供者的 OpenAPI 文档, 正常化, 在内存中验证, 然后替换 `schemas/openapi.json` 解剖论.
-   `pnpm run generate` - 验证供应商的光谱并再生 `src/generated/**` 从它。 离线和可复制:始终相同的承诺 生成相同的输出.
-   `pnpm run generate:check` – 重新生成到抓取目录中并失败 与承诺不同 `src/generated`。 。 。 .
-   `pnpm run schema:validate` - 验证销售商的规格并检查它仍然是 已经恢复正常.
-   `pnpm run tc` - 排入排出量.
-   `pnpm run lint` – ESLint横跨仓库.
-   `pnpm run test` -维特.
-   `pnpm run test:dist` - 烟雾测试 已建成的捆绑 `node --test`。无 dev工具链,因此它也在节点18上运行. 建设先.
-   `pnpm run format` - 更漂亮的检查.
-   `pnpm run build` - 双重无害环境管理/CJS捆绑 `dist/` 通过tsdown.
-   `pnpm run prepare` -运行 `build`,因此软件包也从 基特语( git)`github:Genei-Ltd/respondent-sdk#<sha>`) (中文(简体) ).
-   `pnpm run check` - 计划验证, `generate:check`,类型检查,林特, 格式检查,构建,测试,然后是 Dist 烟雾测试。 这是运行 在出版前, 以及CI运行.

## 节点版本

已公布的一揽子目标节点18及以上`engines.node`) (中文(简体) ). B. 拟订《公约》 SDK需要更新运行时间—— `devEngines.runtime` 请求22.18+节点,即: 什麽 `@hey-api/openapi-ts`,ESLint和Vitest需要.

这一差距得到弥补,而不是假设: `.github/workflows/check.yml` 运行 `pnpm run check` 在节点22上,然后装入已建成的捆绑并锻炼它们—— 无害环境管理和CJS的切入点,每个子路径,请求,拒绝的再定向 超时和多段上传——在节点18,20,22和24上. 这些烟雾测试 (单位:千美元)`tests/dist-smoke.mjs`运行 `pnpm run test:dist`(a) 使用情况 `node --test` 编辑 软件包本身的运行时间依赖性, 仅此而已 .

## 许可证

根据麻省理工学院的许可获释. 见 `LICENSE` 详细情况.

## 常见问题

### @coloop-ai/respondent-sdk 是什么？

Type-security SDK for the President\.io Partner API,由提供者的OpenAPI spec生成\.

### 如何安装 @coloop-ai/respondent-sdk？

在项目目录运行 npm install @coloop-ai/respondent-sdk，或使用 pnpm add / yarn add / bun add。

### @coloop-ai/respondent-sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @coloop-ai/respondent-sdk 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @coloop-ai/respondent-sdk 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=18\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @coloop-ai/respondent-sdk 可以商用吗？

@coloop-ai/respondent-sdk 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @coloop-ai/respondent-sdk 的下载量有多少？

根据 npm 官方下载统计，@coloop-ai/respondent-sdk 在 2026/10/11 – 2026/11/09 这 30 天内被下载 19,453 次，日均约 648 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @coloop-ai/respondent-sdk 还在维护吗？

最新版本 0\.1\.0 发布于 2026/09/22（1 个月前）；至今共发布 1 个版本，其中过去 12 个月发布了 1 个；首次发布于 2026/09/22。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

