calcom/cal.diy · error · HttpError
Failed to fetch project details
Error message
Failed to fetch project details
What it means
Thrown when the upstream call to https://3.basecampapi.com/${basecampUserId}/projects/${projectId}.json returns non-ok. Causes: an invalid or inaccessible projectId, an expired/revoked access token, a Basecamp (37signals) API outage, or rate-limiting. Surfaced as HTTP 400 (note: the status is somewhat inaccurate since the upstream cause may be 401/403/404/429/5xx).
Source
Thrown at packages/app-store/basecamp3/api/projectMutation.ts:64
let credentialKey = credential.key as BasecampToken;
if (credentialKey.expires_at < Date.now()) {
credentialKey = (await refreshAccessToken(credential)) as BasecampToken;
}
const basecampUserId = credentialKey.account.id;
const scheduleResponse = await fetch(
`https://3.basecampapi.com/${basecampUserId}/projects/${projectId}.json`,
{
headers: {
"User-Agent": user_agent as string,
Authorization: `Bearer ${credentialKey.access_token}`,
},
}
);
if (!scheduleResponse.ok) {
throw new HttpError({ statusCode: 400, message: "Failed to fetch project details" });
}
const scheduleJson = await scheduleResponse.json();
const scheduleId = scheduleJson.dock.find((dock: IDock) => dock.name === "schedule").id;
await prisma.credential.update({
where: { id: credential.id },
data: { key: { ...credentialKey, projectId: Number(projectId), scheduleId } },
});
return { message: "Updated project successfully" };
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(handler) }),
});
View on GitHub (pinned to 176037d0af)
Solutions
- Inspect scheduleResponse.status (log it before throwing) to distinguish 401/403/404/429/5xx.
- Re-connect Basecamp 3 if the access token was revoked or the refresh failed.
- Confirm the projectId belongs to the authenticated Basecamp account.
- Retry on transient 5xx/429 with backoff.
Example fix
// before
if (!scheduleResponse.ok) {
throw new HttpError({ statusCode: 400, message: 'Failed to fetch project details' });
}
// after
if (!scheduleResponse.ok) {
throw new HttpError({
statusCode: scheduleResponse.status === 401 ? 401 : 400,
message: `Failed to fetch project details (upstream ${scheduleResponse.status})`,
});
} Defensive patterns
Strategy: retry
Validate before calling
if (!scheduleResponse.ok && scheduleResponse.status >= 500) {
// transient upstream failure - safe to retry with backoff
} Try / catch
try {
// fetch basecamp project
} catch (e) {
if (e instanceof HttpError && e.message === 'Failed to fetch project details') {
// re-connect Basecamp on 401/403; retry on 5xx
}
} Prevention
- Log scheduleResponse.status before throwing to distinguish upstream codes.
- Re-connect Basecamp 3 when the access/refresh token is revoked.
- Confirm projectId belongs to the authenticated Basecamp account.
When it happens
Trigger: projectId that does not exist or is not accessible by the Basecamp account; access token revoked by the user in Basecamp; refresh-token logic failed silently; 37signals API down or rate-limiting.
Common situations: Token revoked in Basecamp account settings; projectId copied from a different Basecamp account; transient 5xx or 429 from 37signals.
Related errors
- Something is wrong with Zoom API
- Could not refresh the token due to connection issue with the
- No credential found for user
- No credential found for user
- Failed to fetch Basecamp projects
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/ee02e4bf20fd2857.
Report an issue: GitHub.