antiwork/gumroad · error · ResponseError
json.message
Error message
json.message
What it means
ResponseError thrown at product_dashboard.ts:13 whose message is the server's own json.message: the DELETE to Routes.link_path(permalink) answered with a JSON body shaped { success: false, message }, and deleteProduct forwards that message verbatim. Note this function never checks response.ok — it trusts the endpoint to always answer inside the {success:true}|{success:false,message} envelope (any 5xx/network failure already became a ResponseError inside request()).
Source
Thrown at app/javascript/data/product_dashboard.ts:13
import typia from "typia";
import { ResponseError, request } from "$app/utils/request";
export async function deleteProduct(permalink: string) {
const response = await request({
method: "DELETE",
url: Routes.link_path(permalink),
accept: "json",
});
const json = typia.assert<{ success: true } | { success: false; message: string }>(await response.json());
if (!json.success) throw new ResponseError(json.message);
}
export async function archiveProduct(permalink: string) {
const response = await request({
url: Routes.products_archived_index_path(),
method: "POST",
accept: "json",
data: { id: permalink },
});
const json = typia.assert<{ success: true } | { success: false; error: string }>(await response.json());
if (!json.success) throw new ResponseError(json.error || "Failed to archive product");
}
export async function unarchiveProduct(permalink: string) {
const response = await request({
url: Routes.products_archived_path(permalink),
method: "DELETE",View on GitHub (pinned to afeacbd394)
Solutions
- Read the thrown message — it is the server's stated reason for refusing deletion, not a generic string
- If the message references dependent resources (bundles, posts), detach or delete those first and retry
- Confirm the acting user owns the product (permalink belongs to their account)
- Guard double-submits in the UI: disable the delete button until the request settles
Defensive patterns
Strategy: try-catch
Type guard
import { assertResponseError } from '$app/utils/request';
assertResponseError(e); // ResponseError here always carries the server's message Try / catch
try {
await deleteProduct(permalink);
} catch (e) {
assertResponseError(e);
showError(e.message); // this is the server's refusal reason — show it verbatim
await refreshProducts(); // resync list (the product may already be gone)
} Prevention
- Show e.message to the user — it is the server's specific reason, not boilerplate
- Disable the delete affordance while the DELETE is in flight
- Refresh the dashboard list after failure; double-deletes produce confusing refusals
When it happens
Trigger: The server refuses product deletion with success:false — typical guards: the product is referenced by something that must be removed first (active bundle/subscription/upsell links), the caller lacks permission for that product, or the permalink no longer exists. Any such refusal arrives as this error with the controller's message text.
Common situations: Dashboard 'Delete' clicked twice quickly (second call finds an already-deleted product); deleting a product still attached to an active bundle or post; a staff account deleting a product it does not own; Rails i18n or flash-based message text leaking into the JSON message field making the UI show raw translation keys.
Related errors
- json.error_message
- responseData.error_message
- Failed to archive product
- Failed to unarchive product
- json.error_message
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/980048addf6e4d33.
Report an issue: GitHub.