paperclipai/paperclip · error · PaperclipApiError
${method} ${path} failed with ${status}: ${body.error}
Error message
${method} ${path} failed with ${status}: ${body.error} What it means
PaperclipMcpClient.requestJson throws a PaperclipApiError whenever the upstream Paperclip /api response is not OK. buildErrorMessage appends ': <body.error>' when the parsed response body is an object containing a string 'error' field — i.e. the server returned a structured JSON error envelope. The thrown object also carries status, method, path, and the full parsed body for programmatic handling.
Source
Thrown at packages/mcp-server/src/client.ts:103
Authorization: `Bearer ${this.config.apiKey}`,
Accept: "application/json",
};
if (options.body !== undefined) {
headers["Content-Type"] = "application/json";
}
if ((options.includeRunId ?? isWriteMethod(method)) && this.config.runId) {
headers["X-Paperclip-Run-Id"] = this.config.runId;
}
const response = await fetch(url, {
method,
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
});
const parsedBody = await parseResponseBody(response);
if (!response.ok) {
throw new PaperclipApiError({
status: response.status,
method: method.toUpperCase(),
path,
body: parsedBody,
message: buildErrorMessage(method.toUpperCase(), path, response.status, parsedBody),
});
}
return parsedBody as T;
}
}
View on GitHub (pinned to 67001ec6eb)
Solutions
- Read body.error on the PaperclipApiError for the precise upstream reason and address that.
- Verify the issueId/approvalId/companyId being passed actually exist and are in scope.
- Re-fetch the resource (e.g. GET the issue) to confirm its current state before retrying.
Example fix
// before
try { await client.requestJson('POST', '/issues/x/comments', {body:{body:''}}) }
catch (e) { /* generic */ }
// after
try { await client.requestJson('POST', '/issues/x/comments', {body:{body: text}}) }
catch (e) {
if (e instanceof PaperclipApiError) console.error(e.status, e.body?.error);
throw e;
} Defensive patterns
Strategy: try-catch
Type guard
function isPaperclipApiError(e: unknown): e is { status: number; method: string; path: string; body: unknown; message: string } {
return typeof e === 'object' && e !== null && 'status' in e && typeof (e as any).status === 'number' && 'path' in e;
} Try / catch
try { await client.requestJson('POST', path, { body }) }
catch (e) {
if (isPaperclipApiError(e)) {
if (e.status === 404) handleMissing(e.path);
else if (e.status === 409) handleConflict(e.body?.error);
else throw e;
} else throw e;
} Prevention
- Always narrow on PaperclipApiError.status rather than parsing message strings.
- For write methods, GET the resource first to confirm state and avoid 409/422.
- Surface body.error to the operator for actionable diagnostics.
When it happens
Trigger: Any Paperclip API call returns non-2xx with a JSON body shaped like {"error":"..."}. Common: 400 validation errors, 404 on a missing issue/approval ID, 409 conflict on a checked-out issue, 422 on bad input — all of which Paperclip formats with an error string.
Common situations: Operating on a stale issue ID; calling an approval endpoint on an already-resolved approval; passing a malformed body to a write endpoint; auth/permission mismatch returning 403 with an error message.
Related errors
- ${method} ${path} failed with ${status}
- companyId is required because PAPERCLIP_COMPANY_ID is not se
- agentId is required because PAPERCLIP_AGENT_ID is not set
- Missing PAPERCLIP_API_URL
- Missing PAPERCLIP_API_KEY
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/16c785206cdac591.
Report an issue: GitHub.