{"record":{"id":"3cce256ebc2defbf","repo":"TryGhost/Ghost","slug":"fallbackmessage","errorCode":null,"errorMessage":"fallbackMessage","messagePattern":"fallbackMessage","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/portal/src/utils/api.js","lineNumber":180,"sourceCode":"            if (res.ok) {\n                return res.json();\n            } else {\n                throw (await HumanReadableError.fromApiResponse(res)) ?? new Error('Failed to save feedback');\n            }\n        }\n    };\n\n    const handleGiftResponse = async (res, fallbackMessage) => {\n        if (res.ok) {\n            return res.json();\n        }\n\n        const humanError = await HumanReadableError.fromApiResponse(res);\n        if (humanError) {\n            throw humanError;\n        }\n\n        throw new Error(fallbackMessage);\n    };\n\n    api.gift = {\n        async fetchRedemptionData({token}) {\n            const url = endpointFor({type: 'members', resource: `gifts/${encodeURIComponent(token)}/redeem`});\n            const res = await makeRequest({\n                url,\n                method: 'GET',\n                headers: {\n                    'Content-Type': 'application/json'\n                },\n                credentials: 'same-origin'\n            });\n\n            return handleGiftResponse(res, 'Failed to load gift data');\n        },\n\n        async redeem({token}) {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/portal/src/utils/api.js#L162-L198","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst humanError = await HumanReadableError.fromApiResponse(res);\nif (humanError) { throw humanError; }\nthrow new Error(fallbackMessage);\n\n// after: include the status so callers can branch\nconst humanError = await HumanReadableError.fromApiResponse(res);\nif (humanError) { throw humanError; }\nconst e = new Error(`${fallbackMessage} (${res.status})`);\ne.status = res.status;\nthrow e;","handlingStrategy":"try-catch","validationCode":"// validate token shape before fetching\nfunction isValidGiftToken(token) {\n    return typeof token === 'string' && token.length >= 16 && /^[A-Za-z0-9_-]+$/.test(token);\n}","typeGuard":null,"tryCatchPattern":"try {\n    const data = await api.gift.fetchRedemptionData({token});\n    return data;\n} catch (err) {\n    // err.message === fallbackMessage; show user-friendly retry\n    showGiftError(err.message);\n}","preventionTips":["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."],"tags":["portal","gift","members-api","network","redemption"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}