SDK · Reference
Errors and retries
Both SDKs surface API failures as a single typed error. This page covers what that error carries, what each status code means, and which failures are worth retrying.
What an error looks like
The TypeScript client throws; the Go client returns. Both expose the HTTP status and a human-readable message. TypeScript also keeps the raw response and response metadata; Go keeps the raw JSON payload. The parsed SDK error types do not expose a separate stable error code or request-ID field.
import { HubflyApiError } from '@hubfly/sdk';
try {
await client.projects.containers.restart('proj_123', 'cnt_404');
} catch (error) {
if (!(error instanceof HubflyApiError)) throw error;
error.statusCode; // number — 404
error.message; // string — 'Container not found'
error.meta?.requestId; // request ID when the error response includes metadata
error.rawResponse; // original error envelope
}_, err := client.Containers.Restart(ctx, "proj_123", "cnt_404")
var apiErr *hubfly.APIError
if errors.As(err, &apiErr) {
apiErr.StatusCode // int — 404
apiErr.Message // string — "Container not found"
apiErr.RawPayload // raw JSON error envelope
}Match on the HTTP status for broad retry and recovery behavior. If your application needs finer-grained behavior, inspect the structured error payload returned by the endpoint and keep that handling tolerant of additive fields.
Status codes
| Status | Meaning | What to do |
|---|---|---|
400 | The request body or a parameter failed validation. | Fix the payload. Retrying the same request will fail again. |
401 | Missing, malformed, expired or revoked token. | Issue a new token. Do not retry. |
403 | The token is valid but lacks the scope for this operation. | Grant the scope, or use a token that has it. Do not retry. |
404 | The resource does not exist, or the token cannot see it. | Check the ID and the project it belongs to. Do not retry. |
409 | Conflicts with current state — a name in use, or a resource mid-transition. | Re-read the resource and decide. A blind retry usually conflicts again. |
429 | Throttled or temporarily unable to accept the request. | Back off, then retry. Do not assume a standard rate-limit header is present. |
500, 502, 503, 504 | Something failed on our side. | Retry with exponential backoff. |
What to retry
Retry on 429, on 5xx, and on transport-level failures such as a dropped connection or a timeout. Do not retry a 4xx other than429 — the request will keep failing until you change it.
Method matters as much as status. GET and DELETE calls are idempotent and always safe to repeat. A POST that creates a resource is not: if it timed out you cannot tell whether the resource was created, so list or fetch first and only re-issue the create when it genuinely is not there.
A timed-out create may still have succeeded
Backoff in practice
Neither SDK retries for you: automatic retries hidden inside a client make failures hard to reason about. Wrap the calls you want retried, and add jitter so a fleet of workers does not synchronise its attempts.
import { HubflyApiError } from '@hubfly/sdk';
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
async function withRetry<T>(operation: () => Promise<T>, attempts = 4): Promise<T> {
for (let attempt = 0; ; attempt++) {
try {
return await operation();
} catch (error) {
const status = error instanceof HubflyApiError ? error.statusCode : 0;
const transport = !(error instanceof HubflyApiError);
const retryable = transport || RETRYABLE.has(status);
if (!retryable || attempt >= attempts - 1) throw error;
// 250ms, 500ms, 1s… plus up to 250ms of jitter
const delay = 250 * 2 ** attempt + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
const { data } = await withRetry(() => client.projects.list());var retryable = map[int]bool{429: true, 500: true, 502: true, 503: true, 504: true}
func withRetry(ctx context.Context, attempts int, op func() error) error {
var err error
for attempt := 0; attempt < attempts; attempt++ {
if err = op(); err == nil {
return nil
}
var apiErr *hubfly.APIError
if errors.As(err, &apiErr) && !retryable[apiErr.StatusCode] {
return err // a 4xx we cannot fix by trying again
}
// 250ms, 500ms, 1s… plus jitter
delay := time.Duration(250*(1<<attempt))*time.Millisecond +
time.Duration(rand.Intn(250))*time.Millisecond
select {
case <-time.After(delay):
case <-ctx.Done():
return ctx.Err()
}
}
return err
}Throttling
Some operations may return 429 when they are throttled. The API does not promise a standard rate-limit header on every response, so use the response status and body as the signal, then apply bounded exponential backoff with jitter. If a future response supplies a server wait hint, prefer that hint.
Reporting a problem
Successful responses expose request metadata as meta.requestId. TypeScript error responses may expose the same value through error.meta; the Go SDK leaves error details in RawPayload. Include any request or error identifier available when you contact support@hubfly.space.