twentyhq/twenty · error · RestApiClientError
Request to failed with status
Error message
Request to ${url} failed with status ${response.status} ${response.statusText} What it means
Thrown by RestApiClient.parseResponse (rest/index.ts:328-337) as a RestApiClientError when `response.ok` is false. Unlike the GraphQL path, this includes structured details (status, statusText, url, body) on the error object. The body is JSON.parsed when possible, otherwise kept as raw text.
Solutions
- Catch RestApiClientError and read `err.status`, `err.body`, and `err.url` for diagnosis.
- For 401, refresh the token and retry (or register the host refresh hook).
- For 404, verify the path and resource id; for 400, validate the body shape against the API.
- For 5xx, check Twenty server logs and retry with backoff.
Example fix
// before
await client.post('/rest/objects/contact', payload);
// after
try {
await client.post('/rest/objects/contact', payload);
} catch (err) {
if (err instanceof RestApiClientError) {
console.error(err.status, err.body);
}
throw err;
} Defensive patterns
Strategy: try-catch
Type guard
const isRestApiClientError = (e: unknown): e is RestApiClientError => e instanceof RestApiClientError;
Try / catch
try {
const result = await client.post('/rest/objects/contact', payload);
} catch (err) {
if (err instanceof RestApiClientError) {
// err.status, err.statusText, err.url, err.body are all available
if (err.status === 401) await refreshToken();
else if (err.status === 404) console.error('Not found:', err.url);
else console.error('Request failed:', err.status, err.body);
}
throw err;
} Prevention
- Always catch RestApiClientError and branch on err.status.
- Validate request bodies and ids before sending to avoid 400/404.
- Refresh tokens proactively before expiry to avoid 401s.
- Retry 5xx with exponential backoff.
When it happens
Trigger: Any REST method (get/post/put/patch/delete) where the server returns a non-2xx status: 400 for validation, 401 for auth, 403 forbidden, 404 not found, 5xx server error.
Common situations: Wrong path or REST resource id (404); insufficient permissions (403); malformed body (400); expired token (401); server error (500); rate limiting (429).
Related errors
- Global `fetch` function is not available, pass a fetch…
- Invalid JSON response
- Missing API url. Set the
- Missing application access token. Set the
- ${res.statusText}: ${await res.text()}
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/c63498d26bf1bb5f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/twenty-client-sdk/src/rest/index.ts:329
}
private async parseResponse<TResponse>(
response: Response,
url: string,
): Promise<TResponse> {
const rawBody = await response.text();
let parsedBody: unknown = undefined;
if (rawBody.trim().length > 0) {
try {
parsedBody = JSON.parse(rawBody);
} catch {
parsedBody = rawBody;
}
}
if (!response.ok) {
throw new RestApiClientError(
`Request to ${url} failed with status ${response.status} ${response.statusText}`,
{
status: response.status,
statusText: response.statusText,
url,
body: parsedBody,
},
);
}
return parsedBody as TResponse;
}
private async execute<TResponse>(
method: string,
path: string,
body: unknown,
requestOptions?: RestApiRequestOptions,View on GitHub (pinned to 1f5dd2bbd2)