paperclipai/paperclip · error
Plugin API routes accept JSON requests only
Error message
Plugin API routes accept JSON requests only
What it means
Returned as HTTP 415 when a non-GET scoped API request carries a Content-Type header that req.is("application/json") rejects. The gateway forwards only JSON bodies to plugin workers, so form-encoded, text, and multipart bodies are refused before dispatch. GET requests and bodyless requests without a content-type header pass through.
Source
Thrown at server/src/routes/plugins.ts:1881
const match = routes
.map((route) => ({ route, params: matchScopedApiRoute(route, req.method, requestPath) }))
.find((candidate) => candidate.params !== null);
if (!match || !match.params) {
res.status(404).json({ error: "Plugin API route not found" });
return;
}
try {
assertScopedApiAuth(req, match.route);
const companyId = await resolveScopedApiCompanyId(match.route, match.params, req);
if (!companyId) {
res.status(400).json({ error: "Unable to resolve company for plugin API route" });
return;
}
assertCompanyAccess(req, companyId);
await enforceScopedApiCheckout(req, match.route, match.params, companyId);
if (req.method !== "GET" && req.headers["content-type"] && !req.is("application/json")) {
res.status(415).json({ error: "Plugin API routes accept JSON requests only" });
return;
}
const requestBody = req.body ?? null;
const bodySize = Buffer.byteLength(JSON.stringify(requestBody));
if (bodySize > PLUGIN_API_BODY_LIMIT_BYTES) {
res.status(413).json({ error: "Plugin API request body is too large" });
return;
}
const actor = getActorInfo(req);
const input: PluginScopedApiRequest = {
routeKey: match.route.routeKey,
method: req.method,
path: requestPath,
params: match.params,
query: normalizeQuery(req.query),
body: requestBody,
actor: {View on GitHub (pinned to a7e689b3c3)
Solutions
- Set Content-Type: application/json and send JSON.stringify(body).
- For empty bodies on non-GET verbs, omit the Content-Type header entirely — the check only fires when the header is present.
- For file uploads use a dedicated upload surface; the scoped gateway is JSON-only and caps bodies at 1 MB.
Example fix
// before
await fetch(`/api/plugins/${id}/api/items`, {
method: "POST",
body: new URLSearchParams({ name: "x" }), // sends application/x-www-form-urlencoded
});
// after
await fetch(`/api/plugins/${id}/api/items`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "x" }),
}); Defensive patterns
Strategy: validation
Validate before calling
function jsonFetch(url: string, init: RequestInit = {}): Promise<Response> {
const headers = new Headers(init.headers);
const hasBody = init.body != null;
if (hasBody) headers.set("Content-Type", "application/json");
else headers.delete("Content-Type"); // avoid 415 on bodyless non-GET calls
return fetch(url, { ...init, headers, body: hasBody ? JSON.stringify(init.body) : undefined });
} Type guard
function isJsonContentType(contentType: string | null | undefined): boolean {
return typeof contentType === "string" && /^application\/json\b/i.test(contentType.trim());
} Prevention
- Always pair JSON.stringify with an explicit Content-Type: application/json header.
- Never pass URLSearchParams or FormData to scoped plugin routes.
- Centralize plugin API calls in one client wrapper that enforces JSON headers.
When it happens
Trigger: POST/PUT/PATCH/DELETE to /api/plugins/:pluginId/api/* with Content-Type application/x-www-form-form-urlencoded, multipart/form-data, or text/plain — e.g. a plain HTML form post or fetch with a URLSearchParams body (which sets form-urlencoded automatically).
Common situations: fetch call missing the JSON header; native form submission targeting the plugin route; a client library that defaults to form encoding; attempting a file upload through the scoped API.
Related errors
- Plugin is not ready (current status: ${plugin.status})
- Plugin worker is not running
- Plugin does not expose scoped API routes
- Plugin API route not found
- Unable to resolve company for plugin API route
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18).
Data as JSON: /api/errors/5fc76340bc257725.
Report an issue: GitHub.