Hubfly spaceDocs
Console

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.

TypeScript
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
}
Go
_, 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

StatusMeaningWhat to do
400The request body or a parameter failed validation.Fix the payload. Retrying the same request will fail again.
401Missing, malformed, expired or revoked token.Issue a new token. Do not retry.
403The token is valid but lacks the scope for this operation.Grant the scope, or use a token that has it. Do not retry.
404The resource does not exist, or the token cannot see it.Check the ID and the project it belongs to. Do not retry.
409Conflicts with current state — a name in use, or a resource mid-transition.Re-read the resource and decide. A blind retry usually conflicts again.
429Throttled or temporarily unable to accept the request.Back off, then retry. Do not assume a standard rate-limit header is present.
500, 502, 503, 504Something 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

A timeout means you stopped waiting, not that the server stopped working. Before retrying a create, check whether the resource now exists — otherwise you can end up with two containers, two volumes or two port reservations where you meant to have one.

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.

TypeScript
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());
Go
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.

Something unclear or out of date? Emailsupport@hubfly.spaceBack to top