API Documentation

API Documentation

API Overview

Base URL

https://yourdomain.com/api

Response Format

All responses are in JSON format:

{
  "success": true,
  "data": { }
}

Error Responses

{
  "success": false,
  "error": "Error description"
}

Authentication

Session

The API authenticates via the active PHP session — the same session used by the browser interface.

To make authenticated API requests, your HTTP client must carry the session cookie obtained after logging in through the normal login form.

Bearer Token

The API supports Authorization: Bearer <token> authentication. A hashed token is stored per user (api_token field).

To generate or revoke your token, go to Profile → Settings → Security → API Token. The plain token is shown once upon generation — copy it immediately.

CSRF on state-changing requests (since 5.6.9)

POST/PUT/DELETE requests to /api/* require a valid CSRF token in addition to a valid session. Send it as an X-CSRF-Token header, reading the value from the <meta name="csrf-token"> tag rendered on every page. The built-in window.apiRequest() helper does this automatically, so first-party frontend code needs no change.

Two endpoints are exempt: /api/webhooks (authenticated by HMAC signature) and /api/markdown/parse (idempotent, GET-safe). Bearer-token clients calling read-only GET endpoints are unaffected.

Rate Limiting

API requests are rate-limited per user (authenticated) or per IP (anonymous).

When the limit is exceeded, the server responds with HTTP 429 Too Many Requests and a Retry-After header indicating how many seconds to wait before retrying.

Endpoints

Discussions

List discussions

GET /api/discussions

Parameters:

  • category_id — Filter by category ID or slug
  • sort — Sort order: latest (default), oldest, newest, most_replied, most_viewed
  • limit — Results per page (default: config value)
  • offset — Offset for pagination (default: 0)

Response:

{
  "success": true,
  "discussions": [ { "id": "...", "title": "...", "category_id": "...", ... } ],
  "html": "<article>...</article>",
  "count": 20,
  "hasMore": true
}

Get discussion

GET /api/discussions/{id}

Response:

{
  "success": true,
  "discussion": { "id": "...", "title": "...", "content": "...", ... },
  "posts": [ { ... } ],
  "category": { ... }
}

Create discussion

Requires authentication.

POST /api/discussions

Request body (JSON or form-encoded):

{
  "title": "New Discussion",
  "content": "Discussion content...",
  "category_id": "category-id-here"
}

Response:

{
  "success": true,
  "discussion_id": "...",
  "post_id": "..."
}

Posts

List posts in a discussion

GET /api/discussions/{id}/posts

Parameters:

  • posts_order — ASC (default) or DESC

Get post

GET /api/posts/{id}

Create post (reply)

Requires authentication.

POST /api/posts

Request body:

{
  "discussion_id": "...",
  "content": "Reply content..."
}

Update post

Requires authentication (own post or moderator).

PUT /api/posts/{id}

Request body:

{
  "content": "Updated content..."
}

Delete post

Requires authentication (own post or moderator).

DELETE /api/posts/{id}

Categories

List categories

GET /api/categories

Returns all categories visible to the requesting user.

Get category

GET /api/categories/{id}

Tags

List tags

GET /api/tags

Get tag

GET /api/tags/{id}

Get discussions by tag

GET /api/tags/{slug}/discussions

Users

Get user

GET /api/users/{id}

Search users

GET /api/users/search?q=username

Get user's discussions

Requires authentication (own profile or admin).

GET /api/users/discussions

Get user's posts

Requires authentication (own profile or admin).

GET /api/users/posts

Get user's reactions

GET /api/users/reactions

Get user's subscriptions

GET /api/users/subscriptions

GET /api/search?q=keyword&type=discussions

Parameters:

  • q — Search query
  • type — discussions (default), posts, users

Reactions

Toggle reaction on a post

Requires authentication.

POST /api/reactions/toggle

Request body:

{
  "post_id": "...",
  "reaction": "like"
}

Get reactions for a post

GET /api/reactions/post/{post_id}

Notifications

List notifications

Requires authentication.

GET /api/notifications

Count unread notifications

Requires authentication.

GET /api/notifications/count

Mark notifications as read

Requires authentication.

POST /api/notifications/mark-read

Presence

Update presence (heartbeat)

POST /api/presence/update

Request body:

{
  "page": "/d/123/discussion-title"
}

This endpoint is excluded from visitor tracking — it never creates anonymous visitor records.

Get presence stats

GET /api/presence/stats

Response:

{
  "total": 12,
  "anonymous": 8,
  "authenticated": 3,
  "bots": 1
}

Get visitor list

GET /api/presence/visitors

Get bot list

GET /api/presence/bots

Markdown

Parse a Markdown string to HTML server-side.

POST /api/markdown/parse

Request body:

{
  "content": "## Hello\n\nThis is **bold**."
}

Typing Indicator

Send typing event

Requires authentication.

POST /api/typing-indicator/typing

Stop typing event

Requires authentication.

POST /api/typing-indicator/stop

Get typing status

GET /api/typing-indicator/status

Version

GET /api/version

Returns the current Flatboard version.


RSS & Atom Feeds

Flatboard exposes RSS 2.0 and Atom 1.0 feeds for the forum globally, per category, per user, and per tag. These endpoints are HTML-served (not part of /api/) but follow the same authentication model as the JSON API.

GET /feed/rss
GET /feed/atom
GET /feed/rss/category/{slug}              # since 5.4.0 — was /category/{id}
GET /feed/atom/category/{slug}              # since 5.4.0 — was /category/{id}
GET /rss/user/{id}
GET /atom/user/{id}
GET /rss/tag/{id}
GET /atom/tag/{id}

Stable identifiers

  • <guid> (RSS) and <id> (Atom) use the slug-free discussion URL (/d/{number}) so that editing the discussion title doesn't change the identifier and trigger feed readers to re-show items as unread.
  • RSS <lastBuildDate> is the max created_at of the fetched discussions (not the wall-clock fetch time), so the feed only appears modified when content actually changes.
  • Atom <updated> per entry uses updated_at when available, falling back to created_at for edited discussions.
  • Feeds are sorted by created_at descending — replies do not bubble old discussions back to the top of the feed.

Private feeds via ?token= (Pro)

On Pro installations, every feed endpoint accepts an optional ?token= query parameter carrying the per-user API token. With a valid token, the feed includes discussions from any category the user is authorised to access. Invalid or missing tokens silently fall back to public-only content. Community installations ignore the parameter entirely. See Pro features for details.

HEAD requests (since 5.6.6)

HEAD /feed/rss (and any other GET feed route) is now correctly handled. RFC 7231 requires HEAD to be supported wherever GET is — feed readers and link-preview bots that probe with HEAD after a FlatSEO 301 redirect no longer trigger "Route not found" warnings.

Immediate cache invalidation (since 5.7.0)

Creating, editing or deleting a discussion or post now purges the feed caches straight away, so the RSS/Atom feeds never serve a deleted discussion or lag behind a new one (previously up to 15 minutes, the RssService cache TTL). The same pass also refreshes /sitemap.xml — both the SitemapService application cache and the static public/sitemap.xml file, so a deleted discussion drops out of the sitemap on the next request instead of lingering indefinitely. Replies are covered too, since any post mutation moves the discussion's updated_at. The only remaining staleness layer is the browser/CDN cache (Cache-Control: public, max-age=3600 on /sitemap.xml).


Webhooks

Receive inbound webhook

POST /api/webhooks

Fires the webhook.received hook with the payload. See Webhooks below.


Code Examples

JavaScript (Fetch)

// List discussions
fetch('https://yourdomain.com/api/discussions', {
  headers: {
    'Authorization': 'Bearer your-token-here'
  }
})
.then(response => response.json())
.then(data => {
  console.log(data.discussions);
});

PHP (cURL)

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://yourdomain.com/api/discussions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer your-token-here'
]);

$response = curl_exec($ch);
$data = json_decode($response, true);
curl_close($ch);

Python (Requests)

import requests

headers = {
    'Authorization': 'Bearer your-token-here'
}

response = requests.get(
    'https://yourdomain.com/api/discussions',
    headers=headers
)

data = response.json()

Error Codes

CodeMeaning
400Bad Request — invalid or missing parameters
401Unauthorized — missing or invalid token
403Forbidden — authenticated but insufficient permissions
404Not Found
429Too Many Requests — rate limit exceeded
500Internal Server Error

Webhooks

Flatboard 5 includes a built-in webhook system for real-time notifications to external services. Configure webhooks at Admin Panel → Webhooks.

Webhook Endpoints

MethodEndpointDescription
POST/api/webhooksReceive inbound webhook payloads
GET/admin/webhooksList configured webhooks
GET/admin/webhooks/historyView delivery history
POST/admin/webhooks/saveCreate or update a webhook
POST/admin/webhooks/testSend a test payload to a webhook
GET/admin/webhooks/statsWebhook delivery statistics

Supported Events

  • discussion.created — New discussion published
  • post.created — New reply published
  • user.registered — New user registered
  • report.created — Content report submitted

Delivery

Each event sends a POST request to the configured URL with a JSON body and an X-Flatboard-Event header identifying the event type. Delivery history and retry status are available in the admin panel.

Best Practices

Security

  • Use HTTPS — Always use HTTPS for API requests
  • Protect tokens — Never expose tokens in client-side code or version control
  • Rate Limiting — Respect rate limits and implement backoff on 429 responses

Performance

  • Use pagination — Always paginate large result sets via limit and offset
  • Use filters — Pass category_id, sort, etc. to reduce data transfer
  • Cache responses — Cache API responses client-side when appropriate

Error Handling

  • Check status codes — Always check HTTP status codes before parsing the body
  • Handle 429 gracefully — Read the Retry-After header and wait before retrying

Resources

Last updated: May 31, 2026