API Documentation
API Documentation
API Overview
Base URL
https://yourdomain.com/api/v1/ prefix. All endpoints are directly under /api/ (e.g. /api/discussions, /api/categories).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/discussionsParameters:
category_idâ Filter by category ID or slugsortâ Sort order:latest(default),oldest,newest,most_replied,most_viewedlimitâ 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/discussionsRequest 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}/postsParameters:
posts_orderâASC(default) orDESC
Get post
GET /api/posts/{id}Create post (reply)
Requires authentication.
POST /api/postsRequest 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/categoriesReturns all categories visible to the requesting user.
Get category
GET /api/categories/{id}Tags
List tags
GET /api/tagsGet tag
GET /api/tags/{id}Get discussions by tag
GET /api/tags/{slug}/discussionsUsers
Get user
GET /api/users/{id}Search users
GET /api/users/search?q=usernameGet user's discussions
Requires authentication (own profile or admin).
GET /api/users/discussionsGet user's posts
Requires authentication (own profile or admin).
GET /api/users/postsGet user's reactions
GET /api/users/reactionsGet user's subscriptions
GET /api/users/subscriptionsSearch
GET /api/search?q=keyword&type=discussionsParameters:
qâ Search querytypeâdiscussions(default),posts,users
Reactions
Toggle reaction on a post
Requires authentication.
POST /api/reactions/toggleRequest body:
{
"post_id": "...",
"reaction": "like"
}Get reactions for a post
GET /api/reactions/post/{post_id}Notifications
List notifications
Requires authentication.
GET /api/notificationsCount unread notifications
Requires authentication.
GET /api/notifications/countMark notifications as read
Requires authentication.
POST /api/notifications/mark-readPresence
Update presence (heartbeat)
POST /api/presence/updateRequest 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/statsResponse:
{
"total": 12,
"anonymous": 8,
"authenticated": 3,
"bots": 1
}Get visitor list
GET /api/presence/visitorsGet bot list
GET /api/presence/botsMarkdown
Parse a Markdown string to HTML server-side.
POST /api/markdown/parseRequest body:
{
"content": "## Hello\n\nThis is **bold**."
}Typing Indicator
Send typing event
Requires authentication.
POST /api/typing-indicator/typingStop typing event
Requires authentication.
POST /api/typing-indicator/stopGet typing status
GET /api/typing-indicator/statusVersion
GET /api/versionReturns 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}{id}. The route parameter is resolved server-side via Category::findBySlug() before delegating to RssService; an unknown slug returns 404. Any external link to /feed/rss/category/{uuid} from before 5.4.0 must be updated.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 maxcreated_atof the fetched discussions (not the wall-clock fetch time), so the feed only appears modified when content actually changes. - Atom
<updated>per entry usesupdated_atwhen available, falling back tocreated_atfor edited discussions. - Feeds are sorted by
created_atdescending â 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/webhooksFires 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
| Code | Meaning |
|---|---|
400 | Bad Request â invalid or missing parameters |
401 | Unauthorized â missing or invalid token |
403 | Forbidden â authenticated but insufficient permissions |
404 | Not Found |
429 | Too Many Requests â rate limit exceeded |
500 | Internal 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
| Method | Endpoint | Description |
|---|---|---|
POST | /api/webhooks | Receive inbound webhook payloads |
GET | /admin/webhooks | List configured webhooks |
GET | /admin/webhooks/history | View delivery history |
POST | /admin/webhooks/save | Create or update a webhook |
POST | /admin/webhooks/test | Send a test payload to a webhook |
GET | /admin/webhooks/stats | Webhook delivery statistics |
Supported Events
discussion.createdâ New discussion publishedpost.createdâ New reply publisheduser.registeredâ New user registeredreport.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
429responses
Performance
- Use pagination â Always paginate large result sets via
limitandoffset - 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-Afterheader and wait before retrying
Resources
- Developer Guide â Advanced development
- Troubleshooting â API issues
Last updated: May 31, 2026