- Platform: XenForo 2.3
- URL: https://voz.vn
- API Type: JSON-wrapped HTML endpoints (no true REST API)
- Authentication: None required for public content
- Last Updated: 2026-06-20
GET https://voz.vn/t/{thread-slug}.{thread-id}.json
GET https://voz.vn/t/{thread-slug}.{thread-id}/page-{N}.json
| Parameter | Type | Required | Description |
|---|---|---|---|
thread-slug |
string | Yes | URL-friendly thread title |
thread-id |
integer | Yes | Numeric thread ID |
N |
integer | No | Page number (default: 1) |
curl "https://voz.vn/t/event-box-cntt-2023-chia-se-kinh-nghiem-phong-van.694369.json"curl "https://voz.vn/t/event-box-cntt-2023-chia-se-kinh-nghiem-phong-van.694369/page-120.json"{
"status": "ok",
"html": {
"content": "<div class=\"p-body-main\">...(rendered HTML of posts)...</div>",
"title": "Thread Title",
"h1": "Thread Title",
"css": "...",
"js": "..."
},
"visitor": {
"conversations_unread": "0",
"alerts_unviewed": "0",
"total_unread": "0"
}
}| Field | Type | Description |
|---|---|---|
status |
string | Always "ok" on success |
html.content |
string | Full rendered HTML of the page posts (~200KB) |
html.title |
string | Thread title |
html.h1 |
string | Thread heading |
html.css |
string | CSS styles for rendering |
html.js |
string | JavaScript for the page |
visitor.conversations_unread |
string | Unread conversation count |
visitor.alerts_unviewed |
string | Unviewed alert count |
visitor.total_unread |
string | Total unread items |
The html.content field contains rendered post HTML. Key elements:
<div class="message message--post" data-content="post-42574302">
<div class="message-inner">
<div class="message-attribution-main">
<a href="/u/username" class="username" data-user-id="12345">Username</a>
<time datetime="2026-06-20T10:30:00+0700" data-time="1750000000">Jun 20, 2026</time>
</div>
<div class="message-body">
<div class="message-content">
<!-- Post content here -->
</div>
</div>
</div>
</div>GET https://voz.vn/f/{forum-slug}.{forum-id}.json
GET https://voz.vn/f/{forum-slug}.{forum-id}/page-{N}.json
curl "https://voz.vn/f/dien-dan-cong-nghe.838.json"Same structure as Thread JSON - returns HTML wrapped in JSON.
{
"status": "ok",
"html": {
"content": "<div class=\"structItem--thread\">...(thread list HTML)...</div>",
"title": "Forum Name",
"h1": "Forum Name",
"css": "...",
"js": "..."
},
"visitor": {
"conversations_unread": "0",
"alerts_unviewed": "0",
"total_unread": "0"
}
}Forum page contains thread list items:
<div class="structItem structItem--thread">
<div class="structItem-title">
<a href="/t/thread-slug.123456/" data-tp-primary="on">Thread Title</a>
</div>
<div class="structItem-cell--meta">
<dd>1.2K</dd> <!-- Replies -->
<dd>45.6K</dd> <!-- Views -->
</div>
<div class="structItem-cell--latest">
<a href="/t/thread-slug.123456/page-100#post-42574302">
<time datetime="2026-06-20T10:30:00+0700">Jun 20, 2026</time>
</a>
</div>
</div>/page-{N}
| URL | Description |
|---|---|
/t/example.123.json |
Page 1 (default) |
/t/example.123/page-1.json |
Page 1 (explicit) |
/t/example.123/page-2.json |
Page 2 |
/t/example.123/page-120.json |
Page 120 (last page for tested thread) |
- Page numbers start from 1
- Page 1 can be accessed with or without
/page-1 - Returns 404 for pages beyond maximum
- Each page contains ~20 posts by default
POST https://voz.vn/search/search.json
| Parameter | Type | Required | Description |
|---|---|---|---|
keywords |
string | Yes | Search query |
type |
string | No | Content type: post, user, etc. (default: post) |
order |
string | No | Sort order: relevance, date, likes (default: relevance) |
users |
string | No | Filter by username |
curl -X POST "https://voz.vn/search/search.json" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "keywords=token&type=post&order=relevance"{
"status": "ok",
"redirect": "https://voz.vn/search/5076122/?q=token&o=relevance"
}| Field | Type | Description |
|---|---|---|
status |
string | Always "ok" |
redirect |
string | URL to the search results page (HTML only) |
- No JSON results: The search endpoint only returns a redirect URL
- Results are HTML-only: The redirected search results page does NOT support
.jsonsuffix - Session required: Search may require valid session cookies for repeated requests
- Rate limiting: Aggressive requests may trigger CAPTCHA or IP blocks
GET https://voz.vn/search/{search-id}/?q={query}&o={order}
curl "https://voz.vn/search/5076122/?q=token&o=relevance"- Returns full HTML page with search results
- Does NOT support
.jsonsuffix - Contains thread snippets, post previews, and matching context
- Pagination available via
&page={N}parameter
GET https://voz.vn/index.rss
curl "https://voz.vn/index.rss"Standard RSS 2.0 XML feed containing recent forum posts.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Voz - Dien dan Cong nghe</title>
<link>https://voz.vn</link>
<description>...</description>
<item>
<title>Thread Title</title>
<link>https://voz.vn/t/thread-slug.123456/</link>
<description>Post content preview...</description>
<pubDate>Mon, 20 Jun 2026 03:30:00 GMT</pubDate>
</item>
</channel>
</rss>- Only provides site-wide recent posts
- No thread-specific RSS: Cannot subscribe to individual thread feeds
- No forum-specific RSS: Cannot subscribe to individual forum feeds
- Limited to most recent posts only
GET https://voz.vn/api/
curl "https://voz.vn/api/"<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>- XenForo 2.x includes a built-in REST API feature
- Not enabled by Voz administrators
- Would require OAuth 2.0 authentication if enabled
- Would provide structured JSON data for threads, posts, users, forums
- Currently unavailable for public use
| Feature | Available | Endpoint | Auth Required | Data Format | Notes |
|---|---|---|---|---|---|
| Thread Content | ✅ | /t/{slug}.{id}.json |
Session + Cloudflare | JSON (HTML content) | Requires HTML parsing |
| Thread Pagination | ✅ | /t/{slug}.{id}/page-{N}.json |
Session + Cloudflare | JSON (HTML content) | Pages start at 1 |
| Forum Listing | ✅ | /f/{slug}.{id}.json |
Session + Cloudflare | JSON (HTML content) | Thread list in HTML |
| Forum Pagination | ✅ | /f/{slug}.{id}/page-{N}.json |
Session + Cloudflare | JSON (HTML content) | Same pattern |
| Search Submit | ✅ | POST /search/search |
Full Login Required | JSON (redirect only) | Returns redirect URL |
| Search Results | /search/{id}/ |
Full Login Required | HTML only | No JSON variant | |
| REST API | ❌ | /api/ |
N/A (disabled) | N/A | 403 Forbidden |
| RSS Feed | ✅ | /index.rss |
None | RSS 2.0 XML | Site-wide only |
| Thread RSS | ❌ | N/A | N/A | N/A | Not supported |
- Thread scraping: Use
.jsonendpoints, parsehtml.contentwith BeautifulSoup/cheerio - Forum monitoring: Use forum
.jsonendpoints to track new threads - Search: Submit via JSON endpoint, follow redirect, parse HTML results
import requests
from bs4 import BeautifulSoup
import json
import re| Data | CSS Selector |
|---|---|
| Post container | .message.message--post |
| Post ID | [data-content="post-{id}"] |
| Username | .message-attribution-main .username |
| User ID | .message-attribution-main .username[data-user-id] |
| Timestamp | .message-attribution-main time[datetime] |
| Post content | .message-body .message-content |
| Thread title | .p-title-value |
| Reply count | .pairs--justified dd |
| View count | .pairs--justified dd (second) |
- No official rate limits documented
- Aggressive scraping may trigger Cloudflare protection
- Use reasonable delays between requests (1-2 seconds)
- Rotate User-Agent strings if scraping extensively
- Consider using session cookies for authenticated endpoints
Voz uses XenForo session-based authentication with the following cookies:
| Cookie | Type | Purpose | Required |
|---|---|---|---|
cf_clearance |
HttpOnly | Cloudflare bot protection clearance | ✅ For all requests |
xf_session |
HttpOnly | XenForo session identifier | ✅ For JSON/Search |
xf_user |
HttpOnly | User ID + auth hash (persistent login) | ✅ For logged-in actions |
xf_csrf |
Regular | CSRF token (refreshes per session) | |
xf_tfa_trust |
HttpOnly | Two-factor auth trust | Optional |
1. Visit voz.vn → Cloudflare challenge → cf_clearance cookie
2. Any page visit → xf_session cookie (anonymous session)
3. Login → xf_user cookie (persistent auth)
4. All requests → xf_csrf cookie (CSRF protection)
xf_session: Expires when browser closes (session cookie)xf_user: Long-lived persistent token (weeks/months)cf_clearance: typically 30 minutesxf_csrf: Refreshes on each page load
| Endpoint | No Cookies | With Session | With Login |
|---|---|---|---|
| Thread JSON | 403 (Cloudflare) | 400 (CSRF error) | 200 ✅ |
| Forum JSON | 403 (Cloudflare) | 400 (CSRF error) | 200 ✅ |
| Search | 403 (Cloudflare) | 400 (CSRF error) | 200 ✅ |
| REST API | 403 (disabled) | 403 (disabled) | 403 (disabled) |
Key Finding: The .json endpoints require:
cf_clearancecookie (Cloudflare clearance)xf_sessioncookie (XenForo session)xf_csrfcookie (CSRF protection)
Without all three, you get "Security error occurred" (400).
All requests to voz.vn pass through Cloudflare bot protection:
- First request: Returns challenge page (403)
- After solving challenge:
cf_clearancecookie issued - Subsequent requests: Allowed with
cf_clearance
curl/automated requests are blocked:
# This gets 403 (Cloudflare challenge)
curl "https://voz.vn/t/example.123.json"
# Even with proper User-Agent, still gets 400 (no session)
curl -H "User-Agent: Chrome/149..." "https://voz.vn/t/example.123.json"To bypass Cloudflare, you need:
- Browser-based approach: Use Playwright/Puppeteer to solve Cloudflare challenge
- Cookie extraction: Extract
cf_clearancefrom browser session - Session reuse: Use the cookie in subsequent requests (valid ~30 min)
- Location:
xf_csrfcookie OR_xfTokenform field - Value: Changes per session (format:
{timestamp},{hash}) - Usage: Required for POST requests (search, forms)
- Bypass: Not needed for GET requests (
.jsonendpoints)
Option 1: Browser Automation (Recommended)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://voz.vn/")
# Wait for Cloudflare challenge to complete
page.wait_for_load_state("networkidle")
# Now use page to make requests
data = page.evaluate("""async () => {
const resp = await fetch('/t/example.123.json');
return resp.json();
}""")Option 2: Cookie Extraction
import browser_cookie3
# Get cookies from installed browser
cookies = browser_cookie3.chrome(domain_name=".voz.vn")
cf_clearance = [c for c in cookies if c.name == "cf_clearance"]
# Use in requests sessionOption 3: Session Persistence
import requests
session = requests.Session()
# Load saved cookies from browser
session.cookies.set("cf_clearance", "...", domain=".voz.vn")
session.cookies.set("xf_session", "...", domain=".voz.vn")
session.cookies.set("xf_user", "...", domain=".voz.vn")
resp = session.get("https://voz.vn/t/example.123.json")- Session duration:
cf_clearanceexpires ~30 minutes, need to refresh - IP binding:
cf_clearanceis tied to IP address - User-Agent: Must match the one used to get
cf_clearance - Rate limiting: Aggressive requests trigger Cloudflare challenges
- No API keys: Voz does not provide API keys for automation