确认当前身份和成员 ID 只读
POST /api/v1/hiring/tools
{
"operation": "read",
"input": {
"resource": "context",
"input": {}
}
}这是完整参考手册,不是默认上下文。Agent 先 GET /api/v1/agent/guide,直接返回精简文本;/agent/SKILL.md 和 guide.md 保持兼容。仅在需要时读 references 下的相关文档,不必再读整个 manifest 或本手册。阅读不需要登录,也不会触发任何业务操作。
本服务是 HTTP JSON 工具适配器,不是标准 MCP transport。不要尝试 /mcp、直接读数据库、调用旧平台端口或自动点击管理页面。若宿主支持 function calling,可按实时 schema 注册本地函数,函数内部转发到这里。
Agent 不需要应用密钥或平台自建授权码。用户在飞书页面登录/扫码并确认后即完成鉴权,不存在第二次平台批准。用户必须是本平台所属飞书租户中已启用的成员。
申请或复用前,先读本次任务的业务参考页,把实际要做的动作对应到下表并取所需权限的并集。只申请本次任务所需,不照抄示例、不默认申请全部权限。写权限不包含读取权限;能上传附件也不代表能读取附件。
| 本次任务 | 需要申请的业务 scope |
|---|---|
| 搜索候选人、看摘要和版本 | candidates:read |
| 读取电话、邮箱,或用电话/邮箱查重 | candidates:read + sensitive:contacts |
| 读取完整履历、列出/预览/下载简历附件 | candidates:read + sensitive:documents |
| 读取候选人跟进备注、薪资和离职原因 | candidates:read + sensitive:notes |
| 先查版本,再新增/修改候选人、上传附件或追加备注 | candidates:read + candidates:write |
| 查询并维护岗位 | jobs:read + jobs:write |
| 查询并维护客户公司 | clients:read + clients:write |
| 查询公司并编辑组织地图;读单个节点备注另加 sensitive:notes | companies:read + companies:write |
| 查询并推进招聘流程、安排 | hiring:read + hiring:write |
| 查询自动搜索任务和精简统计 | recruiting:read |
| 复盘自动搜索策略、进度、工具摘要、错误和已提交判断 | recruiting:read + sensitive:notes |
组合任务要合并权限:例如“查候选人、读完整简历并整理电话邮箱”需要 candidates:read + sensitive:documents + sensitive:contacts,不需要写权限。流程历史/备注还需 sensitive:notes;hiring.jobState/jobAssign 还需 jobs:write,candidateAssign/candidatePolicy 还需 candidates:write。查岗位影响需 hiring:read;详情和版本读取需相应资源的 :read。
复用已有连接时,核对有效期和保存的 business_scope 是否覆盖本次所需权限。business_scope 从登录/续期响应保存,是平台权限;飞书返回的 scope(例如 offline_access)不能代替它,也不要从 token 外形推断。有效且权限足够就直接复用,不需要重新扫码。
缺项时重新 POST /api/v1/agent-auth/device,scope 填本次任务所需的完整集合(不是只填新增项),让本次用户通过新 verification_uri_complete 完成飞书验证。刷新旧 token 不会增加权限,也不要沿用旧 device_code。取得新 token 后再检查 business_scope;仍缺项则停止并说明,不反复登录。用户拒绝授权时停止,不自行扩大权限。
第一步:发起连接。下例仅用于四类普通只读查询,不含完整简历、联系方式、备注或任何写入;按实际任务替换 scope。agent_name 填可识别的名称,不代表可信身份。
curl 'https://dev.planahr.top/api/v1/agent-auth/device' \
-H 'Content-Type: application/json' \
--data '{
"agent_name": "我的招聘助手",
"scope": "candidates:read jobs:read clients:read hiring:read"
}'如果本次要查候选人、读完整履历/附件并整理联系方式,使用这个只读权限组合,不要额外申请写权限:
curl 'https://dev.planahr.top/api/v1/agent-auth/device' \
-H 'Content-Type: application/json' \
--data '{
"agent_name": "我的候选人资料助手",
"scope": "candidates:read sensitive:documents sensitive:contacts"
}'| 返回字段 | 用法 |
|---|---|
| verification_uri_complete | 立即把此飞书完整验证链接展示给本次用户,由用户打开登录/扫码并完成最终同意。 |
| verification_uri / user_code | 备用飞书验证地址和飞书验证码;不是平台生成的授权码。 |
| device_code | 保存在 Agent 端,仅用于 token 轮询;不得展示或泄露。 |
| interval / expires_in | 轮询间隔(秒)和此次连接的有效期(秒);以实际返回值为准。 |
| flow_id / request_id / expires_at | 可选排查编号和本轮到期时间;不是授权凭证,也不能用于查询他人授权。 |
| business_scope | 本次申请的平台业务权限上限,不会扩大用户本身权限。 |
第二步:展示链接后即可按 interval 在后台串行轮询,不必等用户回复“好了”才开始。把本轮链接、device_code、可选 flow_id 和到期时间成组保存;同一连接不要并行创建多轮或混用旧链接与新 device_code。用户须在飞书完成最终同意,口头说已扫码不等于接口已返回凭证。把下例 <DEVICE_CODE> 替换为本轮保存的值,不要拼到 URL 中。
curl 'https://dev.planahr.top/api/v1/agent-auth/token' \
-H 'Content-Type: application/json' \
--data '{
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "<DEVICE_CODE>"
}'| OAuth error | 处理 |
|---|---|
| authorization_pending | 飞书仍在等待本轮最终授权;等待至少 interval 秒继续,不是连接失败。用户称已同意但持续 pending 时,核对是否使用当前这一轮完整链接。 |
| slow_down | 在当前轮询间隔上增加至少 5 秒,并遵守 retry_after / Retry-After。 |
| access_denied / expired_token / invalid_grant | 停止本次流程,告知用户;如仍需要连接,重新发起登录。 |
| temporarily_unavailable / 429 | 临时故障不等于整轮过期;在本轮有效期内有限退避并遵守 retry_after / Retry-After,仍使用同一 device_code。 |
| server_error / contact_admin | 不要反复换链接或扩大权限。带安全排查编号联系管理员,不能粘贴 Token 或完整链接。 |
若响应提供 next_action、reason、stage、flow_id、request_id,优先按这些安全诊断字段处理。next_action=poll 表示等待本轮授权,backoff 表示退避,restart_authorization 表示本轮停止并重新登录,stop / contact_admin 表示停止并说明。stage 可定位 dns/tcp/tls/headers/body/decode/validate/identity/platform;单次飞书请求超时与整轮 expires_in 到期是两件事。HTTP 400 也可能是正常等待授权,不可仅凭状态码判定失败。
成功后保存 access_token、可选的 refresh_token 及它们的有效期。token_type 为 Bearer;scope 是飞书返回的 scope,business_scope 才是平台业务权限上限。可选 client_id=planahr-agent 只是本服务的公开客户端配置名,不是飞书 app_id,也不是密钥。
续期同样由飞书签发凭证。使用最近一次返回的 refresh_token;刷新需要串行,保存返回的最新 token/有效期(飞书可能轮换,也可能保留 refresh_token)。没有可用续期凭证时重新登录。
把下例 <REFRESH_TOKEN> 替换为最近保存的续期凭证。调用业务接口时,下例环境变量 ACCESS_TOKEN 则替换为当前 access_token。
curl 'https://dev.planahr.top/api/v1/agent-auth/token' \
-H 'Content-Type: application/json' \
--data '{
"grant_type": "refresh_token",
"refresh_token": "<REFRESH_TOKEN>"
}'用户可在平台的“Agent 授权”页面查看和撤销自己的连接。平台撤销不等于撤销整个飞书应用授权,也不会让网页登录退出。
| 业务模块 | GET / POST 地址 | 能力 |
|---|---|---|
| 候选人库 | /api/v1/candidate-library/tools | 支持完整履历、多附件、业务字段和标签建议。普通详情不含联系方式、薪资、备注或简历原文;敏感信息需单独 scope。非负责人修改走飞书审批。与招聘筛选数据库独立。 |
| 岗位 | /api/v1/job-library/tools | 查询及维护岗位,支持多附件。暂停/关闭请走 hiring.jobState,无 close 工具。 |
| 客户公司 | /api/v1/client-library/tools | 查询和维护客户公司及多附件。新岗位先关联真实客户。 |
| 招聘流程 | /api/v1/hiring/tools | 管理招聘流程,以及飞书推荐、终止和 Offer 审批。读取资源含context、flows、detail、dashboard、jobImpact、personalNote、progress。 |
| 公司与人脉 | /api/v1/company-library/tools | 行业公司与客户共用公司ID;共同编辑组织地图,保留任职、版本和可恢复历史。 |
| 搜索任务复盘 | /api/v1/recruiting/tools | 只读查看可见渠道的自动搜索记录,不启动/停止/重跑或发送。摘要不含原文,解释需 sensitive:notes,证据摘录另需 sensitive:documents;不是模型内部思维链。 |
同一地址:GET ?operation=search 只返回该操作的 {tools:[{name,inputSchema,description?,annotations?}]};未知操作返回 404。GET ?view=summary 仅返回工具名称和描述,不含 schema,用于不知道操作名时发现能力。裸 GET 仍兼容返回整个模块,不建议默认使用。POST 执行 {operation,input}。同一会话复用已读 schema,验证错误或接口变化时按需刷新。未知字段会被拒绝;结果直接为 JSON,没有 JSON-RPC 包装。
curl 'https://dev.planahr.top/api/v1/candidate-library/tools?operation=search' \
-H "Authorization: Bearer $ACCESS_TOKEN"例如工具名 candidate_library_search 对应 POST operation="search",不是完整工具名。input 必须符合该工具的 inputSchema。下面是可以直接改造的只读调用:
curl 'https://dev.planahr.top/api/v1/candidate-library/tools' \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
--data '{
"operation": "search",
"input": {
"query": "芯片",
"page": 1,
"pageSize": 20
}
}'读取自己的身份/组织和有效成员 ID:调用 hiring 的 read,resource="context"。返回 members、membershipId、displayName、organizationId、角色及 visibleMemberIds;成员目录不代表这些成员的业务数据全部可读或可写。
{
"operation": "read",
"input": {
"resource": "context",
"input": {}
}
}| Scope | 含义 |
|---|---|
| candidates:read / candidates:write | 候选人库读取 / 写入(仍受用户实时权限约束) |
| jobs:read / jobs:write | 岗位读取 / 写入(仍受用户实时权限约束) |
| clients:read / clients:write | 客户公司读取 / 写入(仍受用户实时权限约束) |
| hiring:read / hiring:write | 招聘流程读取 / 写入(仍受用户实时权限约束) |
| companies:read / companies:write | 公司与人脉读取 / 写入(仍受用户实时权限约束) |
| recruiting:read | 搜索任务复盘只读(仍受用户实时权限约束) |
跨资源操作同时需要两项写权限:hiring.jobState / jobAssign 需要 hiring:write + jobs:write;candidateAssign / candidatePolicy 需要 hiring:write + candidates:write。不是拿到 hiring:write 就可以修改所有资源。
创建招聘流程之前必须已有可读取的简历原件:已迁移原始简历,或 kind=resume 的上传附件。只有正文、联系方式、链接或其他附件不算。缺件会返回 409 RESUME_ATTACHMENT_REQUIRED;补传后再操作,不伪造附件来绕过校验。
以下均为请求体,POST 到标注的模块地址。ID、版本、内容都是虚构示例,不是待操作的真实记录;先从读取结果中选择真实目标。写操作的 requestId 应由客户端为每个逻辑动作生成唯一值,不能照抄示例。
POST /api/v1/hiring/tools
{
"operation": "read",
"input": {
"resource": "context",
"input": {}
}
}POST /api/v1/candidate-library/tools
{
"operation": "search",
"input": {
"query": "芯片",
"page": 1,
"pageSize": 20
}
}POST /api/v1/candidate-library/tools
{
"operation": "detail",
"input": {
"id": "1"
}
}POST /api/v1/candidate-library/tools
{
"operation": "duplicates",
"input": {
"name": "示例候选人"
}
}POST /api/v1/job-library/tools
{
"operation": "search",
"input": {
"status": "open",
"page": 1,
"pageSize": 20
}
}POST /api/v1/client-library/tools
{
"operation": "search",
"input": {
"query": "示例公司",
"page": 1,
"pageSize": 20
}
}POST /api/v1/hiring/tools
{
"operation": "read",
"input": {
"resource": "flows",
"input": {}
}
}POST /api/v1/hiring/tools
{
"operation": "read",
"input": {
"resource": "dashboard",
"input": {}
}
}POST /api/v1/hiring/tools
{
"operation": "read",
"input": {
"resource": "jobImpact",
"input": {
"jobId": 1
}
}
}POST /api/v1/candidate-library/tools
{
"operation": "note",
"input": {
"id": "1",
"content": "示例:候选人希望先了解岗位职责。",
"requestId": "example-note-001"
}
}POST /api/v1/candidate-library/tools
{
"operation": "update",
"input": {
"id": "1",
"version": 0,
"data": {
"city": "上海"
},
"requestId": "example-update-001"
}
}POST /api/v1/candidate-library/tools
{
"operation": "update",
"input": {
"id": "1",
"version": 0,
"data": {
"city": "上海"
},
"reason": "核实候选人目前常驻上海",
"requestId": "example-approval-001"
}
}POST /api/v1/candidate-library/tools
{
"operation": "changeStatus",
"input": {
"id": "1"
}
}POST /api/v1/candidate-library/tools
{
"operation": "changeHistory",
"input": {
"id": "1",
"page": 1,
"pageSize": 10
}
}POST /api/v1/hiring/tools
{
"operation": "create",
"input": {
"candidateId": "1",
"jobId": 1,
"requestId": "example-flow-001"
}
}开放上述业务工具及只读搜索任务复盘、候选人履历与二进制附件接口。不开放启动/停止/重跑脉脉搜索或发送、Connector 浏览器与 Cookie、系统管理或成员迁移;不要把凭证发送到其他站点。
可见范围不等于编辑范围;管理员也看不到其他作者的私密工作台备忘和私密流程备注。拿到空列表或受限字段时如实报告,不补造数据。
业务错误格式为 {error:{code,message,details?}};飞书登录/续期协议错误格式为 {error:"...",error_description:"..."}。两者不要混淆。
| 状态 / 类型 | 建议处理 |
|---|---|
| 409 / RESUME_ATTACHMENT_REQUIRED | 候选人缺少可读取的简历原件。先在候选人详情补传类型为 resume 的真实简历附件,或请有附件编辑权限的同事补传,再重试;刷新、重登或换权限不能绕过。正文、联系方式、链接、screenshot/other 和孤立的 PDF 预览不算。 |
| 400 / VALIDATION_ERROR | 读取 error.details 的字段路径,重新查看实时 inputSchema,修正参数;不要原样重试。 |
| 401 / UNAUTHORIZED | 凭证可能过期、撤销或飞书身份失效。有有效 refresh_token 则尝试续期一次,否则重新发起飞书登录。 |
| 403 / FORBIDDEN | 先读 error.reason。agent_scope_missing 表示此连接缺少授权,requiredScopes 列出缺项:按本次任务汇总完整权限集合,重新 POST /api/v1/agent-auth/device 并让用户完成飞书验证;不要只申请缺项而漏掉仍需的读取/写入权限,refresh 不扩权。business_permission 表示用户业务权限不足,应停止并说明,换 token 不会获得权限。agent_endpoint_unsupported 表示用错接口,应改用 /tools,不要反复授权。 |
| 404 / NOT_FOUND | 可能是不存在、不可见的记录或未开放接口。不要猜 ID 或改用管理接口绕过。 |
| 409 / 冲突 | 版本、重复候选人、幂等键或岗位影响预览可能冲突。先读最新数据和错误码再决策,不要覆盖他人更新。 |
| 429 / 限流 | 遵守 Retry-After(如果提供);否则指数退避,限制重试次数,不要并发撞接口。 |
| 5xx / 暂时不可用 | 读取可有限退避重试;写入仅以相同 requestId 和相同内容重试。不要把服务异常当作登录失效。 |
超时并不等于未写入。没有确认结果时保留原 requestId,以同样请求重试;不要生成新键重复添加备注、流程或事件。若最终无法恢复,报告具体接口、操作、状态码/错误码和发生时间,不记录 token 或完整隐私文本。