TryGhost/Ghost · error · Error
fallbackMessage
Error message
fallbackMessage
What it means
Thrown in handleGiftResponse() (apps/portal/src/utils/api.js) as the final fallback when a gift-endpoint response is non-ok AND HumanReadableError.fromApiResponse(res) returns null (no parseable error envelope). The thrown message is whatever fallbackMessage the caller supplied (e.g. 'Failed to fetch gift redemption data').
Source
Thrown at apps/portal/src/utils/api.js:180
if (res.ok) {
return res.json();
} else {
throw (await HumanReadableError.fromApiResponse(res)) ?? new Error('Failed to save feedback');
}
}
};
const handleGiftResponse = async (res, fallbackMessage) => {
if (res.ok) {
return res.json();
}
const humanError = await HumanReadableError.fromApiResponse(res);
if (humanError) {
throw humanError;
}
throw new Error(fallbackMessage);
};
api.gift = {
async fetchRedemptionData({token}) {
const url = endpointFor({type: 'members', resource: `gifts/${encodeURIComponent(token)}/redeem`});
const res = await makeRequest({
url,
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
return handleGiftResponse(res, 'Failed to load gift data');
},
async redeem({token}) {View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Inspect the raw response status and body in the Network tab — if it's HTML, the failure is upstream of Ghost (proxy/CDN/firewall).
- Confirm the gift token in the redemption URL is unused and unexpired (Settings > Gifts in admin).
- Ensure the Ghost server version supports the gift redemption members endpoint.
- If proxying, configure it to pass through JSON error envelopes unchanged.
Example fix
// before
const humanError = await HumanReadableError.fromApiResponse(res);
if (humanError) { throw humanError; }
throw new Error(fallbackMessage);
// after: include the status so callers can branch
const humanError = await HumanReadableError.fromApiResponse(res);
if (humanError) { throw humanError; }
const e = new Error(`${fallbackMessage} (${res.status})`);
e.status = res.status;
throw e; Defensive patterns
Strategy: try-catch
Validate before calling
// validate token shape before fetching
function isValidGiftToken(token) {
return typeof token === 'string' && token.length >= 16 && /^[A-Za-z0-9_-]+$/.test(token);
} Try / catch
try {
const data = await api.gift.fetchRedemptionData({token});
return data;
} catch (err) {
// err.message === fallbackMessage; show user-friendly retry
showGiftError(err.message);
} Prevention
- Validate gift token shape before calling the endpoint.
- Configure reverse proxies to pass through JSON error envelopes unchanged.
- Confirm the gift token in the URL is unused and unexpired.
- Keep Portal and Ghost server versions in sync for the gift contract.
When it happens
Trigger: Gift fetchRedemptionData / redeem call gets non-ok from {siteUrl}/members/api/gifts/<token>/redeem; the body either is not JSON or has no errors[] array, so HumanReadableError cannot be built, and the generic fallback message is thrown.
Common situations: Gift token invalid, expired, or already redeemed; reverse proxy returning an HTML error page (e.g. Cloudflare 502) so the body has no JSON errors[]; Portal bundle out of sync with backend gift endpoints; member-suppression or rate-limit response without a JSON error body.
Related errors
- Failed to apply offer
- Failed to fetch site data
- Failed to send email address verification email
- Failed to update newsletter
- Failed to update member
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/3cce256ebc2defbf.
Report an issue: GitHub.