# Xiaohongshu API Test Findings (2026-06-23)

Live testing results from a Tencent Cloud Lighthouse server (Shanghai, IP 110.40.207.3).

## Test Environment

- Server: Tencent Cloud Lighthouse ap-shanghai
- Package: `xhs` v0.2.13 (ReaJason/xhs)
- Python: 3.11
- Account: **广东高职高考信息网** (gaozhigaokao, user_id: 65066df8000000001603b1d6) — NOT 广东春季高考信息网 (gdcjgk.net)

## Endpoint-by-Endpoint Results

| Endpoint | Method | Host | Sign | Status | Result |
|----------|:------:|------|:----:|:------:|--------|
| `/api/sns/web/v2/user/me` | GET | edith | No | 200 ✅ | Returns user info. Works as login health check. |
| `/api/sns/web/v1/user/selfinfo` | GET | edith | Both | 406 ❌ | `code: -1` — broken, use v2 |
| `/api/sns/web/v1/search/notes` | POST | edith | Yes | 200 ✅ | With correct sign + fresh cookies → 300011 for this account (search restriction). With stale session → -100. With wrong sign → 406/-1. |
| `/api/sns/web/v2/login/send_code` | GET | edith | Yes | 406 ❌ | `code: -1` — cold-start blocked. Requires existing valid web_session to send SMS. |
| `/api/sns/web/v1/login/qrcode/create` | POST | edith | Yes | 406 ❌ | `code: -1` — sign may be outdated |
| `/api/sns/web/v1/homefeed` | POST | edith | Yes | 406 ❌ | `code: -1` even with sign — v1 endpoints still not fully working |

## Key Observations

### 1. v2 endpoints work without sign headers
`/api/sns/web/v2/user/me` returns 200 with just `Cookie` and `User-Agent`. **v2 search does NOT exist** — only v1 has `/api/sns/web/v1/search/notes`.

### 2. Sign algorithm verified correct (2026-06-23)
The built-in `sign()` in `xhs.help` with constants `x1: "3.2.0"`, `x4: "2.3.1"` generates VALID signatures.

**Critical diagnostic chain**:
- 406 / `code:-1` → sign is WRONG or malformed
- `code:-100` → sign is CORRECT, session expired
- `code:300011` + fresh cookies → **sign is CORRECT, account has search restriction** (NOT a sign issue!)

### 3. 300011 can mean TWO things (corrected 2026-06-23)

| Scenario | Sign | Cookie | Response | Meaning |
|----------|:----:|:------:|:--------:|---------|
| Wrong sign | ❌ | Any | `406 / -1` | Malformed sign |
| Correct sign + stale cookie | ✅ | Expired | `-100` | Session expired |
| Correct sign + fresh cookie + restricted account | ✅ | Fresh | **300011** | Account search restriction |
| Correct sign + fresh cookie + unrestricted account | ✅ | Fresh | `0` + results | Success |

### 4. Account-specific search restriction
`广东高职高考信息网` (gaozhigaokao) returns 300011 on search API even with correct sign and fresh cookies. The user confirmed they CAN search via browser on xiaohongshu.com — meaning **API search permission ≠ browser search permission**. The account may need real-name verification or prior posting activity.

### 5. SMS login cannot cold-start
`send_code` (v2, `/api/sns/web/v2/login/send_code`) returns 406 when called with anonymous cookies. An existing valid `web_session` is required to initiate SMS login. This means fully automated cookie renewal from scratch is NOT possible — the user must maintain a browser session.

### 6. Docker/Playwright hostile environment
Neither `docker pull reajason/xhs-api` nor `playwright install chromium` complete on this server due to GFW. Solution: pure-Python sign server on user's local machine (see `references/sign_server.py`).

## Creator Backend (Publishing)

| Endpoint | Method | Host | Result |
|----------|:------:|------|--------|
| `/api/galaxy/creator/data/note_detail_new` | GET | creator | 401, `-100 登录已过期` |
| `/api/galaxy/creator/data/upload_permit` | POST | creator | 401, `-100 登录已过期` |

Creator backend uses **separate cookies** from `creator.xiaohongshu.com`. The `web_session` from `www.xiaohongshu.com` does NOT work.

## Cookie Format Reference

| Cookie | Format | Length | Example |
|--------|--------|:------:|---------|
| `a1` | Alphanumeric | ~68 | `19cffe1b5b2hokiz8xe47nlruk1bizhv8kzm7csnk50000272464` |
| `web_session` | Hex | ~40-47 | `040069b4722eb009e180bf780f384b559c5834` |
| `webId` | Hex | 32 | `527a6a761041a7af2c73e1e441805d34` |

## Error Code Reference

| Code | Meaning | Source |
|:----:|---------|--------|
| 0 | Success | All endpoints |
| -1 | Generic request error / invalid sign | Most v1 POST endpoints |
| -100 | Session expired (web_session stale) | `ErrorEnum.SESSION_EXPIRED` |
| -101 | No login info (need web_session) | Search endpoint |
| 300011 | Sign mismatch **OR** account search restriction. Diagnostic chain: 406/-1 = bad sign; -100 = good sign expired session; 300011 + fresh = account restriction (API-level, not browser-level) | Search endpoint |
| 300012 | IP blocked (web only, edith.xiaohongshu.com not affected) | `ErrorEnum.IP_BLOCK` |
| 300015 | Sign verification failed | `ErrorEnum.SIGN_FAULT` |
| -510001 | Note abnormal / content hidden | `ErrorEnum.NOTE_ABNORMAL` |

## Browser vs Python Sign Comparison (2026-06-23)

Captured from a working browser search request (user-provided curl):

| Field | Browser | Python `sign()` | Delta |
|-------|---------|-----------------|-------|
| `x-s` prefix | `XYS_` + 368 chars | No prefix, 44 chars | Browser has version prefix |
| `x-s` total | 372 chars | 48 chars | 7.75x larger |
| `x-s-common` | 2,203 bytes (base64 decoded) | ~259 bytes | 8.5x larger |
| `x-t` | 13-digit ms timestamp | Same format | Compatible |

The browser's x-s-common decodes to binary (not JSON), suggesting compression or binary encoding. The `XYS_` prefix in x-s indicates a signing algorithm version identifier not present in the Python implementation. Request body from browser also includes additional fields: `ext_flags`, `filters`, `geo`, `image_formats`, `message_id` — these change the signed data payload.

**Impact**: Even though the Python sign is accepted by the server (produces -100 / 300011 rather than 406), it produces **lower-privilege credentials** than the browser's native sign. This is the likely root cause of persistent 300011 on search for accounts where browser search works normally.
