Errors
Catch the five client errors in the right order, tell an outage from a missing schema, and know what the SDK keeps from the server's answer.
Every failed request throws. The client never returns an error object, never retries, and never swallows a status. Catch by class, read status and code, and decide.
import { StandardsAuthError, StandardsRequestError } from "@stndrds/client";
import { getStandards } from "./client";
import { incident } from "./schema";
try {
await getStandards().from(incident).get("inc_404");
} catch (error) {
if (error instanceof StandardsAuthError) throw error; // 401 or 403: fix the key, not the code
if (error instanceof StandardsRequestError && error.status === 404) return null;
throw error;
}The classes
| Class | Extends | When |
|---|---|---|
StandardsRequestError | Error | Any non-2xx answer, plus status: 0 when the API could not be reached. |
StandardsAuthError | StandardsRequestError | 401 or 403 on any route. |
SchemaSourceConflictError | StandardsRequestError | 409 from schema.sync: a name this source declares is owned by core, a bundle or another source. Carries objectName and owner. |
SchemaSourceValidationError | StandardsRequestError | 400 from schema.sync: bad id, oversize payload, hash mismatch, invalid definition or a retype without its migration. The server message is kept verbatim. |
StandardsConfigurationError | Error | The client was built in a way it refuses to run: an apiKey in a browser context. Thrown by createStandards, before any request. |
StandardsRequestError carries three fields: status (the HTTP status), message (the server's message, or Request failed with status N when the body has none) and code (the server's error code, when it sent one, such as SCHEMA_SOURCE_NOT_FOUND). A 204 answer resolves to null and throws nothing.
The builders throw their own family, at declaration time and without a network: ValidationError for a missing name or label, DuplicateError for a repeated attribute or object name, and SchemaError with a code for a bad object name, a reserved name or a missing labelExpression. All of them extend SchemaError.
Order your instanceof checks
Three classes extend StandardsRequestError. An instanceof StandardsRequestError test placed first matches all four, so check the specific classes first:
import {
SchemaSourceConflictError,
SchemaSourceValidationError,
StandardsAuthError,
StandardsRequestError,
} from "@stndrds/client";
import { getStandards } from "./client";
import { statusSource } from "./schema";
try {
await getStandards().schema.sync(statusSource);
} catch (error) {
if (error instanceof SchemaSourceConflictError) {
console.error(`"${error.objectName}" is owned by ${error.owner}; rename it`);
} else if (error instanceof SchemaSourceValidationError) {
console.error(`the server refused the source: ${error.message}`);
} else if (error instanceof StandardsAuthError) {
console.error(`the key lacks architect:update (${error.status})`);
} else if (error instanceof StandardsRequestError) {
console.error(`sync failed with ${error.status} ${error.code ?? ""}`);
} else {
throw error;
}
process.exit(1);
}Status 0
A refused connection and a timeout both throw StandardsRequestError with status: 0, the message Could not connect to <baseUrl> and no code. The default timeout is 30 seconds per request; set timeoutMs on createStandards to change it. The client makes one attempt. A retry, a backoff or a circuit breaker belongs in your code, where you know whether the call is idempotent.
A 429 is a plain StandardsRequestError with status: 429. The client does not read Retry-After and does not wait.
What the SDK drops
The REST API answers an error with { statusCode, message, code, errors[] } and an x-request-id header. The client keeps message and code. It drops the errors[] array, so a validation failure on create tells you the first problem in message but not the per-field list, and it drops x-request-id, so a support request has no correlation id from the client side. When you need either, make the call with createTransport and read the response yourself, or reproduce it with the CLI, which prints both.
The three-way degradation
A server that talks to Standards has three distinct failure modes, and a status page should answer each differently. The status page cookbook sorts them with two predicates:
import { StandardsAuthError, StandardsRequestError } from "@stndrds/client";
/** Connection refused or timeout (status 0), a 5xx, or a key that no longer works. */
export function isStandardsUnreachable(error: unknown): boolean {
if (error instanceof StandardsAuthError) return true;
return error instanceof StandardsRequestError && (error.status === 0 || error.status >= 500);
}
/** A 404 on a records route: the workspace answers but has none of our objects. */
export function isSchemaMissing(error: unknown): boolean {
return (
error instanceof StandardsRequestError &&
!(error instanceof StandardsAuthError) &&
error.status === 404
);
}The cron handler turns them into three responses:
try {
result = await runChecks(deps);
} catch (error) {
if (error instanceof StandardsAuthError) {
return json(500, { error: error.code ?? "auth" }); // our configuration is wrong
}
if (isStandardsUnreachable(error)) {
return json(503, { error: "standards_unreachable" }); // try again later
}
if (isSchemaMissing(error)) {
return json(503, { error: "schema_missing" }); // run the sync script
}
throw error;
}An auth failure is a 500 with the server's code: the key is wrong and a retry will not help. An unreachable API is a 503: the next cron run may succeed. A 404 on a records route is also a 503, with a different reason: the workspace answers but the schema source was never synced, so the fix is schema.sync, not waiting. The public page uses the same split to serve the last stored snapshot as stale during an outage, and an empty one when the schema is missing.
Next steps
- Errors in concepts: the server's payload, codes and rate-limit buckets.
- Sync: the two sync errors and what the summary looks like.
- Records: what each verb sends and which status it can answer.
- Status page cookbook: the degradation above, wired end to end.