Skip to content

Instantly share code, notes, and snippets.

@KevinNitroG
Created July 8, 2026 08:46
Show Gist options
  • Select an option

  • Save KevinNitroG/13564696f36e5f7da3ec62b80bc40765 to your computer and use it in GitHub Desktop.

Select an option

Save KevinNitroG/13564696f36e5f7da3ec62b80bc40765 to your computer and use it in GitHub Desktop.
Voz API, browsed by AI, don't judge

Voz (voz.vn) API Analysis

Overview

  • 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

Thread JSON API

Endpoint

GET https://voz.vn/t/{thread-slug}.{thread-id}.json
GET https://voz.vn/t/{thread-slug}.{thread-id}/page-{N}.json

Parameters

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)

Request Example

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"

Response Structure

{
  "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"
  }
}

Response Fields

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

HTML Content Structure

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>

Forum JSON API

Endpoint

GET https://voz.vn/f/{forum-slug}.{forum-id}.json
GET https://voz.vn/f/{forum-slug}.{forum-id}/page-{N}.json

Request Example

curl "https://voz.vn/f/dien-dan-cong-nghe.838.json"

Response Structure

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"
  }
}

HTML Content Structure

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>

Pagination

Pattern

/page-{N}

Examples

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)

Notes

  • 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

Search API

Endpoint

POST https://voz.vn/search/search.json

Parameters

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

Request Example

curl -X POST "https://voz.vn/search/search.json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "keywords=token&type=post&order=relevance"

Response Structure

{
  "status": "ok",
  "redirect": "https://voz.vn/search/5076122/?q=token&o=relevance"
}

Response Fields

Field Type Description
status string Always "ok"
redirect string URL to the search results page (HTML only)

Limitations

  • No JSON results: The search endpoint only returns a redirect URL
  • Results are HTML-only: The redirected search results page does NOT support .json suffix
  • Session required: Search may require valid session cookies for repeated requests
  • Rate limiting: Aggressive requests may trigger CAPTCHA or IP blocks

Search Results (HTML Only)

Endpoint

GET https://voz.vn/search/{search-id}/?q={query}&o={order}

Request Example

curl "https://voz.vn/search/5076122/?q=token&o=relevance"

Notes

  • Returns full HTML page with search results
  • Does NOT support .json suffix
  • Contains thread snippets, post previews, and matching context
  • Pagination available via &page={N} parameter

RSS Feed

Endpoint

GET https://voz.vn/index.rss

Request Example

curl "https://voz.vn/index.rss"

Response Format

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>

Limitations

  • 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

XenForo REST API

Status: DISABLED (403 Forbidden)

Endpoint (Disabled)

GET https://voz.vn/api/

Request Example

curl "https://voz.vn/api/"

Response

<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>

Notes

  • 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

Summary Table

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

Usage Recommendations

For Data Extraction

  1. Thread scraping: Use .json endpoints, parse html.content with BeautifulSoup/cheerio
  2. Forum monitoring: Use forum .json endpoints to track new threads
  3. Search: Submit via JSON endpoint, follow redirect, parse HTML results

Required Libraries (Python)

import requests
from bs4 import BeautifulSoup
import json
import re

HTML Parsing Selectors

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)

Rate Limiting & Considerations

  • 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

Authentication & Session Management

How Voz Handles Authentication

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) ⚠️ For form submissions
xf_tfa_trust HttpOnly Two-factor auth trust Optional

Authentication Flow

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)

Session Expiry

  • xf_session: Expires when browser closes (session cookie)
  • xf_user: Long-lived persistent token (weeks/months)
  • cf_clearance: typically 30 minutes
  • xf_csrf: Refreshes on each page load

Testing Results

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:

  1. cf_clearance cookie (Cloudflare clearance)
  2. xf_session cookie (XenForo session)
  3. xf_csrf cookie (CSRF protection)

Without all three, you get "Security error occurred" (400).

Cloudflare Protection

All requests to voz.vn pass through Cloudflare bot protection:

  • First request: Returns challenge page (403)
  • After solving challenge: cf_clearance cookie 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:

  1. Browser-based approach: Use Playwright/Puppeteer to solve Cloudflare challenge
  2. Cookie extraction: Extract cf_clearance from browser session
  3. Session reuse: Use the cookie in subsequent requests (valid ~30 min)

CSRF Token

  • Location: xf_csrf cookie OR _xfToken form field
  • Value: Changes per session (format: {timestamp},{hash})
  • Usage: Required for POST requests (search, forms)
  • Bypass: Not needed for GET requests (.json endpoints)

Automation Recommendations

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 session

Option 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")

Important Notes

  1. Session duration: cf_clearance expires ~30 minutes, need to refresh
  2. IP binding: cf_clearance is tied to IP address
  3. User-Agent: Must match the one used to get cf_clearance
  4. Rate limiting: Aggressive requests trigger Cloudflare challenges
  5. No API keys: Voz does not provide API keys for automation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment