Skip to main content
Ayase Quart provides a REST API for programmatic access to archived content. The API is designed to be compatible with the 4chan JSON API format while adding authentication and moderation features.

Overview

The API provides:

4chan compatibility

JSON endpoints matching 4chan’s API structure

Bearer authentication

Token-based authentication for protected resources

Rate limiting

Configurable rate limits to prevent abuse

Moderation support

Filtered content based on user permissions

Enabling the API

The API must be explicitly enabled in configuration:
[app]
api = true
When disabled, API endpoints return 404 errors

Base URL

All API endpoints are prefixed with:
/api/v1
For example:
http://localhost:9001/api/v1/g/catalog.json

Authentication

Public access

Some endpoints work without authentication:
  • Catalog listings
  • Thread data
  • Board indexes
However, reported posts will be filtered from results.

Authenticated access

Authenticated requests see additional content:
  • Reported posts (if user has permissions)
  • Moderation-hidden posts (for moderators)
  • Admin-only data

Obtaining a token

1

Send login request

POST to /api/v1/login with credentials:
{
  "username": "admin",
  "password": "your_password"
}
2

Receive token

Successful response includes:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": "2024-01-02T12:00:00Z",
  "user_id": 1,
  "username": "admin"
}
3

Use token

Include in Authorization header:
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Token expiration

Tokens expire based on configuration:
[moderation.auth]
token_expiration_hours = 24
Expired tokens return 401 Unauthorized.

Core endpoints

Catalog

Get a paginated list of threads on a board:
GET /api/v1/<board>/catalog.json
Parameters:
  • board: Board shortname (e.g., g, a, pol)
Response:
[
  {
    "page": 0,
    "threads": [
      {
        "no": 98765432,
        "sticky": 0,
        "closed": 0,
        "now": "01/15/24(Mon)12:34:56",
        "name": "Anonymous",
        "sub": "Thread subject",
        "com": "Thread comment text",
        "filename": "image",
        "ext": ".jpg",
        "w": 1920,
        "h": 1080,
        "tn_w": 250,
        "tn_h": 140,
        "tim": 1234567890123,
        "time": 1234567890,
        "md5": "abc123...",
        "fsize": 524288,
        "resto": 0,
        "capcode": "none",
        "semantic_url": "thread-subject",
        "replies": 42,
        "images": 15,
        "omitted_posts": 0,
        "omitted_images": 0
      }
    ]
  }
]
Implementation: src/ayase_quart/blueprints/api/bp_app.py:13

Thread

Get all posts in a thread:
GET /api/v1/<board>/thread/<thread_id>.json
Parameters:
  • board: Board shortname
  • thread_id: Thread number (OP post number)
Response:
{
  "posts": [
    {
      "no": 98765432,
      "now": "01/15/24(Mon)12:34:56",
      "name": "Anonymous",
      "sub": "Thread subject",
      "com": "Post comment with <span class=\"quote\">&gt;greentext</span>",
      "filename": "image",
      "ext": ".jpg",
      "w": 1920,
      "h": 1080,
      "tn_w": 250,
      "tn_h": 140,
      "tim": 1234567890123,
      "time": 1234567890,
      "md5": "abc123...",
      "fsize": 524288,
      "resto": 0,
      "capcode": "none",
      "semantic_url": "thread-subject",
      "replies": 42,
      "images": 15
    },
    {
      "no": 98765433,
      "now": "01/15/24(Mon)12:35:12",
      "name": "Anonymous",
      "com": "Reply to thread",
      "time": 1234567906,
      "resto": 98765432
    }
  ]
}
Implementation: src/ayase_quart/blueprints/api/bp_app.py:23

Board index

Get a page of threads from the board index:
GET /api/v1/<board>/<page_num>.json
Parameters:
  • board: Board shortname
  • page_num: Page number (0-indexed)
Response:
{
  "threads": [
    {
      "posts": [
        {
          "no": 98765432,
          "sticky": 0,
          "closed": 0,
          "now": "01/15/24(Mon)12:34:56",
          "name": "Anonymous",
          "sub": "Thread subject",
          "com": "OP comment",
          "time": 1234567890,
          "resto": 0,
          "replies": 42,
          "images": 15
        },
        {
          "no": 98765433,
          "time": 1234567906,
          "com": "Recent reply",
          "resto": 98765432
        }
      ]
    }
  ]
}
Implementation: src/ayase_quart/blueprints/api/bp_app.py:36

Response format

Post object

All posts follow this structure:
  • no: Post number (unique ID)
  • resto: Thread number (0 for OP, otherwise parent thread)
  • time: Unix timestamp
  • now: Formatted timestamp string
  • name: Poster name
  • com: Comment HTML
  • capcode: Poster capcode (“none”, “mod”, “admin”)
  • sub: Thread subject/title
  • semantic_url: URL-safe thread title
  • replies: Total reply count
  • images: Total image count
  • sticky: 1 if pinned, 0 otherwise
  • closed: 1 if archived, 0 otherwise
  • filename: Original filename (without extension)
  • ext: File extension (e.g., “.jpg”, “.png”, “.webm”)
  • tim: Server timestamp for file
  • md5: Base64-encoded MD5 hash
  • fsize: File size in bytes
  • w: Image width
  • h: Image height
  • tn_w: Thumbnail width
  • tn_h: Thumbnail height
  • trip: Tripcode
  • country: Country code (if enabled)
  • country_name: Country name
  • email: Email field
  • spoiler: 1 if spoilered, 0 otherwise

Moderation endpoints

Moderation actions are available via API:

Submit report

POST /api/v1/report/<board>/<thread_num>/<num>
Request body:
{
  "submitter_category": "spam",
  "submitter_notes": "Obvious spam post"
}
Response:
{
  "message": "thank you"
}

Nuke post (admin only)

POST /api/v1/nuke/<board>/<num>
Headers:
Authorization: bearer <token>
X-CSRF-Token: <csrf_token>
Response:
{
  "message": "Post deleted successfully"
}

Error responses

400 Bad Request

{
  "error": "Invalid board name"
}

401 Unauthorized

{
  "error": "Invalid or expired token"
}

404 Not Found

{
  "error": "Thread not found"
}

429 Too Many Requests

{
  "error": "Rate limit exceeded"
}

Rate limiting

API endpoints have rate limits:
  • Authenticated requests: Higher limits
  • Unauthenticated requests: Lower limits
  • Report submissions: 4 per hour per IP
Exceeding limits returns 429 status code.

Content filtering

API responses respect moderation settings:

Unauthenticated requests

  • Reported posts are hidden
  • Moderation-hidden posts are hidden
  • Deleted posts may be excluded

Authenticated requests

Based on user permissions:
  • Moderators see reported posts
  • Admins see all posts
  • Regular users see public posts
Filter implementation: src/ayase_quart/moderation/filter_cache/

Testing the API

Using Bruno

A Bruno API collection is included:
# Import collection
cp dev/bruno_aq.json ~/Bruno/Collections/
The collection includes:
  • Login flow
  • All core endpoints
  • Moderation actions
  • Example requests

Using cURL

# Get catalog
curl http://localhost:9001/api/v1/g/catalog.json

# Login
curl -X POST http://localhost:9001/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}'

# Get thread (authenticated)
curl http://localhost:9001/api/v1/g/thread/98765432.json \
  -H "Authorization: bearer <token>"

Using Python

import requests

BASE_URL = "http://localhost:9001/api/v1"

# Login
response = requests.post(
    f"{BASE_URL}/login",
    json={"username": "admin", "password": "admin"}
)
token = response.json()["token"]

# Get catalog
headers = {"Authorization": f"bearer {token}"}
response = requests.get(
    f"{BASE_URL}/g/catalog.json",
    headers=headers
)
catalog = response.json()

API compatibility

4chan API compatibility

Ayase Quart maintains compatibility with the 4chan API format:
  • Same JSON structure
  • Same field names
  • Same endpoint paths (under /api/v1)
Differences:
  • Authentication required for full access
  • Additional moderation fields
  • Filter cache affects results
  • Asagi schema differences may result in missing fields

Lainchan compatibility

The API also supports Lainchan archives with the same format.

Performance

API performance considerations:
  • Database queries: Optimized for Asagi schema
  • Filter caching: Redis or SQLite cache for fast filtering
  • Pagination: Large threads are not paginated (returned complete)
  • Rate limiting: Protects against abuse
For large instances, ensure Redis is configured for optimal filter cache performance

Configuration

API settings in config.toml:
[app]
api = true
allow_robots = false  # Include robots.txt

[moderation]
enabled = true  # Required for authentication

[moderation.auth]
token_expiration_hours = 24

Best practices

Use authentication

Authenticate to see complete data and get higher rate limits

Handle rate limits

Implement exponential backoff when receiving 429 errors

Cache responses

Cache API responses to reduce load on your archive

Validate data

Check for missing fields - not all Asagi archives have complete data
References:
  • API blueprints: src/ayase_quart/blueprints/api/
  • Authentication: src/ayase_quart/moderation/auth_api.py
  • Data conversion: src/ayase_quart/asagi_converter.py

Build docs developers (and LLMs) love