# Shixian (实现网) remote-work brief API — China's freelance/remote contract board in clean JSON. Every brief carries the client's own CNY budget, the project duration, the complete requirement text, hiring city, role category, applicant count and poster profile. Keyword search, paginated board, single-brief detail. No login, no token, no China exit required.

> Find remote-work briefs by keyword — the ones that mention 爬虫 (scraping), 数据采集 (data collection), API, 小程序 or any term you care about — each with the client's published CNY budget, duration, full requirement text and applicant count. 🔴 shixian publishes no job-search endpoint (its own `keyword=` parameter is accepted and ignored — verified: a nonsense keyword still returns the full front page), so this action scans the board pages and matches here; the response states exactly how many pages and briefs were scanned.
> ReefAPI engine `shixian` · 4 endpoints · clean JSON, no scraping or browsers to manage.

## How to call
- **Endpoint:** `POST https://api.reefapi.com/shixian/v1/<action>` with a JSON body.
- **Auth:** header `x-api-key: <YOUR_REEFAPI_KEY>` — create one free (1,000 credits, no card): https://reefapi.com/signup
- **Response (every call):** `{ ok: boolean, data: ..., meta: { record_count, credits, ... }, error: { code, message } }` — branch on `ok`. Failed or blocked calls are free.
- **One key + one shared credit pool** across every ReefAPI API. Per-call credits are listed on each endpoint below.
- **Use it from an AI agent (MCP):** connect `https://api.reefapi.com/mcp` (remote streamable-http, `Authorization: Bearer <key>`) and your assistant can call these actions directly.

## Endpoints

### POST /shixian/v1/search — 2 credits
Find remote-work briefs by keyword — the ones that mention 爬虫 (scraping), 数据采集 (data collection), API, 小程序 or any term you care about — each with the client's published CNY budget, duration, full requirement text and applicant count. 🔴 shixian publishes no job-search endpoint (its own `keyword=` parameter is accepted and ignored — verified: a nonsense keyword still returns the full front page), so this action scans the board pages and matches here; the response states exactly how many pages and briefs were scanned.

**Parameters:**
- `q` (string, required) — Search term, Chinese or English — e.g. '爬虫' (web scraping), '数据采集' (data collection), 'API', '小程序' (WeChat mini program), 'AI', 'Python'. 🔴 shixian has NO job-search endpoint (its `keyword=` parameter is accepted and ignored — measured: a nonsense keyword still returns the full front page), so this is matched HERE against the briefs on the pages scanned. Every response says so in `note`.
- `match` (enum, optional, default "any") — How strictly a multi-word query must match. Chinese queries have no spaces, so for them 'any' and 'all' behave identically and 'phrase' is the same as a plain substring search. [one of: any, all, phrase]
- `fields` (enum, optional, default "both") — Which part of the brief the query is matched against. The board ships the COMPLETE requirement text in the listing HTML, so 'description' searches the real body, not a teaser. [one of: both, title, description]
- `pages` (integer, optional, default 5) — How many board pages to scan before matching (1–25, default 5). Each page is 20 briefs and roughly 100 KB upstream, so the default reads ~100 briefs and the ceiling ~500. Scanning stops early once `limit` matches are found. Narrow with `category`/`city`/`type` before raising this.
- `limit` (integer, optional, default 20) — How many matching briefs to return (1–200, default 20).
- `city` (enum, optional, default "all") — Hiring city / province, as the board's own slug. The filter genuinely bites (measured: 'shanghai' shared 1 of 20 ids with the unfiltered board, 'beijing' 9 of 20). 🔴 An UNKNOWN slug is silently ignored upstream and would hand you the unfiltered board, so anything outside this list is rejected here instead. Cannot be combined with `type` — they are separate routes on the site. [one of: all, beijing, shanghai, shenzhen, hangzhou, guangzhou, chengdu, nanjing, xian, hubei, xiamen, shandong, suzhou, zhengzhou, fuzhou, changsha, chongqing, tianjin, ningbo, remote, qita]
- `category` (enum, optional, default "all") — Role category. Measured bite against the unfiltered board: 'ui' shared 0 of 20 ids, 'ios' 1 of 20, 'backend' 5 of 20. Unknown values are rejected here because the site ignores them silently. Cannot be combined with `type`. [one of: all, backend, frontend, full_stack, ios, android, ui, pm, operator, tester, others]
- `type` (enum, optional, default "all") — Contract shape. 🔴 On shixian this is a DIFFERENT route from the city/role board (/jobs/full-time vs /job/<city>/<role>), so it cannot be combined with `city` or `category` — passing both is rejected with INVALID_PARAM rather than silently dropping one. [one of: all, full-time, part-time]
- `sort` (enum, optional, default "default") — Board ordering / shortlist. 'nobody' is the interesting one for lead generation — briefs nobody has applied to yet: it shares only 1 of 20 ids with the default board, and `applicant_count` is null on 20/20 of its rows, which is the filter proving itself twice over. [one of: default, last, nobody, accept_remote]

**Returns:** jobs[]{id, title, url, job_type, description, budget{amount,currency,period,label,raw}, rate{amount,currency,per,raw}, duration{days,raw}, employer{id,url,avatar}, published_relative, published_approx_iso, published_approx_days_ago, applicant_count}, returned, matched, jobs_scanned, pages_scanned, query, match, note

**Example request body:**
```json
{
  "q": "爬虫",
  "pages": 6
}
```

### POST /shixian/v1/jobs — 1 credit
One page of the shixian board — 20 remote-work briefs with the complete requirement text and the client's published CNY budget on every row. Filter by city, role category, contract shape or the board's own shortlists (newest, zero-applicant, accepts-remote) and page through roughly 3,200 live briefs.

**Parameters:**
- `page` (integer, optional, default 1) — Which board page to return (1–300). Each page holds 20 briefs and consecutive pages share NO ids (measured: p1/p2, p2/p3 and p1/p3 overlap = 0). The unfiltered board bottoms out around page 164 (~3,200 live briefs) and a filtered one much sooner; past the end the site serves an empty page and this action returns NOT_FOUND rather than an empty success.
- `limit` (integer, optional, default 20) — How many briefs to return (1–20, default 20).
- `city` (enum, optional, default "all") — Hiring city / province, as the board's own slug. The filter genuinely bites (measured: 'shanghai' shared 1 of 20 ids with the unfiltered board, 'beijing' 9 of 20). 🔴 An UNKNOWN slug is silently ignored upstream and would hand you the unfiltered board, so anything outside this list is rejected here instead. Cannot be combined with `type` — they are separate routes on the site. [one of: all, beijing, shanghai, shenzhen, hangzhou, guangzhou, chengdu, nanjing, xian, hubei, xiamen, shandong, suzhou, zhengzhou, fuzhou, changsha, chongqing, tianjin, ningbo, remote, qita]
- `category` (enum, optional, default "all") — Role category. Measured bite against the unfiltered board: 'ui' shared 0 of 20 ids, 'ios' 1 of 20, 'backend' 5 of 20. Unknown values are rejected here because the site ignores them silently. Cannot be combined with `type`. [one of: all, backend, frontend, full_stack, ios, android, ui, pm, operator, tester, others]
- `type` (enum, optional, default "all") — Contract shape. 🔴 On shixian this is a DIFFERENT route from the city/role board (/jobs/full-time vs /job/<city>/<role>), so it cannot be combined with `city` or `category` — passing both is rejected with INVALID_PARAM rather than silently dropping one. [one of: all, full-time, part-time]
- `sort` (enum, optional, default "default") — Board ordering / shortlist. 'nobody' is the interesting one for lead generation — briefs nobody has applied to yet: it shares only 1 of 20 ids with the default board, and `applicant_count` is null on 20/20 of its rows, which is the filter proving itself twice over. [one of: default, last, nobody, accept_remote]

**Returns:** jobs[]{id, title, url, job_type, job_type_en, description, budget{amount,currency,period,label,raw}, rate{amount,currency,per,raw}, duration{days,raw}, employer{id,url,avatar}, published_relative, published_approx_iso, published_approx_days_ago, applicant_count}, returned, page, page_size, filters{city,category,type,sort}, source_url

### POST /shixian/v1/job — 1 credit
One brief in full: the complete requirement text straight out of the page's own schema.org block, the client's budget, the hiring city and role, the project type, workdays per month, how many people have applied, how many have viewed it, and the poster's public marketplace profile (display name, location, team size, funding stage, product and team blurbs) — the profile fields the board hides behind a login wall on the list page.

**Parameters:**
- `id` (string, required) — The brief to fetch: a numeric job id (1192790804) or a full URL (https://shixian.com/jobs/1192790804).

**Returns:** job{id, title, url, job_type, description, budget, project_type, workdays_per_month, work_mode, city{name,slug}, category{name,slug}, employer{id,name,url,avatar,location,team_size,funding_stage,product_intro,team_intro}, published_relative, published_approx_iso, applicant_count, view_count}

**Example request body:**
```json
{
  "id": "999999999"
}
```

### POST /shixian/v1/filters — 1 credit
The board's own filter taxonomy, read live off the site: every city slug, every role-category slug, the contract shapes and the sort modes, each with its Chinese label. Use it to discover the exact values `search` and `jobs` accept — the site silently ignores a slug it does not know, so a typo would otherwise return the unfiltered board.

**Parameters:** none

**Returns:** cities[]{slug,name_zh}, categories[]{slug,name_zh}, types[]{slug,label}, sorts[]{slug,label}, page_size, max_page, board_end_measured

## More
- Try it live, no code: https://reefapi.com/playground?engine=shixian
- Human docs page: https://reefapi.com/docs/shixian
- Every ReefAPI API in one file (for your AI): https://reefapi.com/llms-full.txt
