Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 49edc72f102927812ac0294fdf7816d624a56cde)
+++ src/routes/activitypub.js	(revision a9900ec7d81a0856882491d6ce6cb7e1275dabb4)
@@ -16,6 +16,10 @@
 import db from '../config/database.js';
 import AP from '../services/ActivityPubService.js';
+import { apReadLimiter, apInboxLimiter } from '../middleware/rate-limit.js';
 
 const router = express.Router();
+// Generous per-IP baseline over all /ap/* (reads). The inbox POST gets an
+// additional, tighter cap inline (it triggers outbound fetches).
+router.use(apReadLimiter);
 let _ver = '1.0.0';
 try { _ver = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url))).version || _ver; } catch { /* keep default */ }
@@ -160,5 +164,5 @@
   verify: (req, _res, buf) => { req.rawBody = buf; }, // raw body for digest verification
 });
-router.post(['/ap/users/:slug/inbox', '/ap/inbox'], apJson, async (req, res) => {
+router.post(['/ap/users/:slug/inbox', '/ap/inbox'], apInboxLimiter, apJson, async (req, res) => {
   try { return res.status(await AP.handleInbox(req, req.params.slug || null) || 202).end(); }
   catch (e) { console.warn('[AP inbox] error:', e.message); return res.status(202).end(); }
