RedNote (Xiaohongshu) API Quickstart: Your First Request in Python
This tutorial gets your first RedNote (Xiaohongshu) data API call working in Python in about 5 minutes. No crawler to build, no proxies to maintain — the Rnote API handles the collection infrastructure, so all you do is send a standard HTTP request.
Step 1: Sign up and get an API key
Head to the sign-up page, create an account, and verify your email. You'll get free credits on verification, and you can view and manage your API key in the admin panel.
Step 2: Understand the request shape
Every endpoint is standard REST:
- Base URL:
https://rnote.dev/api/v2/crawler - Auth: send
X-API-Key: YOUR_API_KEYin the request header - Response: JSON
Step 3: Make your first request
Using "search notes" as an example:
import requests
API_BASE = "https://rnote.dev/api/v2/crawler"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}
resp = requests.get(
f"{API_BASE}/search/notes",
params={"keyword": "美食推荐", "page": 1},
headers=HEADERS,
)
print(resp.status_code)
print(resp.json())
Once that works, swap the endpoint for note/image (image-note details), user/info (creator info), and more — see the API docs for parameters.
Step 4: Handle errors and retries
Rnote API returns standard HTTP status codes. A robust helper:
import time
import requests
def call(path, params, retries=3):
url = f"https://rnote.dev/api/v2/crawler/{path}"
headers = {"X-API-Key": "YOUR_API_KEY"}
for i in range(retries):
r = requests.get(url, params=params, headers=headers, timeout=30)
if r.status_code == 200:
return r.json()
time.sleep(2 ** i) # exponential backoff
r.raise_for_status()
The good news: only successful (HTTP 2xx) requests are billed, so retries on failures cost nothing — add retry logic with confidence.
Pricing & credits
- Billed per request. No monthly fee, no minimum spend — pay for what you use.
- Only successful requests are charged.
- Free credits on sign-up, enough to validate the API and run small tests.
- See top-up options and current offers on the pricing page.
Common errors and how to read them
When something doesn't work, the status code has usually already told you why:
| Status | Meaning | What to do |
|---|---|---|
401 |
Key missing or invalid | Check the header name is X-API-Key (not Authorization), and that the key isn't disabled |
402 |
Insufficient balance | Top up. Retrying does nothing — the request never ran and nothing was charged |
429 |
Rate limited | Back off and retry; don't spin in place. See concurrency below |
404 |
Wrong path, or the resource is gone | Confirm the path in Swagger first; if it's right, the note or account no longer exists |
422 |
Invalid parameters | The response body names the offending field — usually a missing required param or a type mismatch |
One frequent mistake is putting parameters in the wrong place: the crawler endpoints are GET with query parameters (params=), while the Pugongying endpoints are POST with a JSON body (json=). Mixing them up gives you a 422.
Pagination
Search endpoints page with page, but from page two onward you must pass back the search_id and search_session_id from the first response — otherwise results can repeat:
import requests
API = "https://rnote.dev/api/v2/crawler"
H = {"X-API-Key": "YOUR_API_KEY"}
def search_all(keyword, max_page=5):
sid = ssid = ""
seen, out = set(), []
for page in range(1, max_page + 1):
r = requests.get(f"{API}/search/notes", headers=H, timeout=30, params={
"keyword": keyword, "page": page,
"search_id": sid, "search_session_id": ssid,
})
if r.status_code != 200:
break
data = r.json()
sid = data.get("search_id", sid) # take from the first response, carry it
ssid = data.get("search_session_id", ssid)
items = data.get("data", {}).get("items", []) or []
if not items:
break # nothing more — stop paying for pages
for it in items:
nid = it.get("id")
if nid and nid not in seen: # pages overlap; dedupe is mandatory
seen.add(nid); out.append(it)
return out
Two things worth remembering: stop on an empty page (paging further just costs money), and dedupe by ID (adjacent pages frequently overlap).
Persist the results
Write to disk instead of printing:
import csv, json, pathlib
notes = search_all("camping gear")
# Keep the raw JSON — you can always re-parse it when you want another field
pathlib.Path("notes.json").write_text(
json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8")
# A spreadsheet for non-technical colleagues
with open("notes.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
w.writerow(["note_id", "title", "likes"])
for n in notes:
w.writerow([n.get("id"), n.get("display_title", ""),
n.get("interact_info", {}).get("liked_count", 0)])
The utf-8-sig encoding is for Excel — a plain utf-8 CSV opens as mojibake for non-ASCII titles.
Next steps
- Pulling content in bulk? See keyword search and watermark-free image/video.
- Vetting creators? See influencer analytics.
- Full endpoints and fields: API docs.
Ready? Sign up free and send your first request now.