Nothing runs outside your permissions
An API credential is always bound to one real forum member. You grant it a set of scopes — the categories of action it’s allowed to attempt — and phpBB’s own permission system decides, on every single request, whether that action is actually allowed. A scope without the matching phpBB permission does nothing; a phpBB permission without the scope is unreachable. A credential can never do more than the member it’s bound to could do themselves, and every action still shows up in your normal moderation logs.
- Topics & posts — create, read, edit, delete, bulk-fetch, polls, up to 10 attachments per post
- Moderation — lock/unlock, soft-delete/restore, approve/disapprove, move, merge, split
- Members — register, search, manage groups, ban/unban, delete accounts
- Messaging & reports — private messages, notifications, moderator report queue
- Real-time — a Server-Sent Events stream and 18 outgoing webhook events, signed with HMAC-SHA256
- Operability — per-credential rate limits, IP/forum allowlists, expiry, token & secret rotation, a full ACP dashboard
Installation
- Upload the extension to
ext/dmzx/phpbbapiin your phpBB root, then enable it under ACP → Customise → Manage extensions. - Grant the permission. Nobody can manage credentials until you do this: ACP → Permissions → Administrator permissions → “Can manage phpBB API credentials and view the request log” (
a_phpbbapi). The founder account already has it. - Check Settings. ACP → phpBB API → Settings — confirm the master switch is on, and decide whether to require HTTPS (on by default).
- Create a credential. ACP → phpBB API → API credentials. The token is shown once, immediately after creation — copy it before leaving the page.
Sending your token
Send the token in every request using one of two headers:
Authorization: Bearer <token>
X-API-Key: <token>
Use X-API-Key if your host strips the Authorization header — common on Apache/FastCGI setups.
Entry point: app.php vs index.php
Endpoints are shown below as /app.php/api/v1/... — the version-safe choice, since index.php doesn’t route extension paths on 3.3.x. On a 4.0.x board, app.php still works but is now just a 301-redirect to the equivalent index.php URL.
POST/PATCH/DELETE calls — many HTTP clients resend the method after a 301 but without the original body. Call /index.php/api/v1/... directly for write requests on a 4.0.x board, or make sure your client preserves the body across redirects.
PATCH/DELETE blocked by your host?
Some Apache/ModSecurity setups reject PATCH/DELETE outright with a bare 403, before phpBB ever sees the request. Send a normal POST to the same path instead, with either header:
X-HTTP-Method-Override: PATCH
…or a _method field in the body. It’s handled identically to a real request. A plain POST with no override on a PATCH/DELETE-only path returns method_override_required.
Scopes
A credential only has the scopes you explicitly grant it — checked on top of, never instead of, the bound member’s own phpBB permissions. Credentials can additionally be restricted to specific forums and/or caller IPs, rate-limited, and given an expiry date.
| Scope | Grants |
|---|
members.write needs a_group/a_user for group management/registration and a_userdel to delete a member; messages.write needs u_sendpm; posts.attach needs f_attach; topics.vote needs f_vote (plus f_votechg to change a vote); members.ban needs a_ban; reports.* are further scoped per forum by m_report. phpBB itself refuses to ban, or delete, yourself or a founder account — regardless of permissions.
All endpoints
Every endpoint lives under /api/v1/ and returns application/json. Bodies may be sent as JSON or form-encoded.
| Method | Path | Requires |
|---|
Topics
POST /forums/{forum_id}/topics accepts title, content, and optional type (normal/sticky/announcement). Editing replaces both title and content — send the current value for whichever you’re not changing.
Create a new topic.
curl -X POST https://example.com/app.php/api/v1/forums/2/topics \
-H "X-API-Key: <token>" \
-H "Content-Type: application/json" \
-d '{"title": "Test topic via API", "content": "Created through the phpBB API extension."}'{"success": true, "topic_id": 303, "post_id": 7572, "url": "https://example.com/viewtopic.php?t=303", "attachment_ids": []}Read a single topic. Bulk variant: GET /topics?ids=1,2,3 — unknown ids are silently omitted, capped at 100 ids per call.
curl https://example.com/app.php/api/v1/topics/303 -H "X-API-Key: <token>"
{"success": true, "topic": {"topic_id": 303, "forum_id": 2, "title": "Pick a colour", "type": "normal", "poster": "dmzx", "replies": 0, "views": 1, "locked": false, "url": "https://example.com/viewtopic.php?t=303", "poll": null}}Edit a topic’s title and first-post content. If your host blocks PATCH, send POST with X-HTTP-Method-Override: PATCH.
curl -X POST https://example.com/app.php/api/v1/topics/303 \
-H "X-API-Key: <token>" -H "X-HTTP-Method-Override: PATCH" \
-H "Content-Type: application/json" \
-d '{"title": "Pick a colour (updated)", "content": "Vote below - now with more options."}'{"success": true, "topic_id": 303, "post_id": 7572, "url": "https://example.com/viewtopic.php?t=303"}Permanently delete a topic. For a reversible alternative see soft-delete under Moderation below.
curl -X POST https://example.com/app.php/api/v1/topics/303 \ -H "X-API-Key: <token>" -H "X-HTTP-Method-Override: DELETE"
{"success": true}Watch or unwatch a topic (DELETE to unwatch) — same mechanism as “Watch topic” in the UCP. Calling either state twice is a harmless no-op. The same pattern applies to /forums/{forum_id}/watch.
curl -X POST https://example.com/app.php/api/v1/topics/303/watch -H "X-API-Key: <token>" curl -X DELETE https://example.com/app.php/api/v1/topics/303/watch -H "X-API-Key: <token>"
Posts
Reply to a topic.
curl -X POST https://example.com/app.php/api/v1/topics/303/posts \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"content": "This is a reply."}'{"success": true, "topic_id": 303, "post_id": 7573, "url": "https://example.com/viewtopic.php?t=303&p=7573#p7573", "attachment_ids": []}Read a single post. Bulk variant: GET /posts?ids=1,2,3, same rules as bulk topics.
curl https://example.com/app.php/api/v1/posts/7573 -H "X-API-Key: <token>"
{"success": true, "post": {"post_id": 7573, "poster": "dmzx", "subject": "Re: Pick a colour", "content_html": "This is a reply.", "attachments": []}, "topic": {"topic_id": 303, "forum_id": 2, "locked": false}}Edit a post’s content.
curl -X POST https://example.com/app.php/api/v1/posts/7573 \
-H "X-API-Key: <token>" -H "X-HTTP-Method-Override: PATCH" \
-H "Content-Type: application/json" -d '{"content": "Edited content."}'{"success": true, "topic_id": 303, "post_id": 7573, "url": "https://example.com/viewtopic.php?t=303&p=7573#p7573"}Permanently delete a post.
curl -X POST https://example.com/app.php/api/v1/posts/7573 \ -H "X-API-Key: <token>" -H "X-HTTP-Method-Override: DELETE"
{"success": true}Polls
POST /forums/{forum_id}/topics accepts an optional poll object when creating a topic — polls can’t be added afterwards, and editing the topic never touches an existing poll’s options or votes.
Create a topic with a poll.
curl -X POST https://example.com/app.php/api/v1/forums/2/topics \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"title": "Pick a colour", "content": "Vote below.",
"poll": {"question": "Favourite colour?", "options": ["Red", "Green", "Blue"],
"max_options": 1, "length_days": 7}}'"poll": {"question": "Favourite colour?", "options": [
{"option_id": 1, "text": "Red", "votes": 0},
{"option_id": 2, "text": "Green", "votes": 0},
{"option_id": 3, "text": "Blue", "votes": 0}
], "max_options": 1, "vote_change": false, "expires": 1756104800, "your_votes": []}Vote — or change your vote. option_ids is the complete set you want selected, same semantics as phpBB’s own poll form.
curl -X POST https://example.com/app.php/api/v1/topics/303/poll/vote \
-H "X-API-Key: <token>" -H "Content-Type: application/json" -d '{"option_ids": [2]}'{"success": true, "your_votes": [2]}Attachments
Up to 10 files per post, sent as multipart/form-data. Each file is its own field — attachment, attachment2, … attachment10 — not an attachment[] array (PHP would merge that into one nested entry phpBB’s uploader can’t split back apart). Requires posts.attach on top of topics.write/posts.write, plus f_attach in that forum.
Create a topic with multiple attachments.
curl -X POST https://example.com/app.php/api/v1/forums/2/topics \ -H "X-API-Key: <token>" \ -F "title=Screenshots attached" -F "content=See attached." \ -F "attachment=@screenshot1.png" -F "attachment2=@screenshot2.png" -F "attachment3=@notes.pdf"
{"success": true, "topic_id": 305, "post_id": 7600, "attachment_ids": [12, 13, 14]}Download a file — not JSON, streams the file back with the correct headers. Same visibility checks as reading the parent post.
curl https://example.com/app.php/api/v1/attachments/12 -H "X-API-Key: <token>" -o downloaded_file
Moderation
Lock/unlock, soft-delete/restore, approve/disapprove, move, merge, split — each still checks the bound member’s own moderator permissions (m_lock, m_softdelete, m_approve, m_move, m_merge, m_split…), same as phpBB’s own moderator UI.
Lock or unlock a topic (/unlock for the reverse).
curl -X POST https://example.com/app.php/api/v1/topics/303/lock -H "X-API-Key: <token>" curl -X POST https://example.com/app.php/api/v1/topics/303/unlock -H "X-API-Key: <token>"
Hide a topic reversibly (optionally with a reason) — undo with /restore. Same pattern exists for posts.
curl -X POST https://example.com/app.php/api/v1/topics/303/soft-delete \
-H "X-API-Key: <token>" -H "Content-Type: application/json" -d '{"reason": "spam"}'
curl -X POST https://example.com/app.php/api/v1/topics/303/restore -H "X-API-Key: <token>"Approve a post awaiting moderation, or /disapprove to reject it. Disapproving deletes it outright — same as phpBB’s own moderator queue, a rejected never-published post has nothing to restore from.
curl -X POST https://example.com/app.php/api/v1/posts/7573/approve -H "X-API-Key: <token>" curl -X POST https://example.com/app.php/api/v1/posts/7573/disapprove -H "X-API-Key: <token>"
Move a topic to another forum.
curl -X POST https://example.com/app.php/api/v1/topics/303/move \
-H "X-API-Key: <token>" -H "Content-Type: application/json" -d '{"to_forum_id": 11}'Merge into another topic. Omit post_ids to merge the entire source topic, or list specific posts.
curl -X POST https://example.com/app.php/api/v1/topics/303/merge \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"to_topic_id": 290, "post_ids": [7572, 7573]}'Split posts into a genuinely new topic.
curl -X POST https://example.com/app.php/api/v1/topics/303/split \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"post_ids": [7574, 7575], "subject": "Split-off discussion", "to_forum_id": 2}'{"success": true, "topic_id": 304}Search
A plain LIKE match against subject and body — deliberately, so results don’t depend on your board’s configured full-text backend or whether its index is built. Optional author and forum_id filters.
curl "https://example.com/app.php/api/v1/search?type=posts&q=welcome&author=admin&forum_id=2" \ -H "X-API-Key: <token>"
Members
Register a new member — always active immediately, regardless of the board’s activation policy.
curl -X POST https://example.com/app.php/api/v1/members \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"username": "newuser", "password": "correct-horse-battery-staple", "email": "newuser@example.com"}'{"success": true, "user_id": 15, "username": "newuser"}List or search members.
curl "https://example.com/app.php/api/v1/members?q=dmzx&limit=10" -H "X-API-Key: <token>"
{"success": true, "members": [{"user_id": 2, "username": "dmzx", "posts": 128, "colour": "AA0000"}]}Add to a group (group_id in the body) — remove with DELETE /members/{user_id}/groups/{group_id}.
curl -X POST https://example.com/app.php/api/v1/members/15/groups \
-H "X-API-Key: <token>" -H "Content-Type: application/json" -d '{"group_id": 5}'Ban (length_minutes: 0 = permanent) or /unban. phpBB refuses to ban yourself or the founder, regardless of permissions.
curl -X POST https://example.com/app.php/api/v1/members/15/ban \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"length_minutes": 0, "reason": "spam (moderator-only)", "public_reason": "Banned for spam"}'Delete an account. mode: "remove" also deletes everything they posted; anything else retains their posts under the deleted username. Same self/founder protection as banning.
curl -X POST https://example.com/app.php/api/v1/members/15 \
-H "X-API-Key: <token>" -H "X-HTTP-Method-Override: DELETE" \
-H "Content-Type: application/json" -d '{"mode": "retain"}'Messages
Send a private message — to accepts a username or numeric user id.
curl -X POST https://example.com/app.php/api/v1/messages \
-H "X-API-Key: <token>" -H "Content-Type: application/json" \
-d '{"to": "someuser", "subject": "Welcome", "content": "Hi there!"}'{"success": true, "msg_id": 10, "to": {"user_id": 3, "username": "someuser"}}Read a single message — marks it read, same as opening it in the UCP. GET /messages lists your own inbox.
curl https://example.com/app.php/api/v1/messages/9 -H "X-API-Key: <token>"
Reports
List post reports, scoped to forums the bound member moderates. Resolve one with POST /reports/{report_id}/resolve. Creating a report isn’t part of this API — reporting is member-facing, this surface is moderator-facing.
curl "https://example.com/app.php/api/v1/reports?status=open" -H "X-API-Key: <token>"
Notifications
Your own board notifications, same feed as the bell icon. Mark one read with POST /notifications/{id}/read.
curl https://example.com/app.php/api/v1/notifications -H "X-API-Key: <token>"
{"success": true, "unread_count": 1, "notifications": [{"notification_id": 21, "title": "someuser replied to Pick a colour", "unread": true}]}Event stream (SSE)
GET /stream is a Server-Sent Events connection that emits topic.created/post.created as they happen, instead of polling. Only approved, non-deleted content the bound member can read is included.
curl -N -H "X-API-Key: <token>" "https://example.com/index.php/api/v1/stream"
const source = new EventSource('/index.php/api/v1/stream?access_token=<token>');
source.addEventListener('topic.created', e => console.log(JSON.parse(e.data)));
source.addEventListener('post.created', e => console.log(JSON.parse(e.data)));Pass ?since=<unix time> to resume, or just let the browser’s native EventSource send Last-Event-ID automatically on reconnect.
EventSource can’t set custom headers, so ?access_token= is accepted as a fallback — only for this endpoint. Prefer a header wherever your client supports one; a token in a URL can leak via logs or browser history.
Webhooks
ACP → phpBB API → Webhooks lets you register a URL to receive a signed POST whenever a subscribed event happens through this API. Each webhook has a Fire for forum-UI actions too toggle (off by default) to also fire for the same kind of action done through the normal forum UI, not just the API. Configuration is ACP-only.
Events
| Category | Events |
|---|
Delivery
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Webhook-Event: topic.created
X-Webhook-Signature: sha256=5a8f...c3e1
{"event": "topic.created", "time": 1755500000, "data": {"topic_id": 303, "post_id": 7572, "forum_id": 2, "title": "Test topic via API", "url": "https://example.com/viewtopic.php?t=303"}}
Verifying a signature
$payload = file_get_contents('php://input');
$sig = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($sig, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
const crypto = require('crypto');
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody).digest('hex');
crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(req.headers['x-webhook-signature'] || '')
);
Retries & rotation
- Best-effort delivery — a failed attempt never affects the API response that triggered it, and is retried with backoff: 1 min, 5 min, 30 min, 2h, then 12h (5 tries, ~15h total).
- Retries run opportunistically, on phpBB’s own cron beacon — a quiet board won’t retry until someone loads a page, or system cron polls it.
- Secret rotation has a 24-hour grace period: deliveries carry both
X-Webhook-Signature(new) andX-Webhook-Signature-Previous(old) so you can update your verification without a hard cutover.
Errors
{"success": false, "error": "<code>"}| HTTP | Code | Meaning |
|---|
X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset — check Remaining to back off before hitting a 429. A credential with rate_limit: 0 (unlimited) sends none of these.
Security notes
- Permissions are never bypassed — every request runs phpBB’s own
submit_post(), delete functions, and ACL checks exactly as the bound member would trigger them via the web UI. - Deletion requires moderator-level
m_delete— self-service deletion of your own post isn’t exposed in this version; the destructive-action surface stays moderator-only. - Tokens are hashed — only a SHA-256 hash is ever stored.
- Token rotation — Regenerate issues a new token immediately; the old one keeps working for 24 hours so you can update whichever app uses it.
- Rate limiting runs through phpBB’s own cache layer — no extra database writes on the hot path.
- CORS is off by default — only needed if browser JavaScript calls the API directly.
- Full audit log — every request, successful or not, recorded with time, IP, method, route, status and error code.