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

  1. Read body.error on the PaperclipApiError for the precise upstream reason and address that.
  2. Verify the issueId/approvalId/companyId being passed actually exist and are in scope.
  3. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/16c785206cdac591. Report an issue: GitHub.