# Offer岛 (OfferDao) 开发者文档

> https://offerdao.ai/docs

*OfferDao API documentation · 给 agent 与开发者的取数与发布入口*

**Offer岛**（OfferDao，`https://offerdao.ai`）是面向 AI 方向的求职 / 招聘平台。岗位、公司、面经与每日行业资讯尽量公开——**对人开放，也对工具开放**：整个已审核岗位库都可以让你自己的 agent 直接查询，也可以把一份 JD 结构化后发布进审核队列。

这一页是 Offer岛 API 的人类可读入口。机器可读的那份在 [/openapi.json](https://offerdao.ai/openapi.json)（OpenAPI 3.1），站点索引在 [/llms.txt](https://offerdao.ai/llms.txt)，自然语言用法说明在 [/skill.md](https://offerdao.ai/skill.md)。

## 机器可读入口

- [/openapi.json](https://offerdao.ai/openapi.json) — Offer岛 API 的 OpenAPI 3.1 规格（同一份也在 [/api/openapi.json](https://offerdao.ai/api/openapi.json) 与 [/.well-known/openapi.json](https://offerdao.ai/.well-known/openapi.json)）
- [/llms.txt](https://offerdao.ai/llms.txt) — 站点与开发者资源索引（[llmstxt.org](https://llmstxt.org) 格式）
- [/skill.md](https://offerdao.ai/skill.md) — agent skill 全文，可直接装进 Claude Code / Codex / Cursor Agent
- [/skill/references/posting-fields.md](https://offerdao.ai/skill/references/posting-fields.md) — 岗位对象与发布字段逐字段说明
- [/skill/references/company-object.md](https://offerdao.ai/skill/references/company-object.md) — 公司对象字段说明
- [/skill/references/news-format.md](https://offerdao.ai/skill/references/news-format.md) — 资讯 markdown 与 feed JSON 格式
- [/agent-instructions.md](https://offerdao.ai/agent-instructions.md) — 给外部 agent 的「何时该用本站 / 每类任务怎么调」说明
- [/.well-known/api-catalog](https://offerdao.ai/.well-known/api-catalog) — API Catalog（[RFC 9727](https://www.rfc-editor.org/rfc/rfc9727.html) linkset），只知道域名时从这里入手
- [/api/versions](https://offerdao.ai/api/versions) — 机器可读的版本与弃用策略
- [/agent.md](https://offerdao.ai/agent.md) — 站内 AI 助手的产品级 prompt
- [/sitemap.xml](https://offerdao.ai/sitemap.xml) — 全部可抓取页面

这一页本身也有 markdown 版：请求时带 `Accept: text/markdown`，或直接取 [/docs.md](https://offerdao.ai/docs.md)。首页、关于、条款、隐私、联系同理（`/index.md`、`/about.md` …）。

## Base URL

生产环境是 `https://offerdao.ai`。**不要把域名写死**——用环境变量解析一次再复用，将来换域名时只改这一处：

```
OFFERDAO_BASE="${OFFERDAO_BASE:-https://offerdao.ai}"
```

带版本的规范写法是 `$OFFERDAO_BASE/api/v1/…`；不带版本段的 `$OFFERDAO_BASE/api/…` 是**永久别名**，两者完全等价（见下面的[版本与弃用策略](#versioning)）。本页示例为了与既有集成一致，仍写不带版本的形式。

## 版本与弃用策略

agent 不该去接一个「随时可能变、且不打招呼」的接口。这里把承诺写死：

- **版本在 URL 路径里。**`/api/v1/…` 是当前 major。每个 `/api` 响应都带 `X-API-Version: v1`。
- **`/api/…` 是永久别名。**它始终指向当前 major，既有集成一行都不用改。新集成建议直接写 `/v1`。
- **破坏性变更只走新 major。**v1 的响应字段不会被删除，也不会改变含义；`/api/v2` 出现之前，v1 就是稳定的。
- **新增是兼容变更。**新字段、新可选参数、新端点随时可能出现——请忽略未知字段，不要用严格模式解析。
- **弃用有信号也有时间表。**被弃用的端点会在响应上带 `Deprecation`（[RFC 9745](https://www.rfc-editor.org/rfc/rfc9745.html)，值为 `@<unix 秒>`）与 `Sunset`（[RFC 8594](https://www.rfc-editor.org/rfc/rfc8594.html)，IMF-fixdate），并附 `Link; rel="deprecation"`；有替代端点时再附 `Link; rel="successor-version"`。**从打上 Deprecation 到真正下线，至少保留 180 天。**
- **目前没有任何公开端点处于弃用状态。**

机器可读的同一份声明在 [/api/versions](https://offerdao.ai/api/versions)（`/api` 与 `/api/v1` 返回同一份），OpenAPI 规格里对应 `info.x-api-versioning`。

```
curl -s "$OFFERDAO_BASE/api/versions"

# 两种写法等价
curl -sI "$OFFERDAO_BASE/api/health"
curl -sI "$OFFERDAO_BASE/api/v1/health"   # → X-API-Version: v1
```

## 鉴权

岗位**搜索**、**精选**与**发布**需要 API key；公司目录、面经攻略、行业资讯、健康检查**不需要**。两种写法等价：

```
curl -sG "$OFFERDAO_BASE/api/postings/search" \
  -H "Authorization: Bearer $OFFERDAO_API_KEY" \
  --data-urlencode "q=infra" --data-urlencode "limit=5"

# 或者
curl -sG "$OFFERDAO_BASE/api/postings/search" -H "X-API-Key: $OFFERDAO_API_KEY"
```

### 拿 key 的两条路

- **个人 key**（推荐，查询与发布都可用，不限量）：登录后在「我的设置 → API keys」自助创建。用某个个人 key 发布的岗位会归属到该 key 的拥有者，出现在 TA 的「我的发布」里。
- **开放测试 key**（免注册，仅查询）：`GET /api/agent/test-key` 领取。共享、按日限量，调发布接口会 `403 scope_forbidden`。

## 端点总览

下表由 [/openapi.json](https://offerdao.ai/openapi.json) 直接生成，字段与参数细节以规格为准。

| 方法 | 路径 | 用途 | 鉴权 |
| --- | --- | --- | --- |
| `GET` | `/api/v1/postings/search` | 搜索已审核岗位 | 需要 API key |
| `GET` | `/api/v1/postings/featured` | 岛上精选（随机抽样） | 需要 API key |
| `POST` | `/api/v1/postings/agent` | 发布岗位（进审核队列） | 需要 API key |
| `GET` | `/api/v1/companies` | AI 公司目录（无需 API key） | 公开 |
| `GET` | `/api/v1/guides` | 面经攻略外链（无需 API key） | 公开 |
| `GET` | `/api/v1/news/feed` | 每日行业资讯 JSON（无需 API key） | 公开 |
| `GET` | `/news/md` | 资讯 markdown 索引（无需 API key） | 公开 |
| `GET` | `/news/md/{name}` | 某一天的资讯原文（无需 API key） | 公开 |
| `GET` | `/api/v1/agent/test-key` | 免注册领取开放测试 key（仅查询） | 公开 |
| `GET` | `/api/v1/agent-config` | agent 接入配置（无需 API key） | 公开 |
| `GET` | `/api/v1/health` | 健康检查（无需 API key） | 公开 |
| `GET` | `/skill.md` | agent skill 全文（自然语言用法说明） | 公开 |
| `GET` | `/agent.md` | 站内 AI 助手的产品级 prompt | 公开 |
| `GET` | `/llms.txt` | llms.txt 站点索引（llmstxt.org 格式） | 公开 |
| `GET` | `/openapi.json` | 这份 OpenAPI 规格本身 | 公开 |
| `GET` | `/docs` | 开发者文档（HTML / markdown 按 Accept 协商） | 公开 |
| `GET` | `/api/versions` | 版本与弃用策略（机器可读） | 公开 |
| `GET` | `/.well-known/api-catalog` | API Catalog（RFC 9727 linkset） | 公开 |
| `GET` | `/agent-instructions.md` | 给外部 agent 的「何时使用 / 怎么调用」说明 | 公开 |

## 快速上手

### 搜索岗位

```
# 北京的 infra 岗位，只要正式，最近 30 天
curl -sG "$OFFERDAO_BASE/api/postings/search" \
  -H "Authorization: Bearer $OFFERDAO_API_KEY" \
  --data-urlencode "q=infra" \
  --data-urlencode "location=北京" \
  --data-urlencode "employment_type=正式" \
  --data-urlencode "published_within_days=30"
```

返回 `{ items, total, limit, offset }`。`total` 是该筛选条件的总条数，不受分页影响。岗位详情页短链是 `$OFFERDAO_BASE/j/<posting_id>`，可以直接给用户点开。

### 发布岗位

必填五组：公司、岗位名称（多岗位改用 `positions`）、工作类型、工作地点、至少一个联系方式。**缺什么就问用户，绝不要编造**——联系方式尤其不能凭空捏造。发布总是进审核队列，审核通过前 `/j/<posting_id>` 是 404。

```
curl -s -X POST "$OFFERDAO_BASE/api/postings/agent" \
  -H "Authorization: Bearer $OFFERDAO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organization": "某 AI 公司",
    "role": "多模态算法工程师",
    "employment_type": ["正式"],
    "location": "北京 / 上海",
    "contact_email": "hr@example.com",
    "role_tags": ["多模态"]
  }'
```

### 免 key 的公开数据

```
curl -s "$OFFERDAO_BASE/api/companies"      # AI 公司目录
curl -s "$OFFERDAO_BASE/api/guides"         # 面经攻略外链
curl -s "$OFFERDAO_BASE/news/md"            # 每日资讯的 markdown 索引
curl -s "$OFFERDAO_BASE/news/md/20260610.md" # 某一天的资讯原文
```

## 限流

**每个 `/api` 响应都带标准限流头**（[draft-ietf-httpapi-ratelimit-headers](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)），所以不用先撞一次墙才知道额度：

```
RateLimit-Policy: "api";q=1200;w=60, "search";q=60;w=60
RateLimit: "api";r=1199;t=57, "search";r=59;t=57
Retry-After: 57          # 只在 429 上出现
```

- `RateLimit-Policy` — 生效的配额策略，跨响应稳定。`q` 是配额，`w` 是窗口秒数。
- `RateLimit` — 实时余量。`r` 是剩余可用量，`t` 是这批余量的剩余秒数。**`r` 接近 0 就该主动降速**，别等 429。
- 同时还发老写法 `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` 与 `X-RateLimit-*`，取最紧那条策略；三套值同源，随便读哪套都行。
- `Retry-After`（秒）只在 `429` 上出现，且不会早于 `t`——可以直接 sleep 这个值。
- 来自缓存的响应（`Age > 0`）里的 `RateLimit` 可能已经过期，按规范应当忽略。

一次请求可能同时受多条策略约束（兜底闸 + 端点闸 + 开放 key 的每日额度），头里就会同时出现多个成员。当前策略：

| 策略名 | 配额 | 作用范围 | 说明 |
| --- | --- | --- | --- |
| `api` | 1200 次 / 60 秒 | 全部 /api/* 请求 | 兜底闸，防跑飞的循环；正常调用不会碰到 |
| `search` | 60 次 / 60 秒 | 岗位搜索 / 精选 / 快照 / mention-index、公司目录、面经攻略、资讯 feed | 读接口的主闸 |
| `manual` | 10 次 / 1 小时 | 网页表单发布岗位 | 写接口从严 |
| `agent` | 10 次 / 1 小时 | POST /api/v1/postings/agent（agent 发布岗位） | 与网页表单同额度但独立计数 |
| `daemon` | 120 次 / 60 秒 | 本机 daemon 注册 / 心跳 | 单台 daemon 约 1 次/分；额度按同一 NAT 后面多台留 |
| `compile` | 120 次 / 60 秒 | 简历 LaTeX 编译 | 前端有 600ms 防抖，只在停手时触发 |
| `test-key` | 20 次 / 60 秒 | GET /api/v1/agent/test-key（免注册领开放 key） | 领一次存起来复用，不要每次请求前都领 |
| `activity` | 60 次 / 60 秒 | 访问日历心跳 | 正常一个账号一天一次 |
| `contacts` | 60 次 / 60 秒 | 岗位联系方式明文（需登录） | 挡的是拿一个账号遍历 posting_id |
| `open-key-daily-search` | 随 key 而定 / 24 小时 | 开放测试 key 的每日查询次数 | 只有开放测试 key 有；个人 key 不限量 |
| `open-key-daily-publish` | 随 key 而定 / 24 小时 | 开放测试 key 的每日发布次数 | 只有开放测试 key 有；个人 key 不限量 |

频率闸按**客户端 IP** 分区；开放测试 key 的每日额度按 **key** 分区、北京时间零点重置。诚实的边界：线上每个 Serverless 实例各记一份计数，所以实际放行量可能比广告值宽松（只会更宽，不会更严）。机器可读的同一份声明在 [/openapi.json](https://offerdao.ai/openapi.json) 的 `info.x-rate-limits`。

## 错误码

- `429 rate_limit_exceeded` — 按 IP 的频率闸。按 `Retry-After` 头（正文 `retry_after` 同值）退避后重试。
- `429 rate_limited` — 开放测试 key 当日额度用尽，`Retry-After` 指向次日重置。等它没意义，重新领 key 也没用（发的是同一把），改用个人 key。
- `401 missing_api_key` / `401 invalid_api_key` — 别拿同一把 key 重试；个人 key 失效请在「我的设置 → API keys」重建。
- `403 scope_forbidden` — key 有效但没这个权限（典型是用测试 key 调发布）。
- `400` — 校验失败。`error` 已是面向用户的中文文案，可直接转述。

## 内容协商与 404

页面类 URL 支持 `Accept` 协商（[acceptmarkdown.com](https://acceptmarkdown.com) 口径）：浏览器拿 HTML，带 `Accept: text/markdown` 拿 markdown，响应带 `Vary: Accept, Accept-Encoding`；一个表示都满足不了时回 `406` 并在正文列出可用类型。

```
curl -sI -H "Accept: text/markdown" "$OFFERDAO_BASE/docs"
```

首页也一样：`$OFFERDAO_BASE/` 带 `Accept: text/markdown` **就地**返回站点总览 markdown（同一个 URL，不再重定向）。HTML 响应带 `Link: </index.md>; rel="alternate"; type="text/markdown"`，不做协商的客户端也能发现同一份内容在 [/index.md](https://offerdao.ai/index.md)。

不存在的路径回**真正的 404**（不是 200 + 首页外壳），markdown 正文里带着 llms.txt / 开发者文档 / OpenAPI / sitemap 的入口——探测失败时照着它重新定位，不要顺着猜路径。

## agent 接入

把 Offer岛 装进你自己的 agent：`npx skills add offerdaoai/skills`，或直接把 [/skill.md](https://offerdao.ai/skill.md) 交给它。站内 AI 助手（[/agent](https://offerdao.ai/agent)）跑在**用户自己的电脑**上——本机 daemon + 用户自己的 CLI（Claude Code / Codex / Cursor Agent / Kimi CLI），简历原件留在本地。安装脚本见 [/install.sh](https://offerdao.ai/install.sh)，接入配置见 [/api/agent-config](https://offerdao.ai/api/agent-config)。

## 使用边界

数据可以查、可以推荐给用户，但请遵守[服务条款](https://offerdao.ai/terms)：不要批量抓取转售，不要把联系方式拿去做无关营销。有商务合作或接入问题，[联系我们](https://offerdao.ai/contact)。
